次の方法で共有


Windows Azure 開発

Windows Azure での CQRS

Mark Seemann

コード サンプルのダウンロード

Microsoft Windows Azure には、独特のメリットと課題があります。Windows Azure 環境には、融通性の高いスケーラビリティ、コストの削減、展開の柔軟性といったメリットがありますが、現在の Microsoft .NET Framework サービスやアプリケーションの大半をホストする標準の Windows サーバーとは性質が異なることから課題もあります。

アプリケーションやサービスをクラウドに配置する場合に最も魅力的なメリットの 1 つとなるのが、"融通性の高いスケーラビリティ" です。これにより、必要なときにはサービスの能力を高め、不要になれば能力を落とすことができます。Windows Azure で処理能力を調整するときに最も混乱が少ないのは、既存のサーバーの能力を高めるのではなく、サーバーの台数を増やしてスケールアウトする方法です。そのため、このようなモデルのスケーラビリティに適合していくには、アプリケーションのスケールを動的に変化させることができなければなりません。この記事では、スケーラブルなサービスを構築する効果的な手法について説明し、これを Windows Azure に実装する方法を紹介します。

Command Query Responsibility Segregation (CQRS) は、スケーラブルなアプリケーションを構築するための新しい手法です。使い慣れた .NET アーキテクチャとは見た目が異なるかもしれませんが、CQRS は、スケーラビリティを実現するための実証済みの原則とソリューションに基づいてアプリケーションを構築します。スケーラブルなアプリケーションの構築方法の説明に利用できる知識体系は膨大ですが、いずれも発想の転換が必要です。

CQRS は、言うなれば、懸案事項を分離するという考え方に他なりませんが、ソフトウェア アーキテクチャの観点からは、多くの場合、一連の関連パターンを表します。つまり、CQRS という用語には、パターンとしての意味と、アーキテクチャ スタイルとしての意味の 2 つがあります。今回は、この両方の観点について簡単に説明し、Windows Azure で実行する Web アプリケーションに基づく例を示します。

CQRS パターンについて

CQRS の基盤となる用語は、オブジェクト指向パターン言語に由来します。コマンド (Command) は、なんらかの状態に変更を加える操作で、クエリ (Query) は、状態についての情報を取得する操作です。つまり、コマンドは "書き込み"、クエリは "読み取り" を表します。

CQRS パターンとは、単に、読み取りと書き込みをそれぞれ独立した責務として明確にモデル化する必要があるという考え方です。データの書き込みが 1 つの責務で、データの読み取りはまた別の責務になります。ほとんどのアプリケーションには両方の責務が必要ですが、図 1 に示すように、責務はそれぞれ別々に扱う必要があります。

image: Segregating Reads from Writes

図 1 読み取りと書き込みを分離する

アプリケーションは、読み取り元とは概念上異なるシステムに書き込みを行います。

当然のことながら、アプリケーションから書き込むデータは、最終的に読み取りでも使用できるようにすべきです。CQRS パターンでは、これを行う方法について何も触れていませんが、おそらく最もシンプルな実装では、読み取りシステムと書き込みシステムは基盤として同じデータ ストアを使用することになります。

この観点から、読み取りと書き取りは厳密に分離されており、書き込み処理でデータを返すことはありません。実は、この一見当たり障りのない考え方により、非常にスケーラブルなアプリケーションを作成できる可能性が高くなります。

CQRS アーキテクチャ スタイル

CQRS パターンだけでなく、CQRS アーキテクチャ スタイルの基盤もシンプルですが、このスタイルではデータの表示について完全に認識します。予約アプリケーションの UI を示す図 2 をご覧ください。レストランの予約アプリケーションだと考えてください。

image: Display Data Is Stale at the Moment It’s Rendered

図 2 表示の瞬間には既に古くなった表示データ

カレンダーには、指定月の日付が表示されますが、何日かは既に予約が埋まり無効になっています。

このような UI に表示されるデータは、どれくらい最新のものなのでしょう。データを表示して、ネットワーク上を転送し、ユーザーが確認して予約操作を行う間に、表示の基になったデータ ストアのデータは既に変更されているかもしれません。ユーザーが予約しないでデータを表示したままにしている時間が長いほど、データは古くなります。電話がかかってきて操作を中断するなど、操作を続行する前に気がそれる可能性があるため、待ち時間 (ユーザーが Web ページを十分確認するのにかかる時間) が数分発生する場合があります。

このような問題を解決するには、オプティミスティック同時実行制御を使用して、競合が発生する状況に対処するのが一般的な方法です。アプリケーション開発者は、このような状況に対処するためのコードを作成する必要がありますが、CQRS アーキテクチャ スタイルは、これを例外的な状況として扱うのではなく、基本的な状況として対処します。表示される瞬間には既に表示データが古くなっていても、集中管理している中央のデータ ストアのデータを反映する必要はありません。代わりに、アプリケーションは、"実際" のデータ ストアよりも少し遅れて更新される、非正規化されたデータ ソースからデータを表示できます。

表示データが常に古い状態であるという事実は、書き込みではデータが返されることはないという CQRS の原則と相まって、スケーラビリティが向上することになります。UI はデータが書き込まれるまで待機する必要がなく、非同期メッセージを送信して、ユーザーにビューを返すことができます。バックグラウンド ワーカーがそのメッセージを取得し、独自のペースでメッセージを処理します。図 3 は、CQRS スタイルのアーキテクチャの包括的なビューを示します。

image: CQRS-Style Architecture

図 3 CQRS スタイルのアーキテクチャ

アプリケーションからデータを更新する必要があるときは必ず、多くは永続性のあるキューを通じて、非同期メッセージとしてコマンドを送信します。コマンドを送信するとすぐに、UI はユーザーに自由にビューを返します。バックグラウンド ワーカーは、独立したプロセスでコマンド メッセージを取得し、データ ストアに適切な変更を書き込みます。この操作の一部として、別の非同期メッセージとしてイベントも発生します。他のメッセージ ハンドラーで、このようなイベントをサブスクライブし、イベントに応じてデータ ストアの非正規化されたビューを更新できます。

このビュー データは "実際" のデータよりも遅れて更新されますが、このイベント伝達は多くの場合、非常に高速で行われるため、ユーザーが気付くことはありません。あまりにも負荷が高くなり、システムの処理速度が低下した場合でも、ビュー データは最終的に一貫性が確保されます。

この種のアーキテクチャはさまざまなシステムに実装できますが、ワーカー ロールとキューという明確な考え方から Windows Azure が非常に適しています。ただし、Windows Azure にも CQRS に関連するいくつかの独特の課題があります。ここからはサンプル アプリケーションを利用して、メリットと課題の両方について説明します。

予約アプリケーション

簡単な予約アプリケーションは、Windows Azure に CQRS を実装する方法の良い例になります。このアプリケーションはレストランの予約を受け付けます。ユーザーに最初に表示するのは、図 2 に示す日付選択画面です。繰り返しますが、何日かは既に予約で埋まり無効になっています。

ユーザーが予約可能な日付をクリックすると、予約フォームを表示し、ユーザーが入力を完了したら予約内容の確認ページを表示します (図 4 参照)。

image: Reservation UI Flow

図 4 予約の UI フロー

確認ページでは、予約がこの時点では確実に完了していないことをユーザーに通知するメッセージを表示していることに注目してください。最終決定は電子メールで通知することになります。

CQRS では、処理がバックグラウンドで実行されるため、期待を抱かせるという点で UI が重要な役割を果たします。ただし、通常の負荷では、確認ページが表示されるまである程度の時間がかかるため、ユーザーが確認ページに移動したときには、予約は既に処理されています。

では、このサンプルの予約アプリケーションの実装について重要なポイントを示します。このような単純なアプリケーションでもたくさんの不確定要素があるので、ここでは最も興味深いコード スニペットに絞って説明します。完全なコードは、この記事付属のダウンロードで入手できます。

コマンドを送信する

Web ロールを ASP.NET MVC 2 アプリケーションとして実装します。ユーザーが図 4 に示したフォームを送信すると、適切なコントローラー アクションが呼び出されます。

[HttpPost] 
public ViewResult NewBooking(BookingViewModel model) 
{ 
  this.channel.Send(model.MakeNewReservation()); 
  return this.View("BookingReceipt", model); 
}

この単純な IChannel インターフェイスに、チャネル フィールドをインスタンスとして挿入します。

public interface IChannel 
{ 
  void Send(object message); 
}

NewBooking メソッドからチャネルを通じて送信するコマンドは、データ転送オブジェクトにカプセル化した HTML 形式のデータです。次に示すように、MakeNewReservation メソッドでは単純に、入力されたデータを MakeReservationCommand インスタンスに変換します。

public MakeReservationCommand MakeNewReservation() 
{ 
  return new MakeReservationCommand(this.Date, 
    this.Name, this.Email, this.Quantity); 
}

Send メソッドは void を返すため、コマンドが正常に送信されたら、UI はすぐに自由に HTML ページをユーザーに返すことができます。IChannel インターフェイスをキューの先頭に実装して、Send メソッドからできるだけ速く値が返されるようにします。

Windows Azure では、Windows Azure ストレージの一部である組み込みのキューの先頭に、IChannel インターフェイスを実装できます。このような永続性のあるキューにメッセージを配置するには、実装でメッセージをシリアル化する必要があります。これを実行するにはさまざまな方法がありますが、ここでは簡潔さを保つために、.NET Framework に組み込まれているバイナリ シリアライザーを使用します。ただし、運用アプリケーションでは、バイナリ シリアライザーを使用するとバージョン管理の問題の処理が困難になるため、代替案を検討することを強くお勧めします。たとえば、新しいバージョンのコードを使用して、古いバージョンのコードでシリアル化された BLOB をシリアル化解除しようとするとどうなるか考えてみてください。考えられる代替案には、XML、JSON、または Protocol Buffers があります。

このテクノロジ スタックを使えば IChannel.Send メソッドを実装するのは簡単です。

public void Send(object command) 
{ 
  var formatter = new BinaryFormatter(); 
  using (var s = new MemoryStream()) 
  { 
    formatter.Serialize(s, command); 
    var msg = new CloudQueueMessage(s.ToArray()); 
    this.queue.AddMessage(msg); 
  } 
}

Send メソッドではコマンドをシリアル化して、結果のバイト配列から新しく CloudQueueMessage インスタンスを作成します。Windows Azure SDK から CloudQueue クラスに、キュー フィールドをインスタンスとして挿入します。適切なアドレス情報と資格情報に基づいて初期化すると、AddMessage メソッドによって、適切なキューにメッセージが追加されます。通常、この処理は驚くほど高速に行われるため、メソッドから戻ったときには、呼び出し元は自由に他の処理を実行できます。これと同時に、メッセージはキューに格納されるようになり、バックグラウンド プロセスで取得されるまで待機します。

コマンドを処理する

Web ロールが HTML を適切に表示し、IChannel インターフェイスを通じて送信されるデータを受け取っている間、ワーカー ロールは独自のタイミングでキューからメッセージを受け取り、処理します。このようなバックグラウンド ワーカーはステートレスで自律的なコンポーネントなので、着信するメッセージの処理が間に合わなければ、インスタンスを追加することができます。その結果、メッセージ ベースのアーキテクチャのスケーラビリティが向上することになります。

先ほど説明したように、Windows Azure キューを通じてメッセージを送信するのは簡単です。ただし、安全かつ一貫した方法でメッセージを処理するのは少し厄介です。各コマンドは、アプリケーションの状態を変更するという目的をカプセル化しているため、バックグラウンド ワーカーは、メッセージを失うことなく、基になるデータを一貫した方法で変更しなければなりません。

これは、Microsoft メッセージ キューなどの分散トランザクションをサポートするキュー テクノロジを使えば、非常に簡単に実現できます。Windows Azure キューはトランザクション対応ではありませんが、独自の保証が備わっています。メッセージは読み取り時に失われませんが、一定時間非表示になります。クライアントはメッセージをキューから取得して、適切な操作を実行し、プロセスの最後の手順としてメッセージを削除する必要があります。これが、サンプルの予約アプリケーションの汎用ワーカー ロールで実行される処理です。つまり、図 5 に示すような PollForMessage メソッドを無限ループで実行します。

図 5 PollForMessage メソッド

public void PollForMessage(CloudQueue queue) 
{ 
  var message = queue.GetMessage(); 
  if (message == null) 
  { 
    Thread.Sleep(500); 
    return; 
  } 
  
  try 
  { 
    this.Handle(message); 
    queue.DeleteMessage(message); 
  } 
  catch (Exception e) 
  { 
    if (e.IsUnsafeToSuppress()) 
    { 
      throw; 
    } 
    Trace.TraceError(e.ToString()); 
  } 
}

現在キューにメッセージが格納されていなければ、GetMessage メソッドは null を返します。この場合、メソッドは 500 ミリ秒だけ待機してから null を返します。そして、外側の無限ループですぐに再び呼び出されます。GetMessage メソッドはメッセージを受け取ると、Handle メソッドを呼び出してメッセージを処理します。ここでは、実際のすべての処理を行うことを想定しているため、このメソッドから例外をスローすることなく戻ると、メッセージは安全に削除されます。

一方で、メッセージの処理中に例外が発生する場合は、この例外を抑制することが重要になります。ハンドルされない例外があると、ワーカー インスタンス全体がクラッシュし、キューからのメッセージの取得を停止することになります。

運用環境で使用できる実装は、いわゆる有害メッセージを処理できるように、より高度にする必要がありますが、このサンプル コードでは簡潔さを保つために省略しています。

メッセージの処理中に例外がスローされると、そのメッセージは削除されません。タイムアウト後、再び処理できるようになります。というのも、Windows Azure キューでは、メッセージは少なくとも 1 回は処理できることが保証されているためです。当然の結果として、メッセージは何度か再生される可能性があります。したがって、すべてのバックグラウンド ワーカーは、メッセージの再生に対応できる必要があります。基本的に、永続性のある書き込み操作はすべてアイデムポテント (何度処理しても結果が同じ) です。

書き込み操作をアイデムポテントにする

メッセージを処理するすべてのメソッドは、アプリケーションの状態に問題が発生しないように、再生に対応できる必要があります。MakeReservationCommand インスタンスの処理が良い例です。図 6 は、メッセージ フローの概要を示しています。

image: Workflow for Handling the Make Reservation Command

図 6 MakeReservationCommand インスタンスを処理するためのワークフロー

アプリケーションがまず実行する必要があるのは、要求された日に、レストランに十分な空きがあるかどうかを確認することです。指定日によっては、すべてのテーブルが既に予約済みであったり、数席しか空いていなかったりする可能性があります。空き状況を表示するため、アプリケーションは永続性のあるストレージで現在の空き状況を追跡します。これを行うには、いくつか方法があります。SQL Azure データベースで予約データをすべて追跡するのも 1 つの方法ですが、SQL Azure データベースのサイズには限界があるため、よりスケーラビリティの高い方法として、Windows Azure BLOB またはテーブル ストレージを使用します。

サンプルの予約アプリケーションでは、BLOB ストレージを使用して、シリアル化されたアイデムポテントな値オブジェクトを格納します。この Capacity クラスは、メッセージの再生を検出できるように、受け取った予約を追跡します。残りの空き状況を表示するには、アプリケーションで目的の日付の Capacity インスタンスを読み取り、現在の予約 ID を指定して CanReserve メソッドを呼び出します。

public bool CanReserve(int quantity, Guid id) 
{ 
  if (this.IsReplay(id)) 
  { 
    return true; 
  } 
  return this.remaining >= quantity; 
} 
  
private bool IsReplay(Guid id) 
{ 
  return this.acceptedReservations.Contains(id); 
}

MakeReservationCommand インスタンスには、それぞれ関連する ID が設定されています。アイデムポテントな動作を実現するには、Capacity クラスで再生を検出できるように、受け取った予約 ID をそれぞれ保存します。メソッド呼び出しが再生でない場合のみ、実際のビジネス ロジックを呼び出し、要求された席数と残りの空き状況を比較します。

アプリケーションでは、それぞれの日付の Capacity インスタンスをシリアル化して格納します。そのため、レストランに空きがあるかどうかを表示するには、BLOB をダウンロードして CanReserve メソッドを呼び出します。

public bool HasCapacity(MakeReservationCommand reservation) 
{ 
  return this.GetCapacityBlob(reservation) 
    .DownloadItem() 
    .CanReserve(reservation.Quantity, reservation.Id); 
}

空きがある場合、アプリケーションは、その結果に関連のある一連の操作を呼び出します (図 6 参照)。まず、図 7 に示すように、Capacity.Reserve メソッドを呼び出して、空席の数を減らします。

図 7 Capacity.Reserve メソッド

public Capacity Reserve(int quantity, Guid id) 
{ 
  if (!this.CanReserve(quantity, id)) 
  { 
    throw new ArgumentOutOfRangeException(); 
  } 
  
  if (this.IsReplay(id)) 
  { 
    return this; 
  } 
  
  return new Capacity(this.Remaining - quantity,  
    this.acceptedReservations 
      .Concat(new[] { id }).ToArray()); 
}

これは、最初に CanReserve メソッドと IsReplay メソッドを念のため呼び出す、別のアイデムポテントな操作です。メソッド呼び出しで、席をいくつか予約するための完全に新しい要求を送信すると、空席が減った新しい Capacity インスタンスが返され、受け取った ID の一覧に ID が追加されます。

Capacity クラスは値オブジェクトなので、操作が完了する前に、Windows Azure BLOB ストレージにコミットする必要があります。図 8 は、元の BLOB を Windows Azure BLOB ストレージから初めてダウンロードする方法を示しています。

図 8 空席の数を減らしてストレージにコミットする

public void Consume(MakeReservationCommand message) 
{ 
  var blob = this.GetCapacityBlob(message); 
  var originalCapacity = blob.DownloadItem(); 
  
  var newCapacity = originalCapacity.Reserve( 
    message.Quantity, message.Id); 
  
  if (!newCapacity.Equals(originalCapacity)) 
  { 
    blob.Upload(newCapacity); 
    if (newCapacity.Remaining <= 0) 
    { 
      var e = new SoldOutEvent(message.Date); 
      this.channel.Send(e); 
    } 
  } 
}

これは、要求された予約の日付に対応するシリアル化済みの Capacity インスタンスです。空き状況が変更されると (つまり、インスタンスが再生ではなかった場合)、新しい Capacity インスタンスが BLOB ストレージにアップロードされます。

これまでの処理で例外がスローされたらどうなるでしょう。CanReserve メソッドが呼び出されてから Capacity インスタンスが変更されると、例外がスローされる可能性があります。これは、たくさんの競合する要求が同時に処理される、大規模なシナリオで発生すると考えられます。このような場合は、十分な空きがないため、Reserve メソッドで例外がスローされる可能性があります。これは問題ありません。単純に、この特定の予約の要求と同時に行われた要求が失われることになります。例外は図 5 に示す例外ハンドラーでキャッチされますが、メッセージは削除されないため、後で再表示され、もう一度処理されます。例外がスローされると、CanReserve メソッドではすぐに false を返すので、要求は丁重に拒否されます。

ただし、図 8 には、別の同時実行の競合が隠れている可能性があります。2 つのバックグラウンド ワーカーが、同じ日付の空き状況を同時に更新するとどうなるでしょう。

オプティミスティック同時実行制御を使用する

図 8 の Consume メソッドでは、BLOB ストレージから Capacity インスタンスの BLOB をダウンロードし、変更があった場合は新しい値をアップロードします。たくさんのバックグラウンド ワーカーが、この処理を同時に実行している可能性があるため、アプリケーションでは、ある値で別の値が上書きされないようにする必要があります。

Windows Azure ストレージは REST ベースなので、このような同時実行の問題に対処するには ETag を使用することをお勧めします。アプリケーションが指定日の Capacity インスタンスを初めて作成する場合、ETag は null になりますが、ストレージから既存の BLOB をダウンロードする場合、ETag は CloudBlob.Properties.ETag を通じて利用できる値になります。アプリケーションから Capacity インスタンスをアップロードするときは、BlobRequestOptions インスタンスに正しい AccessCondition を適切に設定する必要があります。

options.AccessCondition = etag == null ?  
  AccessCondition.IfNoneMatch("*") :  
  AccessCondition.IfMatch(etag);

アプリケーションで新しい Capacity インスタンスを作成する場合、ETag を null にし、AccessCondition には IfNoneMatch("*") を設定する必要があります。これで、BLOB が既に存在する場合は、例外がスローされるようになります。一方で、現在の書き込み操作が更新を表す場合、AccessCondition には IfMatch を設定する必要があります。これで、BLOB ストレージ内の ETag が、指定した ETag に一致しなければ例外がスローされるようになります。

ETag に基づくオプティミスティック同時実行制御はよく使用する重要なツールですが、適切な BlobRequestOptions インスタンスを指定して明示的に有効にする必要があります。

空席の数を減らす間に例外がスローされなければ、アプリケーションは図 6 の次の手順に進み、テーブル ストレージに予約を書き込みます。この処理については、空席の数を減らすときとほぼ同じ原則に従うため、ここでは説明を省略します。このコードは、この記事付属のダウンロードから入手できます。繰り返しますが、書き込み操作をアイデムポテントにすることが最大のポイントです。

このワークフローの最後の手順では、予約を受け付けたことを示すイベントが発生します。これは、Windows Azure キューを通じて、別の非同期メッセージを送信することで実行できます。このドメイン イベントをサブスクライブする任意のバックグラウンド ワーカーが、このメッセージを取得して処理できます。関連するアクションにより、ユーザーに確認の電子メールが送信されますが、アプリケーションではビュー データ ストアを更新して、UI のループ処理を終了する必要があります。

ビュー データを更新する

コマンドの処理中に発生するイベントは、IChannel インターフェイスを通じて、非同期メッセージとして送信されます。たとえば、図 8 の Consume メソッドでは、空席が 0 になると新しく SoldOutEvent が発生します。次に示すように、他のメソッド ハンドラーでこのようなイベントをサブスクライブして、ビュー データを適切に更新することができます。

public void Consume(SoldOutEvent message) 
{ 
  this.writer.Disable(message.Date); 
}

挿入したライターは、BLOB ストレージ内の対応する月の無効日付の配列を更新する、Disable メソッドを実装します。

public void Disable(DateTime date) 
{ 
  var viewBlob = this.GetViewBlob(date); 
  DateTime[] disabledDates = viewBlob.DownloadItem(); 
  viewBlob.Upload(disabledDates 
    .Union(new[] { date }).ToArray()); 
}

このメソッド実装では、BLOB ストレージから無効な DateTime インスタンスの配列を単純にダウンロードし、配列に新しい日付を追加して、再びアップロードします。Union メソッドを使用しているため、操作はアイデムポテントになり、ここでも Upload メソッドが ETag ベースのオプティミスティック同時実行制御をカプセル化します。

ビュー データをクエリする

これで、UI はビュー データから直接クエリできるようになります。データは静的で、計算の必要がないため、これは効果的な方法です。たとえば、図 2 の日付選択の無効日付を更新するには、この日付選択からコントローラーに AJAX 要求を送信して、配列を取得します。

コントローラーでは、次のように要求を処理するだけです。

public JsonResult DisabledDays(int year, int month) 
{ 
  var data = this.monthReader.Read(year, month); 
  return this.Json(data, JsonRequestBehavior.AllowGet); 
}

挿入したリーダーは、SoldOutEvent ハンドラーが書き込む BLOB を読み取る、Read メソッドを実装します。

public IEnumerable<string> Read(int year, int month) 
{ 
  DateTime[] disabledDates =  
    this.GetViewBlob(year, month).DownloadItem(); 
  return (from d in disabledDates 
    select d.ToString("yyyy.MM.dd")); 
}

これで、ループは終了します。ユーザーは最新のビュー データに基づくサイトを参照し、フォームに入力して、非同期メッセージを通じて処理データを送信します。最後に、ビュー データは、ワークフローで発生したドメイン イベントに基づいて更新されます。

データを非正規化する

以上をまとめると、ほとんどのアプリケーションでは、書き込むデータよりもはるかに多くのデータを読み取ります。そのため、読み取りを最適化することで、特に、BLOB などの静的リソースからデータを読み取る場合に、スケーラビリティを向上することができます。画面に表示するデータは常に非接続の状態なので、表示する時点では古いデータです。CQRS では、データの読み取りと書き込みを切り離すことで、このように古くなっていくデータに対応します。データを読み取る際、データの書き込み先と同じデータ ソースから直接取得する必要はありません。代わりに、データの準備と操作のコストが 1 度しかかからないビュー固有のデータ ストアに、データの書き込み先のストアから非同期に転送できます。

組み込みのキューと、スケーラブルで非正規化されたデータ ストアを備えているため、Windows Azure はこのようなアーキテクチャに非常に適しています。分散トランザクションがサポートされていないにもかかわらず、キューでは、メッセージが失われることなく、少なくとも 1 回は処理されることが保証されます。再生が行われる場合に備えて、非同期書き込み操作はすべて、アイデムポテントにする必要があります。BLOB、テーブル ストレージなど、非正規化されたデータに対しては、ETags を使用して、オプティミスティック同時実行制御を実装する必要があります。このようなシンプルな手法を使用することで、結果として一貫性を確保できます。

今回は、CQRS についてほんの少し説明しただけです。CQRS に関する詳細情報をお探しであれば、現在はインターネット上のたくさんのリソースで知識体系が展開されています。中でも、Rinat Abdullin による CQRS スタート ページ (abdullin.com/cqrs、英語) は、学習を始めるのに最適です。

Mark Seemann は、Commentor A/S (コペンハーゲンを拠点とするデンマークのコンサルティング会社) の Windows Azure テクニカル リードを務めています。『Dependency Injection in .NET』(Manning Publications、2011 年) の著者であり、オープン ソース プロジェクトの AutoFixture の開発者でもあります。彼のブログは、blog.ploeh.dk (英語) で公開されています。

この記事のレビューに協力してくれた技術スタッフの Rinat AbdullinKarsten Strøbæk に心より感謝いたします。