Go+Reactのアプリ開発メモ(クリーンアーキテクチャ)
クリーンアーキテクチャの勉強を兼ねて、アプリを作ろうと思いました。
その過程で自分自身が混乱しないようにするために記事にします。
全体像
レイヤー層
レイヤー名 | 役割 | 用語 |
---|---|---|
handler層 | URLと1対1 | Controller/Handler |
usecase層 | ユースケースごとのロジック | Usecase/Interactor |
service層 | ビジネスロジック、個別機能実行 | Service |
repository層 | DBとのやりとり | Repository |
データ構造
名前 | 役割 | 用途 | 使用する処理層 |
---|---|---|---|
model | DBと対応 | Gorm用、永続化 | Repository |
domain | アプリの本質的なデータ構造 | ビジネスルール | Usecase/Service |
dto | 外部とのデータ受け渡し | APIリクエスト/レスポンス | Handler |
types(TS) | フロントの型 | UI表示 | フロント |
命名規則
言語 | 命名規則 | 使用例 | 使用場所 |
---|---|---|---|
Go | PascalCase | UserName | 構造体、フィールド名 |
Go | camelCase | getUser() | 変数名、非公開フィールド |
TS | PascalCase | UserFormProps | 構造体、コンポーネント名 |
TS | camelCase | userEmail | 変数名、関数名 |
JSON | snake_case | user_id | APIリクエスト/レスポンス |
変換と処理の流れ
タイミング | 次の処理 | 変換前 | 変換後 | 言語 | 備考 |
---|---|---|---|---|---|
リクエスト | バックエンド | camelCase | snake_case | JSON | |
Router | Handler | snake_case | PascalCase(DTO構造体) | Go | JSONタグによる変換 |
Handler | Usecase | DTO構造体 | domain構造体 | Go | 変換してから受け渡す |
Usecase | Service | - | - | Go | domain構造体 |
Service | Repository | - | - | Go | domain構造体 |
Repository | Service | domain構造体 | model構造体 | Go | 変換してから返却 |
Service | Usecase | - | - | Go | domain構造体 |
Usecase | Handler | domain構造体 | DTO構造体 | Go | 変換してから返却 |
Handler | フロント | DTO構造体 | snake_case | Go | JSONタグによる変換 |
レスポンス | リクエスト | snake_case | camelCase | TS | 型変換 |
レイヤー層(処理層)について
レイヤー層
レイヤー名 | 役割 | 用語 |
---|---|---|
handler層 | URLと1対1 | Controller/Handler |
usecase層 | ユースケースごとのロジック | Usecase/Interactor |
service層 | ビジネスロジック、個別機能実行 | Service |
repository層 | DBとのやりとり | Repository |
handler層
ルートとhandlerは1対1の関係です。
処理ごとにhandlerに関数を設けました。
責務は、外部とのやりとりです。
リクエストを受け取り、JSONとしてフロント側に返す役割です。
それ以外の処理はusecase層に渡します。
handler層ではDTO構造体としてデータを保持し、
usecase層へ渡すときに、domain構造体に変換してから渡します。
usecase層
handlerからdomain構造体のデータを受け取ります。
serviceとの橋渡しを行います。
トランザクション処理を行うときは、この層で行いました。
それにより、複数同時登録or更新にて、どれか1つでも処理が失敗するとロールバックされます。
service層との役割を完全に理解しきれていないので手探り状態です。
service層
usecase層からデータをもらい、repository層へ渡します。
1番記述が少ないと感じている層です。
データの保存や画像の保存もこの層で行います。
1つのデータに対して何かしらの処理を行うのがservice層だと思っています。
repository層
DBとのやりとりを行う場所です。
domain構造体を受け取り、model構造体へ変換、返却時はdomain構造体に戻します。
CRUD処理(取得、登録、更新、削除)を担当してくれます。
DBとrepositoryは1対1のイメージです。
そのため、他のテーブルからデータを取りたいときは別repositoryを呼び出します。
外部キーで繋がっている別テーブルに登録をするとき、
repository層でトランザクションを使うことでデータの整合性を保つようにしてます。
usecase層は全体のデータ管理、repositoryは外部キーのあるデータの整合性を保つ役割だと思っています。
フロントエンドについて
TypeScriptを使用し、フロントを作成しています。
変数名はキャメルケースを使うのが一般的らしいので、それに倣ってます。
しかし、APIはスネークケースを使うのが一般的らしいです。
そのため、Goへリクエスト/レスポンスするときは、キャメルケース、スネークケースへの変換をしてます。
思ったより面倒なので、共通化して対応しました。
変換用のライブラリがあるのでそれを使ってます。
npm install camelcase-keys snakecase-keys
Discussion