この記事は Go2 Advent Calender 2020 の 19 日目の記事です。
今年、業務の中でメンバー間でコードの各パッケージの役割とその依存関係に対する認識が異なり、コードの依存関係が複雑化してしまう。といったようなことを感じる機会が何度かありました。パッケージ間の依存関係が整理されず複雑化してしまうと、以下のような問題が生じると思います。
- 外部のAPIと連携するためのパッケージにロジックが直接依存し、コードが具体的なことを知りすぎてしまい、メンテナンスがしにくくなってしまう
- 複数のpackageの機能を跨いだテストが随所に書かれてしまい、パッケージを独立に開発・テストすることが難しくなってしまう
このような状況を避けるために、依存関係を明文化し、継続的にメンテナンスするための仕組みは作れないかな?と思い作ってみたという話です。
UMLで依存関係を定義する
UML (Unified Modeling Language)は、システムの全体的な構成やその依存関係を記述するのに広く使われており、ソフトウェア開発の中でよく使われていると思います。
私も業務でよくPlantUMLを使い、システムの設計やシーケンスその他もろもろの資料を作成しています。
ここでは、UMLライクにgoのpackage間の依存関係を記述します。
手元に適切なコードがなくて恐縮なのですが、今回の場合は、自分の適当なpackageを選びます。
このpackageがimportしている全てのpackageを役割ごとに分類してみると以下のようになります。
main : github.com/sodefrin/appengine-boilerplate
app : github.com/sodefrin/appengine-boilerplate/app
controller : github.com/sodefrin/appengine-boilerplate/app/controller
model : github.com/sodefrin/appengine-boilerplate/app/model/xo
proto : github.com/sodefrin/appengine-boilerplate/proto
proto : github.com/grpc-ecosystem/grpc-gateway/runtime
proto : github.com/golang/protobuf/descriptor
proto : github.com/golang/protobuf/proto
proto : github.com/golang/protobuf/ptypes/empty
proto : github.com/grpc-ecosystem/grpc-gateway/utilities
proto : google.golang.org/genproto/googleapis/api/annotations
proto : google.golang.org/grpc
proto : google.golang.org/grpc/codes
proto : google.golang.org/grpc/grpclog
proto : google.golang.org/grpc/status
util : github.com/sodefrin/appengine-boilerplate/log
util : go.uber.org/zap
- main : コードのエントリーポイント。
- app : 各種DIを実行し、サーバーを起動するコード。
- controller : リクエストをハンドリングし、各種ロジックを実行するコード。
- model : DBを操作するコード。
- proto : GRPCのサーバの生成コード。
- util : loggerなどのどこから呼び出してもいいコード。
これらの間の依存関係として矢印のような依存関係が存在します。
main -> app
app -> proto
app -> controller
app -> model
controller -> proto
main -> util
app -> util
controller -> util
この関係性をPlantUMLを使って図示すると以下のようになります。
packageの依存関係をvalidationする
今回作成したlinterでは、前述したようなUMLでの依存関係の定義に基づいてソースコードを解析し、違反するimportがないかvalidationします。
例えば、下記のように、違反するimportがあると教えてくれます。
$ archi-checker -s $(go list ./... )
main.go:9:2: cannot import github.com/sodefrin/appengine-boilerplate/proto (proto) from github.com/sodefrin/appengine-boilerplate (main)
参照リンク:
試してみた結果
現在、私が業務で書いているコードの依存関係をUMLで定義し、上記linterを回してみたのですが、あまりにも多くのエラーが存在し、修正はできませんでした。現実は厳しい・・・。
また、UMLをメンテナンスするコストもかなり高く、結構しんどいかもな、という気がしています。
linterにしなくても依存関係がUMLなどで明文化して共有されていれば、コードレビューで指摘できるので十分では・・・・?という個人的な結論になりました。
とはいえ、定期的に依存関係を整理して図示して見直してみたりすることは有益だと思うので、機会があると試してみるといいかもしれません!
最後までお付き合いいただきありがとうございました。