#70 DMMF 集約の見直しとバリデーション関数実装 ─ 個人開発者向けのサービスの開発記録

tyshgc
·

本日の作業

  • ✅ 日報・分報のcommand/event/policyのまとめ書く

    • ✅ 集約見直しとバリデーション関数

    • 🚴 command関数の

    • 🚴 event, policy

  • 日報プレゼンテーション層(Remix loader/action)との結合

  • 日報画面実装

  • サインインの実装

    • 認証画面実装

    • 認証ロジック実装(主にClient側)

    • Server側の認証チェック実装

    • Client側の認証チェック実装

  • 🛑 🚴 イベントストーミングとサービスブループリンの融合についてzennにまとめる

  • 🛑 🚴 Feature-Sliced Designについてzennにまとめる

command関数のバリデーションつづき

この続き。

これらを解決していきます。

  1. 入力に対してバリデーションをしていない

  2. 開発者ユーザの情報が参照されていない

Domain Model Made Functionalの流れをおさらい

DMMFのアーキテクチャ・実装全体をざっと説明すると以下。

Domain Model Made Functional

  • ドメインイベントはコマンドに含まれるロジックとそれを踏まえた状態の更新を永続層に記録するコマンド関数の結果、イベントの完了としてその時の状態の集約を返す高階関数でありEither型を返す全域関数である純粋関数。内部的には入力値とバリデーション関数をpipe関数で連結しそれぞれの返り値を渡していく

    • コマンド関数は関係する依存(値の存在チェック用やユーザ情報、認証情報のgateway関数などを引数で受け取る高階関数で実行関数を持ち、その引数にバリデーションインスタンス(バリデーションの結果)を渡すEither型を返す全域関数である。

    • バリデーションインスタンスつまりバリデーション関数は集約の入力用のZodのパーサーが内包されている。

  • その際にさらに特定のロジック(ポリシー)をきっかけにして別のドメインイベントへ向かうか、特にポリシーがなければ終わり

尚、Remixを採用した場合には1-3つまりはドメインイベントインスタンスをactionで呼ぶ形になる

またloaderはリードモデル(ビューモデル)を呼ぶがloaderはリードオンリーのため、routesへのリクエストの時にのみリードモデル関数的なものから呼び出される。

日報の作成のワークフローとDMMF

  1. アクター: 開発者ユーザ(日報の記録者)自身

  2. コマンド: 日報を作成する

    • バリデーション関数に注入する依存

      • 開発者ユーザ自身の情報を取得するgateway関数を注入

    • バリデーションへの入力

      • 日報のタイトル: 文字列かつ文字数256文字まで

      • 日報の本文: 文字列、文字数制限なし

      • 日報の公開設定: 即時公開・下書き

    • prismaでEvent Sourcingを意識して定義されたデータベースへ保存されるgateway関数を依存注入し、バリデーション後に実行

  3. 集約: 日報

    • 日報のタイトル

    • 日報の本文

    • 公開設定

  4. ドメインイベント: 日報を作成した

  5. リードモデル: 開発者自身の日報一覧

  6. ポリシー: 日報が即時公開の場合

  7. コマンド: Slackの任意チャンネルに日報について投稿する

    • SlackへのPOSTをするgateway関数を注入

    • Slackへ投稿する情報を生成し、ポストを実行

  8. 外部サービス: Slack API

  9. ドメインイベント: Slackの任意チャンネルに日報について投稿した

Event Storminも見直し

見直したところは主にPolicy。下書き・公開でポリシーをそれぞれ用意していたけど公開のみにしました。下書きはドメインイベントとしては作成したで終わっているためです。※この図がポリシー以降蛇行しているのは図的には他にPush通知・メール通知があるが画像として書き出しに含めていないためです。

日報集約の見直し

まずはちょっと集約を見直し。見直しのポイントは次のとおり。

  1. dailySchemaのinferした型はバリデーションの結果、つまりdailySchema.parse()の結果なので「ValidatedDaily」と

バリデーション関数の実装

  • GetAuthorUser型は認証を踏まえて(開発者)ユーザ自身の情報を取得する関数。内部では認証はSupabase AuthなのでそれとPrisma Client。今は型のみ定義。

  • CreateDailyInputは日報作成の入力型。ユーザの入力でこれはRemixのAction経由で渡ってくるもの。

  • ValidatedDaily型は日報のバリデーション関数の型で高階関数。getAuthorUser関数を依存注入。高階関数なので実行する関数を返す。「validateDaily({ getAuthorUser })({ ...ユーザの入力 })」という感じで呼び出す。

その他、メモ

  • 関数型プログラミングに慣れないとDMMFは難しいが少し慣れてきた。

  • しかし型がない状況でこれを実装していくのは一人で実装するには良いがチームでは難しいだろう。具体的にはpipeやchainで連結された各種関数の引数・返り値の流れがわかった上で定義しないと型定義が難しく感じる。

  • OOP DDDの方が値オブジェクトにバリデーションを置きエンティティや集約でそれぞれをイミュータブル(またはミュータブル)に生成していくので連結とか意識しなくて良い。

  • 実装自体は型・高階関数・純粋関数で疎結合にはできるんだけど、型が各種関数同士関連しるので全くのそ結合として考えてると訳がわからなくなる。もしかしたら進め方・型定義の仕方・実装の仕方などが悪いのかもしれないけど…。

← #69 #71 →

@tyshgc
デザインファーム及びスタートアップ(上場)などを経てフリーランスとして、様々なスタートアップや大手企業の新規事業の立ち上げ期における事業設計・アプリケーションの設計・開発、サービスのUX分析とデザインとエンジニアリングの両軸でお手伝いさせていただいています。 現在、個人開発者向けの支援サービスを個人開発中。 X Account: @tyshgc