【Typescript】Clean Architecture + DDDでAPIを実装してみた

【Typescript】Clean Architecture + DDDでAPIを実装してみた

2021年12月18日
アーキテクチャ

Typescript + ​​Clean Architecture + DDDでAPIを実装してみました。

ソフトウェア要件の理解

今回は簡単なメンバー管理システムとして作っていきました。まずソフトウェア要件としてどんなものがあるか挙げていきます。

このように要件を挙げていくことで曖昧な要件から明確な仕様へと発展させていきます。本来なら開発するソフトウェアに詳しい人からヒアリングするのですが、今回は個人開発なので自分で挙げていきました。

  • メンバーを管理するシステム
  • メンバーは1つの部署に所属し、役職が付いている
  • メンバーの基本情報(名前、性別、生年月日など)を入力して登録する
  • メンバーの情報は更新や削除ができる
  • メンバー一覧や詳細を見ることができる
  • 部署一覧と所属しているメンバーの人数を見ることができる
  • 役職一覧と役職ごとのメンバーの人数を見ることができる
  • メンバーにはそれぞれ雇用形態がある
  • 雇用形態一覧と雇用形態ごとのメンバーの人数を見ることができる
  • メンバーの給与情報を登録・削除できる
  • 給与情報一覧を見ることができる

 

ユースケース図の作成

ソフトウェア要件を挙げたらユースケース図を作っていきます。このソフトウェアで何ができるのかをユーザー目線で表現します。

今回は管理者のみ使う想定となっており、主にメンバー管理に関するユースケースが想定されます(認証系は省略しています)。

 

エンティティの抽出

ソフトウェア要件やユースケース図が作成できたら、モデリング(エンティティの抽出)していきます。今回は以下のエンティティ候補が抽出されました。またユビキタス言語として用語を統一させておきます。

その後、モデリングの流れとしてエンティティを識別する属性・振る舞いやバリデーションの検討などがありますが、割愛します。

 

Typescript + Clean Architecture + DDDでAPIを開発する

本記事ではメンバー関連の一連の処理を中心に取り扱います。Clean Architectureの中心部分から説明していきます。

Enterprise Business Rules

Enterprise Business Rulesではアプリケーション全体の最重要ビジネスルールをカプセル化したドメインを格納しています。

まず先程定義したMemberエンティティをドメインのエンティティとしてどう定義していく考えていきます。

Memberの情報として必要なデータを値オブジェクトとして定義し、それらをまとめたMemberProfileという値オブジェクトを作成します。このMemberProfileにはMember自身の情報のみ持つようにしています。

次にMemberProfileに一意の識別子であるMemberIdをもっとMemberというエンティティを作成しました。

次にMember自身の情報として持っていないが、役職や部署などMemberに関連づけられたデータを持ったMemberRelationというエンティティとMemberの識別子を持っていないMemberRelationAttributeという値オブジェクトを作成しました。

MemberRalationエンティティのstaticなfromメソッドはプリミティブなオブジェクトからエンティティに変換する際に使われます。

Memberに関連する値オブジェクトとエンティティを作成したらそれらの振る舞いとバリデーションを実装していきます。

・Member.ts

 

・MemberRelation.ts

 

Application Business Rules

Application Business Rulesではアプリケーション固有のビジネスルールが含まれ、入出力するデータの流れを調整します。

MemberPortにインターフェイスを定義し、MemberGatewayで具象クラスを実装するようにしています。

MemberUseCaseではMemberControllerから受け取ったドメインをバリデーションなどで精査し、MemberGatewayの具象メソッドを呼び出すようにしています。ちなみにDIライブラリとしてTSyringeを使用しています。

・MemberPort.ts

 

・MemberUseCase.ts

 

Interface & Adapter

Interface & Adapterではドメインやユースケースに便利な形式からDBに便利な形式にデータを変換します。

MemberGatewayではMemberUseCaseから渡されたドメインをプリミティブなJson形式のオブジェクトに詰め替えてMemberDriverを呼び出しています。

なぜならDriver層にはドメインの知識を流出させたくないためであり、あくまでDriverに必要なデータだけをプリミティブな形で渡すようにします。

そしてDriverから返ってきた値をドメインに詰め替えてからUseCaseに返すようにします。

MemberControllerではリクエストされたパラメーターをドメインに詰め替えてUseCaseに渡し、UseCaseから返ってきたドメインをレスポンスとして返したい形式に変換して返します。またレスポンスとして返す時もリスポンスの型を定義し、変換して返すようにしています。

このようにこのレイヤでは主に入出力用に変換する処理を行っています。

・MemberGateway.ts

 

・MemberController.ts

 

Framework & Driver

Framework & Driverはフレームワークやツールで構成されています。

このレイヤにあるMemberDriverはDBから欲しいデータを取得しMemberGatewayに返すようにしています。今回はDBから取得(ORMはSequelizeを使用)していますが、APIならここでAPIリクエストをして返ってきたデータを返します。返すデータはEntityとして詰め直してGatewayに返します。

MemberGatewayでドメインからJson形式に変換したJsonオブジェクトの型とMemberGatewayに返すEntityの型をここで定義しています。

他にもRouterなどありますがここでは割愛しています。

・MemberDriver.ts