27
13

More than 5 years have passed since last update.

GoのプロジェクトをMonorepo対応する

Posted at

概要

  • 最近、社内プロダクトを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
go.mod
module server

require (
)

Goland等のInteliJ製品でのmodules(vgo)設定

デフォルトの状態だとGolandでimportがエラーになります。

image.png

Preferences -> Go -> Go Modules (vgo) で Enable Go Modules (vgo) integration にチェックをいれます。

image.png

これで、importが認識されるようになります。

image.png

方法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エラーになります。

image.png

今回は、modules(vgo)を使わないので
Preferences -> Go -> Go Modules (vgo) で Enable Go Modules (vgo) integration のチェックは外します。

image.png

Use GOPATH that's defined in system environment のチェックは外して、Project GOPATHに <repository>/project1/server を設定します。

image.png

複数projectの場合は、とりあえずdirenvとか使ってる分には問題なさそうですが、Golandとかは複数GOPATHを設定すればいいのかな?と思ってます :innocent:

image.png

GolangのMonorepoする際のハマりどころ

Golang以外のProject(clientなど)をGOPATHに依存させたくない

webやclientのエンジニアにもgit cloneする際にGOPATH下にcloneしてください... :pray: とか go get でinstallしてくださいとなるのはさすがに... :innocent:

ので.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のリファクタリングについて 書こうと思っています。

27
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
27
13