BT

最新技術を追い求めるデベロッパのための情報コミュニティ

寄稿

Topics

地域を選ぶ

InfoQ ホームページ アーティクル Spring FrameworkとAOPを使ったダイナミックルーティング

Spring FrameworkとAOPを使ったダイナミックルーティング

ブックマーク
今回の記事では主に、サブシステム処理に関してビジネストランザクションがどのように動的にビジネスイベントをトリガーするかを示します。記事内で示した例では、Spring Framework 2.0とSpring AOPを効果的に用いて、ビジネスサービスをサブシステム処理の機能から切り離します。それではビジネス要件を詳しく見ていきましょう。

ビジネス要件

顧客登録システム(CRS)では、オンライン登録が完了したあと顧客に通知を送り、料金請求書を作成するため、住所データを請求書システムに送信する必要があります。

技術的設計

上記のビジネス要件を技術的設計に分解してみましょう。今回の例では、顧客登録プロセスを表わすカスタムのビジネスイベントを定義します。

1つのイベントは、特定の時間的間隔の間に生じるものであるとみなすことができます。今回の例では顧客登録プロセスがイベントに相当します。通常1つのイベントには、そのイベントが生じるときに起こる必要のある1つ以上のアクションが含まれる場合があります。ビジネス要件にしたがい、次のような2つのアクションを特定しました。

  1. 顧客にメール通知する
  2. 顧客の住所データを請求書システムに送信する

それでは、イベントデータベーステーブルに格納される情報を保持するためのイベントデータ構造を設計します。次のイベント属性が特定されます。

  • イベント識別子: 1
  • イベントの説明: 顧客登録イベント
  • アクションコード: MT

イベント識別子はデータベースでマップされる主キーです。イベントの説明はイベントについての説明を定義します。最後の1つはイベントが生じたとき起こる必要のある様々なアクションを示すアクションコードです。アクションコードはアクションコード参照テーブルで定義されます。

上記のイベントに関して特定されたアクションコードはMとTです。Mは顧客へのメール(Mail)通知、Tは顧客の住所データの請求書システムへの送信(Transmit)を表わしています。

例: Event.java

 /**
*Event.java - The event domain object
*@author - Vigil Bose
*/
public class Event implements Serializable {

private Integer eventId;
private String eventDesc;
private String eventActionCodes;
private static final long serialVersionUID = 1L;

/** The cached hash code value for this instance. Settting to 0 triggers
re-calculation. */
private int hashValue = 0;

/**
*@return the eventActionCodes
*/
public String getEventActionCodes(){
return eventActionCodes;
}
/**
* @param eventActionCodes the eventActionCodes to set
*/
public void setEventActionCodes(String eventActionCodes) {
this.eventActionCodes = eventActionCodes;
}
/**
* @return the eventDesc
*/
public String getEventDesc() {
return eventDesc;
}
/**
* @param eventDesc the eventDesc to set
*/
public void setEventDesc(String eventDesc) {
this.eventDesc = eventDesc;
}
/**
* Return the simple primary key value that identifies this object.
* @return the eventId
*/
public Integer getEventId() {
return eventId;
}
/**
* Set the simple primary key value that identifies this object.
* @param eventId the eventId to set
*/
public void setEventId(Integer eventId) {
this.hashValue = 0;
this.eventId = eventId;
}
/**
*Implementation of the equals comparison on the basis of equality
*of the primary key values.
* @param rhs
* @return boolean
*/
public boolean equals(Object rhs){
if (rhs == null)
return false;
if (! (rhs instanceof Event))
return false;
Event that = (Event) rhs;
if (this.getEventId() == null || that.getEventId() == null)
return false;
return (this.getEventId().equals(that.getEventId()));
}

/**
* Implementation of the hashCode method conforming to the Bloch pattern with
* the exception of array properties (these are very unlikely primary key types).
* @return int
*/
public int hashCode(){
if (this.hashValue == 0){
int result = 17;
int eventIdValue = this.getEventId() == null ? 0 :
this.getEventId().hashCode();
result = result * 37 + eventIdValue;
this.hashValue = result;
}
return this.hashValue;
}
}

さて、顧客登録イベントを示すためのイベントドメインオブジェクトを設計しました。次にWeb層とビジネスサービス層の間のAPIコントラクトの設計に移ります。設計上の制約の1つに、ドメインモデルの変更が、層の間のAPIコントラクトを崩すことがあってはならないという点があります。この設計上の制約に対応するため、AbstractDataUserDataという2つのデータラッパークラスが特定されます。AbstractDataは一部のふるまいを定義する抽象的な性質を持ちます。UserDataはその他のふるまいを提供するAbstractDataのサブクラスに当たります。例えばあるアプリケーションフレームワークがある場合、抽象型クラスはイベントやメッセージ処理などのデフォルトのサービスを提供する場合があります。

今回の例では、AbstractDataは様々なビジネスイベントを収集する業務を担当します。AbstractDataのサブクラスがUserDataで、これは主要なドメインオブジェクトであるUserオブジェクトを保持するために用いられます。

Userというドメインオブジェクトは、userId、firstName、lastName、暗号化されたパスワードなど、ユーザーの特定に必要な様々な属性で構成されています。また住所の一行目、二行目、都市、州など様々な住所属性を持つAddressドメインオブジェクトからも構成されています。

例: AbstractData.java

/**
*AbstractData.java - A template pattern like class that implments
*the event collection behavior. This class is used by all data
*transfer wrapper objects between UI Layer and Server side Layer
*@author - Vigil Bose
*/
public abstract class AbstractData{

/**
*Stores all the events identified during the transaction
*Processing.
*/
private Set eventSet = Collections.synchronizedSet(new HashSet());

/**
* @return Returns the eventSet.
*/
public Set getEventSet() {
return eventSet;
}

/**
*@param event - An instance of a business event resulted from a particular
*business transaction
*/
public void addToEventSet(Event event) {
this.eventSet.add(event);
}

}

AbstractDataでセットを宣言するのは、ある時点で収集において同じイベントが重複するのを避けるためです。UserDataがどのようなものか見てみましょう。UserDataは、実際のUserドメインオブジェクトを含みます。したがってUserドメインオブジェクトへのあらゆる変更は、このラッパークラスの範囲内にとどめられ、クライアント層とビジネスサービス層の間のインターフェースとなるコントラクトを崩すことにはなりません。

例: UserData.java

/**
*UserData.java - A concrete POJO data wrapper whose responsibility is to
*holds the main domain object reference and used between client and business
*service layers.
*@author - Vigil Bose
*/
public class UserData extends AbstractData{

private User user;

/**
* @return The user domain object instance
*/
public Users getUsers(){
return this.users;
}
/**
*@param The user instance to set.
*/
public void setUser(User user){
this.user = user;
}

}

ビジネスサービスインターフェースを見てみましょう。この例では、ユーザー登録のビジネスプロセスを完了するための、IRegistrationServiceインターフェースにあるdoRegister()というAPIコントラクトを定義します。このAPIは複数のデータベーステーブルにレコードを挿入するため、トランザクションの性質を持ちます。クライアント層とビジネス層はこのインターフェースを介して通信を行います。

例: IRegistrationService.java

/**
*IRegistrationService.java - A classic example of EJB's business
*methods interface pattern that exposes all the Registration
*related business methods that can be implemented by both the
*enterprise session bean as well as the Pojo service. Moreover
*this pattern allows us to switch to POJO implementation later
*if it makes sense to do so.
*@author - Vigil Bose
*/
public interface IRegistrationService{

/**
*API to complete the registration business process
*@param userData - The data wrapper object
*/
public void doRegister(AbstractData userData);

}

便宜上、この例ではPOJO(Plain Old Java Object)サービスのみを使用しています。ビジネスサービスの実装は、顧客登録プロセスを完了するためにビジネスロジックを実装します。この場合、このサービス実装の唯一の責務は、適切なデータベーステーブルにおいて顧客登録のトランザクションの状態を記録するため、呼び出しをデータアクセス層に委譲するということです。

例: RegistrationServiceImpl.java

/**
*The primary business method implementation of Customer Registration Service.
*This is a POJO. It does not depend on any Spring APIs. It's usable outside a
*Spring container, and can be instantiated using new in a JUnit test. However,
*we can still apply declarative transaction management to it using Spring AOP.
*@author - Vigil Bose
*/
public class RegistrationServiceImpl implements IRegistrationService{

private IRegistrationServiceDao registrationServiceDao;

/**
* A setter method of dependency injection
* @param registrationServiceDao - The registrationServiceDao to set.
*/
public setRegistrationServiceDao(IRegistrationServiceDao
registrationServiceDao){
this.registrationServiceDao = registrationServiceDao;
}
/**
* API to register the user
* @param user - The user domain object
*/
public void doRegister(AbstractData userData){
this.registrationServiceDao.completeRegistration(userData.getUser());
}
}

DAOパターンのプログラムでの利用

データアクセスオブジェクト(DAO)は、Core J2EE Design Patternという本でカタログ化されているとおり、統合レイヤ設計パターンです。永続ストアアクセスと操作コードを別の層にカプセル化します。今回の記事に関連する永続ストアはRDBMSとなります。

このパターンはビジネスロジック層と永続ストレージ層の間に抽象化層をもたらします。ビジネスオブジェクトは、データアクセスオブジェクトを介してRDBMS(データソース)にアクセスします。この抽象化層はアプリケーションコードを簡素化し、柔軟性をもたらします。データベースベンダまたはその種類の切り替えといったデータソースに対する変更は、データアクセスオブジェクトに対してのみの変更を必要とするのが理想的で、ビジネスオブジェクトへの影響は最小限であるべきです。今回の例では、Hibernate実装データアクセス戦略を用いています。

DAO設計パターンが提供する柔軟性は、Program to Interfaceという、主にオブジェクト設計におけるベストプラクティスに起因します。この原則によると、具体的なオブジェクトは具体的なオブジェクト自体というよりも呼び出し元プログラムで使われるインターフェースを実装しなければなりません。したがって、クライアントコードにわずかな影響しか与えずに、違う実装に容易に置き換えることが可能です。

上記の原則にしたがい、completeRegistration()というふるまいを持つIRegistrationServiceDao.javaというRegistration Service DAOインターフェースを定義します。 ビジネスコンポーネントはこのインターフェースを介してDAOと通信することになります。

例: IRegistrationServiceDao.java

/**
*A POJO data access object interface for the CRS services business layer.
*The API's defined in this interface are all transactional APIs within the
*business services layer
*@author - Vigil Bose
*/
public interface IRegistrationServiceDao{
/**
* Data Access API to create the new user in the system
* @param user - The composite user domain object
*/
public void completeRegistration(User user) throws DataAccessException;
}

データアクセスインターフェースを定義したら、次はIRegistrationServiceDaoの具体的な実装であるRegistrationServiceDaoImplを提供しなければなりません。

この例に示された実装はHibernateに固有のものです。ここで使われるパターンは1つの戦略であり、どのObject Relational Mapping製品またはJDBCによっても置き換えることが可能です。このクラスの責務は、顧客登録のトランザクションの状態をデータベーステーブルに記録することにあります。

例: RegistrationServiceDaoImpl.java

/**
*The Registration Services Data Access Strategy implementation
*using Hibernate persistence mechanism that support various
*registration related business transactions.
*@author - Vigil Bose
*/
public class RegistrationServiceDaoImpl extends HibernateDaoSupport
implements IRegistrationServiceDao{

/**
* Data Access API to create the new user in the system
* @param users - The composite users domain object
*/
public void completeRegistration(Users users) throws DataAccessException {

getHibernateTemplate().save(users);
}

}

どのアプリケーションにおいても、読み出し専用データへのアクセスは重要です。それでは、顧客登録システムにおいて使われるILookUpServiceDaoという簡易なJavaインターフェースの例を見てみましょう。これは読み出し専用データへのアクセスにおいてファインダおよびゲッターメソッドを顕在化させます。

例: ILookUpServiceDao.java

/**
*A POJO data access object interface that exposes the lookup API's in Customer
*Registration System.
*The API's defined in this interface can be used with or without any other
*transactional APIs within the business services layer
*@author - Vigil Bose
*/
public interface ILookUpServiceDao{
/**
* Data Access API to find the event instance based on its primary key
* @param eventId - The event tables primary key identifier
*/
public Event findEventById(Integer eventId) throws DataAccessException;

}

下記の例はHibernateの戦略的な実装です。ILookUpServiceDaoインターフェースで定義されたAPIはLookUpServiceDaoImplという具体的なクラスにおいて実装されます。便宜上、この例では1つのAPI実装のみを示しています。

例: LookUpServiceDaoImpl.java

/**
*A POJO data access implementation that implements the lookup API's in Customer
*Registration System.
*The API's defined in this interface can be used with any other
*transactional APIs within the business services layer
*@author - Vigil Bose
*/
public classe LookUpServiceDaoImpl extends HibernateDaoSupport
implements ILookUpServiceDao {
/**
* Data Access API to find the event instance based on its primary key
* @param eventId - The event tables primary key identifier
* @return an instance of Event domain object
* @throws DataAccessException
*/
public Event findEventById(Integer eventId) throws DataAccessException{
return (Event)getHibernateTemplate().get(Event.class, eventId);
}

}

Spring Frameworkが提供するHibernateDaoSupportクラスは、テンプレートパターン実装であり、Hibernateに関連するAPIとHibernate Sessionに関係するリソース管理を抽象化します。

データアクセス実装に伴い、顧客登録プロセスのビジネス要件の1つを満たしました。それでは要件の二点目である設計中に特定されるサブシステム機能に対応します。要件は、登録が完了したら、システムは請求書作成のため顧客の住所データを請求書システムに送信すると共に、当該顧客にメール通知するというものでした。それでは、よく知られたコマンドパターンを通じてサブシステムプロセス処理を実現することにします。

コマンドパターン

コマンドパターンは、様々なコマンドを実行するための共通インターフェースを提供するために使われます。コマンドインターフェースを実装するクラスはいずれも、execute()メソッドにおけるタスクの具体的な実装を提供することができます。

設計中、同一のインターフェースに2つの異なるコマンド実装を特定しました。とは、ビジネスサービス層に関する限り、サブシステム機能は関心事の分離のことです。したがって、アスペクト指向プログラミング(AOP)手法を用いて、主要なビジネスサービス層の実装から関心事を切り離しました。

AOPの概念についてさらに知りたい場合、こちら(英語)をクリックしてください。またAOPの概論についてはスクロールダウンしてください。それではコマンドインターフェースを設計しましょう。

例: ICommand.java

/**
*ICommand.java - The famous command interface that exposes the execute API.
*@author - Vigil Bose
*/
public interface ICommand{

/**
*The Command design pattern encapsulates the concept of the
*command into an object. The issuer holds a reference to the
*command object rather than to the recipient.The issuer sends
*the command to the command object by executing a specific
*method on it. The command object is then responsible for
*dispatching the command to a specific recipient to get the
*job done.
*@param data - The data transfer object
*/
public void execute(Object data);

}

標準的なコマンド実装は、1つの計算(受け取り側と一連のアクション)をひとまとめにし、ファーストクラスオブジェクトとして順に廻していく方法を提供します。このコマンドオブジェクトはコマンド要求の受け取り側が実際に要求を処理するというメソッドを呼び出します。またある要求を受け取り側に委譲する必要なく、特定のタスクを自身が取り扱うコマンド実装もよく見られます。この場合、MailingCommandImplは、メール通知するためEmailServiceを呼び出すことによって、メールのタスクを実行します。簡単にするため、EmailServiceの実装はこの例では示されていません。結局意図するところは、AOPとSpring 2.0を活用してビジネスイベントをどのようにサブシステムプロセッサに送ればいいかについての知識を提供することにあります。

例: MailingCommandImpl.java

/**
*MailingCommandImpl.java - A command implementation that implements
*the task of sending mail notification to the customer who completed
*the registration process.
*@author - Vigil Bose
*/
public class MailingCommandImpl implements ICommand{

private IEmailService emailService;

/**
*A setter method of dependency injection
*@param emailService - The emailService instance to set.
*/
public void setEmailService(IEmailService emailService){
this.emailService = emailService;
}
/**
*API execute is used to execute the mailing tasks implemented
*@param args - An instance of AbstractData used in business service layer
*/
public void execute(Object args){

//get the reference of user object
User user = (User)args;

//get the reference of address object via its parent object User.
Address address = user.getAddress()

//Invoke the EmailService API here to send out the notifications....

}
}

それでは、顧客の住所データを請求書アプリケーションに送信するというビジネス要件を実現しやすくする2つ目のコマンド実装を設計したいと思います。この特定の実装においては、請求書アプリケーションがアプリケーション統合のためどのプロトコルでも(例:Webservices、MessagingまたはXML over HTTP )使えるという条件で、好きなプロトコルを選ぶことができます。簡単にするため、下記の例ではJMS Messagingを用いています。JMS Messagingの内部は例の中で示されていません。

例: SendCustomerInfoCommandImpl.java

 /**
*SendCustomerInfoCommandImpl.java - A command implementation that implements
*the task of transmiting the customer's address data to the invoice system.
*@author - Vigil Bose
*/
public class SendCustomerInfoCommandImpl implements ICommand{

private IMessagingService messagingService;

/**
* A setter method of dependency injection
*/
public void setMessagingService(IMessagingService messagingService){
this.messagingService = messagingService;
}

/**
*API execute is used to execute the messaging task implemented.
*@param args - An instance of AbstractData used in the business service layer
*/
public void execute(Object args){

User user = (User)args;

//Invoke the appropriate messagingService API
//to send the customer information here....
}
}

AOPの基本概念

アスペクト指向プログラミング(AOP)は、関心事、具体的には横断要素の分離においてプログラマーを支援することを意図しています。プロシージャ、パッケージ、クラス、メソッドはいずれも、プログラマーが関心事を単独のエンティティにカプセル化しやすくするものです。しかし関心事にはこれらのカプセル化形式を受けつけないものもあります。これらの関心事は1つのプログラム内で多くのモジュールを横断しているため、横断要素と呼ばれています。一部のコードが分散するか複雑に絡まりあい、理解・維持がしづらくなっているのです。1つの関心事(この場合、イベントルーティングなど)が多数のモジュール(クラスやメソッドなど)全体に広がっているときは、分散している状態です。この場合、イベントの配送を変更するには、影響を受けるすべてのモジュールの変更が必要となることを意味しています。

様々な新しい要素が基本機能(時にビジネスロジック要素とも言う)とからまった状態になっているため、このコードは上品さと簡素さを失いました。トランザクション、メッセージング、セキュリティ、ロギングはすべて横断要素の例です。

AOPは、プログラマーがアスペクトと呼ばれる独立型モジュールで横断要素を表わせるようにすることで、この問題を解決します。アスペクトはアドバイス(プログラム内で特定のポイントに結合されたコード)およびインタータイプ宣言(他のクラスに追加された構造上のメンバー)を含むことができます。例えば、セキュリティモジュールは銀行口座にアクセスする前にセキュリティチェックを実行するアドバイスを含むことが可能です。ポイントカットは銀行口座にアクセスできる時間(ジョインポイント)を定義し、アドバイスの本体にあるコードはセキュリティチェックがどのように実行されるかを定義します。そのようにして、チェックおよび場所の両方が一箇所で保守されうるのです。さらに、ポイントカットが優れていれば、後で生じるプログラム変更を予測できるため、別の開発者が銀行口座にアクセスするための新しいメソッドを作成したら、そのアドバイスが実行時に新しいメソッドに適用されます。主要なAOP実装には、AspectJ、AspectWorkz、Spring AOPなどがあります。

Spring AOPは純粋なJavaで実装され、特別なコンパイルプロセスを必要としません。一方AspectJは特別なコンパイルプロセスが必要です。Spring AOPではクラスローダーの階層を制御する必要がなく、したがってJ2EEのWebコンテナまたはアプリケーションサーバーでの使用に適しています。Spring 2.0はAspectJとのより緊密な統合を提供します。

イベントルーティング

ビジネス要件を満たすために、RegistrationBeforeAdviceRegistrationAfterAdviceという2つのAOPコンポーネントを特定しました。様々なAOPのアドバイスおよび他の概念についての詳細は、Springの参考資料をご覧ください。

AOPコンポーネントを2つ特定した論理的根拠は、イベントルーティングをサポートし、コード内の相互依存性を最小限にとどめることです。RegistrationBeforeAdviceの責務は、適切なビジネスイベントを特定し、収集することに限定されています。Spring AOPのbeforeアドバイスの実装は、適切なビジネスイベントを特定し、イベント収集に追加するためのカスタムのふるまいを注入するためビジネスサービスインターフェースAPIをさえぎるためのSpringアプリケーションコンテキストファイルにて設定することができます。

この場合、RegistrationBeforeAdviceはビジネスサービスインターフェースであるIRegistrationServicedoRegister(AbstractDataデータ)APIをさえぎります。このアドバイスはサービス・インターフェースAPIであるAbstractDataの入力引数にアクセスできるようになっています。AbstractData層に以前実装されたイベント収集メカニズムがこの時点で有用になります。RegistrationBeforeAdviceは正しいビジネスイベントを特定し、イベント収集に追加します。

Springアプリケーションコンテキストで設定されたeventMapはグローバルなイベントマップです。eventKeyが、適切なビジネスサービスインターフェースAPI名を伴ってイベント識別子にマップされます。これにより、AOPコンポーネントのRegistrationBeforeAdviceでコード変更することなく、ビジネスサービスインターフェースで定義された新しいビジネスAPIをグローバルなイベントマップ設定のイベントIDにシームレスにマップすることができます。しかしこのアプローチには1つ注意点があります。プログラマーがグローバルなイベントマップでイベントIDに対し誤ったメソッド名を設定すると、コンパイル時にはそれが明確に分からないのです。しかし簡単なJUnitテストをすれば、この名称の誤りに気づきやすくなります。

ビジネスAPIの名称    : doRegister
イベントID: 1

例えばdoUpdate()という別のビジネスAPI名を別のイベントID2にマップするのは非常に容易です。唯一行う変更といえば、インターフェースにおいて新しいAPIを定義したあと、Springアプリケーションコンテキストファイルでイベントマップにマッピングを追加するだけです。下記の設定情報サンプルの一部をご覧ください。




1


2

一部のケースでは、1つのビジネスプロセスが複数のイベントをもたらす結果となる場合があります。この場合でも、RegistrationAfterAdviceとイベントマップ設定に若干の修正を加えるだけで実行可能です。このような場合、ビジネストランザクションごとのイベント一覧を明白にする必要があります。簡単にするため、今回の記事での例はビジネストランザクション1つにつきイベントは1つだけに限定しています。

beforeアドバイスが実行中の状態を確認するには、下記のコード例を参照してください。

例: RegistrationBeforeAdvice.java

/**
*RegistrationBeforeAdvice.java - This advise acts before the doRegister() API in
*the business service interface executes and sets the appropriate event in the
*eventSet collection implemented in the AbstractData layer. The event is Customer
*Registered Event. This advice inserts custom behavior in IRegistrationService API
*doRegister() before it is executed and identifies the correct business event add
*it to the event collection
*@author - Vigil Bose
*/
public class RegistrationBeforeAdvice implements MethodBeforeAdvice{

private Map eventMap;
private ILookUpServiceDao lookUpServiceDao;

/**
* A setter method of dependency injection
* @param Map - The eventMap instance to set.
*/
public void setEventMap(Map eventMap){
this.eventMap = eventMap;
}
/**
* A setter method of dependency injection
* @param lookUpServiceDao - The lookUpServiceDao instance to set.
*/
public void setLookUpServiceDao(ILookUpServiceDao lookUpServiceDao){
this.lookUpServiceDao = lookUpServiceDao;
}
/**
*Before advice can insert custom behavior before the join point
*executes, but cannot change the return value.If a before advice
*throws an exception, this will abort further execution of the
*interceptor chain. The exception will propagate back up the
*interceptor chain. If it is unchecked, or on the signature of the
*invoked method, it will be passed directly to the client; otherwise
*it will be wrapped in an unchecked exception by the AOP proxy.
*@param method - method being invoked
*@param args - arguments to the method
*@param target - target of the method invocation. May be null
*@throws Throwable - if this object wishes to abort the call.
*Any exception thrown will be returned to the caller if it's allowed
*by the method signature. Otherwise the exception will be wrapped
*as a runtime exception
*@see org.springframework.aop.MethodBeforeAdvice#before(java.lang.reflect.Method
*java.lang.Object[], java.lang.Object)
*/
public void before(Method method, Object[] args, Object target) throws Throwable {

AbstractData data = (AbstractData)args[0];

Set keySet = this.eventMap.keySet();
Integer eventId;

Event event = null;
String eventKey = null;

//Iterate through the key set and extract event identifier and
//retrieve the event from the database and add it to the event
//collection.

for (Iterator iter = keySet.iterator(); iter.hasNext();) {
eventKey = (String) iter.next();

//Check whether the eventKey is matched with the business
//service interface API name. If it does, extract the eventId
//and retrieve the event instance from the datastore and add it
//to the event collection.

if (eventKey.equalsIgnoreCase(method.getName()){
eventId = (Integer)eventMap.get(eventKey);

event = this.lookupService.findEventById(Integer eventId);
data.addToEventSet(event);
}
}
}
}

In this example, one of the design constraints that should be considered is the exceptions that arise from either the Before advice or After advice components should not affect the online business transaction. The online customer should not be penalized for event routing errors. For simplicity, I am not showing how the exception handling should work in this example.

The RegistrationAfterAdvice's responsibility is to iterate through the event collection and action codes and initiate the routing process. The action codes used in this example are M and T. There are commands mapped for each action code in the spring application context file. It then iterate through each of the action code associated with the event (Customer Registered Event) and retrieves the mapped command object instance. Once the command object references are obtained, the routing takes place automatically by passing the customer data to each of the command implementation for appropriate task execution.

Example: RegistrationAfterAdvice.java

/**
*RegistrationAfterAdvice.java - This advise acts after when the doRegister()
*API of the business service implementation is executed. This advise will
*actually delegate the event actions associated with the Customer Registered
*Event to the appropriate command. This advice inserts custom behavior to
*IRegistrationService interface API's after the API is executed.
*@author - Vigil Bose
*/
public class RegistrationAfterAdvice implements AfterReturningAdvice {
/**
*After returning advice is invoked only on normal method return,
*not if an exception is thrown. Such advice can see the return
*value, but cannot change it.
*
*@param returnValue - the value returned by the method, if any
*@param method - method being invoked
*@param args - arguments to the method
*@param target - target of the method invocation. May be null
*@throws Throwable - if this object wishes to abort the call.
*Any exception thrown will be returned to the caller if it's allowed
*by the method signature. Otherwise the exception will be wrapped as a runtime
*exception
*@see org.springframework.aop.AfterReturningAdvice#afterReturning
*(java.lang.Object, java.lang.reflect.Method, java.lang.Object[],
*java.lang.Object)
*/
public void afterReturning(Object returnValue, Method method, Object[] args,
Object target) throws Throwable {

AbstractData data = (AbstractData)args[0];
User userInfo = (User)data.getUser();
Set eventSet = data.eventSet();

Set keySet = this.commandMap.keySet();
Event event = null;
String actionCodes = null;
String actionCode = null;

//Iterate through the event set
for (Iterator iter = eventSet.iterator(); iter.hasNext();) {

event = (Event) iter.next();
actionCodes = event.getEventActionCodes();

//Loop through the action codes and extract each command mapped to
//the action code in spring application context file.

for (int i=0; i < actionCodes.length();i++){
actionCode = new Character(eventActionCodes.charAt(i)).toString();
  command = (ICommand)commandMap.get(actionCode);
if (command != null){
command.execute(userInfo);
}
}
}
}
}

上記の例では、RegistrationAfterAdvice自体にイベント収集とイベントルーティングメカニズムの両方を実装することが可能でした。しかしイベント収集とイベントルーティングの責務を切り離しておくために、サブシステム・ルーティング機能を処理するのに2つのAOPアドバイスコンポーネントを使うと決めたわけです。

集約する

それではSpringのアプリケーションコンテキストxmlファイルですべてをひとまとめにしましょう。下記の例では、データアクセス層のオブジェクトリレーショナルマッピングとしてHibernate実装が用いられています。複数のリソースプロバイダ(データベースとJMSなど)を扱う場合、JTAトランザクション戦略を用いることを推奨します。単純にするため、ローカルトランザクション戦略のみが示されています。

例: applicationContext.xml




xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:util="http://www.springframework.org/schema/util"
xmlns:jee="http://www.springframework.org/schema/jee"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.0.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-2.0.xsd
http://www.springframework.org/schema/jee
http://www.springframework.org/schema/jee/spring-jee-2.0.xsd">














class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"/>
name="dataSource" ref="dataSource"/>




org.hibernate.dialect.Oracle9Dialect
3
true
true
true
3

org.hibernate.cache.EhCacheProvider

node1




Users.hbm.xml
Address.hbm.xml




id="transactionManager"
class="org.springframework.orm.hibernate3.HibernateTransactionManager">
















expression="execution(public * *..IRegistrationService.*(..))"/>


advice-ref="registrationBeforeAdvice" order="1"/>



advice-ref="registrationServiceTransactionAdvice" order="2"/>



advice-ref="registrationAfterAdvice" order="3"/>




transaction-manager="transactionManager">












class="RegistrationAfterAdvice">



































class="org.springframework.mail.javamail.JavaMailSenderImpl">





 





1




アスペクト指向プログラミング(AOP)は、プログラミングにおいて比較的新しい概念です。この技術はオブジェクト指向技術を補完するものであり、オブジェクト指向技術が不十分でない場合に、より大きな力を発揮し、要素をさらに分離します。

まとめ

要素の分離はサービス指向アーキテクチャの主要原則であり、構築・実装のそれぞれのレベルにおいて適用する必要があります。今回の記事では、Spring Frameworkの依存性注入およびアスペクト指向プログラミング機能の原則を使って、横断要素をどのように分離するかを説明してきました。サンプルコードでご覧になったように、このアプローチによりサービスの各要素を処理したコードにおける相互依存性を最小限にとどめることができました。

参考

Spring Reference Document: http://www.springframework.org/docs/reference/


免責条項

提供されたコードは、認定済みの本番稼動体勢にあるものではなく、あくまで手引き情報だとお考えください。このコードを使って何か問題が起きたら、お知らせいただければ更新します。

この記事に星をつける

おすすめ度
スタイル

BT