概要
- 最近、社内プロダクトをMonorepoに移行しているが、かなり良い感じで開発が回っている
- GoでMonorepoする方法はいろいろある ( modules(vgo)と非modules(vgo) )
- まあまあハマりどころがあったのでその共有
※今回はCIの話はしません
※モノリシック vs マイクロサービスのようなアーキテクチャの話ではないです
Monorepoとは?
- 何らかの形で関連してる場合とそうでない場合がある複数のプロジェクトのコードを保持する単一のリポジトリのこと
- 自社のWebサイトのコードとiOSとAndroidのアプリ、そして両方が呼び出しているAPIを1つのリポジトリで管理すること (1例)
良い点としてはざっくり以下という認識です。
- コードベースの構築やテストなどのために書かなければならない繰り返しの定型コードの量を減らすのに役立つ
- Monorepoのアプローチを使用する場合、会社のすべての従業員は常に1つのリポジトリだけを見ていれば良い
- コードを複数のプロジェクトで簡単に共有して再利用することができる(これはこれで問題が発生する可能性がある)が、できるだけ多くのコードを再利用することは良い
- 大規模なリファクタリングは非常に簡単で、コードベースの複数の部分に影響を及ぼすAPIを変更することで、複数のリポジトリに触れる必要がなく、1回のコミットまたは1回のPullRequestで行うことができる
- 異なるプロジェクトに影響を及ぼすバグは、同じバグを修正するために複数の他のチームを待つ必要はなく修正できる
こちらの記事がオススメです。
Monorepos in the Wild – Markus Oberlehner – Medium
golangのMonorepo
方法1: modules(vgo)でMonorepoする例
└── project1
├── client // UnityとかAndroidとかiOSとかなんかいろいろどうぞ
├── server
│ ├── api1
│ │ ├── hoge
│ │ │ └── hoge.go
│ │ └── main.go
│ ├── api2
│ │ ├── fuga
│ │ │ └── fuga.go
│ │ └── main.go
│ ├── common
│ │ └── piyo
│ │ └── piyo.go
│ ├── go.mod
│ └── go.sum
├── tools
│ └── hogecli
│ ├── go.mod
│ ├── go.sum
│ └── main.go
└── web // frontendのJavaScriptとかなんかいろいろどうぞ
- server内のapi1とapi2でvendorを共有している
- server/commonはapi1とapi2で共通のライブラリとして利用している
- api1 -> api2 や api2 -> api1が呼べてしまうので、もしNGにする場合はそういうlintツール導入しないとだめです
- tools下はツール毎にvendorを設定している(もしapiと揃えたいならserver下へ)
- server下がgolang前提になってるのでもしgolang以外のapiとかがあるなら
server/go
とかにするといいかも? - src直下にgo modが作れないので
appname
というディレクトリを掘っています
GOPATH以外でのgo.modの作り方
$ go mod init server
module server
require (
)
Goland等のInteliJ製品でのmodules(vgo)設定
デフォルトの状態だとGolandでimportがエラーになります。
Preferences -> Go -> Go Modules (vgo) で Enable Go Modules (vgo) integration
にチェックをいれます。
これで、importが認識されるようになります。
方法2: 非modules(vgo)でプロジェクト内にGOPATHを指定してMonorepoする例
└── project1
├── client // UnityとかAndroidとかiOSとかなんかいろいろ
├── server
│ ├── pkg
│ └── src
│ ├── appname
│ │ ├── api1
│ │ │ ├── hoge
│ │ │ │ └── hoge.go
│ │ │ └── main.go
│ │ ├── api2
│ │ │ ├── fuga
│ │ │ │ └── fuga.go
│ │ │ └── main.go
│ │ ├── common
│ │ │ └── piyo
│ │ │ └── piyo.go
│ │ ├── go.mod
│ │ ├── go.sum
│ │ └── vendor
│ └── tools
│ └── hogecli
│ ├── go.mod
│ ├── go.sum
│ └── main.go
└── web // frontendのJavaScriptとかなんかいろいろ
- server/src/appname内のapi1とapi2でvendorを共有する
- server/src/appname/commonはapi1とapi2で共通のライブラリとして利用している
- api1 -> api2 や api2 -> api1が呼べてしまうので、もしNGにする場合はそういうlintツール導入しないとだめです
- tools下はツール毎にvendorを設定している(もしapiと揃えたいならserver/src/appname下へ)
- server下がgolang前提になってるのでもしgolang以外のapiとかがあるなら
server/go
とかにするといいかも?
Goland等のInteliJ製品でのGOPATHの設定
デフォルトの状態だとこんな感じにimportエラーになります。
今回は、modules(vgo)を使わないので
Preferences -> Go -> Go Modules (vgo) で Enable Go Modules (vgo) integration
のチェックは外します。
Use GOPATH that's defined in system environment
のチェックは外して、Project GOPATHに <repository>/project1/server
を設定します。
複数projectの場合は、とりあえずdirenvとか使ってる分には問題なさそうですが、Golandとかは複数GOPATHを設定すればいいのかな?と思ってます
GolangのMonorepoする際のハマりどころ
Golang以外のProject(clientなど)をGOPATHに依存させたくない
webやclientのエンジニアにもgit cloneする際にGOPATH下にcloneしてください... とか go get
でinstallしてくださいとなるのはさすがに...
ので.envrc等でGOPATHを設定してもらうようにするかmodules(vgo)の対応が必要になる。
GOPATHに依存しているライブラリがmodules(vgo)構成でうまく動かなかった
goaのdesignがGOPATH下にある前提になっているためmodules(vgo)によるGOPATH外のプロジェクトだとimportのpathが通らなくてbuildすることができませんでした。
なのでgoa等を使う場合は、現状は 方法2
の方でやる必要があります。
vendorを分けたい / vendorを一緒にしたい
- 共通ライブラリとかはvendorを別にしちゃうとimport時にエラーになりますので同じvendorを使う必要があります
- server が例になります(server内で1つのgo.modにする)
- 便利ツールなどはvendorを別にしてAWSのSDKなど別のversionで固定したい等がありvendorを分けたくなります
- toolsが例になります(mainパッケージ毎にgo.modにする)
GOPATH/src直下にgo modできない
GOPATH/src
直下で go mod init
すると以下のようなエラーがでます。
言われてみれば確かにそうなので、 必ず今回の例だと GOPATH/src/appname
という形でアプリ名のディレクトリを間に挟むようにしました。
go: cannot determine module path for source directory
/Users/kyokomi/workspace/ghq/github.com/kyokomi/gomonorepo_not_modules/project1/server/src
(outside GOPATH, no import comments)
CirlceCIのjobをうまいことやらないと管理が大変
- deployやworkflowの設定が大体コピペで微妙に異なるみたいな感じで管理が大変...
12/15のアドベントカレンダー記事 CircleCIのリファクタリングについて
書こうと思っています。