Entityクラスのドキュメントと型注釈からInteractorもRouterも生成する
最初の動機: Entityクラスの型注釈とドキュメントからTypeDocでJSONを生成して、そのJSONを基にInteractorやRouterも記述できるのでは? Interactorでやること
RepositoryからEntityを取得する。
Entityクラスのメソッドを呼び出すのに必要なものを取得する。
Entityクラスのメソッドを呼び出す。
RepositoryにEntityを保存する。
Routerでやること
リクエストにバリデーションをかける。
Interactorを呼び出す。
レスポンスを返す。
どのようなEntityクラスのメソッドが、どのようなInteractor、Routerになるのか?
例えば、次のようなシグネチャを持つEntityクラスがある。
スバラシの掲示板機能に投稿をする組織の単位である「グループ」を表すエンティティクラス。 Groupクラスのcreateメソッドに注目されたい。
Groupオブジェクトとその周辺の集約ルートにあたるオブジェクトを生成する。
GroupMemberDirectory: グループのメンバーの参加、退出、役割を管理する。
GroupPermissionDirectory: グループ自体がインスタンス内で持っている権限を管理する。
GroupProfile: グループのプロフィール。
学生認証を受けているか確認するために、UserProfileを受け取る。
スバラシでは、グループを作成する前に学生認証を受けて自分のUserProfileを作成する必要がある。
UserProfileが正しく取得されているものであることを確認するために、UserProfileと同じIDを持つMyselfCertificateを受け取る。
UserProfileやMyCertificateは、Entityのメソッドを呼び出す前にInteractorで準備されていることを期待している。
code:ts
const groupTypeSymbol = Symbol('groupTypeSymbol');
/** Groupクラスの各プロパティの型 */
export interface IGroupProperties {
readonly id: TNominalPrimitive<TId, typeof groupTypeSymbol>;
readonly createdAt: Date;
}
export class Group<
Id extends IGroupProperties'id' = IGroupProperties'id', {
public readonly id: Id;
public readonly createdAt: CreatedAt;
public static create<
Name extends IGroupProfileProperties'name', UserId extends IUserProperties'id', Id extends IGroupProperties'id' = IGroupProperties'id', (param: {
readonly name: Name;
readonly displayName: DisplayName;
readonly userProfile: UserProfile<UserId>;
readonly myselfCertificate: MyselfCertificate<UserId>;
}): TResult<
{
readonly group: Group<Id>;
readonly groupMemberDirectory: GroupMemberDirectory<
Id,
;
readonly groupPermissionDirectory: GroupPermissionDirectory<Id, 'default', readonly []>;
readonly groupProfile: GroupProfile<Id, Name, DisplayName, readonly []>;
},
UserProfileExpiredException
;
// 以下省略
}
Entityクラスのメソッドのシグネチャは、型引数の決定によって実際に渡すべき引数が導かれるような記述になっている。
Interactorの引数は、Entityクラスのメソッドの型引数を見て決めれば良い。
Interactor内部で準備するものは、Entityクラスのメソッドの引数の型を見て決めれば良い。
すると、Group.createに対応するInteractorのシグネチャは次のようになる。
code:ts
// 一部省略
const createGroupInteractor = (param: {
readonly name;
readonly displayName;
readonly userId;
}): TResult<
// 以降省略
RouterはHTTPリクエストにバリデーションをかけてからInteractorを呼び、Interactorから返ってきた結果をシリアライズしてHTTPレスポンスを返すだけなので、Interactorのシグネチャが決まっていれば生成できる。
プログラムの方針
利用者に設定ファイルを用意させ、次の項目を記述させる。
entry-points: Entityクラスのメソッドの一覧
services: Entityの外側にあるが、Interactorで使用する必要のあるサービスの一覧
containers: いわゆるDIコンテナのインターフェースの一覧
関数やメソッドの引数と戻り値の情報を集める。
serviceやcontainerのメソッドの引数と戻り値の情報を集め、entry-pointに渡す値を得るために用いるべき関数やメソッドを明らかにする。
型から、その型の値を得るのに用いるべき関数やメソッドを引けるような辞書をつくる。
entry-pointが使用するserviceやcontainerのメソッドを決める。
interactorの引数が決まる。