Naked Objects for .NET(リンク)はnaked objectsと呼ばれるアーキテクチャパターンの(リンク).NET向け実装フレームワークです。naked objectsの背景にあるコンセプトは、ビジネスアプリケーションを作る時にはドメインオブジェクトだけを作り、ビジネスロジックはそのドメインオブジェクトにカプセル化するというものです。そのため、それを実装するフレームワークにおいては、リッチなオブジェクト指向的ユーザインタフェースという形でドメインオブジェクトがユーザに対して表現され、またそれらオブジェクトの永続化や管理は通常ORマッピングを介して行われます。このパターンはドメイン駆動デザイン(リンク)の考え方に賛同する人にもっとも興味を持たれることでしょう。ユーザインタフェースのレイヤやデータアクセスのレイヤを書く必要がないだけでなく、naked objectsパターンは良いオブジェクトモデリングも促します。なぜならドメインモデルのプロトタイプを直ちにエンドユーザが評価できるアプリケーションにすることができるからです。
これらのことを聞いた時にほとんどの人は、大規模で複雑なビジネスアプリケーションではおそらく有効でないだろう、という反応を示します。しかしアイルランド政府の一省庁であるDSFA(Department of Social and Family Affairs:社会および家庭問題の管轄省) (リンク)では社会福祉扶助金の管理のためにNaked Objectsで作られたアプリケーションが1000人以上の職員に利用されていて、その効果は年に数十億ユーロに相当するという実績があります。
Naked Objectsの歴史
Naked ObjectsフレームワークはJavaプラットフォームで誕生しました。最初のバージョンはJava 1.1で書かれました。当時はソフトウェア開発の歴史上稀にある幸運から、Naked ObjectsをJ#によって.NETで何の変更もなく実行することができました。しかしその後のバージョンでNaked ObjectsがJava 1.5へ移植されると.NETとの互換性は失われてしまいました。最後の一撃となったのは、.NET 3.5以降はJ#をサポートしないとMicrosoftが2007年1月に発表したことでした。
この問題を解決するためにNaked Objectsを完全に再実装し直したNaked Objects for .NETが生まれました。これはC#で書かれ、ジェネリックスやLINQといった.NETの機能を最大限に生かすように設計されています。Naked Objects for .NETで生成されるジェネリック・ユーザ・インターフェースはWPFで書かれています。
Naked Objectsの仕組み
このジェネリック・ユーザ・インターフェースはコンパイル時のコードに依存しない、つまり動的に生成されます。フレームワークは実行時に、ドメインオブジェクトをユーザインタフェースに描画するのにリフレクションの機能を使います。このテクニックはイントロスペクションとして知られ、これによってすぐにドメインモデルを利用可能なアプリケーションへとすることができます。下のスクリーンショットはあるオブジェクトを開いた時の画面で、その中には関連オブジェクト(アイコンのそれぞれがドメインオブジェクトを表しています)へのリンクが含まれています。ユーザはクリックするだけで関連オブジェクトに辿り着けるようになっています。:
(この記事内の全てのスクリーンショットおよびサンプルコードは、Naked Objectsをダウンロード(リンク)した時に含まれるシンプルな経理処理アプリケーションのものです。)
世の中には単にCRUDのためのユーザインターフェースを提供する方法としてドメインオブジェクトモデルを描画するフレームワークが多くあり、それらはデータベースを見たり管理するのに適しています。Naked Objectsの特徴は、より高機能なアプリケーションを作成することができることにあります。Naked Objectsのデフォルト設定では次の図のように、全てのパブリックメソッドがアイコンのポップアップメニューで選択できます。:
メニューではメソッド名をアクション名に変換して表示されます。メソッドが引数を持つ場合は、アクションを実行するのに必要な情報を指定するためのダイアログボックスを生成します。:
public IExpenseItem CopyAnExistingExpenseItem( IExpenseItem otherItem)
これが描画される時は次の図のようになります。:
Other Item欄ではユーザがIExpenseItem
型の既存オブジェクトをドラッグしてきたり貼り付けます(この欄で他の型のオブジェクトを指定しても拒否されます)。欄の右側にはドロップダウンの矢印があり、そのリストには他のタブで現在開いているオブジェクトが自動的に表示されるため、タブを選択しなおしてドラッグするような手間がいりません。
それでは画面やドロップダウンリストにないExpense Item、あるいは利用可能なExpense Itemが全くない時に新しいオブジェクトインスタンスを作るにはどうするでしょうか。ここでドメイン駆動デザインにおいて標準的なパターンであるRepositoryとFactoryを利用します。Naked Objects的には、RepositoryとFactoryはサービスのひとつにすぎず、サービス自体がNaked Objectsでの最も基本的なドメインオブジェクトです。しかしサービスの使い方には3通りの方法があります。
まず、サービスは画面の上部にあるメインメニューとなります。通常サービスは新しいCustomerインスタンスを作成したり既存のCustomerを検索するといったビジネスアクティビティのスタートポイントを備えています。それらがメニューから実行できることでユーザは任意のインスタンスを操作できます。第二に、他のドメインで必要な時に、依存性注入(DI)(リンク)パターンによってサービスをそのドメインに注入するこができます。Naked Objectsはこの操作を透過的に管理します。ディベロッパは必要なサービスの型をドメインのプロパティにセットできるようにするだけでよく、多くのDIフレームワークで用いられる外部設定ファイルは必要ありません。
3つ目のサービスの使い方は「contributed actions(与えられるアクション)」と呼んでいる方法です。もしあるサービスが次のようなパブリックメソッドを持っていたとします。:
public virtual IList <RecordedAction> allRecordedActions(IRecordedActionContext context)
こうするとIRecordedActionContext
の実装オブジェクトに対してこのサービスがユーザアクションとして与えられます。その時、アクション時にユーザが選択したオブジェクトが自動的にダイアログの最初の欄に入ります。(このアクションは上のスクリーンショットにある「Recorded Actions」サブメニューとして表示され、そのサブメニュー内にはアクションが提供するサービスの名称がリストアップされます。)
この強力な能力は、少なくともユーザの視点から、多重継承の一種を達成する方法を与えます。それは、Mix-inのコンセプトや.Net 拡張メソッドとコンセプトの類似性があります。 しかしながら、インプリメンテーションは幾分違っています。
ビジネスルールの実装
今度はビジネスルールの実装についてですが、これには2つの方法があります。1つは属性を使った方法です。属性はクラスやプロパティ、メソッド、引数に適用されるものです。例をあげましょう。:
- デフォルトでは、ユーザがオブジェクトを保存するには画面にある全ての欄を埋めてから保存する必要があり、またアクションを実行するのにもダイアログにある欄を全て埋める必要があります。[
Optionally(
)] 属性を使うとこのフレームワークのデフォルト動作をオーバーライドできます。 [MaxLength()]
、[Mask()]
、[RegEx()]
を使って入力文字列やその書式に条件を指定するることができます。- プログラミング上の理由からユーザには関係ないプロパティやメソッドをパブリックにしないといけない場合には、
[Hidden]
属性が使えます。この属性に対してさまざまな条件を適用することもできます。 - デフォルトでは、クラスやプロパティやアクションの名前を変換した名称がユーザインターフェース上で使われます。これは私たちが実用的であると考えている方法です。しかしそれらの名前では使えない表示名称(たとえば記号や句読点を含むもの)が必要な場合は
[Named()]
属性が使えます。(付け加えると、全ての表示名称は全く別の機構で多言語化することもできます。)
2つめはずっと柔軟な方法で、次のような規約でプログラミングします。:
public virtual void CopyFrom(IExpenseItem otherItem) {...} public virtual string ValidateCopyFrom(IExpenseItem otherItem) {...}
この例では、Naked ObjectsによりCopyFrom
メソッドがユーザアクションとして描画されることになります。一方ValidateCopyFrom
メソッドについてはアクションの引数をバリデートするロジックを提供するメソッドとして認識されます。もしこのメソッドがnullでない文字列を返した場合は、ダイアログボックス上の「OK」ボタンを無効にします。そして戻り値である文字列をツールチップでユーザに見せることもできます。同じように、DisableCopyFrom
という名前のメソッドもアクションを無効にするためのロジックを持ったメソッドとして認識され、たとえば対象となるClaimオブジェクトが既に保存されている場合はアクションを無効にする、というようなことができます。(付け加えると、フレームワークは全く別の機構でユーザロールによる権限管理をおこなうこともできます。)このような規約は他にもいろいろあり、デフォルト値や選択肢(ドロップダウンのリスト)を引数で指定することもできます。
POCOオブジェクト
このようなコーディング規約のためには振る舞いにも定められたコード変更がいります。それは3つの単純なコーディング規約に従います。そのうちの2つはプロパティに対してのもので、次に示すようなものです。:
public virtual Employee Claimant {
get {
Resolve(employee);
return employee;
}
set {
employee = value;
ObjectChanged();
}
}
Resolve()はオブジェクトがメモリに読み込まれていることを保証し、ObjectChanged()はフレームワークにプロパティの値が変わった可能性があることを知らせるためのものです。どちらもそのオブジェクトについての画面を更新したり、変更が保存されたことを保証するためのものです。(付け加えると、変更の保存はフレームワークにより自動的に処理され、オブジェクトの読み込みや登録あるいは更新のためにメソッドを書く必要はありません。)
もう一つは、オブジェクトを自前で生成する時はフレームワークに知らせるための変更です。:
Employee employee = new Employee();
上のように書く代わりに、次のように書く必要があります。:
Employee employee = Container.NewTransientInstance< Employee>();
規約により呼び出しているこれらのメソッドはNaked Objectsフレームワークで利用されるのではなく、 IDomainObjectContainer
で定義されているメソッドにデリゲート(委譲)するためのものです。このようなプロパティをもつオブジェクトがある場合、フレームワークでは上記の機能を備えたコンテナを実行時に作ります。また、ドメインオブジェクト(それらの一番の親はAbstractDomainObject
)を継承すればこの数行のコードを書く手間を軽減することもできます。
しかしこの継承はオプションであることを強調しておく必要があるでしょう。Naked ObjectsはPOCO(Plain Old C# Object)ベースの方法を採っています。POCOを使わない場合、それは同じドメインオブジェクトを旧来型のアーキテクチャで実行することを意味します。つまり、ユーザインターフェースやその他のレイヤを独自に作る必要があるのです。そのような場合、通常であればContainerに対する3つの呼び出しを元のコードから取り除くだけで他のアーキテクチャでも使えるようになります。(もしアスペクト思考プログラミング(AOP)を利用したいとなった時は、Resolve()
、ObjectChanged()
、newTransientInstance()
をドメインオブジェクトのコードから取り除けばいいだけです。しかし私たちはNaked ObjectsをなんらかのAOP実装に結合させようとは考えません。)
これが魅力的なのは、ドメイン駆動デザインをサポートするためだけにNaked Objectsを使えるということです。みなさんもNaked Objectsでシステム全部を構築するのを強要されるのは嫌なはずです。またNaked ObjectsのExpress Editionでも必要を満たし、それであれば無償でダウンロード(リンク)できることも魅力です。