前回までのあらすじ

  • 動機と目指すものを話した
  • 開発環境とGoの基礎を作った

全体設計+Userルーティング実装

まずはアプリケーション全体の設計をどうするかを検討していった。
今回はレイヤードアーキテクチャ+DIというのは基礎としてあったので、最低限Controller(リクエスト受けるとこ)→Service(処理するとこ)→Repository(DB)となる…はず。
このあたりの考え方は、以下のURLの記事読んだりして自分なりに解釈して進める→フィードバックを貰うという流れで理解を深めていったので、最初の方は結構苦戦した。

https://qiita.com/yoshinori_hisakawa/items/a944115eb77ed9247794
https://qiita.com/nrslib/items/aa49d10dd2bcb3110f22
https://qiita.com/muroon/items/8add8da911341312176d

色々あって理解したことは、

  • 外側のレイヤーを呼び出す(依存する)のは駄目。
  • レイヤー間のやりとり(関数呼び出し)にインターフェースを用いることで、レイヤー外の具体的な処理を知る必要がなくなる
  • 引数や返り値が専用のStructになることで、Mock含めインターフェースと実装を作りやすくなる。

結果として、依存が少なく、結果として変更が他の箇所に影響しづらくテストもMockを用いて書きやすいという状況が重要なのであって、あまり正しい形にこだわる必要もないのかなというのが現在の理解。
この辺はそのうち別記事に書きたいと思う。

さてそんな考え方に基づいてUserのCRU(D)を実装していく。
まずは、api.goにDBやDI初期化周りを追加。
ここはエントリーポイントから呼び出されるので、アプリケーション起動時に一回呼べば良い処理を書いておく。
ここから、/user/にアクセスがあった場合に作成したUserRouterに遷移するように記述。
これから追加されていくであろうルーティングも同様となる。

net/http周りの参考にした記事はこの辺。
https://qiita.com/convto/items/2822d029349cb1b4df93
https://qiita.com/taizo/items/bf1ec35a65ad5f608d45

ついでにリクエスト/レスポンスで必要となるjsonの扱いに関してはここ。

https://qiita.com/nayuneko/items/2ec20ba69804e8bf7ca3
https://qiita.com/nyamage/items/e07de57d486238567ba7

MySQLへの接続はここ。
https://akrfjmt.hatenablog.com/entry/2018/05/15/014455
https://www.sambaiz.net/article/189/

router/配下では、PathとMethodを見てどのコントローラーのメソッドを呼び出すのかを記述。
書いてて思ったけど、該当なしの場合ErrorControllerでも作ってそこに飛ばす方がスマートだったかもしれない。
あと、この辺の処理がnet/http の弱い部分でライブラリを入れたくなる部分。
もっときれいに書けるとは思うけど、基本を学ぶ意味ではとりあえずはこれでいいのかも。

そしてcontroller/
ここでリクエストの内容確認と必要な情報を抽出してからの処理部分への受け渡し。
そして返ってきたデータのPresenterへの受け渡し。
controller作成時にInteractorとPresenterを注入しているのでそれを使います。
この辺、最初は困惑したけど組んでみるととても直感的に思えてくるから不思議。
確かに動作変更時の変更箇所が集約されるので考えた人は賢い。
データの詰め替えが結構面倒だけど、今回のコンセプト的に必要なので…。

次はInteractor/
ここはServiceを複数使って処理をするところ。
けどUser周りは単純な処理しかなくて1Serviceで収まっちゃうのであんまり恩恵がない。
前述の通りServiceを使うので、注入されてる。
複数Serviceを使うこともあるので使うやつを入れとく。
この辺の注入の手間を考えるとDIコンテナに繋がるんだろうけど、今回はそこまで考えてない。

続いてService/
ここで細かい単位に分割した処理を行ないリポジトリへのアクセスを担当。
というわけで注入されるrepository。
分割するほどでもない処理を正確に分割しているので、解説すべきコードがほぼない…。

最後にrepository。 DBまで長かった。
実際のDBに対して処理を行っている部分。
Transactionを含めたかったので全てのModelに対するリポジトリを作成し、RunTransaction()を用いないと更新系処理を実行できないように組んでいる。
Transactionを毎回書かなくていいのと、connectionが表面に出てこないまま使い回すことができる点でいい構造だと思うんだけど、如何せん直感的とは思えないのでもっといいやり方を考えたい。
ここをMockにしちゃえばテストもしやすいよね。

参考URLはこちら。
https://qiita.com/mayah/items/a235a52a336095545e9d
https://qiita.com/tenntenn/items/dddb13c15643454a7c3b
https://precure-3dprinter.hatenablog.jp/entry/2018/11/22/Golang%E3%81%A7%E3%83%88%E3%83%A9%E3%83%B3%E3%82%B6%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3%E3%82%92%E4%BD%BF%E3%81%86%E8%A9%B1
https://blog.withnic.net/2018/08/golang%E3%81%A7transaction%E5%87%A6%E7%90%86%E3%82%92%E8%80%83%E3%81%88%E3%82%8B/

一番上のURLは参考になったし、問題点もその通りだと思うのでもっと自分なりの方法を見つけていきたいね。

ついでに各層ごとにinput/output用のstructが存在していて引数や返り値がこれに集約されている。
この構造は見た目わかりやすいことと共に、パラメータが増えた場合の引数変更を行わなくてもstructのフィールドに追加すればいいよねっていう便利さもあったりする。

内部処理についてはただの登録や更新、参照なので割愛。
一応TokenでUserを取る処理があるので、Tokenは登録時にUUIDv4を用いて一意に生成。
Tokenで検索する処理のパフォーマンスを考えてUniqueIndexをTokenに付与しているくらい。

この項は今回のミッションの一つの肝となる部分だったので長くなった。
次以降は短いはず。

次回: (3) ミドルウェアとエラーハンドリング