mcpField
A
alchemy-domain

alchemy-domain

github.com/alchemy-run開発2.0k165 回インストール今日更新
リポジトリ

alchemy-domain について

`@alchemy.run/domain`を使い、型付きメッセージと状態遷移でバックエンドを構築するためのスキルです。要件分析やモデル設計から、ストーリーによるテスト、Cloudflareでのホスト、foldkitのフロントエンドまで案内します。

主な機能

  1. 01要件からモデルへの分解
  2. 02型付きドメイン構成要素
  3. 03ストーリーによる動作テスト
  4. 04Cloudflareホスティングの手順
  5. 05foldkitフロントエンドのワークフロー

活用シーン

  1. 01コマンドと業務イベントのモデル化
  2. 02ストーリーを使ったドメイン動作のテスト
  3. 03Cloudflareでのモデルのホスト
  4. 04foldkitのWebアプリからモデルを操作

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/alchemy-run/alchemy --skill alchemy-domain

インストール後、Claude Code で/alchemy-domainを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
building-blocks.md
frontend.md
hosting.md
layout.md
modeling.md
testing.md

詳細

@alchemy.run/domain を使った開発

@alchemy.run/domain は、バックエンドを型付きメッセージと純粋な状態遷移としてモデル化します。コマンドは集約が判断し、イベントを記録します。ビューはイベントを畳み込んで読み取りモデルを作成します。ポリシーはイベントに反応します。サガは長時間にわたるプロセスを実行します。プロジェクションはモデルの外部に書き込み、クエリは書き込まれたデータを提供します。同じモデルを、テストではメモリ上で、本番環境では Cloudflare Durable Objects 上で実行できます。

ドキュメント:https://alchemy.run/domain。すべてのページは https://alchemy.run/llms.txt に掲載されています。ここおよびリファレンスファイルの各セクションの末尾には、その内容を詳しく解説するドキュメントページへの「詳細」リンクがあります。完全なリファレンスプロジェクトは、alchemy リポジトリの examples/cloudflare-domain です(foldkit アプリを備えた銀行)。

ワークフロー

次の順序で進めます。各ステップにはテンプレートを含むリファレンスファイルがあります。

  1. 要件を分解する:構成要素に分けます。references/modeling.md を参照してください。
  2. モノレポを構成する:ドメインパッケージ、サーバーパッケージ、ウェブパッケージを用意し、ブロックごとに種類を示す接尾辞付きのファイルを1つずつ配置します。references/layout.md を参照してください。
  3. 各ブロックを作成する:テンプレートに沿って作成します。references/building-blocks.md を参照してください。
  4. ストーリーで動作を仕様化する:実装の前と実装中に行います。references/testing.md を参照してください。
  5. モデルをホストする:サーバーパッケージから Cloudflare 上にモデルをホストし、そのトレースをエクスポートして、1つの alchemy スタックでデプロイします。references/hosting.md を参照してください。
  6. ウェブパッケージから foldkit で操作する:references/frontend.md を参照してください。

詳細:チュートリアル、第1部。

要件から構成要素へ

要件を事実のストーリーとして読み解き、見つかった要素をそれぞれの種類として名付けます。

  • ビジネスで記録される事実。過去形で命名します(MoneyWithdrawn):イベント。
  • 何かを変更するリクエスト。<Verb><Noun> の形式で命名します(WithdrawMoney):コマンド。
  • リクエストが拒否される理由(InsufficientFunds):拒否。
  • ルールを1つのトランザクション内で守る必要があるもの(1つの口座の残高):集約。集約は自身の状態に照らしてコマンドを判断します。
  • 誰かが尋ねる質問(残高、明細、ダッシュボード):対象をキーとするビュー。
  • 「Xが起きたらYを実行する」:ポリシー。
  • 手順、待機、取り消しを伴うプロセス(送金:引き落とし、入金、失敗時の返金):ポリシーによって開始されるサガ。
  • 呼び出すモデル外部のもの(不正利用のスコアリング、決済):外部システム。
  • モデル外部のストアへの書き込み(台帳テーブル):プロジェクション。キーごとに順序付けられ、読み取り可能なログ(口座カード)は、プロジェクションが追記するストリームです。
  • そのストアを基にした、キーをまたぐ質問(ページ分割された口座の台帳、全口座を対象とした高額出金の金額順一覧):クエリ。手書きのルートを使ってはいけません。
  • 誰が何を実行し、何を閲覧できるか:コマンドと読み取りを列挙するApi。ミドルウェア(セッション、MFA)を備え、各ブロックが自身で認可します。コマンドは decide で、ビューまたはストリームはそれぞれの Layer の validate で、クエリはハンドラーで認可します。

複数の集約にまたがるルールが、集約間のトランザクションになることはありません。不変条件に、それが保護するものをキーとする独自の集約を割り当てます(ユーザー名をキーとする UsernameClaim)。または、ポリシーやサガで先に予約し、後から確定します。待機が発生するものには期限を設けます。

詳細:ドメイン駆動設計とは?、ドメインモデル。

規約

  • ファイルごとにブロックを1つ作成し、<Noun>.<kind>.ts という名前を付けます。 種類は .aggregate、.command、.view、.policy、.saga、.projection、.stream、.query、.external、.api、.model です。さらに .middleware、.context、.rejection があります。集約の ID は、隣接する <Aggregate>.id.ts に置きます。通常の値(Money.ts)やヘルパーには通常の名前を付けます。
  • コマンドファイルには、コマンド、そのコマンドが記録するイベント、そのコマンドだけが返す拒否をまとめます。 複数のコマンドで使われる拒否やイベントには、独自のファイルを用意します。
  • ブロックの実装は、そのブロックのファイルに置きます(TransferExecution.policy.ts 内の TransferExecutionLive)。インフラストラクチャに触れる場合は、サーバーパッケージに置きます。
  • フォルダーはサブドメインです(Account/、Transfer/)。モデル、Apis、ミドルウェア、リクエストコンテキストはパッケージのルートに置きます。
  • ドメインパッケージがインポートするのは @alchemy.run/domain と effect だけです。 Cloudflare、Stripe、データベース、HTTP セッションの処理はサーバーパッケージに置きます。
  • モデル(境界づけられたコンテキスト)ごとにドメインパッケージを1つ用意します。 2つ目のモデルには、2つ目のドメインパッケージが必要です。1つのサーバーで複数のモデルをホストできます。

詳細:プロジェクトの構成。

モデルの正しさを保つルール

  • decide は純粋関数にするか、そのコマンドのミドルウェアが提供するもの(yield* CurrentActor)だけを読み取る Effect.fn にします。イベントまたは拒否を返し、I/O は決して実行しません。
  • evolve はイベントを状態に適用するだけです。何も判断しません。また、evolve と reply は全域関数にします。すべての状態を網羅し、失敗しないようにします。実行できない処理はすでに decide が拒否しているためです。イベントを適用できない状態はそのまま保持し(s._tag === "Open" ? { ...s, balance: e.balanceAfter } : s)、例外を投げるガードは使いません。
  • イベントには decide が算出した結果(balanceAfter)を含めます。evolve はルールを繰り返すのではなく、その値をコピーします。
  • コマンドが返す可能性のある拒否は、すべて rejects に列挙します。呼び出し側では、それぞれを Effect.catchTag で処理します。
  • プロジェクションが書き込むストアを提供するには、手書きのルートではなく、Api 上の Domain.Query を使います。ハンドラーが呼び出し元を検証し、読み取る行の範囲を限定します。
  • クライアントは集約を直接読みません。ビュー(単一キー、ライブ)、ストリーム、またはクエリを通じて読み取ります。クエリのハンドラーは、yield* Account の後に accounts.get(ref) を実行して、集約の最新状態を読み取ることができます。
  • ポリシーとプロジェクションは少なくとも1回実行されます。処理を冪等にするため、ハンドラーから送信するコマンドには決定的な ID を付け、ストアの行は stream:seq をキーにします。
  • ポリシー内でサーガの完了を待ちません。対象となる集約をキーにして start し、そのステップが記録するイベントに反応します。
  • ハンドラーは toLayer で依存関係を一度だけ解決し、呼び出しごとに依存関係を要求しません。
  • Layer を1つ構築し、一度だけ提供します(Layer.mergeAll、Layer.provide)。Effect.provide を連鎖させてはいけません。
  • throw は決して使いません。タグ付きエラー(Domain.Rejection、Schema.TaggedError)で失敗を表します。
  • トレースは自動で行われます。ペイロード値を記録する場合は、ハンドラー内でのみ Effect.annotateCurrentSpan を使います。値はそのハンドラーに対応するエンジンのスパンに記録されます。シークレットは Schema.Redacted にして、トレースやログに出ないようにします。
  • モデルの動作はストーリーを通してテストします。イベント、応答、拒否、ビュー、サーガの結果を検証します。サーガの内部ステップはテストしません。

詳細:ドメインモデル:集約が判断し、すべてを一度にコミットする、Cloudflare:これらの保証を前提にする。

変更のチェックリスト

  1. 新しい事実、コマンド、拒否を列挙します。各コマンドは、イベントと拒否を含めて、専用の <Command>.command.ts に記述します。
  2. コマンドをそのアグリゲートの commands に追加し、対応する decide の分岐と、各イベントに対応する evolve の分岐を追加します。
  3. 新しいイベントに関係するビュー、ポリシー、プロジェクションを更新します。
  4. クライアントが送信または読み取る場合は、必要なミドルウェアとともに Api 経由で公開し、最後の引数に { description } を指定します。
  5. ハッピーパス、すべての拒否、すべてのポリシーの反応、各クエリの認可について、ストーリーを作成します。
  6. Domain.verify(Model) が空になることを確認するテストを含め、ドメインパッケージのテストを実行します。その後、モノレポの型チェック(tsc -b)を行い、サーバーと Web のテストを実行します。

コメント