Go言語を学習し始めて、簡単なCRUDアプリをクリーンアーキテクチャで作成するまで③
このシリーズ目次
- Go言語を学習し始めて、簡単なCRUDアプリをクリーンアーキテクチャで作成するまで①|Wano Group Developers Blog
- Goを学習し始めて、簡単なCRUDアプリをクリーンアーキテクチャで作成するまで②|Wano Group Developers Blog
- Go言語を学習し始めて、簡単なCRUDアプリをクリーンアーキテクチャで作成するまで③←今ココ
はじめに
Goを学習し始めて、簡単なCRUDアプリをクリーンアーキテクチャで作成するまで②|Wano Group Developers Blog
の続きとなります。
しかしこの記事から読まれても全く支障がないので、お好きに読まれてください!
今回は、②で作ったCRUDのWebアプリをクリーンアーキテクチャに書き換えてみる編です。
③クリーンアーキテクチャで②のアプリ作り変え
1.まずクリーンアーキテクチャとやらを学ぶ
まずボブおじさんのブログ読む
The Clean Architectureとは、ボブおじさん(Robert C. Martin)によって提唱されたもののようです。
依存関係をコントロールし持続可能なソフトウェアを実現するための体系的な手法。
メリットは
– フレームワークに依存しない
– テストが可能で書きやすい
– UIから独立
– データベースから独立
– 外部機能独立
なるほど。
Clean Coder Blog
(上記サイトより引用)
上記が有名なクリーンアーキテクチャの同心円の図なのですが、正直さっぱりわからん、、 となりました。
前提である、DIとSOLIDの法則を学ぶ
【必須科目 DI】DIの仕組みをGoで実装して理解する – Qiita
【ボブおじさんのClean Architectureまとめ】オブジェクト指向 ~SOLIDの原則~ – Qiita
とりあえず前提らしいのでDIとSOLIDの法則の概念をつかむことにしました。
上記の@yoshinori_hisakawaさんの記事がとても感覚を掴むのに役に立ちました。
つまづきポイントは、DIとDIP(SOLIDの法則のD)だと思うのですが、双方の解説が非常にわかりやすい。
- DI(Dependency Injection)
> オブジェクトをインタフェースとして定義し、使う側は実装オブジェクトでなく、インタフェースを利用するようにする。
実装オブジェクトは外部からそのインタフェースに外部から注入する事で、実装を入れ替えたりできる。
なるほど。わかるようなわからないような。
以下の例を見ていきましょう。
//post_service.go @fukubaka0825
package service
import (
"sample-clean-arch/domain/model"
"sample-clean-arch/usecase/presenter"
"sample-clean-arch/usecase/repository"
)
type postService struct {
PostRepository repository.PostRepository
PostPresenter presenter.PostPresenter
}
type PostService interface {
Create(post *model.Post) error
Get(posts []*model.Post) ([]*model.Post, error)
GetForEditPost(id int) (*model.Post, error)
Update(post *model.Post) error
Delete(post *model.Post) error
}
func NewPostService(repo repository.PostRepository, pre presenter.PostPresenter) PostService {
return &postService{repo, pre}
}
//以降でpostServiceをPostServiceたらしめるが略
//main.go
func main(){
//repoはrepository.PostRepositoryインターフェース実装されてればなんでも良い
repo := xxx...
//preはpresenter.PostPresenterインターフェース実装されてればなんでも良い
pre := xxx...
se := service.NewPostService(repo,pre)
}
mainで直接service呼ぶなんてことないけど、説明の便宜上そうしてみています。
上のソース(post_service.go)のように、postService構造体のメンバがrepositoryとpresenterそれぞれのインターフェース型であるおかげで、NewPostServiceコンストラクタでの引数もそれぞれの当然インターフェース型。
なのでmainでpostServiceをNewするときも、引数であるrepoとpreはそれぞれのインターフェース満たしてればなんでもいいことになります。実装じゃなくて抽象(インターフェース)に依存ってやつです。
こうすることで抽象に依存させて、それぞれの層を疎結合(相手の中身を知らない)にして、独立してテストしやすかったり変更できたりとメリットを生み出すことができるようですね。なんでDIしなきゃいけないんだっけってことから思考すると掴みやすいと思います(僕が掴んでるとはいってない)
- DIP(依存性逆転の原則)
ルール
・上位のモジュールは下位のモジュールに依存してはならない。
・どちらのモジュールも「抽象」に依存すべきである。
・「抽象」は実装の詳細に依存してはならない。
・実装の詳細が「抽象」に依存すべきである。
実装の詳細が抽象に依存すべきなのは、DIの感覚があれば理解できるのかなと思います。
しかし、外から中(下位から上位)にしか依存しちゃダメだよってしても以下みたいな不都合が起きてきます。
「usecase層のservice、下位のinfrasrtucture層のrepositoryに依存しちゃいたい」(ダメな例)
そういう時に依存性逆転は使えます。
「usecase層にrepository IFを公開して、そこにserviceを依存させる。下位層のrepositoryはそのIFを元に実装する」(良い例)
今までなら上位モジュールが下位モジュールに依存してしまっていたところを逆転させて、安定した関係性に正せる。 こうすることでそれぞれのレイヤーが高い独立性をもてるようになる(僕の理解ですので間違っているかもしれません。)
(画像は@yoshinori_hisakawaさんの記事からの引用)
再度ボブおじさんの記事読む
結構スラスラ読めるようになっていました(これが人間の慣れか)
大事なところはたくさんあるのですが、以下抜粋。
This layer is where all the details go. The Web is a detail. The database is a detail. We keep these things on the outside where they can do little harm.
This layerはinfrastructure層。
ここ凄い大事だなと思いました。DBとかWebとか詳細にスギナイヨネ。
>Only Four Circles?
という疑問に対しては
>The inner most circle is the most general.
であればそんなことないよって。4つのレイヤーで思考停止しないようにしよう。
2.作ったCRUDアプリをクリーンアーキテクチャに改造
Clean ArchitectureでAPI Serverを構築してみる – Qiita
[DDD]ドメイン駆動 + オニオンアーキテクチャ概略 – Qiita
Goでクリーンアーキテクチャを試す | POSTD
GitHub – bxcodec/go-clean-arch: Go (Golang) Clean Architecture based on Reading Uncle Bob’s Clean Architecture
ここら辺を参考に以下を作成
GitHub – takafk9/sample-clean-arch
やっぱり手を動かして自分で作ってみると全然理解度が変わってきますね。(理解できたとはいってない)
クリーンアーキテクチャの書籍を読んだのでAPIサーバを実装してみた – Qiita
>3.DIコンテナ
registryではコンストラクタインジェクションを用いて全ての依存を解決している。
ここも以下の記事にまとめたので参照してほしい
https://qiita.com/yoshinori_hisakawa/items/a944115eb77ed9247794
一部抜粋すると、@yoshinori_hisakawaさんの自作DIコンテナを参考に実装しました。パッケージ構成もかなり参考にさせていただきました。(大大感謝)
//app_registry.go@fukubaka0825
package registry
import "github.com/jinzhu/gorm"
type interactor struct {
memberInteractor
postInteractor
}
type Interactor interface {
MemberInteractor
PostInteractor
}
func NewInteractor(conn *gorm.DB) Interactor {
return &interactor{
memberInteractor{conn},
postInteractor{conn},
}
}
//post_registry.go@fukubaka0825
package registry
import (
"github.com/jinzhu/gorm"
"sample-clean-arch/infrastructure/web/handler"
"sample-clean-arch/infrastructure/datastore"
"sample-clean-arch/interface/controllers"
"sample-clean-arch/interface/presenters"
"sample-clean-arch/usecase/presenter"
"sample-clean-arch/usecase/repository"
"sample-clean-arch/usecase/service"
)
type postInteractor struct {
conn *gorm.DB
}
type PostInteractor interface {
NewPostHandler() handler.PostHandler
}
func NewPostInteractor(conn *gorm.DB) PostInteractor {
return &postInteractor{conn}
}
func (i *postInteractor) NewPostHandler() handler.PostHandler {
return handler.NewPostHandler(i.NewPostController())
}
func (i *postInteractor) NewPostController() controllers.PostController {
return controllers.NewPostController(i.NewPostService())
}
func (i *postInteractor) NewPostService() service.PostService {
return service.NewPostService(i.NewPostRepository(), i.NewPostPresenter())
}
func (i *postInteractor) NewPostRepository() repository.PostRepository {
return datastore.NewPostRepository(i.conn)
}
func (i *postInteractor) NewPostPresenter() presenter.PostPresenter {
return presenters.NewPostPresenter()
}
コンポーネント関係で、handler呼び出す時にとても冗長になってしまうので、こいつで一行でnewできるようにしました。
DIPとDIについて、だから必要なのかぁと本当に慣れたのはこのフェーズでですね。
アクターが違うから、interface層としてpresenterとcontrollerは分けるべきで、usecase層のserviceが同じ層のpresenterIFに依存して、そのserviceにcontrollerが依存するから間接的にcontrollerはpresenterIFに依存するという部分が面白かった。
前回と同様至らないところはたくさんありますが、クリーンアーキテクチャってどういう特徴があって、どういう旨味があるかというところを実感できたので十分かなと思います。
3.これから実務で理解を深め、サービスに落とし込んでいく
印象としては、こんな簡単なものでもこのコード量になるのかぁって感じではありました。
工数との兼ね合いはありそうですが、サービスがスケールしていくと仮定して、MVCでベタベタにモノシリックに作ってしまった場合の改修や保守コスト、技術的負債を返していく労力を考えれば極力クリーンな構造で作りたいなぁと思いました。
しかし理想ばっかりインプットしても机上の空論なので、こういった視点も深めつつ、とりあえずは一刻も早くVideo Kicksチームの一員として戦力になれるよう、業務で悪戦苦闘しながら実装力メインで鍛えていきます。
個人としては僕はもともと金融系SIで働いていて、Dev|絶壁|Ops開発だったことの反動で、devops思想に取り憑かれているので、インフラのコード化やその辺の組織論文化論を中心に深掘っていき、その辺のアウトプットもしていこうと思っています。ここら辺を徐々に自分の強い領域にして業務でも貢献していきたい。
Web系一年目、頑張りの軌跡もWanoの技術ブログにもちょこちょこアウトプットしていきますので暖かく見守りながら、愛のまさかりをぶん投げてください。
次回は、「AWS 認定ソリューションアーキテクト – アソシエイトとってみた」で来月投稿予定です!(これでとれなかったらどうしよう、、、笑)
== 以下雑談 ==
先日いってきたmercarigoというイベントで、二つも可愛いgopher君ステッカーを手に入れました。
イベントも素晴らしく勉強になったし次回も参加必須(多分当たらないけど笑)。
(うちでも僕が脱ビギナーできたら主催したいな)
あとこのiphoneケース欲しい、、、
The Go gopher was designed by Renée French.