前回のTerraformでAWSサーバーレスなサービスのインフラ構築をコード化する - Inside Closure - にへろぐではサンプルとして空っぽのAPIを用意しました。せっかくなのでこのサンプルを動くように実装してみました。
何を作ったのか
特にネタがなかったのでTodoを登録・一覧・取得・更新・削除できるだけのREST APIを作成しました。
POST /todos
: Register todoGET /todos
: List todosGET /todos/:id
: Get todoPUT /todos/:id
: Update todoDELETE /todos/:id
: Delete todo
アーキテクチャ
目指したところ
Clean Architecture(日本語訳)の考え方を取り入れたアーキテクチャにしようと思いました。*1
Clean Architectureとはざっくり下記のようにして抽象的な実装(ビジネスロジックなど)を具体的な実装(フレームワークやDBなど)から切り離して保守性を高めようとするアーキテクチャです。
- システムを4つのレイヤ(
Entities
,Use Cases
,Interface Adapters
,Frameworks and Drivers
)にわける - レイヤ間の依存を具体的なレイヤから抽象的なレイヤへの一方通行にする(
Frameworks and Drivers
→Interface Adapters
→Use Cases
→Entities
)
レイヤ間の依存と処理/呼び出しの順序が一致しない場合は依存性逆転の原則を駆使するなどしてレイヤ間の依存の方向を整える必要があります。
図の方がわかりやすいです。(原文より)
実際のところ
今回はClean Architectureの原則に全て従うのではなく具体レイヤから抽象レイヤへ一方通行にして抽象レイヤの保守性を上げる
という目的/考え方だけを取り入れた構成にしました。原則に全て従うのはお遊びだしめんどくさいため小規模な開発では実装が肥大してやりすぎ感がありデメリットがメリットを上回ると判断したためです。
具体的にはルールを下記のように崩してInterface Adapters, Frameworks and Drivers
からEntities and Use Cases
が一方通行になるところだけ原則に従いました。
Interface Adapters
の責務を具体レイヤ(Frameworks and Drivers
)と抽象レイヤ(Entities and Use Cases
)のマッピングとする。そして、全てのレイヤに依存して良いことにする。
Interface Adapters
とUse Cases
の間の矢印の向きは全て内向きになってます。*2
これだけでもひとまずServerless FrameworkやLambda、DynamoDBなどをやめたくなってもビジネスロジックは(ほぼ)再利用できるような気がします。たぶん。
実装
コードをGitHubにアップしました。
大まかなディレクトリ構成です。
$ tree -ad -I '.git' . ├── .circleci ... CircleCI用 ├── app .. アプリケーションコード │ ├── adapters ... Interface Adaptersレイヤ │ │ ├── commands │ │ ├── databases │ │ └── http │ │ ├── requests │ │ └── responses │ ├── commands ... Frameworks and Driversレイヤ(コマンド) │ ├── databases ... Frameworks and Driversレイヤ(DynamoDB) │ ├── entities ... Entitiesレイヤ │ ├── http ... Frameworks and Driversレイヤ(Serverless Framework) │ │ ├── controllers │ │ ├── middlewares │ │ ├── models │ │ ├── utils │ │ └── views │ ├── providers ... DI関連 │ └── usecases ... Use Casesレイヤ │ ├── implementations │ ├── inputs │ ├── outputs │ └── stores ├── dockers ... docker環境設定 │ ├── dynamodb │ └── serverless └── test ... テストコード ├── units │ ├── http │ │ ├── controllers │ │ └── middlewares │ └── usecases └── utils 33 directories
今回、middleware engineにはmiddyを使ってhttp周りの処理をミドルウェアに切り出しapp/http/middlewares
にまとめました。
aws-serverless-expressやserverless-httpでもよかったのですがServerless Framework
とExpress
のフレームワーク2つを使うことになるのが何となく嫌だったのでミドルウェアに特化しているmiddyを使ってみました。まだバージョンがalphaですので業務では使えないかもですがそこそこ使いやすい気がしました。
あと一応、tslintでtslint-config-standard
に準拠するようにとmochaでユニットテストを(できるだけ)書いてクリーンな状態を保つように心がけました。*3
使い方
dockerを使ってローカル環境で実行する方法とAWSで動かす方法があります。
docker環境で動かす
まずリポジトリをcloneします。
$ git clone -b v0.0.1 https://github.com/nihemak/aws-sls-spa-sample-api.git sample-spa-api $ cd sample-spa-api
そしてdocker環境のビルドとDynamoDB Localの初期化を行います。
$ npm run docker-build $ npm run docker-up-dev $ npm run docker-reset-tables $ npm run docker-down
最後にdocker環境でserverless-offlineを実行します。
$ npm run docker-up $ npm run docker-offline
するとAPIが使えます。
# POST /todos $ curl -X POST http://localhost:3000/v1/todos -H "Content-Type: application/json" --data '{ "text": "foo" }' # GET /todos $ curl http://localhost:3000/v1/todos # GET /todos/:id $ curl http://localhost:3000/v1/todos/:id # PUT /todos/:id $ curl -X PUT http://localhost:3000/v1/todos/:id -H "Content-Type: application/json" --data '{ "text": "bar", "checked": true }' # DELETE /todos/:id $ curl -X DELETE http://localhost:3000/v1/todos/:id
DynamoDB Localの管理画面へは下記からアクセスできます。
AWS環境で動かす
TerraformでAWSサーバーレスなサービスのインフラ構築をコード化する - Inside Closure - にへろぐの使い方の通りです。
ただ今回のコードにはタグv0.0.1
を打ってあるのでそこだけは変える必要があります。具体的にはCodeCommitのリポジトリにGitHubのリポジトリをpushする箇所が下記に変わります。
# spa infra repository... $ git clone -b v0.0.1 https://github.com/nihemak/aws-sls-spa-sample-terraform.git sample-spa-infra $ cd sample-spa-infra $ git checkout -b master $ git push ssh://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/foobar-sample-spa-infra $ cd .. # spa api repository... $ git clone -b v0.0.1 https://github.com/nihemak/aws-sls-spa-sample-api.git sample-spa-api $ cd sample-spa-api $ git checkout -b master $ git push ssh://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/foobar-sample-spa-api $ cd .. # spa web repository... $ git clone -b v0.0.0 https://github.com/nihemak/aws-sls-spa-sample-web.git sample-spa-web $ cd sample-spa-web $ git checkout -b master $ git push ssh://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/foobar-sample-spa-web $ cd ..
aws-sls-spa-sample-terraform.git
とaws-sls-spa-sample-api.git
のclone元がv0.0.1
になります。
まとめ
今回はServerless FrameworkとTypeScriptでTodoを管理するREST APIのサンプルを作ってみました。
- Clean Architectureの考え方の理解とやりすぎると実装コストが膨らみそうなことの実感を得ることができました
- 普通にTypeScriptを書くのはほぼ初めてだったので苦戦するところもありましたが文法が少し分かるくらいにはなれた気がするのでよかったです
*1:ちょうど書籍のClean Architecture 達人に学ぶソフトウェアの構造と設計を読んでいたというだけの理由で
*2:他のレイヤ間も依存性逆転の原則を使って矢印の向きを内向きにすることはできると思いますがレイヤ間マッピングだらけになると思います
*3:心がけただけです