Digital Romanticism このページをアンテナに追加 RSSフィード

2010-07-12

Greg Young流CQRS - Mark Nijhof

この記事はMark Nijhof氏のブログ記事「CQRS à la Greg Young」を氏の許可を得て翻訳したものです。(原文公開日:2009/11/11)




この記事は以前のブログである"blog.fohjin.com"にて公開していたものです。


以前、2日間の講習を受けた時に、ビールを飲みながらGreg Young氏とドメイン駆動設計について語るという幸運に恵まれたことがあります。その時の話題は専ら、コマンドクエリ責務分離(CQRS:Command and Query Responsibility Segregation)パターンに関するものでした。Gregは、Eric Evans氏が著作において説明したドメイン駆動設計を受け継ぎ、主に技術的な実装を進化させています。コマンドクエリ分離(CQS)は元々Bertrand Meyer氏によって考案されたもので、オブジェクトのレベルで適用されていました。

CQS: 「あらゆるメソッドは、アクションを実行するコマンドか、呼び出し元にデータを返すクエリかのいずれかであって、両方を行ってはならない。これは、質問をすることで回答を変化させてはならないということだ。

-Bertrand Meyer

これに対して、Gregは同様の原則を用いつつ、それをシステムのアーキテクチャ全体に適用し、システムの更新処理("Command")を参照処理("Query")から明確に分離したのです。このうち更新処理は、私たちがすでに「ドメイン」として知っているもので、システムの価値を生むすべてのふるまいを含んでいます。参照処理は特定のレポートを行う必要性に特化したものです。例えば、ユーザがドメインのふるまいを実行できるようにするアプリケーションの画面を考えてみて下さい。このような伝統的なレポートは、データベースの参照によって実現されてきました。


詳細に踏み込む前に、アーキテクチャ全体について簡単に見て行きましょう。

Command and Query Responsibility Segregation (CQRS) - Overview Command and Query Responsibility Segregation (CQRS) – 概要

Visioを使って書いていないことを大目に見て下さい。今夜はこれ以上別のレイヤで複雑なことに取り組みたくなかったのです。最低限、ラベルを打ち込んでおきましたので、読めるようにはなっています。このアーキテクチャについては4つのパートに分けて議論していきます。この順序はあなた方がこのアーキテクチャについて自然に考える順序だと思っています。最終的には原則全体が比較的シンプルであることが分かるでしょう。本当ですよ。

Command and Query Responsibility Segregation (CQRS) - Division Command and Query Responsibility Segregation (CQRS) – パート分割

  1. クエリ
  2. コマンド
  3. 内部イベント
  4. 外部イベント(公開)

この種のアーキテクチャをより良く理解するために、私はサンプルアプリケーションを構築することにしました。このサンプルは既に野に放たれていて(つまりYahooDDDグループのことですが)、ここで見ることができます:http://github.com/MarkNijhof/Fohjin


クエリ(レポーティング

最初に議論したいのは、システムにおけるレポートの必要性です。Gregはシステムからデータを取得する必要性はどれもレポートの必要性だと定義しています。ここにはユーザが意思決定をする際に用いる様々なアプリケーションの画面も含まれます。一見奇妙な定義に思えますが、深く考えれば考えるほど、この意味が分かってきます。このデータはユーザ(あるいは他のシステム)に対して、ユーザがおかれた特定のコンテキストにおけるシステムの現在の状態を示すことで、ユーザがなんらかの意思決定を行い、ドメインのふるまいを実行できるようにするものです。


これらのレポートは、レポートの利用者、すなわちドメインの状態を表すデータを見る人によって直接更新されることはなく、それを更新する責務を負うのはドメインです。したがって、この部分で実際に行うのは、システムの現在の状態について、それを必要とする人にそれが誰あるいは何であれレポートすることだけです。
アプリケーションによって特定の画面に表示するためにデータが要求された場合、これはクエリレイヤに対する単一の呼び出しによって実現され、戻り値としては必要なデータをすべて含んだDTOが返されます。このようにデータの使用方法が特定されるので、システムのニーズに応じて並び替えないしグルーピングを行うことには意味があります。したがって、単一の画面ないし特定のクライアントアプリケーションにおける使用法を反映させるような単一のテーブルを作成するために、データの非正規化を行うことになります。なぜなら、データは通常ドメインのふるまいが実行されるよりも参照されることの方が多いのであり、ここを最適化することでシステムの体感性能を向上させることができるのです。


ここで、データベースからの読み取りを容易にするために、NHibernateのようなORMを使おうと思うかもしれません。しかし、これでは適切なORMが持つ機能のごく一部しか使っていないということを考慮すれば、このようにする必要はまったくありません。DTOから直接SQL文生成するためにリフレクションを利用することをGregが提案したように(リフレクションとCoCを利用することでさらにシンプルになります)、Linq2Sqlを試してみる方がよい考えかもしれません。これはあなた次第で、特定のシナリオと何を良いと思うかに依存します。


このアーキテクチャを説明するのに作ったサンプルでは、私はDTOのリフレクションを使うことにしました。これは私が強調したかったのがCQRSの実装であって、ORMのそれではなかったからです。


より伝統的なレポートが必要とするのは、独自のデータベーススキーマであり、そのニーズに合わせて最適化されたデータです。そうなると最終的には、大量のデータが重複することになりますが、それは構わないのです。別々のデータベース上でデータを更新する責務を追ったプロセスにより、この更新が正しい仕方で起こることを保証します。これについては後半で議論することにします。


コマンド(ドメイン上のふるまいを実行する)

まずは、DTOを受け取った後で通常何が起こるのかを考えましょう。ユーザはデータを変更し、それをDTOに戻します。その際、このDTOはバックエンドに送り返され、エンティティに変換された上で、その変更がデータベース上に永続化されることはORMによって保証されます。


この結果、きわめて重要な情報が失われることになります。すなわち、なぜその変更が起きたのかということがです。データを変更した際にユーザが持っていた意図が完全に見失われてしまうこと、それがGregのCQRS実装によって解決される問題です。


名前が示す通り、CQRSはコマンドを利用します。これらのコマンドはクライアントアプリケーション上に生成され、ドメインレイヤに送信されます。例を見てみましょう:銀行の顧客がオフィスに入り、受付にいる人に住所を変更したいと声をかけます。そこで単に顧客情報を開いて住所を直接変更するのではなく、銀行員はまず質問をします。「どうして住所を変更したいのでしょうか?」ほとんどの場合、引越したという答えが返ってくるでしょう。しかし、存在しない住所だったとか、手紙がすべて階下の住人に届いてしまったということも考えられます。つまり顧客の住所を更新するのには全く異なる理由が2つ考えられるのです。なぜこれが重要なのでしょうか?確かにこの例は若干馬鹿げているかもしれません。しかし、顧客が引っ越した後で競合他社に行くケースがどのくらいあるのか知りたいと想定して下さい。この銀行に対して顧客はどの程度忠誠心を持っているのか、そしてXマイル離れた場所に引っ越した後も特定の情報を送り続けるべきなのか?まさにこうした情報が当初のやり方では失われてしまうのです。しかしコマンドとイベント(イベントについては後述)を利用することで、このアクションの当初の意図を保持することができます。さて、質問を受けた顧客が引越をしたのだと答えたところで、銀行員はアプリケーション上で「引越による」を選択し、住所だけを変更することができるようになります。保存をクリックした際に、変更された住所だけを保持したCustomerMovedCommandが生成され、ドメインへと送信されます。


こういったコマンドを利用することで得られるメリットがもう1つあります。それはこれらのコマンドを使うと、システムの構築ないし運用時に顧客とのコミュニケーションが容易になるということです。これは顧客が自分たちが行いたいことを説明するのに、この種のふるまいを使うことが多いからです。Gregも時代は変わったと考えていますが("Our grand failure")、このことは顧客が自分たちのドメイン言語を語る時にまさに当てはまります。こういったコマンドを利用することで、私たちは、コードの中でも同じ言語を話すことができるようになります。


こうしたことはまさにドメイン駆動設計が語っていることです。顧客情報を更新するといった技術的なことを実行するのではなく、顧客の引越といったユーザが使用しているプロセスをコードの中で実際に記述するのです。

コマンド
namespace Fohjin.DDD.Commands
{
    [Serializable]
    public class ClientIsMovingCommand : Command
    {
        public string Street { get; private set; }
        public string StreetNumber { get; private set; }
        public string PostalCode { get; private set; }
        public string City { get; private set; }

        public ClientIsMovingCommand(Guid id, string street, string streetNumber, string postalCode, string city) : base(id)
        {
            Street = street;
            StreetNumber = streetNumber;
            PostalCode = postalCode;
            City = city;
        }
    }
}

これらのコマンドはすべてコマンドバスに送信され、コマンドバスは各コマンドを1つないし複数のコマンドハンドラに委譲します。これが意味しているのは、ドメインに対するエントリポイントが1つしかなく、それはバスだということです。これらコマンドハンドラが持つ責務は、ドメイン上で適切なふるまいを実行するというものです。これらコマンドハンドラのほぼすべてに対して、集約ルート("Aggregate Root")をロードできるようにリポジトリがインジェクトされ、その集約ルートに対する適切なふるまいが実行されます。通常、1つのコマンドハンドラに対して必要な集約ルートは1つです。あとでリポジトリについても詳細に見ておきましょう。通常のDDDのリポジトリとは異なるからです。

コマンドハンドラ
namespace Fohjin.DDD.CommandHandlers
{
    public class ClientIsMovingCommandHandler : ICommandHandler<ClientIsMovingCommand>
    {
        private readonly IDomainRepository _repository;

        public ClientIsMovingCommandHandler(IDomainRepository repository)
        {
            _repository = repository;
        }

        public void Execute(ClientIsMovingCommand compensatingCommand)
        {
            var client = _repository.GetById<Client>(compensatingCommand.Id);

            client.ClientMoved(new Address(compensatingCommand.Street, compensatingCommand.StreetNumber, compensatingCommand.PostalCode, compensatingCommand.City));
        }
    }
}

ご覧の通り、コマンドハンドラに責務は1つしかなく、それは適切なドメインのふるまいを実行することで特定のコマンドを処理するというものです。コマンドハンドラはドメインロジック以外、何も行ってはいけません。必要があれば、ロジックはサービスに移されるべきです。サンプルコード中の例としては、収入の振替があります。これについては後述します。


内部イベント(意図をとらえる)

ついに実際のドメインに到着しました。クライアントは私たちのドメインのあるビューを要求し、適切なレポートDTOを受け取り、意思決定を行った上で公開されたコマンドを呼び出します。所定のコマンドハンドラが、そこで正しい集約ルートをロードし、適切なドメインのふるまいを実行します。次になにがあるでしょう?


ここで行うべきは、ドメインのふるまいを、その結果として生じる状態("state")の変化から切り離すということです。なお、ここでいう変化には外部のふるまいをキックすることも含まれます。こうしたことを今行っているやり方とはずいぶん異なると思います。まず入力チェックを行い、実行すべきことを実行し、内部の状態を設定することなく、また外部のふるまいをキックすることのないやり方とは異なるということです(確かに後半部はあまり考えられることがないかもしれません。状態がここでのポイントです)。こういった状態の変更を、生成した内部変数に直接書き込む代わりに、イベントを生成して内部的にキックするのです。ふるまいに与えられたメソッド名と同様、このイベントもドメインのユビキタス言語を記述するものとなっているべきです。そのとき、イベントはドメインの集約ルートの内部で処理され、正しい値に対して内部の状態を設定します。イベントハンドラが状態を設定するということ以外に一切のロジックを実行しないということを忘れないで下さい。このロジックはドメインメソッドの内部にあるのです。

ドメインのふるまい
public void ClientMoved(Address newAddress)
{
    IsClientCreated();

    Apply(new ClientMovedEvent(newAddress.Street, newAddress.StreetNumber, newAddress.PostalCode, newAddress.City));
}
 
private void IsClientCreated()
{
    if (Id == Guid.Empty)
        throw new NonExistingClientException("The Client is not created and no opperations can be executed on it");
}
ドメインイベント
namespace Fohjin.DDD.Events.Client
{
    [Serializable]
    public class ClientMovedEvent : DomainEvent
    {
        public string Street { get; private set; }
        public string StreetNumber { get; private set; }
        public string PostalCode { get; private set; }
        public string City { get; private set; }
 
        public ClientMovedEvent(string street, string streetNumber, string postalCode, string city)
        {
            Street = street;
            StreetNumber = streetNumber;
            PostalCode = postalCode;
            City = city;
        }
    }
}
内部ドメインイベントハンドラ
private void onNewClientMoved(ClientMovedEvent clientMovedEvent)
{
    _address = new Address(clientMovedEvent.Street, clientMovedEvent.StreetNumber, clientMovedEvent.PostalCode, clientMovedEvent.City);
}

こういったイベントが必要になるのは、これが今や永続化戦略の一部になっているからです。これはつまり集約ルートに関して永続化すべき情報は生成されたイベントだけだということです。状態のあらゆる変更が何らかのイベントによってキックされ、内部イベントハンドラが正しい状態を設定するということ以外にロジックを持たなかったとすれば(これはイベントにおいてデータから他の情報を取り出さないということを意味します)、私たちにできるのは履歴にあるイベントをすべてロードし、集約ルートに対して内部的にリプレイすることで、最初と全く同じ状態に戻すということです。これはテープの再生と同じです。


特筆すべきは、こうしたイベントが書き込み限定のものであり、イベントを追加したり、変更したり、削除したりすることができないということです。もし誤ったイベントを生成するバグが出てしまった時に、それを修正するには、バグの結果を修正するような埋め合わせイベントを生成するほかありません。もちろん、バグを修正したいとも思うでしょう。このようなやり方により、いつバグが修正されたか、そしてバグの結果が正されたかを追跡するのです。


このようなアーキテクチャを採用することにより、本来の意図を見失ってしまうという問題は基本的に解決されました。発生したイベントはすべて保持されており、こういったイベントは意図が明確だからです。興味深い点がもう1つあります。それは監査ログを自由に取得できるということです。イベントがなければ状態が変更されることがなく、イベントは保存され、集約ルートを構築するのに使用されるので、相互に同期が取れていることが保証されます。


ドメインリポジトリ

先ほど、ドメインリポジトリは通常DDDを実践する場合と比べて完全に異なることになるだろうと述べました。普通は、ドメインからあらゆる種類の情報を引き出せるようにする、要件に特化したリポジトリを作ることになります。しかしGregのCQRS実装を利用する場合、ドメインは完全に書き込み専用となるため、リポジトリはIDによって集約ルートを取得できればよく、また生成されたイベントを保存できなければなりません。同じくドメインと永続化レイヤとの間にあるインピーダンス不整合も完全に回避されます。

ドメインとリポジトリのコントラクト
namespace Fohjin.DDD.EventStore
{
    public interface IDomainRepository 
    {
        TAggregate GetById<TAggregate>(Guid id)
            where TAggregate : class, IOrginator, IAggregateRootEventProvider, new();

        void Add<TAggregate>(TAggregate aggregateRoot)
            where TAggregate : class, IOrginator, IAggregateRootEventProvider, new();
    }
}

一方、レポート用リポジトリはDDDの伝統的なリポジトリに近いものになります。


ここで、もし100,000イベントが存在し、集約ルートをロードするたびにリプレイする必要があるとすれば、システムは恐ろしく遅くなってしまうでしょう。この問題に対処するには、メメントパターンを使い、一定数のイベントがあるたびに集約ルートの内部的な状態についてスナップショットを取ることが考えられます。リポジトリはまずスナップショットを要求し、集約ルート内にロードし、スナップショットの後で発生したすべてのイベントを要求して、本来の状態に戻します。これは最適化のテクニックにすぎないため、スナップショット以前のイベントを消してはいけません。それではこのアーキテクチャの目的が失われてしまいます。(※訳註1)


データマイニング

すべてのイベントを保存しておくことについて、興味深い事実が他にもあります。こういったイベントを後でリプレイし、重要な業務情報をそこから引き出すことができるのです。しかもこの情報はシステムの状態から取得できるのであり、特殊なログを組み込んで信頼できる情報を得るために数ヶ月待つ必要はありません。


外部イベント(公開、外部に知らせる)

ついにこの解説の4つ目のパートにたどり着きました(もう一言自分のために。「やれやれ」)。さて、ここでは何が起こるでしょうか。ここまででできるようになったこととしては、ドメインの状態を読み取ることができ、ふるまいを実行してドメインの内部の状態を更新することができます。明らかに足りないのは、レポート用データベースにドメインの現在の状態を設定する方法です。これは、内部のドメインイベントを外部に公開することによって行われます。そこで、こういったイベントを検知し、レポート用データベースと同期するイベントハンドラが登場します。ここでORMを使うこともできますが、必要なSQL文を生成し、実行するのもきわめて容易です。


GregはこれらのSQL文をキャッシュするにあたり、実に優れた方法を説明しています。ある単独のバッチ処理の中に束ね、そのバッチを数秒おきに実行するか、(これが面白いところですが)参照リクエストが送信された時に実行すると言っています。参照リクエストが送信された時、このSQL文がバッチに加えられ、すべて実行されます。その際、システムのこの場所で得られる最新のデータに対し、参照リクエストがアクセスできることが保証されます。詳細については後述します。


ドメインリポジトリはイベントを公開するという責務を持ちます。これは通常であれば、単一のトランザクション内でイベント格納箇所にイベントを格納することで実施されます。


イベントはまた異なる集約ルート間でのコミュニケーションにも使われます。サンプルでは、ある口座から別の口座への取引を用いています。ここでは資金を別の口座に移動するイベントを生成していますが、このイベントはまた、現在の口座の残高も減らします。後になって、イベントハンドラがレポート用データベースに対して同じ変更を加えます。このもう1つのイベントハンドラは実際には、取引情報をサービスに送信します。このサービスは送金対象口座がローカルにある口座なのかどうかを調べ、そうでなければ送金情報を別の銀行に送信します(サンプルでは、ここでいう別の銀行も実際には同じですが、別のルートを辿ります)。しかし、送金が内部の口座に対して行われると想定してください。この場合、サービスはコマンドバス内にある送金受信コマンドを公開し、その後のプロセスはすべて別の集約ルートに対して行われます。この場合であれば、コマンドはGUIからではなく、システムの別の部分からキックされることになります。


他の銀行が資金を受け取るというこのシナリオに関しては、興味深い事実がもう1つあります。対象となる口座を識別するにあたって送金情報に含まれているのは口座番号であって集約ルートのIDではありませんので(外部システムがこの集約ルートIDを知ることを期待すべきではありません。周知の通り自然キーとしても生成できるからです)、資金受取サービスはまずレポート用データベースに対してクエリを実行し、口座番号が対象口座と同じである口座DTOを取得します。このクエリが成功した際には、口座DTOから取得したIDを利用して公開されるコマンドに設定します。


しかし、これはここに留まりません。前述した通りイベントの中には状態を変更する情報を含まないけれども、例えばメッセージ(イベントハンドラに応じて電子メールやSMSなどさまざまなものがある)をユーザに通知する必要があるということを示すものもあります。これらすべてについてドメインイベントを使っているために、すべてはイベント格納庫に保存されるので、履歴を保持することができます。


結果整合性("Eventual Consistency")

普通、CQRSの実装を始めた場合には、イベントの格納とレポート用データベースの更新が同一のスレッドで実行されるよう、直接公開を行う仕組みから始めるでしょう。この手法を採用した場合には、結果整合性に関する問題は発生しません。

しかし、システムが大きくなるにつれて、なんらかの性能問題が発生するでしょうし、その時にはバスを実装してイベントの公開とこれらイベントの処理を分けはじめるかもしれません。これが意味するのは、イベント格納庫とリポート用データベースが相互に同期していない可能性があり、その可能性が高いということです。両者が整合するのは結果的なのです。つまり、ユーザが画面上で古いデータを見る可能性があるということです。


これがどの程度クリティカルであるかに応じて、この問題について別の対策を取ることもできます。これについてはサンプルに含まれていませんので、別の機会に紹介したいと思います。


仕様("Specifications")

このアーキテクチャを用いて書くことができる仕様のことを、私は心底気に入っています。やるべきことは、顧客と話し合い、このプロセスがどう機能してほしいと考えているかを聞くことです。こんなシナリオも考えられます。(※訳註2)


ある口座から資金を引き出す

これはどのように行われるでしょうか。まず、預金引出を行うためには、顧客はこの銀行において予め口座を開設し、資金を入れておく必要があります。そして引出が行われた場合には、口座残高は正しい金額に減らされなければなりません。つまり、口座が開設され、かつ、現金が預けられている状態で(given)、現金引出が行われた場合には(when)、現金引出イベントが発生する(then)のです。

namespace Test.Fohjin.DDD.Scenarios.Withdrawing_cash
{
    public class When_withdrawing_cash : CommandTestFixture<WithdrawlCashCommand, WithdrawlCashCommandHandler, ActiveAccount>
    {
        protected override IEnumerable<IDomainEvent> Given()
        {
            yield return PrepareDomainEvent.Set(new AccountOpenedEvent(Guid.NewGuid(), Guid.NewGuid(), "AccountName", "1234567890")).ToVersion(1);
            yield return PrepareDomainEvent.Set(new CashDepositedEvent(20, 20)).ToVersion(1);
        }

        protected override WithdrawlCashCommand When()
        {
            return new WithdrawlCashCommand(Guid.NewGuid(), 5);
        }

        [Then]
        public void Then_a_cash_withdrawn_event_will_be_published()
        {
            PublishedEvents.Last().WillBeOfType<CashWithdrawnEvent>();
        }

        [Then]
        public void Then_the_published_event_will_contain_the_amount_and_new_account_balance()
        {
            PublishedEvents.Last<CashWithdrawnEvent>().Balance.WillBe(15);
            PublishedEvents.Last<CashWithdrawnEvent>().Amount.WillBe(5);
        }
    }
}

これはいいでしょう。ここで口座に十分な資金がないということ以外はまったく同じストーリーが考えられます。この場合には例外をスローしなければなりません。

namespace Test.Fohjin.DDD.Scenarios.Withdrawing_cash
{
    public class When_withdrawling_cash_from_an_account_account_with_to_little_balance : CommandTestFixture<WithdrawlCashCommand, WithdrawlCashCommandHandler, ActiveAccount>
    {
        protected override IEnumerable<IDomainEvent> Given()
        {
            yield return PrepareDomainEvent.Set(new AccountOpenedEvent(Guid.NewGuid(), Guid.NewGuid(), "AccountName", "1234567890")).ToVersion(1);
        }

        protected override WithdrawlCashCommand When()
        {
            return new WithdrawlCashCommand(Guid.NewGuid(), 1);
        }

        [Then]
        public void Then_an_account_balance_to_low_exception_will_be_thrown()
        {
            CaughtException.WillBeOfType<AccountBalanceToLowException>();
        }

        [Then]
        public void Then_the_exception_message_will_be()
        {
            CaughtException.WithMessage(string.Format("The amount {0:C} is larger than your current balance {1:C}", 1, 0));
        }
    }
}

ここで優れているのは、ドメイン全体がブラックボックスと見なされ、使われているのと全く同じ状態に変更し、アプリケーションが実行するであろうコマンドを公開し、その後でドメインが正しいコマンドを公開し、その値が正しいことを確認します。これが意味しているのは、ドメインが自然に至ることのない状態である場合をテストしないということです。これによりテストはより信頼性の高いものになります。


ここであらゆるクラス名を受け取るパーサを想像してみて下さい。各クラス名について現在の状態に至るために発生したイベント出力することになります。続いて、テスト対象であるコマンドを出力します。そして最後に、実際の出力結果をテストするメソッド名を出力します。これはきわめて可読性の高い仕様であり、顧客は少なくとも理解することができます。


他のメリット

この種のアーキテクチャを採用することで得られるメリットとしてもう1つ強調しておきたいものがあります。それは異なるチーム間で作業負荷を分担するのがきわめて簡単であるということです。これはチーム間に時差がある場合に特に言うことができます。ドメインロジックは正しくなければならないものです。これは単価の高い開発者を投入したいと思う場所でしょう。つまり、業務を理解し、正しいコーディングプラクティスを理解した開発者をということです。言っていることは分かりますよね?しかし、参照部分はそれほど重要ではありません。もちろん正しい必要はあるのですが、価値がある場所ではなく、素早く作って1、2年のうちに作り替えることができます。つまり、単価の低い開発者に作ってもらうことができるものだということです。ドメインに関する知識が多く求められることもなく、本当に重要なのは、GUIがどのように機能し、どのようなコマンドが使え、どのようなイベントが要求されるかということだけです。


これについて、私はビジネスにおけるきわめて大きな価値だと思っています。これは見過ごされやすいものでもあります。


最後に

見て頂いた通り、これはきわめてシンプルです。これはまったく異なる考え方ですが、馴染んでしまえば、CRUDよりもふるまいの記述力が高い("behavioral")アプリケーションになることが分かるでしょう。そして顧客も、私たちがこれまで押し付けてきたようなCRUD的考え方ではなく、ビジネスロジックに考えることに立ち戻ることができるのです。最後に、Greg Young氏に感謝したいと思います。氏は多くの情報を提供してくれ、私の下らない質問に耐えてくれました。また、Jonathan Oliver氏とMike Nichols氏にも感謝します。両氏は技術的な面でいくつか改善してくれました。



訳註

1:Event Sourcingについて

集約ルートを永続化する際に「最新の状態」ではなく、「イベントの履歴」を保存する方式は、"Event Sourcing"と呼ばれているパターンです。厳密にはCQRSとEvent Sourcingは異なるパターンですが、Greg Young氏はCQRSとEvent Sourcingとの間に強い相互関係があると考えています。


2:仕様について

given / when / thenという表現から明らかな通り、この「仕様」という表現はふるまい駆動開発(BDD:Behaviour Driven Development)の文脈で理解するべきものです。BDDについてはDan North氏による紹介記事を以前翻訳しました。

投稿したコメントは管理者が承認するまで公開されません。

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証



Copyright(C) 2005-2011 Digital Romanticism. All Rights Reserved.