コマンドハンドラーを使用して永続性を処理するパターンは、IO関連のコードをできるだけ薄くしたい純粋に機能的な言語にどのように適合しますか?
オブジェクト指向言語でドメイン駆動設計を実装する場合、コマンド/ハンドラーパターンを使用して状態の変更を実行するのが一般的です。この設計では、コマンドハンドラーはドメインオブジェクトの上に配置され、リポジトリの使用やドメインイベントの発行など、永続性に関連する退屈なロジックを担当します。ハンドラーは、ドメインモデルのパブリックフェイスです。UIなどのアプリケーションコードは、ドメインオブジェクトの状態を変更する必要があるときにハンドラーを呼び出します。
C#のスケッチ:
public class DiscardDraftDocumentCommandHandler : CommandHandler<DiscardDraftDocument>
{
IDraftDocumentRepository _repo;
IEventPublisher _publisher;
public DiscardDraftCommandHandler(IDraftDocumentRepository repo, IEventPublisher publisher)
{
_repo = repo;
_publisher = publisher;
}
public override void Handle(DiscardDraftDocument command)
{
var document = _repo.Get(command.DocumentId);
document.Discard(command.UserId);
_publisher.Publish(document.NewEvents);
}
}
document
ドメインオブジェクトは、((「あなたは既に破棄されていた文書を破棄することはできません」または「ユーザーが文書を破棄する権限を持つべきである」のような)ビジネスルールを実装するため、我々は公開する必要があるドメインイベントを発生させるための責任があるdocument.NewEvents
だろうIEnumerable<Event>
おそらく含まれていますDocumentDiscarded
)イベントを。
これは素晴らしいデザインです-拡張が簡単で(ドメインモデルを変更せずに新しいコマンドハンドラーを追加することで新しいユースケースを追加できます)、オブジェクトの永続化方法に依存しません(MongoのNHibernateリポジトリを簡単に交換できます)リポジトリ、またはRabbitMQパブリッシャーをEventStoreパブリッシャーに交換します)これにより、偽物やモックを使用して簡単にテストできます。また、モデル/ビューの分離に従います-コマンドハンドラーは、バッチジョブ、GUI、またはREST APIのいずれで使用されているかわかりません。
Haskellのような純粋に機能的な言語では、おおよそ次のようにコマンドハンドラーをモデル化できます。
newtype CommandHandler = CommandHandler {handleCommand :: Command -> IO Result)
data Result a = Success a | Failure Reason
type Reason = String
discardDraftDocumentCommandHandler = CommandHandler handle
where handle (DiscardDraftDocument documentID userID) = do
document <- loadDocument documentID
let result = discard document userID :: Result [Event]
case result of
Success events -> publishEvents events >> return result
-- in an event-sourced model, there's no extra step to save the document
Failure _ -> return result
handle _ = return $ Failure "I expected a DiscardDraftDocument command"
ここに私が理解するのに苦労している部分があります。通常、GUIやREST APIなど、コマンドハンドラーを呼び出す何らかの「プレゼンテーション」コードがあります。そのため、プログラムにIOを実行する必要がある2つのレイヤーがあります。コマンドハンドラーとビューです。これはHaskellの大きな問題です。
私の知る限り、ここには2つの対立する力があります。1つはモデル/ビューの分離であり、もう1つはモデルを維持する必要があることです。モデルをどこかに永続化するにはIOコードが必要ですが、モデル/ビューの分離では、他のすべてのIOコードとともにプレゼンテーション層に配置することはできません。
もちろん、「通常の」言語では、IOはどこでも発生する可能性があります(実際に発生します)。優れた設計では、さまざまなタイプのIOを分離しておく必要がありますが、コンパイラはそれを強制しません。
それでは、モデルを永続化する必要があるときに、IOコードをプログラムの端にプッシュしたいという欲求と、モデル/ビューの分離をどのように調和させるのでしょうか?2つの異なるタイプのIOを別々に保ちながら、すべての純粋なコードからどのように離すのですか
更新:賞金は24時間以内に期限切れになります。現在の回答のいずれかが私の質問に対処しているとはまったく感じていません。@ Ptharien's Flameのコメントacid-state
は有望に思えますが、それは答えではなく、詳細に欠けています。これらのポイントが無駄になるのは嫌だ!
acid-state
そのリンクに感謝します。API設計の観点からは、まだ拘束されているようIO
です。私の質問は、永続フレームワークがより大きなアーキテクチャにどのように適合するかについてです。acid-state
プレゼンテーション層と一緒に使用するオープンソースアプリケーションを知っていますか?
Query
そしてUpdate
モナドはかなり遠くから削除されIO
、実際に、。私は答えの中で簡単な例を挙げようとします。
acid-state
あなたが記述しているものに近いようです。