Amazon Web サービスを利用したエンタープライズ・アプリケーションの統合

Amazon SQS による XML メッセージング

XML と Amazon Web サービスを利用してエンタープライズ・アプリケーションを統合する方法、そして Microsoft® .NET (C#) および Java™ という異なるプラットフォームで動作するアプリケーションの統合を利用して新しい機能を作成する方法を学びましょう。

Brian J Stewart, Principal Consultant, Aqua Data Technologies, Inc.

Photo of Brian StewartBrian J. Stewart は現在、Aqua Data Technologies で主任コンサルタントを務めています。彼が設立したこの会社は、コンテンツ・マネージメント、XML 技術、そしてエンタープライズ・クライアント/サーバーおよび Web システムを専門としています。彼は Java EE および Microsoft .NET プラットフォームをベースとしてエンタープライズ・ソリューションを設計、開発しています。Brian は BrianJStewart.com でブログを公開しています。



2009年 6月 16日

キューとは、処理待ちメッセージを格納しておくための一時的なデータ構造のことです。Amazon SQS (Amazon Simple Queue Services) は極めて可用性の高いスケーラブルなメッセージ・キューで、Web サービスに対応します。Amazon SQS の主な利点は以下のとおりです。

よく使われる頭字語

  • API: Application Programming Interface
  • DOM: Document Object Model
  • HTTP: Hypertext Transfer Protocol
  • XML: Extensible Markup Language
  • クラウド・ベースのソリューション。Amazon が管理するため、自前のインフラストラクチャーやサポートの専門知識などは一切必要ありません。
  • インターネット・ベース。インターネットにアクセスできるクライアントであれば、どのクライアントでも Web サービスを介してこのサービスを利用できるため、企業間 (B2B) での統合も可能です。
  • 冗長性。このサービスではすべてのメッセージを複数のサーバーに保管するため、高可用性と耐障害性がもたらされます。
  • 複数の同時読み取り/書き込み操作。Amazon SQS はキューに対する複数の同時読み取り操作と書き込み操作に対応するとともに、処理時間枠内はメッセージをロックして 2 つのクライアントがメッセージを同時に処理できないようにします。
  • 構成可能であること。Amazon SQS サービスでは、キューに格納するメッセージ固有の処理要件に応じてロック時間枠を指定することができます。ロック時間枠は、2 つのキュー・リーダーがキュー内の同じデータを処理するのを防ぎます。処理に時間がかかるキューのデータには、それだけ長い時間枠が必要です。ロック時間枠を制御する可視性タイムアウト・パラメーターは、それぞれのキューごとに構成することができます。
  • 使いやすい API。Java および Microsoft .NET プラットフォームをはじめとする一般的な言語に対して用意された API ラッパーが、迅速な開発と既存のアプリケーションへのシームレスな統合を可能にします。
  • 低コストのソリューション。企業が支払うのは、使用した分の帯域幅と HTTP リクエストに対してのみです。Amazon SQS では最低料金体系を設定していません。

Amazon SQS を利用したアプリケーションの開発の詳細を調べるには、その前に Amazon SQS の特徴をいくつか知っておくと参考になります。これらの特徴を知らなければ、初めのうちは Amazon SQS での作業がもどかしく、わかりにくいという印象を持つかもしれません。

第 1 の特徴として、Amazon ではキュー内のデータを処理する順序を保証しません。これはつまり、多くのメッセージ・キューの実装で一般的なファーストイン・ファーストアウト (FIFO) 処理が保証されないということです。Amazon で保証しているのは、すべてのメッセージが配信されることだけです。

次に重要な Amazon SQS の特徴は、結果整合性という概念です。大規模なデータベース・システムでは、整合性、高可用性、スケーラビリティーが重要な特性となります。しかし Amazon ではこの 3 つすべての特性に重点を置く代わりに、高可用性とスケーラビリティーに重点を置き、整合性については結果整合性を提供することにしました。具体的に言うと、Amazon はすべてのメッセージを複数のサーバーに伝播させ、高可用性とスケーラビリティーを実現します。その一方、Amazon は最終的にすべてのメッセージが配信されることは保証するものの、いつ配信されるかについては保証しません。実際的な観点からすると、例えば 3 つのメッセージをキューに送信した場合、次にメッセージを受信しようとしても、3 つすべてのメッセージを受信するとは限らないことを意味します。1 回の Read で 3 つすべてのメッセージを受信する可能性もあれば、最初の Read では 2 つのメッセージを受信し、その後の Read で 3 つ目のメッセージを受信する可能性も考えられます。キューを継続的にポーリングすることで、最終的には 3 つすべてのメッセージを受信することになります。

Amazon SQS の利用を開始するには、使用している言語に応じた Amazon SQS の API ライブラリーを取得する必要があります。Amazon では、Perl、Microsoft Visual Basic®.NET、C#、Java、PHP など、一般的に使用されているすべての言語に対応したライブラリーを用意しています。これらのライブラリーはオープンソースで、簡単に使用することができます。ライブラリーをダウンロードするには、「参考文献」のリンクを参照してください。

Java 言語での Amazon SQS の使用方法

まずは Java 言語を使用して、キューを作成し、メッセージの送受信を行う方法を見ていきます。最初のステップは、Amazon SQS キューを作成することです。リスト 1 のコードで、Amazon SQS 用の HTTP クライアントを作成し、CreateQueueRequest オブジェクトをインスタンス化して、このキューを作成するリクエストを開始する方法を説明します。アクセス・キー ID (20 文字からなる英数字の文字列) は、認証を要求したり、キューの内容を読み出したりするために必要なキーです。キューにデータを書き込んだり、キュー内のデータを操作したりするには、シークレット・アクセス・キーが必要になります (40 文字からなる英数字の文字列)。この 2 つのキーは、Amazon への登録時に提供されます (クラウドのセキュリティーに関する記事へのリンクは、「参考文献」を参照してください。その記事では、これらのキーについて詳しく説明しています)。

リスト 1. Amazon SQS キューを作成する
String queueName = "TestQueue";

// create http client
AmazonSQS service = new AmazonSQSClient(accessKeyId, secretAccessKey);

// instantiate create queue request
CreateQueueRequest request = new CreateQueueRequest();
request.setQueueName(queueName);
request.setDefaultVisibilityTimeout(30);

// execute create queue operation and get the server response
System.out.print("Creating Queue: " + queueName);    
CreateQueueResponse response = service.createQueue(request);
if (response.isSetCreateQueueResult()) {
	System.out.print("Create Queue Result:");    
	CreateQueueResult createQueueResult = response.getCreateQueueResult();
	if (createQueueResult.isSetQueueUrl()) {
		System.out.print("Queue Url: " + createQueueResult.getQueueUrl());
	}
}

次のステップでは、新しく作成したキューにメッセージを送信します。リスト 2 のコードで、Amazon SQS 用の HTTP クライアントを作成する方法、そして単純なメッセージをキューに送信する方法を説明します。

リスト 2. メッセージをキューに送信する
String queueName = "TestQueue";

// create http client
AmazonSQS service = new AmazonSQSClient(accessKeyId, secretAccessKey);

// instantiate send message request
SendMessageRequest request = new SendMessageRequest();
request.setQueueName(queueName);
request.setMessageBody("Test SQS Message");

// execute the send message operation and get the server response
SendMessageResponse response = service.sendMessage(request);
if (response.isSetSendMessageResult()) {
	System.out.print("Send Message Result: ");

	SendMessageResult sendMessageResult = response.getSendMessageResult();
	if (sendMessageResult.isSetMessageId()) {
		System.out.print("\tMessageId: " + sendMessageResult.getMessageId());
	}
}

今度は、キューからメッセージを受け取ります。Amazon SQS 用の HTTP クライアントを作成し、キューからメッセージを受信する方法をリスト 3 に記載します。キューから送信されたメッセージが含まれている Message には、以下の重要なメソッドが提供されています。

  • getMessageId。メッセージの一意の ID を返します。メッセージ ID が設定されているかどうかを判断するには、isSetMessageId を使用します。
  • getReceiptHandle。メッセージのハンドルを返します。メッセージを削除するには、このハンドルが必要です。メッセージのハンドルが設定されているかどうかを判断するには、isSetReceiptHandle を使用します。
  • getBody。メッセージ本体をストリングで返します。メッセージはプレーン・テキストにすることも XML にすることもできます。メッセージ本体が設定されているかどうかを判断するには、isSetBody を使用します。
リスト 3. メッセージをキューから受信する
String queueName = "TestQueue";

// create http client
AmazonSQS service = new AmazonSQSClient(accessKeyId, secretAccessKey);

// instantiate the receive message request
ReceiveMessageRequest request = new ReceiveMessageRequest();
request.setQueueName(queueName);
// the following two parameters are optional
request.setMaxNumberOfMessages(10); // set maximum number of messages to receive
request.setVisibilityTimeout(30); // set visibility window
		 
// execute the receive messages operation and get server response
ReceiveMessageResponse response = service.receiveMessage(request);

System.out.print("Receive Message Response:");

if (response.isSetReceiveMessageResult()) {
	ReceiveMessageResult  receiveMessageResult = response.getReceiveMessageResult();
	java.util.List<Message> messageList = receiveMessageResult.getMessage();
	for (Message message : messageList) {			
		if (message.isSetMessageId()) {
			System.out.print("MessageId: " + message.getMessageId());
		}

		if (message.isSetReceiptHandle()) {
			System.out.print("ReceiptHandle: " + message.getReceiptHandle());
		}
		if (message.isSetBody()) {
			System.out.print("Body: " + message.getBody());
		}
}

C# での Amazon SQS の使用方法

今度は C# を使って、オブジェクトを XML にシリアライズしてから Amazon SQS メッセージとして送信します。

最初のステップは、シリアライズするビジネス・オブジェクトを作成することです。リスト 4 に Product オブジェクトを記載します。public なプロパティーは、XML のシリアライズを制御するための属性で修飾されます。C# の属性は Java のアノテーションと同様で、プロパティーを XML 要素または XML 属性にマッピングする方法を定義します。さらに、このクラスにはオブジェクト・インスタンスを XML にシリアライズするための ToXml() メソッドがあります。

リスト 4. シリアライズするビジネス・オブジェクトを作成する
namespace Stewart.Test
{
/// <summary>
/// Product
/// </summary>
[XmlRoot(ElementName="Product")]
public class Product
{
	/// <summary>
	/// Product Name
	/// </summary>
	[XmlElement("ProductName")]
	public string ProductName;

	/// <summary>
	/// Product Price
	/// </summary>
	[XmlElement("ProductPrice")]
	public decimal ProductPrice;

	/// <summary>
	/// Quantity in stock
	/// </summary>
	[XmlElement("InStock")]
	public bool InStock;

	/// <summary>
	/// Product Id
	/// </summary>
	[XmlAttributeAttribute(AttributeName = "Id", DataType = "integer")]
	public string Id;

	/// <summary>
	/// Initializes a new instance of the <see cref="Product"/> class.
	/// </summary>
	public Product()
	{
	}

	/// <summary>
	/// Initializes a new instance of the <see cref="Product"/> class.
	/// </summary>
	/// <param name="productName">Name of the product.</param>
	/// <param name="productPrice">The product price.</param>
	public Product(string productName, decimal productPrice)
	{
		this.ProductName = productName;
		this.ProductPrice = productPrice;
	}

	/// <summary>
	/// Converts to XML.
	/// </summary>
	/// <returns></returns>
	public String ToXml()
	{
		StringBuilder output = new StringBuilder();

		// no name space
		XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
		ns.Add("", "");

		// settings to omit xml declaration
		XmlWriterSettings settings = new XmlWriterSettings();
		settings.OmitXmlDeclaration = true;

		// finally serialize to string
		XmlWriter writer = XmlTextWriter.Create(output, settings);
		XmlSerializer serializer = new XmlSerializer(typeof(Product));            
		serializer.Serialize(writer, this, ns);

		// return string containing XML document
		return output.ToString();
	}
}

次に、XML メッセージを送信します。Amazon SQS の Amazon C# API は、Java API と機能的に等価でよく似ています。リスト 5 のコードでは、C# を使用してメッセージを送信する方法を説明します。

リスト 5. C# を使用してメッセージを送信する
Product prod = new Product("Widget", 1.5M);
string accessKeyId = ConfigurationSettings.AppSettings["AmazonAccessKeyID"];
string secretAccessKey = ConfigurationSettings.AppSettings["AmazonSecretAccessKey"];

AmazonSQS service = new AmazonSQSClient(accessKeyId, secretAccessKey);
SendMessageRequest request = new SendMessageRequest();
request.MessageBody = prod.ToXml();
request.QueueName = "TestQueue";

SendMessageResponse response = service.SendMessage(request);
if (response.IsSetSendMessageResult())
{
	Console.WriteLine("Send Message Response: ");
	SendMessageResult sendMessageResult = response.SendMessageResult;
	if (sendMessageResult.IsSetMessageId())
	{
		Console.WriteLine(String.Format("MessageId {0}", 
			sendMessageResult.MessageId));
	}
	if (sendMessageResult.IsSetMD5OfMessageBody())
	{
		Console.WriteLine(String.Format("MD5OfMessageBody: {0}", 
			sendMessageResult.MD5OfMessageBody));
	}
}

リスト 5 の出力は図 1 のようになります。

図 1. XML メッセージ送信による出力
XML メッセージ送信による出力

最後のステップでは、キューから XML メッセージを受信して、そのインスタンスをデシリアライズします。XML メッセージを Product インスタンスにデシリアライズするためのコードをリスト 6 に記載します。

リスト 6. XML メッセージをデシリアライズする
Product prod = null;            

string accessKeyId = ConfigurationSettings.AppSettings["AmazonAccessKeyID"];
string secretAccessKey = ConfigurationSettings.AppSettings["AmazonSecretAccessKey"];

AmazonSQS service = new AmazonSQSClient(accessKeyId, secretAccessKey);

ReceiveMessageRequest request = new ReceiveMessageRequest();
request.QueueName = "TestQueue";
ReceiveMessageResponse response = service.ReceiveMessage(request);

if (response.IsSetReceiveMessageResult())
{
	Console.WriteLine("Receive Message Result:");
	ReceiveMessageResult receiveMessageResult = response.ReceiveMessageResult;
	List<Message> messageList = receiveMessageResult.Message;
	foreach (Message message in messageList)
	{                    
		if (message.IsSetMessageId())
		{
			Console.WriteLine(String.Format("MessageId: {0}",
				message.MessageId));
		}                                      
		if (message.IsSetBody())
		{
			Console.WriteLine(string.Format("Body: {0}", message.Body));
			String xml = message.Body;
			StringReader sr = new StringReader(xml);
			XmlSerializer serializer = new XmlSerializer(typeof(Product));
			prod = (Product) serializer.Deserialize(sr);
			Console.WriteLine(string.Format("Id: {0}", prod.Id));
			Console.WriteLine(string.Format("Name: {0}", prod.ProductName));
			Console.WriteLine(string.Format("Price: {0}", prod.ProductPrice));
		}
	}
}

リスト 6 による出力は図 2 のようになります。

図 2. XML メッセージ受信による出力
XML メッセージ受信による出力

上記の例は極めて単純なものですが、オブジェクトをデシリアライズして、アプリケーション境界がローカル側の物理ネットワークに制約されていない別のアプリケーションにメッセージを送信できるという点で、非常に強力です。ここには複雑なファイアウォールの制約も、セキュリティーに関する懸念事項もありません。その上、メッセージの送信側と受信側を同じ言語で作成する必要はなく、同じプラットフォームを使用する必要さえありません。

技術的な概要および設計

このデモ用のソリューションは、再販業者 (Reseller) とビジネス・プロセスを統合する必要のあるメーカー (Manufacturer) からなります。再販業者がメーカーから商品を購入し、顧客 (Customer) に販売します。

顧客が商品を注文すると、再販業者が C# WinForm クライアントを使用して顧客の注文を送信します。すると発注プログラムがその注文内容をローカルの MySQL データベースに保存します。また、このクライアントではユーザーが在庫を調べたり、注文を確認したり、Amazon SQS メッセージ・キューの中身を確認したりすることもできます。

通常、再販業者の在庫には顧客の注文に対応できるだけの十分な商品がありますが、そうでない場合、再販業者はほぼリアルタイムでメーカーに発注書を送信します。メーカーは商品を出荷すると、再販業者に注文明細書を送信します。このすべてのやり取りは、Amazon SQS を使用して行われます。

同じく C# を使用して作成されている再販業者の注文調達および在庫管理サービス (Order fulfillment and inventory management Service) は、商品の入荷がないか、また顧客の注文で未処理のものがないかポーリングします。顧客の注文を処理する際に、商品の在庫が注文された数に満たない場合、Amazon SQS を使用して新しい発注書をメーカーに送信します。キューに送信されるメッセージ本体は、発注書が含まれる XML 文書です。

発注書のキューを処理するのは、Java プラットフォームで作成されている、メーカーの注文処理サービス (Order processing service) です。このサービスは商品の出荷後に Amazon SQS を使用してメッセージを再販業者に送信します。このメッセージ本体は、注文明細書が含まれる XML 文書です。

図 3 に、関連するシステムを示します。

図 3. ソリューションの概要
ソリューションの概要

XML スキーマを作成する

最初のステップでは、再販業者とメーカーとの間で送信されるメッセージの XML スキーマを定義します。発注書用と注文明細書用に 2 つのスキーマが必要です。

リスト 7 に、発注書のスキーマを記載します。

リスト 7. 発注書のスキーマ
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="PurchaseOrder">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="Id" type="xs:string" minOccurs="0" />
        <xs:element name="OrderDate" type="xs:string" minOccurs="0" />
        <xs:element name="Company" minOccurs="0" maxOccurs="unbounded">
          <xs:complexType>
            <xs:sequence>
              <xs:element name="CompanyName" type="xs:string" minOccurs="0" />
              <xs:element name="StreetAddress" type="xs:string" minOccurs="0" />
              <xs:element name="City" type="xs:string" minOccurs="0" />
              <xs:element name="State" type="xs:string" minOccurs="0" />
              <xs:element name="ZipCode" type="xs:string" minOccurs="0" />
            </xs:sequence>
          </xs:complexType>
        </xs:element>
        <xs:element name="Vendor" minOccurs="0" maxOccurs="unbounded">
          <xs:complexType>
            <xs:sequence>
              <xs:element name="CompanyName" type="xs:string" minOccurs="0" />
              <xs:element name="StreetAddress" type="xs:string" minOccurs="0" />
              <xs:element name="City" type="xs:string" minOccurs="0" />
              <xs:element name="State" type="xs:string" minOccurs="0" />
              <xs:element name="ZipCode" type="xs:string" minOccurs="0" />
            </xs:sequence>
          </xs:complexType>
        </xs:element>
        <xs:element name="Items" minOccurs="0" maxOccurs="unbounded">
          <xs:complexType>
            <xs:sequence>
              <xs:element name="Item" minOccurs="0" maxOccurs="unbounded">
                <xs:complexType>
                  <xs:attribute name="Id" type="xs:string" />
                  <xs:attribute name="Name" type="xs:string" />
                  <xs:attribute name="Quantity" type="xs:string" />
                </xs:complexType>
              </xs:element>
            </xs:sequence>
          </xs:complexType>
        </xs:element>
      </xs:sequence>
    </xs:complexType>
  </xs:element>  
</xs:schema>

発注書の XML スキーマは主に以下の要素で構成されています。

表 1. 発注書のスキーマの主な要素
主な要素説明
Id発注書の一意の ID が含まれるストリング
OrderDate発注書の日付が含まれるストリング
Company再販業者の宛先情報 (会社名、住所、郵便番号など) が含まれます。
Vendorベンダー (メーカー) の宛先情報 (会社名、住所、郵便番号など) が含まれます。
Items注文されたすべての商品の商品 ID、商品名、数量が含まれます。

リスト 8 に、注文明細書のスキーマを記載します。

リスト 8. 注文明細書のスキーマ
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
  <xs:element name="OrderSummary">
    <xs:complexType>
      <xs:sequence>
        <xs:element name="OrderId" type="xs:string" minOccurs="0" />
        <xs:element name="ReferenceId" type="xs:string" minOccurs="0" />
        <xs:element name="OrderDate" type="xs:string" minOccurs="0" />
        <xs:element name="CompanyAddress" minOccurs="0" maxOccurs="unbounded">
          <xs:complexType>
            <xs:sequence>
              <xs:element name="CompanyName" type="xs:string" minOccurs="0" />
              <xs:element name="StreetAddress" type="xs:string" minOccurs="0" />
              <xs:element name="City" type="xs:string" minOccurs="0" />
              <xs:element name="State" type="xs:string" minOccurs="0" />
              <xs:element name="ZipCode" type="xs:string" minOccurs="0" />
            </xs:sequence>
          </xs:complexType>
        </xs:element>
        <xs:element name="CustomerAddress" minOccurs="0" maxOccurs="unbounded">
          <xs:complexType>
            <xs:sequence>
              <xs:element name="CompanyName" type="xs:string" minOccurs="0" />
              <xs:element name="StreetAddress" type="xs:string" minOccurs="0" />
              <xs:element name="City" type="xs:string" minOccurs="0" />
              <xs:element name="State" type="xs:string" minOccurs="0" />
              <xs:element name="ZipCode" type="xs:string" minOccurs="0" />
            </xs:sequence>
          </xs:complexType>
        </xs:element>
        <xs:element name="Items" minOccurs="0" maxOccurs="unbounded">
          <xs:complexType>
            <xs:sequence>
              <xs:element name="Item" minOccurs="0" maxOccurs="unbounded">
                <xs:complexType>
                  <xs:attribute name="ItemId" type="xs:string" />
                  <xs:attribute name="ItemName" type="xs:string" />
                  <xs:attribute name="Quantity" type="xs:string" />
                </xs:complexType>
              </xs:element>
            </xs:sequence>
          </xs:complexType>
        </xs:element>
      </xs:sequence>
    </xs:complexType>
  </xs:element>  
</xs:schema>

注文明細書の XML スキーマは、主に以下の要素で構成されています。

表 2. 注文明細書のスキーマの主な要素
Id注文明細書の一意の ID が含まれるストリング
ReferenceId基になる発注書の ID が含まれるストリング
OrderDate注文明細書の日付が含まれるストリング
CustomerAddress再販業者の宛先情報 (会社名、住所、郵便番号など) が含まれます。
VendorAddressベンダー (メーカー) の宛先情報 (会社名、住所、郵便番号など) が含まれます。
Items注文されたすべての商品の商品 ID、商品名、数量が含まれます。

データベース・エンティティー・モデルを定義する

次に、データベース・スキーマを定義します。図 4 は、データベース・エンティティーのモデル図です。

図 4. データベース・エンティティー・モデル
データベース・エンティティー・モデル

再販業者のデータ・エンティティーには以下のものがあります。

  • Customer。注文した顧客の連絡先情報が含まれます。
  • CustomerOrder。顧客の注文情報が含まれます。
  • CustomerOrderDetail。顧客が注文した商品の詳細が含まれます。
  • Inventory。再販業者の在庫情報が含まれます。

メーカーのデータ・エンティティーには以下のものがあります。

  • VendorOrder。メーカーの注文処理サービスによって処理された発注書を追跡します。

メッセージ・キューを定義する

定義しなければならない最後のコンポーネントは、メッセージ・キューです。表 3 に、このソリューションの場合のメッセージ・キューを記載します。

表 3. Amazon SQS メッセージ・キュー
キューの名前可視性タイムアウト対象
POQueue30 秒再販業者からメーカーに送信された発注書メッセージ
OSQueue30 秒メーカーから再販業者に送信された注文明細書メッセージ

再販業者のアプリケーションの実装

再販業者のアプリケーションは、表 4 に記載するビジネス・エンティティーで構成されます。

表 4. 再販業者のアプリケーションに含まれるビジネス・エンティティー
クラス説明
CompanyAddressEntity顧客または勤務先の住所が含まれます。
CustomerEntity顧客が含まれます。
OrderEntity顧客の注文が含まれます。
OrderDetailEntity顧客の注文の商品ごとの詳細が含まれます。
InventoryItemEntity商品の在庫情報が含まれます。
PurchaseOrderEntity発注書が含まれます。
PurchaseOrderDetailEntity発注書の商品ごとの詳細が含まれます。

Microsoft .NET 側では、BaseSQSDataProvider クラスがさらに Amazon SQS メッセージング用のユーティリティー・クラスを提供し、BaseDbProvider が MySQL データベースを操作するためのユーティリティー・クラスを提供します。


再販業者の注文管理システム

再販業者の注文管理クライアントはこの記事の焦点ではありませんが、このクライアントは至って単純明快なものです。これは C# で作成された n 層アプリケーションで、すべてのビジネスとデータはプレゼンテーション層から切り離されています。フォームのバインディングは一貫して、Microsoft .NET の強力なオブジェクト・バインディング機能によって行われます。ドロップダウン・リスト、グリッド・ビュー、そしてフォームのフィールドはすべて、ビジネス・オブジェクトにバインドされています。ビジネス・ロジックはビジネス・ロジック層にあるため、Windows® フォームのためのコードは最小限です。

図 5 に、このクライアントを示します。

図 5. 再販業者の注文管理クライアント
再販業者の注文管理クライアント

再販業者の注文調達サービス

OrderFulfillmentService クラスの役割は、App.config ファイルに指定されたポーリング間隔に基づいて、顧客注文を処理することです。このサービスは、まだ処理されていない注文のリストを取得します。リストに含まれる注文ごとに、以下のアクションがそれぞれに対応するメソッドで実行されます。

  • 注文された商品を出荷できるかどうか、つまり十分な在庫があるかどうかをチェックします (ProcessPendingOrders メソッド)。
  • 出荷可能であれば、注文を処理します (CanShip メソッド)。
  • 出荷できない場合は注文を入荷待ちの状態にして、発注書をメーカーに送信します (ProcessBackorder メソッド)。

リスト 9 に、ProcessPendingOrders() メソッドを記載します。

リスト 9. ProcessPendingOrders() メソッド
	public int ProcessPendingOrders()
	{
		int itemsProcessed = 0;

		// get orders not yet shipped
		CustomerOrderFactory factory = new CustomerOrderFactory();
		IList<OrderEntity> orders = factory.GetOrdersNotYetShipped();

		// iterate through all orders not processed
		IEnumerator<OrderEntity> ordersEnum = orders.GetEnumerator();
		while (ordersEnum.MoveNext()) {
			// get next order
			OrderEntity curOrder = ordersEnum.Current;
			Console.WriteLine(string.Format("Processing Order '{0}'...", 
				curOrder.Id));

			// check if merchandise is available to ship
			if (this.CanShip(curOrder)) {
				// process order
				if (this.ProcessOrder(curOrder)) {
					itemsProcessed++;
				}
			}// if can ship order
			else if (!curOrder.IsBackordered){
				// set order to backordered
				if (this.ProcessBackorder(curOrder)) {
					itemsProcessed++;
				}
			} // if can't ship order (not enough merchandise)
		} // while more orders to process

		return itemsProcessed;
	}

注文を処理できるかどうかを判断するため、注文された商品の数量が各商品の在庫レベルと照合されます。この CanShip() メソッドはリスト 10 のとおりです。

リスト 10. CanShip() メソッド
private bool CanShip(OrderEntity order)
{
bool hasMerchandise = true;            

// get items
IEnumerator<OrderDetailEntity> detailEnum = order.GetOrderItems();

// iterate through all items
while (detailEnum.MoveNext())
{
	// get current item
	OrderDetailEntity detailEntry = detailEnum.Current;

	InventoryItemEntity inventoryItem = detailEntry.Item;
	if (detailEntry.Quantity > inventoryItem.Quantity)
	{
		Console.WriteLine(
			string.Format("Order {0} - Insufficient Inventory: {1} ({2})",
			order.Id, inventoryItem.Name, inventoryItem.Id));
		hasMerchandise = false;
	} // if inventory is sufficient
} // while more entries to process

Console.WriteLine(string.Format("Order {0} - Can Ship: {1}", 
	order.Id, hasMerchandise));
return hasMerchandise;
}

CanShip() が False を返し、商品の取り寄せ注文がまだ行われていない場合、ProcessBackorder() メソッドが呼び出され、注文の IsBackordered プロパティーが True に設定されます。MessageQueueFactory は、キューに入れる内容を作成して、発注書メッセージを送信するために使用するオブジェクトです。リスト 11 に、このプロセスを示します。

リスト 11. ProcessBackorder() メソッド
private bool ProcessBackorder(OrderEntity order)
{
	// set to backordered
	order.IsBackordered = true;       

	// update order
	CustomerOrderFactory factory = new CustomerOrderFactory();
	bool result = factory.UpdateOrder(order);

	if (!result) return result;

	// get purchase order xml
	string poXml = this.GetPurchaseOrderAsXml(order);            

	// create message queue
	MessageQueueFactory queueFactory = new MessageQueueFactory();
	return queueFactory.CreatePOQueueItem(poXml);
}

PurchaseOrderEntity オブジェクトは、GetPurchaseOrder() メソッドによって作成されます。OrderDetailEntity は、在庫レベルが十分でない商品ごとに注文に追加されます。リスト 12 に、このプロセスを示します。

リスト 12. GetPurchaseOrder() メソッド
private PurchaseOrderEntity GetPurchaseOrder(OrderEntity order)
{
PurchaseOrderEntity po = new PurchaseOrderEntity();

po.PurchaseDate = DateTime.Now;

// set company address of the Purchase Order - the Reseller
po.CompanyAddress.CompanyName = "The Widget Factory";
po.CompanyAddress.StreetAddress = "100 Main Street";
po.CompanyAddress.CityName = "Las Vegas";
po.CompanyAddress.StateName = "NV";
po.CompanyAddress.ZipCode = "89044";

// set vendor address of the Purchase Order - the Manufacturer
po.VendorAddress.CompanyName = "Widget Supplies";
po.VendorAddress.StreetAddress = "100 Main Street";
po.VendorAddress.CityName = "Orlando";
po.VendorAddress.StateName = "FL";
po.VendorAddress.ZipCode = "32801";

// while more items to process
IEnumerator<OrderDetailEntity> orderEnum = order.GetOrderItems();
while (orderEnum.MoveNext())
{
	OrderDetailEntity orderItem = orderEnum.Current;
	InventoryItemEntity inventoryItem = orderItem.Item;

	// if insufficient inventory
	if (orderItem.Quantity > inventoryItem.Quantity)
	{
		// order the number needed plus 100
		int quantityToOrder = (orderItem.Quantity - inventoryItem.Quantity) + 100;
		PurchaseOrderDetailEntity poItem = new PurchaseOrderDetailEntity();
		poItem.ItemName = inventoryItem.Name;
		poItem.ItemId = inventoryItem.Id;
		poItem.Quantity = quantityToOrder;

		// add item to po
		po.AddItem(poItem);
	}
}

return po;            
}

次のステップは、PurchaseOrderEntity を XML にシリアライズすることです。Microsoft .NET Framework には、強力な XML シリアライズ機能が用意されています。まず、XmlSerializerNamespaces のインスタンスを作成して、出力文書の XML 名前空間を設定します。そして XML 出力を制御するために XmlWriterSettings のインスタンスを作成します。この出力からは XML 宣言を省かなければなりません。XML 宣言はメッセージ本体に組み込まれているためです。次に、XmlTextWriter クラスを使用してオブジェクトを XML テキスト・ライターにシリアライズし、このライターによって出力を StringBuilder のインスタンスに書き込みます。最後に XmlSerializerSerialize() メソッドを使って、PurchaseOrderEntity のインスタンスを XML にシリアライズします。リスト 13 に、このプロセスを示します。

リスト 13. GetPurchaseOrderAsXml() メソッド
private string GetPurchaseOrderAsXml(OrderEntity order)
{
// get purchase order
PurchaseOrderEntity po = this.GetPurchaseOrder(order); ;

StringBuilder output = new StringBuilder();

// no name space
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("", "");

// settings to omit 
XmlWriterSettings settings = new XmlWriterSettings();
settings.OmitXmlDeclaration = true;

XmlWriter writer = XmlTextWriter.Create(output, settings);
XmlSerializer serializer = new XmlSerializer(typeof(PurchaseOrderEntity));
serializer.Serialize(writer, po, ns);

Debug.WriteLine(output.ToString());

return output.ToString();}

今度は、発注書が含まれるメッセージを Amazon SQS キューに送信する番です。それにはリスト 14 のコードを使用します。

リスト 14. CreatePOQueueItem() メソッド
public bool CreatePOQueueItem(String poXml)
{
MessageQueueSQSDataProvider queueFactory = DW4DataContext.MessageQueueData;
MessageQueueEntity message = new MessageQueueEntity();
message.MessageBody = poXml;
message.MessageType = MessageTypes.PurchaseOrder;
return queueFactory.InsertQueueItem(message);			
}

MessageQueueSQSDataProviderInsertQueueItem() メソッドは、メッセージのタイプに応じた適切なキューにメッセージを格納します。このメソッドでは、メッセージを Amazon SQS キューに送信するために SendMessage() という基本メソッドを呼び出します (リスト 15 を参照)。

リスト 15. InsertQueueItem() メソッド
public bool InsertQueueItem(MessageQueueEntity message)
{
String queueName = null;
if (message.MessageType == MessageTypes.OrderSummary)
{
	queueName = ConfigurationSettings.AppSettings["OrderSummaryQueue"];
	return this.SendMessage(queueName, message.MessageBody);
}
else if (message.MessageType == MessageTypes.PurchaseOrder)
{
	queueName = ConfigurationSettings.AppSettings["PurchaseOrderQueue"];
	return this.SendMessage(queueName, message.MessageBody);
}

return false;            
}

SendMessage() メソッドが作成する SendMessageRequest は、Amazon API の一部となります。必須パラメーターはキューの名前とメッセージ本体 (XML 文書) です。SendMessageResponse がヌルでない場合、メッセージは正常に送信されたことになります。これに続く数行は、単に問題のトラブルシューティング用に主要なデバッグ情報を書き出すだけにすぎません。完全な SendMessage() メソッドをリスト 16 に記載します。

リスト 16. SendMessage() メソッド
public bool SendMessage(string queueName, string messageBody) {
bool result = true;
try {
	String accessKeyId = 
		ConfigurationSettings.AppSettings["AmazonAccessKeyID"]; ;
	String secretAccessKey = 
		ConfigurationSettings.AppSettings["AmazonSecretAccessKey"];
	AmazonSQS service = new AmazonSQSClient(accessKeyId, secretAccessKey);

	// build request
	SendMessageRequest request = new SendMessageRequest();
	request.QueueName = queueName;
	request.MessageBody = messageBody;
	// send message
	SendMessageResponse response = service.SendMessage(request);
	if (response.IsSetSendMessageResult()) {
		Debug.WriteLine("Send Message Result:");
		SendMessageResult sendMessageResult = response.SendMessageResult;
		if (sendMessageResult.IsSetMessageId()) {
			Debug.WriteLine(String.Format("\tMessageId: {0}", 
				sendMessageResult.MessageId));
		}
		if (sendMessageResult.IsSetMD5OfMessageBody()) {
			Debug.WriteLine("\tMD5 Of Message Body: ", 
				sendMessageResult.MD5OfMessageBody);
		}
	}
	if (response.IsSetResponseMetadata()) {
		Debug.WriteLine("Response Metadata:");
		ResponseMetadata responseMetadata = response.ResponseMetadata;
		if (responseMetadata.IsSetRequestId()) {
			Debug.WriteLine(String.Format("\tRequest Id: {0}", 
				responseMetadata.RequestId));
		}
	}
}
catch (AmazonSQSException ex) {
	Debug.WriteLine("Caught Exception: " + ex.Message);
	Debug.WriteLine("Response Status Code: " + ex.StatusCode);
	Debug.WriteLine("Error Code: " + ex.ErrorCode);
	Debug.WriteLine("Error Type: " + ex.ErrorType);
	Debug.WriteLine("Request ID: " + ex.RequestId);
	Debug.WriteLine("XML: " + ex.XML);
	result = false;
}
return result;
}

再販業者の在庫管理サービス

InventoryManagementService の役割は、在庫を管理し、メーカーから受信した注文明細書を処理することです。OrderFulfillmentService と同様に、このサービスでは App.config ファイルに指定されたポーリング間隔を使用します。このサービスで取られるアクションは以下のとおりです。

  • まず、ProcessIncomingMerchandise() メソッドが、受信したけれども、まだ処理されていないすべての注文明細書 (OrderSummaryEntity) のリストを取得します。
  • InventoryManagementService メソッドが、Amazon SQS OSQueue からメッセージを取得します。
  • 次に、処理されていない出荷ごとに ProcessOrderReceipt() メソッドが呼び出されます。

リスト 17 に、ProcessIncomingMerchandise() メソッドがリストを取得する方法を示します。

リスト 17. ProcessIncomingMerchandise() メソッド
public int ProcessIncomingMerchandise()
{
int itemsProcessed = 0;

OrderSummaryFactory osFactory = new OrderSummaryFactory();
IList<OrderSummaryEntity> orders = osFactory.GetOrderSummariesToProcess();

// iterate through all order summaries
IEnumerator<OrderSummaryEntity> orderEnum = orders.GetEnumerator();
while (orderEnum.MoveNext())
{
	// get current order summary
	OrderSummaryEntity order = orderEnum.Current;

	// process order summary received
	if (this.ProcessOrderReceipt(order))
	{
		itemsProcessed++;
	}
}

Debug.WriteLine(String.Format("Orders Processed: {0}", itemsProcessed));
return itemsProcessed;
}

ProcessOrderReceipt() メソッドは注文明細書の各項目 (OrderSummaryDetailEntity) を繰り返し処理し、受け取った項目ごとに在庫の数量を増やしていきます。リスト 18 に、このメソッドを記載します。

リスト 18. ProcessOrderReceipt() メソッド
private bool ProcessOrderReceipt(OrderSummaryEntity order)
{
	if (order == null) return false;

	bool result = true;

	// add to inventory
	InventoryFactory inventoryFactory = new InventoryFactory();
	
	// iterate through all items in order summary
	IEnumerator<OrderSummaryDetailEntity> itemsEnum = order.Items.GetEnumerator();
	while (itemsEnum.MoveNext())
	{
		// get current item
		OrderSummaryDetailEntity orderItem = itemsEnum.Current;
		
		// get item
		int itemId = orderItem.ItemId;
		InventoryItemEntity item = inventoryFactory.GetInventoryItem(itemId);

		// increase inventory
		item.Quantity = item.Quantity + orderItem.Quantity;
		result = inventoryFactory.UpdateInventoryItem(item);
		if (!result) break;
	}

	if (result)
	{
		MessageQueueFactory queueFactory = new MessageQueueFactory();
		queueFactory.DeleteQueueItem(order.QueueItem);
	}

	return result;
}

最後のステップは、出荷が正常に処理された場合に、注文明細書のメッセージ・キューから正常に処理されたものを削除することです。リスト 19 に、C# を使用して Amazon SQS キューからメッセージを削除する方法を記載します。

リスト 19. DeleteMessage() メソッド
public bool DeleteMessage(string queueName, string messageHandle) {
bool result = false;
try {
  String accessKeyId = 
           ConfigurationSettings.AppSettings["AmazonAccessKeyID"];
  String secretAccessKey = 
           ConfigurationSettings.AppSettings["AmazonSecretAccessKey"];
  AmazonSQS service = new AmazonSQSClient(accessKeyId, secretAccessKey);
 
  // Create delete message request object
  DeleteMessageRequest request = new DeleteMessageRequest();
  
  // Set queue name containing item to be deleted
  request.QueueName = queueName;
  
  // Set handle of message to be deleted
  request.ReceiptHandle = messageHandle;
  
  // Delete message
  DeleteMessageResponse response = service.DeleteMessage(request);
  Debug.WriteLine("Service Response");                
 
  Debug.WriteLine("\tDelete Message Response");
  // If message response
  if (response.IsSetResponseMetadata())
  {
           Debug.WriteLine("\tResponse Metadata");
           ResponseMetadata responseMetadata = response.ResponseMetadata;
           if (responseMetadata.IsSetRequestId())
           {
                   Debug.WriteLine(String.Format("\tRequest Id: {0}", 
                            responseMetadata.RequestId));
           }
  }
}
catch (AmazonSQSException ex)
{
  Debug.WriteLine("Caught Exception: " + ex.Message);
  Debug.WriteLine("Response Status Code: " + ex.StatusCode);
  Debug.WriteLine("Error Code: " + ex.ErrorCode);
  Debug.WriteLine("Error Type: " + ex.ErrorType);
  Debug.WriteLine("Request ID: " + ex.RequestId);
  Debug.WriteLine("XML: " + ex.XML);
}
 
return result;
}

メーカーのアプリケーションの実装

このソリューションの最後のコンポーネントはメーカーの注文処理サービスです。これは Java ベースのサービスで、再販業者からの注文を処理します。注文処理サービスの内容は以下のとおりです。

  • 再販業者が送信した発注書が含まれる発注書キューをポーリングします。
  • 発注書を受信した時点で注文明細書を作成し、この注文明細書を別の Amazon SQS キューを使用して再販業者に送信します。
  • 再販業者から送信された発注書を削除します。

このメーカーのアプリケーションは、表 5 に記載するビジネス・エンティティーで構成されます。

表 5. メーカーのアプリケーションに含まれるビジネス・エンティティー
クラス説明
AddresssEntity顧客または勤務先の住所が含まれます。
CustomerOrderEntity顧客の注文 (発注書) が含まれます。
CustomerOrderDetailEntity顧客の注文 (発注書) の商品ごとの詳細が含まれます。
MessageQueueEntityメッセージ・キューの内容 (つまり、発注書または注文明細書) が含まれます。
OrderSummaryEntity出荷明細書が含まれます。
OrderSummaryDetailEntity出荷明細書の商品ごとの詳細が含まれます。

このソリューションでは、すべてのデータベース操作に JdbcQuery ヘルパー・クラスを使用します。ここではさらに、2 つのヘルパー・クラスを使用します。1 つはデータのフォーマットを設定する DateUtil、もう 1 つは構成パラメーターを読み取る AppConfig です。また、SqsDataProvider クラスは、基本的な Amazon SQS 機能を提供するユーティリティー・クラスです。


メーカーの注文処理サービス

OrderProcessingService クラスは、AppConfig.properties ファイルに指定されたポーリング間隔に基づいて発注書を処理します。このサービスは、まだ出荷が済んでいない発注書のリストを取得して処理します。リスト 20 の getMessageQueueItems メソッドを見ると、指定された Amazon SQS キューから Java コードを使用してメッセージのリストを取得する方法がわかります。

リスト 20. getMessageQueueItems() メソッド
protected ArrayList<HashMap<String, String>> getMessageQueueItems(String queueName) {
ArrayList<HashMap<String, String>> results = new ArrayList<HashMap<String, String>>();
 
try {
  String accessKeyId = AppConfig.getProperty("AmazonAccessKeyID");
  String secretAccessKey =  AppConfig.getProperty("AmazonSecretAccessKey");
 
  AmazonSQS service = new AmazonSQSClient(accessKeyId, secretAccessKey);
  
  // get messages from queue
  ReceiveMessageRequest request = new ReceiveMessageRequest();
  request.setQueueName(queueName);
  request.setVisibilityTimeout(1);
  
  ReceiveMessageResponse response = service.receiveMessage(request);
  
  // if received response
  if (response.isSetReceiveMessageResult()) {
           System.out.println("\tReceive Message Result");
           ReceiveMessageResult receiveMessageResult = 
                   response.getReceiveMessageResult();
           
           // get all messages and iterate through them
           List<Message> messageList = receiveMessageResult.getMessage();
           for (Message message : messageList) {
                   // build hashmap containing each message
                   HashMap<String, String> row = new HashMap<String, String>();
                   
                   if (message.isSetMessageId()) {
                            row.put(COLUMN_ID, message.getMessageId());
                            System.out.println("\t\tMessageId: " + 
                                     message.getMessageId());
                   }
                   if (message.isSetReceiptHandle()) {
                            row.put(COLUMN_HANDLE, message.getReceiptHandle());
                   }
                   if (message.isSetBody()) {                                   
                            row.put(COLUMN_MESSAGE, message.getBody());
                   }
                   
                   // add row
                   results.add(row);
           }
  }        
} catch (AmazonSQSException ex) {
  System.out.println("Caught Exception: " + ex.getMessage());
  System.out.println("Response Status Code: " + ex.getStatusCode());
  System.out.println("Error Code: " + ex.getErrorCode());
  System.out.println("Error Type: " + ex.getErrorType());
  System.out.println("Request ID: " + ex.getRequestId());
  System.out.println("XML: " + ex.getXML());
}
 
return results;
}

Java 技術には .NET フレームワークのような組み込みの XML シリアライズ機能がありません。そのため、XML 文書を生成するには JDOM API が使用されます。JDOM は DOM に似ていますが、単純な Java API で XML を構文解析、操作、出力することができます。JDOM の使用方法については、「参考文献」を参照してください。

PurchaseOrderSerializer クラスは、メッセージの XML を Java クラスにシリアライズする役割を果たします。XML の構文解析には、リスト 21 に示されているように JDOM を使用します (このコードを辿っていくと、リスト 7 の発注書のスキーマを参照するのに役立ちます)。

リスト 21. PurchaseOrderSerializer クラス
public class PurchaseOrderSerializer {

public CustomerOrderEntity deserializePO(MessageQueueEntity message) {
  CustomerOrderEntity order = null;
  
  String xml = message.getMessageBody();
  System.out.println("Purchase Order:\n" + xml);
  
  try {
           // Create input source from string containing XML
           InputSource xmlIS = new InputSource();
           xmlIS.setCharacterStream(new StringReader(xml));
           
           // Initialize SAX Builder object
           SAXBuilder builder = new SAXBuilder();
 
           // Build JDOM Document object from input source
           Document doc = builder.build(xmlIS);
           Element rootElem = doc.getRootElement();
           
           // get order id
           String id = rootElem .getChildText("Id");        
           
           // create customer order 
           order = new CustomerOrderEntity(id);
           
           // set order date
           String dateStr = rootElem.getChildText("OrderDate");
           order.setOrderDate(DateUtil.getDate(dateStr));
            
           // get company (customer) address element node and parse it
           Element addrElem = rootElem.getChild("Company");
           this.setCompanyAddress(order, addrElem);

           // get vendor address element node and parse it
           addrElem = rootElem.getChild("Vendor");
           this.setVendorAddress(order, addrElem);

           // get order items list and parse it
           Element itemsElem = rootElem.getChild("Items");
           this.setOrderItems(order, itemsElem);
           
           System.out.println(order.toString());
  } catch (IOException e) {
           e.printStackTrace();
  } catch (NullPointerException e) {
           e.printStackTrace();
  } catch (JDOMException e) {
           e.printStackTrace();
  }
  return order;
}
 
private void setVendorAddress(CustomerOrderEntity order, Element addrElem) {
  // get data from xml
  String coName = addrElem.getChildText("CompanyName");
  String streetAddress = addrElem.getChildText("StreetAddress");
  String city = addrElem.getChildText("City");
  String state = addrElem.getChildText("State");
  String zipCode = addrElem.getChildText("ZipCode");
  
  AddressEntity coAddress = order.getVendorAddress();
   
  // build address entity object using data read from xml
  AddressEntity coAddress = order.getCompanyAddress();
  coAddress.setCity(city);
  coAddress.setCompanyName(coName);
  coAddress.setStreetAddress(streetAddress);
  coAddress.setState(state);
  coAddress.setZipCode(zipCode);
}
  
private void setCompanyAddress(CustomerOrderEntity order, Element addrElem) {
  // get data from xml
  String coName = addrElem.getChildText("CompanyName");
  String streetAddress = addrElem.getChildText("StreetAddress");
  String city = addrElem.getChildText("City");
  String state = addrElem.getChildText("State");
  String zipCode = addrElem.getChildText("ZipCode");
  
  // build address entity object using data read from xml
  AddressEntity coAddress = order.getCompanyAddress();
  coAddress.setCity(city);
  coAddress.setCompanyName(coName);
  coAddress.setStreetAddress(streetAddress);
  coAddress.setState(state);
  coAddress.setZipCode(zipCode);
}
 
private void setOrderItems(CustomerOrderEntity order, Element itemsElem) {
  List itemList = itemsElem.getChildren();
  if (itemList == null) return;
  
  // iterate through all items in collection
  for (int index = 0; index < itemList.size(); index++) {
           // get current element
           Element curElem = (Element) itemList.get(index);
           
           // get item values
           String itemId = curElem.getAttributeValue("Id");
           String itemName = curElem.getAttributeValue("Name");
           String quantity = curElem.getAttributeValue("Quantity");
           
           // create order item
           CustomerOrderDetailEntity detail = new CustomerOrderDetailEntity();
           detail.setItemId(itemId);
           detail.setItemName(itemName);
           detail.setQuantity(Integer.parseInt(quantity));
  
           // add order item to order summary
           order.addItem(detail);
  }
}
}

次に、発注書の注文明細書を作成します。そのためのコードはリスト 22 のとおりです。

リスト 22. getOrderSummaryForCustomerOrder() メソッド
public OrderSummaryEntity getOrderSummaryForCustomerOrder(
	CustomerOrderEntity customerOrder) {
	
	if (customerOrder == null) return null;
	
	// create order
	OrderSummaryEntity orderSummary = new OrderSummaryEntity();
	orderSummary.setOrderDate(new Date());
	orderSummary.setReferenceId(customerOrder.getId());
	
	// set addresses
	orderSummary.setCompanyAddress(customerOrder.getVendorAddress());
	orderSummary.setCustomerAddress(customerOrder.getCompanyAddress());
	
	// add items
	Iterator<CustomerOrderDetailEntity> itemIter = customerOrder.getItems();
	while(itemIter.hasNext()) {
		CustomerOrderDetailEntity item = itemIter.next();
		
		OrderSummaryDetailEntity detail = new OrderSummaryDetailEntity();
		detail.setItemId(item.getItemId());
		detail.setItemName(item.getItemName());
		detail.setQuantity(item.getQuantity());
		
		orderSummary.addItem(detail);
	}
	
	return orderSummary;
}

続いて OrderSummaryEntity を XML にシリアライズする必要があります。メッセージをシリアライズするには、リスト 23 のように JDOM を使用します (このコードは、リスト 8 の注文明細書のスキーマを参照する上で役立ちます)。

リスト 23. OrderSummarySerializer クラス
public class OrderSummarySerializer {
	public String serializeOrder(OrderSummaryEntity order) {
		Document doc = new Document();
		
		Element rootElem = new Element("OrderSummary");
		doc.addContent(rootElem);
		
		// add id
		Element idElem = new Element("OrderId");
		idElem.setText(order.getOrderId());
		rootElem.addContent(idElem);
		
		// add reference id
		Element referenceIdElem = new Element("ReferenceId");
		referenceIdElem.setText(order.getReferenceId());
		rootElem.addContent(referenceIdElem);
		
		// add order date
		Element dateElem = new Element("OrderDate");
		String dateStr = DateUtil.getDateString(new Date());
		dateElem.setText(dateStr);		
		rootElem.addContent(dateElem);
		
		// set company address
		Element addrElem = this.getElementForAddress("CompanyAddress", 
			order.getCompanyAddress());
		rootElem.addContent(addrElem);
		
		// set customer address
		addrElem = this.getElementForAddress("CustomerAddress", 
			order.getCustomerAddress());
		rootElem.addContent(addrElem);
		
		// iterate through all items and add each item to order
		Element itemsElem = new Element("Items");
		Iterator<OrderSummaryDetailEntity> itemIter = order.getItems();
		while(itemIter.hasNext()) {
			OrderSummaryDetailEntity item = itemIter.next();
			
			Element itemElem = new Element("Item");
			itemElem.setAttribute("ItemId", item.getItemId());
			itemElem.setAttribute("ItemName", item.getItemName());
			String quantityStr = String.valueOf(item.getQuantity());
			itemElem.setAttribute("Quantity", quantityStr);
			
			itemsElem.addContent(itemElem);
		}
		rootElem.addContent(itemsElem);
		
		// convert xml document to string
		XMLOutputter outp = new XMLOutputter();
		StringWriter strWriter = new StringWriter();        
		try {
			outp.output(doc, strWriter);
		} catch (IOException e) {
			e.printStackTrace();
		}		
		return strWriter.toString();
	}

	private Element getElementForAddress(String elemName, AddressEntity address) {
		Element addrElem = new Element(elemName);
		
		String coName = address.getCompanyName();
		String streetAddress = address.getStreetAddress();
		String cityName = address.getCity();
		String stateName= address.getState();
		String zipCode = address.getZipCode();
		
		Element coElem = new Element("CompanyName");
		coElem.setText(coName);
		addrElem.addContent(coElem);
		
		Element streetElem = new Element("StreetAddress");
		streetElem.setText(streetAddress);
		addrElem.addContent(streetElem);
		
		Element cityElem = new Element("City");
		cityElem.setText(cityName);
		addrElem.addContent(cityElem);
		
		Element stateElem = new Element("State");
		stateElem.setText(stateName);
		addrElem.addContent(stateElem);
		
		Element zipCodeElem = new Element("ZipCode");
		zipCodeElem.setText(zipCode);
		addrElem.addContent(zipCodeElem);
		
		return addrElem;
	}
}

この XML メッセージを、Amazon Java API を使って Amazon SQS キューに送信します。リスト 24 に、メッセージの送信方法を記載します。

リスト 24. sendMessage() メソッド
protected boolean sendMessage(String queueName, String messageBody) {
boolean result = false;
try {
	String accessKeyId = AppConfig.getProperty("AmazonAccessKeyID");
	String secretAccessKey =  AppConfig.getProperty("AmazonSecretAccessKey");

	AmazonSQS service = new AmazonSQSClient(accessKeyId, secretAccessKey);
	
	SendMessageRequest request = new SendMessageRequest();
	request.setQueueName(queueName);
	request.setMessageBody(messageBody);
	
	SendMessageResponse response = service.sendMessage(request);
	// Send Message Response
	if (response.isSetSendMessageResult()) {
		System.out.println("\tSend Message Result");
		SendMessageResult sendMessageResult = response.getSendMessageResult();
		if (sendMessageResult.isSetMessageId()) {
			System.out.println("\t\tMessageId: " + 
				sendMessageResult.getMessageId());
			result = true;
		}
	} 		
} catch (AmazonSQSException ex) {
	System.out.println("Caught Exception: " + ex.getMessage());
	System.out.println("Response Status Code: " + ex.getStatusCode());
	System.out.println("Error Code: " + ex.getErrorCode());
	System.out.println("Error Type: " + ex.getErrorType());
	System.out.println("Request ID: " + ex.getRequestId());
	System.out.println("XML: " + ex.getXML());
}

return result;
}

最後に、Amazon SQS キューから、いま処理した発注書の内容を削除します。リスト 25 に、メッセージの削除方法を記載します。

リスト 25. deleteMessage() メソッド
protected boolean deleteMessage(String queueName, String handle) {
boolean result = false;

try {
	String accessKeyId = AppConfig.getProperty("AmazonAccessKeyID");
	String secretAccessKey =  AppConfig.getProperty("AmazonSecretAccessKey");

	AmazonSQS service = new AmazonSQSClient(accessKeyId, secretAccessKey);
	DeleteMessageRequest request = new DeleteMessageRequest();
	request.setQueueName(queueName);
	request.setReceiptHandle(handle);
	
	DeleteMessageResponse response = service.deleteMessage(request);

	// Delete Message Response
	if (response.isSetResponseMetadata()) {
		// Response Metadata
		ResponseMetadata  responseMetadata = response.getResponseMetadata();
		if (responseMetadata.isSetRequestId()) {
			System.out.print("RequestId: " + 
				responseMetadata.getRequestId());
			result = true;
		}
	} 
} catch (AmazonSQSException ex) {
	System.out.println("Caught Exception: " + ex.getMessage());
	System.out.println("Response Status Code: " + ex.getStatusCode());
	System.out.println("Error Code: " + ex.getErrorCode());
	System.out.println("Error Type: " + ex.getErrorType());
	System.out.println("Request ID: " + ex.getRequestId());
	System.out.println("XML: " + ex.getXML());
}

return result;
}

ソリューションのテスト

これで、このソリューションをテストする準備が整いました。ソリューションをテストするために、在庫が不足している商品に対して注文を送信します。注文を送信した後、以下のプロセス全体を追跡することができます。

  • 注文を送信します。
  • 再販業者が注文を入荷待ちの状態にして、発注書を作成します。
  • メーカーが発注書を受け取り、注文明細書を再販業者に送信します。
  • 再販業者が注文明細書を受け取り、入荷待ちの注文を処理します。

図 6 の再販業者のクライアントには、新規の注文が行われる前の在庫レベルが示されています。

図 6. 在庫の一覧表示
在庫の一覧表示

図 7 に、注文を送信するためのウィンドウを示します。

図 7. 注文の送信
注文の送信

図 8 に、注文の一覧を表示するためのウィンドウを示します。

図 8. 注文の一覧表示
注文の一覧表示

新規の注文が行われた後に商品が取り寄せ注文されると、図 9 のウィンドウに注文調達サービスの出力が表示されます。

図 9. 再販業者の注文調達および在庫管理サービス
再販業者の注文調達および在庫管理サービス

一方、図 10 のウィンドウには、新規の注文が行われると POQueue メッセージ・キューの内容が表示されます。

図 10. メッセージ・キューの表示
メッセージ・キューの表示

メーカーはメッセージ・キューから発注書を受け取り、それを処理します。図 11 に、メーカーの注文処理システムの出力を示します。

図 11. メーカーの注文管理サービス
メーカーの注文管理サービス

まとめ

この記事では、XML と Amazon SQS Web サービスを利用してエンタープライズ・ビジネス・アプリケーションを統合する例を説明しました。このように、XML の拡張性を Amazon SQS と組み合わせることで、極めてスケーラブルで柔軟なアプリケーションを実現することができます。このソリューションの主な利点は、スケーラビリティー、拡張性、そして最小限の統合作業です。この統合には、特にファイアウォールやセキュリティーを考慮する必要はありませんでした。そしてこのソリューションには、プラットフォームにも技術にも依存しないソリューションであるというもう 1 つの利点があります。これは Web サービスにとって重要な要素です。メーカーと再販業者は、それぞれが既存の技術プラットフォームを維持しながらも、ビジネス・プロセスを統合して経営効率および投資収益率 (ROI) を高めることができます。

このソリューションには、数は少ないものの制約事項がいくつかあります。第 1 の制約は、Amazon SQS メッセージ本体の最大サイズが 8KB であることです。この制約に対処するには、以下のソリューションが考えられます。

  • Amazon S3 (Amazon Simple Storage Service) を利用して大きなサイズの XML 文書を保管し、Amazon S3 の項目のハンドルを Amazon SQS メッセージに組み込みます。
  • Amazon SimpleDB を使用して構造化データを保管し、その項目のハンドルを Amazon SQS メッセージに組み込みます。

第 2 の制約は、Amazon がキューに適用している結果整合性という手法です。これには利点と欠点の両方がありますが、明らかに Amazon はその利点が欠点を遙かに上回ると確信しています。そしてこのソリューションの最後の制約は、優先順位や FIFO の手法がまったくないことです。これも同じく Amazon 側の意図で、この制約によって一層スケーラブルで信頼性と可用性に優れたソリューションを提供しています。

データ交換用としてメッセージ本文に XML を使用する場合のベスト・プラクティスとしては、XML スキーマ (XSD) 検証を実行して、送信側と受信側が同じ言語で対話することを確実にすることが推奨されています。あらゆる XML 統合プロジェクトでの場合と同じく、この場合も送信側と受信側の両方が共通のスキーマに同意する必要があります。


ダウンロード

内容ファイル名サイズ
C# projects for the reseller applicationsNETSolution.zip223KB
Java project for the manufacturer applicationsJavaSolution.zip24KB
Contains the configuration scriptsDbScripts.zip1KB

参考文献

学ぶために

  • Amazon SQS 技術資料: Amazon SQS ライブラリーに関する情報を参照してください。
  • JDOM 資料: JDOM ライブラリーに関する情報、そして Java コードから XML データのアクセス、操作、出力を行うこの Java ベースのソリューションについての情報を入手してください。
  • クラウドに接続する: 第 3 回 クラウドのガバナンスとセキュリティー: HybridCloud アプリケーションをセキュアにする」(Mark O'Neill 著、developerWorks、2009年6月): Amazon SQS で使用するキーについてわかりやすく説明している他、機密情報を保護し、クラウド・アプリケーションに対するアクセスと使用状況をモニターする監視ポリシーをセットアップする方法を紹介しています。まずはこの連載の第 1 回で、クラウド・コンピューティング・サービスとインフラとのハイブリッド・クラウド・モデルの例を調べ (Mark O'Neill 著、developerWorks、2009年4月)、第 2 回でそのハイブリッド・アプリケーションを開発してください (Mark O'Neill 著、developerWorks、2009年4月)。
  • 結果整合性: Amazon SQS の設計論拠については、Werner Vogels (Amazon CTO) の結果整合性に関するブログ・エントリーを参照してください。
  • JDOM で XML プログラミングを単純化する」(Wes Biggs、Harry Evans 共著、developerWorks、2001年5月): JDOM は DOM に似ていますが、単純な Java API で XML を構文解析、操作、出力できます。このオープンソース API により、Java 開発者にとって XML 文書の操作が簡単になる仕組みを学んでください。
  • IBM XML 認定: XML や関連技術の IBM 認定技術者になる方法について調べてください。
  • XML Technical library: 広範な技術に関する記事とヒント、チュートリアル、標準、そして IBM Redbooks については、developerWorks XML ゾーンを参照してください。
  • developerWorks の Technical events and webcasts: これらのセッションで最新情報を入手してください。
  • developerWorks podcasts: ソフトウェア開発者向けの興味深いインタービューとディスカッションを聞いてください。

製品や技術を入手するために

  • JDOM: JDOM Java API ライブラリーをダウンロードしてください。
  • C# Library for Amazon SQS: Amazon SQS 用の Amazon C# API ライブラリーをダウンロードしてください。
  • Java Library for Amazon SQS: Amazon SQS 用の Amazon Java API ライブラリーをダウンロードしてください。
  • MySQL バージョン 5.1 Server: このよく使われているオープンソースのデータベース・プラットフォームをダウンロードしてください。
  • MySQL Connector/Net バージョン 5.2: Microsoft .NET データベース・ドライバーをダウンロードしてください。
  • MySQL Connector/J バージョン 5.1: JDBC (Java Database Connectivity) ドライバーをダウンロードしてください。
  • IBM 製品の評価版: DB2®、Lotus®、Rational®、Tivoli®、および WebSphere® のアプリケーション開発ツールとミドルウェア製品を体験するには、IBM SOA Sandbox のオンライン試用版をダウンロードするか、検討してみてください。

議論するために

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=XML, Web development
ArticleID=407527
ArticleTitle=Amazon Web サービスを利用したエンタープライズ・アプリケーションの統合
publish-date=06162009