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

# 概要
- 最近、社内プロダクトを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](https://medium.com/@maoberlehner/monorepos-in-the-wild-33c6eb246cb9)

# golangのMonorepo

## 方法1: modules(vgo)でMonorepoする例

https://github.com/kyokomi/gomonorepo


```
└── 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の作り方

```shell
$ go mod init server
```

```:go.mod
module server

require (
)
```

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

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

![image.png](https://qiita-image-store.s3.amazonaws.com/0/40887/06542e46-4e45-411e-c0a5-483d27388ef7.png)

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

![image.png](https://qiita-image-store.s3.amazonaws.com/0/40887/af82b6a5-8bec-e6fe-1688-93ce644df762.png)

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

![image.png](https://qiita-image-store.s3.amazonaws.com/0/40887/62cc7b46-e3ce-20c2-19c3-d0d43b9c26eb.png)


## 方法2: 非modules(vgo)でプロジェクト内にGOPATHを指定してMonorepoする例

https://github.com/kyokomi/gomonorepo_not_modules

```
└── 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](https://qiita-image-store.s3.amazonaws.com/0/40887/446fb8c0-f234-80bf-1c03-4125795dc4f2.png)

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

![image.png](https://qiita-image-store.s3.amazonaws.com/0/40887/88ff97ad-1d26-b880-3715-48b058ac8e36.png)

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

![image.png](https://qiita-image-store.s3.amazonaws.com/0/40887/8a8df38b-a347-d182-ccf3-e8f912cfdbf0.png)

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

![image.png](https://qiita-image-store.s3.amazonaws.com/0/40887/7c6d025f-495d-6611-59c1-343935713097.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)構成でうまく動かなかった

https://github.com/goadesign/goa

goaのdesignがGOPATH下にある前提になっているためmodules(vgo)によるGOPATH外のプロジェクトだとimportのpathが通らなくてbuildすることができませんでした。
なのでgoa等を使う場合は、現状は `方法2` の方でやる必要があります。

### vendorを分けたい / vendorを一緒にしたい

- 共通ライブラリとかはvendorを別にしちゃうとimport時にエラーになりますので同じvendorを使う必要があります
    - [server](https://github.com/kyokomi/gomonorepo/tree/master/project1/server) が例になります（server内で1つのgo.modにする）
- 便利ツールなどはvendorを別にしてAWSのSDKなど別のversionで固定したい等がありvendorを分けたくなります
    - [tools](https://github.com/kyokomi/gomonorepo/tree/master/project1/tools/hogecli)が例になります（mainパッケージ毎にgo.modにする）

### GOPATH/src直下にgo modできない

`GOPATH/src` 直下で `go mod init` すると以下のようなエラーがでます。
言われてみれば確かにそうなので、 必ず[今回の例](https://github.com/kyokomi/gomonorepo_not_modules/tree/master/project1/server/src/appname)だと `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のリファクタリングについて` 書こうと思っています。