Next.js + サーバーサイドTypeScript + 関数フレーバーでクリーンなアプリを作ったので実装意図とか書く Advent Calendar 2022
の5日目。株式会社mofmofに生息しているshwldです。
前日はプロジェクトのアーキテクチャについて書きました。
型で状態を表現する
TypeScriptを利用するにあたり、最初はクラスベースでドメインモデルを書いており、ドメインモデルを表現する際に状態をプロパティとして表現していました。
例えば isDeleted
のようなプロパティで削除状態を表現したりしていました。
class Story {
readonly isDeleted
...
}
プロパティで状態を表現し、リポジトリ層で状態を永続化するという流れで考えていたのですが、そうなるとリポジトリ層で状態を見て分岐するという処理が多数出来上がりました。
このあたり、リポジトリパターンを用いた開発に不慣れというのもあるのですが、モヤモヤとしていました。
ちょうど今年は、Haskellに入門してみたり、DDDを関数型で実装するDomain Modeling Made Functional を見てみたり(読みきれてないですが...)
このあたりを導入してみたいなと思っていたタイミングで、TypeScriptによるGraphQLバックエンド開発 ──TypeScriptの型システムとデータフローに着目した宣言的プログラミングの考え方を見たので、自分もこの流れに乗ってみました。
上記記事にもありますが、イベントによる状態の遷移一つ一つを一つの方として捉えることで、イベントがとても局所的なアクションになり、責任が単一に近づきます。
例えば、
export interface Account_BuiltAttributes {
id: ID;
createdAt: Date;
updatedAt: Date;
name: string;
createdById: string | null;
__state: 'Built'
}
のような型で新規作成を表現しました。
これとは別に、編集のイベントの結果は __state
を別のものになるように設定し、型で区別できるようにしています。
export interface Account_DraftAttributes {
...
__state: 'Draft'
}
こうすることで、永続化の際には、Account_DraftAttributes
のように、あるイベントを通った状態のデータのみを入力にすることで、その状態を永続化するリポジトリの処理も単一責任にしつつ、利用シーンを明確にすることができました。
参照した記事と違うのは、バリデーション済かどうかを型として表現していないところですが、それに関しては後日の記事で書こうと思います。
次回予告
明日は関数の合成について書きます