【Go】GormでシンプルなCRUDのREST APIを実装する

【Go】GormでシンプルなCRUDのREST APIを実装する

2020年8月6日
Go

【Go】DDD + レイヤードアーキテクチャでREST APIを実装する

Go + DDD + レイヤードアーキテクチャを本やネットで調べ、実際に動かしな…
mintaku-blog.net

先日上記記事でアウトプットした知識から、より理解を深めるため、ToDoリストをイメージしたシンプルなCRUDのREST APIをDDD + レイヤードアーキテクチャで実装してみました。

おさらい

レイヤードアーキテクチャの概念であるdomain層を確立させ、そこにドメインロジックを凝集させようという発想に、DDDのDIP(依存関係逆転の原則)を用いることで、上の図のような各レイヤごとの依存関係を意識した実装を行います。

実現したい機能を抽象化し、各レイヤごとの依存関係を意識しながら実装に落とし込んでいく作業が、従来のMVCで書いていた時よりも格段に頭を使うことを実感しています。

自分が実装している範囲内ではまだまだこのアーキテクチャを活かしきれていないですが、少しづつ理解を深めてものにしていきたいです。

 

domain層

Domain層は、システムが扱う業務領域に関するコードを置くところです。今回ToDoリストで必要なItemモデルを定義します。

今回はシンプルなToDoリストなのでこれだけですが、実際はここに実現したいシステムの業務領域を集約させて、全体の開発に展開させていきます。

・domain/model/item.go

 

repositoryはDBとのやりとりを定義しますが、技術的関心ごとはinfrastructure層に書くため、ここではインターフェースとしてメソッドを定義します。

今回のToDoリストでは一覧取得、タスク登録、タスク更新のメソッドを定義します。実際の処理はinfrastructure層に書き、domain層に依存するように実装します。

ちなみに削除も追加しようと思ったのですが、ToDoリストの場合はステータスで判断するため、削除はUpdate()で行われることを想定しています。

もし削除機能を追加する場合は、DeletedAtカラムを追加して論理削除するようにします。Gormだとdeleteするとデフォルトで論理削除し、データ取得時もDeletedAtがnullのレコードを取得するようにしてくれます。

・domain/repository/item.go

 

infrastructure層

infrastructure層ではdomain層に依存するように実装します。

先ほどdomain層でインターフェースとして定義したFindAll()、Create()、Update()の3つの技術的処理を書いていきます。

FindAll()では、ToDoリスト一覧を取得し、その結果を返すようにします。

Create()では、POSTで送られてきたパラメータを元にToDoタスクを登録します。

Update()では、PUTで送られてきたパラメータを元にToDoタスクを更新します。

・infrastructure/persistence/item.go

 

usecase層

今回はシンプルなCRUD処理しかしないので、この層の存在価値がわかりにくいですが、複雑なビジネスロジックがあるときは、この層の存在価値が発揮されるようです。

Itemの取得や登録などでDBにアクセスする時にdomain層のrepositoryを介してアクセスすることによって、infrastructure層ではなくdomain層のみに依存させています。

この層で送られてきたパラメータのバリデートしていますが、実際はこの層でバリデートするのが正しいのかちょっとまだわかっていないです。。

usecase層の責務として、interface層から情報を受け取り、domain層で定義してあるメソッドを用いてビジネスロジックを実行することを考えると、この層で行うのが正しい気がしています。

・usecase/item.go

 

interface層

interface層は、usecase層と切り離すことでリクエストやレスポンスの形に変わってもinterface層の修正だけで済むようになります。

Index()では、usecase層のメソッドを呼び出してToDoリスト一覧を取得し、返り値をJSON形式に変換しレスポンスとして返却しています。

Create()とUpdate()では、パラメータを引数にusecase層のメソッドを呼び出し、正常に成功した場合はHTTPレスポンスコード 200 No Contentで返すようになっています。http.Error(w, “成功”, 200)のように明示的にHTTPレスポンスコードを指定してハンドリングすこともできます。

DB接続やバリデートでエラーが発生した場合は、HTTPレスポンスコード 500でerr.Error()によるエラーメッセージを返却します。

・interface/handler/item.go

 

main.go

main.goでは依存関係の定義とルーティングを設定しています。各層の依存関係を定義することで、利用可能な状態にします。

  1. DBを操作するためconfig.Connect()でDB情報をitemPersistence(repository.ItemRepositoryを満たす)に注入
  2. そのitemPersistenceをusecase層のitemUsecase(repository.ItemRepositoryをフィールドに持つ)に注入
  3. 生成したitemUsecaseをitemHandler(itemUsecaseをフィールドに持つ)に注入

また、サーバ起動のタイミングであわせてCORSの設定もしておきます。

・main.go

 

DB設定

開発環境と本番環境のDB設定はdatabase.goに書いています。.env.developmentと.env.productionにそれぞれの環境におけるDB情報を定義しておき実行環境変数から取得しています。

db.AutoMigrate(&model.Item{})でサーバ起動時にItemテーブル生成のマイグレーションが実行されます。

ちょっと疑問なのが、すでに生成されたテーブルのカラム変更をしたい場合はどのようにするのでしょうかね。Railsのようにマイグレーションファイルで履歴管理するみたいな感じではないので、別途マイグレーション関連のツールを導入するのが正しいのでしょうか。

→ 調べてみたら以下のようにカラムの型変更などができるみたいです。

参考:

GOのORMを分かりやすくまとめてみた【GORM公式ドキュメントの焼き回し】 #Go - Qiita

はじめにGORMは公式ドキュメントがすごく良いのですが、途中から分かりづらかった…
qiita.com

 

・config/database.go

 

動作確認

最後にTalend API Testerを使って動作確認をします。

・POSTでToDoタスク新規登録

 

・GETで新規登録したToDoタスクを取得

 

・PUTで新規登録したToDoタスクを更新

 

・GETで更新したToDoタスクを取得

 

・バリデーションエラー時

 

・DB接続エラー時

 

参考

Goプログラミング実践入門

実践ドメイン駆動設計

DDDを意識しながらレイヤードアーキテクチャとGoでAPIサーバーを構築する #ドメイン駆動設計 - Qiita

今の現場で初めてDDDに触れたので、よく採用されるアーキテクチャとしてレイヤード…
qiita.com

今すぐ「レイヤードアーキテクチャ+DDD」を理解しよう。(golang) #Go - Qiita

今すぐ「レイヤードアーキテクチャ+DDD」を理解しよう。(golang)とはいっ…
qiita.com