はじめに
現在関わっている新規開発プロジェクトで、新しくgolangを採用することにしました。
その際、アーキテクチャや使用するライブラリなどいろいろ試したり調べたりしたのでサンプルプロジェクト(Github)と一緒に紹介します。
golangのパッケージ構成
golangで開発を行うにあたり、プロジェクトのパッケージ構成を検討しましたが、今の所golangのパッケージ構成はこれ!というものはなく、世の中的にもいろいろと試行錯誤がなされている段階のようです。
今回作るシステムでは、最終的にはある程度の規模になることが予想されるのと、立ち上げ後のメンテナンス性も踏まえ、DDDで紹介されているアーキテクチャを取り入れてみることにしました。
DDDにはいくつかのアーキテクチャがよく登場します。
今回の場合、レイヤードアーキテクチャやヘキサゴナルアーキテクチャでは少し不足で、クリーンアーキテクチャまでを取り入れると冗長すぎるように感じたため、最終的なパッケージ構成はオニオンアーキテクチャの考え方を基本に構成しました。
オニオンアーキテクチャについて
オニオンアーキテクチャは、The Onion Architecture : part 1で紹介されているDDDに適したアーキテクチャです。
アプリケーションにオニオンアーキテクチャを導入することで、「関心の分離」や「依存関係逆転の原則」を順守しやすくなります。
玉ねぎの層のようにレイヤという考え方でアプリケーションを分離することで依存を管理し、各レイヤの役割を明確にすることで、各機能が独立するため、例えばテストが書きやすくなったり、アプリケーションを変更する場合にも他の層に影響しにくくなったりするので、結果的にアプリケーションの継続的な開発が容易になる、ということですね。
参考 : オニオンアーキテクチャ
The Onion Architecture : part 1
ドメイン駆動設計とオニオンアーキテクチャ
[DDD]ドメイン駆動 + オニオンアーキテクチャ概略
ドメイン駆動設計で実装を始めるのに一番とっつきやすいアーキテクチャは何か[DDD]
参考 : DDD(ドメイン駆動設計)
書籍:エリック・エヴァンスのドメイン駆動設計
書籍:実践ドメイン駆動設計
プロジェクトのパッケージ構成
オニオンアーキテクチャの考え方を元に、サンプルプロジェクトを作成しました。(Github)
APIの基本となるディレクトリ構成は下記の通りです。(アーキテクチャと関係のない部分は省略します。)
.
├── presenter
│ └── http
│ ├── handler
│ │ └── app_handler.go
│ │ └── xxx_handler.go
│ ├── middleware
│ └── router
│ └── router.go
├── usecase
│ └── xxx_usecase.go
├── domain
│ └── model
│ │ └── xxx_model.go
│ └── repository
│ │ └── xxx_repository.go
│ └── service
│ └── xxx_service.go
├── infrastructure
│ └── persistence
│ └── mysql
│ └── xxx_repository.go
├── interactor
│ └── interactor.go
└── main.go
プロジェクトの各ディレクトリは、オニオンアーキテクチャの各レイヤと対応しています。
UI(Presentation)層 : /presenter
Infrastructure層 : /infrastructure
ApplicationService層 : /usecase
DomainService層 : /domain/service, /domain/repository
DomainModel層 : /domain/model
UI(Presentation)層 : /presenter
UI(Presentation)層はpresenterとして表現しています。
ユーザーからの入力を受け付け入力をサニタイジングしApplicationService層に渡す役割を持ちます。
また、ApplicationService層からの出力を受け取り、外界とのやり取りに適した形式に変換を行います。
Infrastructure層 : /infrastructure
Infrastructure層はinfrastructureとして表現しています。
データの永続化,メッセージ送信などの技術的機能をここで定義します。
/domain/service/xxx_repository.goに記載するinterfaceの実装をここで行います。(キーワード:DIP, 依存関係逆転の原則)
ここで実装を行うことで、永続化の方法をmysqlからpostgresに変更したい、といった場合にも変更が容易になります。
ApplicationService層 : /usecase
ApplicationService層はusecaseとして表現しています。
アプリケーション固有のロジックを定義し、処理順序やドメイン層とのデータの流れをここで管理します。
DomainService層 : /domain/service
DomainService層は/domain/service, /domain/repositoryとして表現しています。
エンティティや値オブジェクトの責務ではないドメインモデルのロジックをここで管理します。
/domain/repositoryにはDIPのためのinterfaceを定義します。
DomainModel層 : /domain/model
DomainModel層は/domain/modelとして表現しています。
エンティティ、値オブジェクト、集約といった「ドメインオブジェクト」をここで管理します。
/interactor
interactorはDIコンテナの役割をします。
最終的な依存の解決はinteractorが行います。
参考 ※本当に大変参考にさせていただきましたm(_ _)m
Goのpackage構成と開発のベタープラクティス
Goのサーバサイド実装におけるレイヤ設計とレイヤ内実装について考える
Challenge to Advanced API Architecture in Go
サンプルプロジェクトで利用したライブラリ
- フレームワーク : echo
- ORM : gorm
- config : viper
- test : assert, sqlmock
- マイグレーション : goose
- 依存管理 : dep
- linter : gometalinter
- タスクランナー : task
まとめ
普段はgolangは趣味で触っており、チーム開発への導入は初めてでしたが、シンプルな言語機能や周辺ツールのおかげで、開発者によってコードの品質がブレにくく、チーム開発にとても向いている言語だと思えました。
アーキテクチャの導入は、最初は概念を理解するまでに苦労した部分もありましたが、実際に導入してみると、明らかにアプリケーションの持続・保守性が高くなったことがわかります。
責務が明確になったことで実装で迷うことが少なくなり、実装時の不明瞭なことが少なくなる分、ジュニアメンバーとベテランメンバーのコードの品質のぶれがさらに少なくなり、導入してよかったなと思えます。
サンプルプロジェクトで利用したライブラリについては、また別の記事で紹介できればと思います。