Facadeクラスの実装
次はFacadeクラスの実装です。
実はO/Rマッピングツールの説明をすると言っておきながら、ここまでほとんどO/Rマッピングツールに関するコードが登場していませんでした。
ここからようやくNHibernateを使ったコードが登場します。
注文Facadeクラス(BookOrderFacade.cs)
using System; using System.Collections.Generic; using NHibernate; namespace Junichi.Ito.NHibernateBookStore.Facade { // 注文用Facadeクラス public class BookOrderFacade { // 新しい注文を登録する public BookOrder CreateBookOrder(string customerName, IList<int> bookIDs) { BookOrder bookOrder = null; NHibernateHelper.ExecuteTransaction(delegate(ISession session) { // 注文の作成 bookOrder = new BookOrder(); bookOrder.CustomerName = customerName; bookOrder.OrderDate = DateTime.Now; // 注文明細リストの作成 IList<BookOrderItem> bookOrderItems = new List<BookOrderItem>(); foreach (int bookID in bookIDs) { // 注文する書籍を取得する Book book = session.Get<Book>(bookID); // 発送予定日を取得し、書籍の在庫を減らす // 注) トランザクションが終了すると書籍の在庫の減少も自動的に保存される DateTime shippingDate = book.ReserveShipping(); // 注文明細を作成する BookOrderItem bookOrderItem = new BookOrderItem(); bookOrderItem.Book = book; bookOrderItem.ShippingDate = shippingDate; bookOrderItems.Add(bookOrderItem); } // 注文に注文明細リストをセットする bookOrder.Items = bookOrderItems; // 注文を保存する(注文明細も同時に保存される) session.Save(bookOrder); }); return bookOrder; } // 他のコードは省略 } }
これが「新しい注文を登録する」メソッドの実装になります。
「NHibernateHelper.ExecuteTransaction」というのはこちらで作成したヘルパーメソッドです。
無名デリゲートを使っているので人によっては難しく感じるかもしれませんが、とりあえずここでは一つのトランザクションを処理するためのブロックが続いているものと考えてください。
基本的に処理の流れはPart2で説明した「処理シーケンス」と同じです。
コードだけではイメージが付きにくい方はシーケンス図と合わせて読むと分かりやすくなるかもしれません。
この中で登場している「ISession session」がNHibernateとやり取りするためのオブジェクトになります。
ASP.NETでもSessionという用語が使われますが、ここでは注釈がない限りSessionというと基本的にNHibernateのSessionオブジェクトを指していると考えてください。
Sessionが使われているのは以下の2カ所です。
Book book = session.Get<Book>(bookID); session.Save(bookOrder);
上のコードはオブジェクトの検索を、下のコードはオブジェクトの保存を実行しています。
「あ〜、ここでデータベースにSELECT文とINSERT文を送っているのね」
と考えたそこのあなた!
ちょっと待ってください!!
Part1でも説明した通り、O/Rマッピングツールは「背後にあるデータベースの存在を感じさせないデータの管理」を実現します。
ここで注目してほしいのは、新しい注文データを作成し保存するためにオブジェクトの操作しか行っていないという点です。
ここまでずっとデータベースに関する説明はしてきませんでした。
データの保持はドメインモデルクラスが担当するとしか書いてきませんでした。
実際にここまでデータベースやテーブルに関する説明は一切してきていません。
説明したのはクラスの説明だけです。
つまり、開発者はクラスのことだけを理解し、クラスやオブジェクトを操作することだけに集中すればプログラムができあがってしまうのです。
・・・はい、ここであなたがこのことを本当に理解するまでちょっと待ちます。
上の文章を読み、それからもう一度ソースコードを眺めてください。
もうOKですか?
素晴らしい、では先へ進みましょう。
元ネタとなったエントリでも書きましたが、このようにO/Rマッピングツールを活用すれば、バリバリのオブジェクト指向テクニックが使えるようになります。
さて、これまでに何度か登場している「書籍クラスのポリモーフィズム」は以下の部分で実現されています。
Book book = session.Get<Book>(bookID); DateTime shippingDate = book.ReserveShipping();
「Book book」は実際には和書クラスまたは洋書クラスのインスタンスのどちらかです。
しかし、和書クラスでも洋書クラスでも「ReserveShipping」メソッドを呼び出すことが可能なので、「book.ReserveShipping()」と書いてしまえば、呼び出す側は「book」が和書なのか洋書なのか気にする必要がありません。
本来の発送予定日の計算ルールは明らかに条件分岐が発生しそうなビジネスルールが仕様として書かれていましたが、ここでは条件分岐が一つも登場していません。
また、NHibernateがその手助けをしてくれている点にも要注目です。
NHibernate(=Sessionオブジェクト)に「このbookIDの書籍をくれ」と命令すれば、NHibernateがそのIDを検索し、それが和書なのか洋書なのかを判断して適切なクラスを使ってインスタンスを復元してくれます。
次は注文情報を保存している以下のコードです。
session.Save(bookOrder);
ここでもさらりとこれだけで注文情報の保存が実現されています。
もちろん注文明細の情報も同時に保存してくれています。
「あ〜、データベースにINSERTね」
あ、またそんなことを考えた人がいますね!?
え、いない?
失礼、それだったらうれしいです。
ここでも「データベースを感じさせないコード」になっていることを十分感じ取ってほしいです。
イメージとしては「NHibernateがデータを保存する先はデータベースかもしれないし、XMLファイルかもしれないし、どこかのバイナリファイルかもしれないけど、とりあえずNHibernateがオブジェクトをまた後で復元できることを保証してくれた」っていう感じです。
「Book book = session.Get
最後に、ある意味すごくトリッキーなのが「書籍クラスの在庫数減少はどこで保存されたのか」という点です。
「book.ReserveShipping()」と書いた時点で、bookの内部にある在庫数は減少しています。
しかし、特にbookの変更をNHibernateに対して明示的に依頼したようなコードはどこにもありません。
実は「Book book = session.Get
Sessionがクローズされたり、NHibernate上のトランザクションがコミットされたりした時点で、Sessionは書籍オブジェクトへの変更の有無を確認し、変更があればそれを保存します。
ゆえにbookの変更を明示的にNHibernateに依頼する必要はないのでした。
ふう、注文Facadeクラスの説明だけでかなりの文量になってしまいました。
しかし、ここにはO/Rマッピングツールに関する非常に重要なトピックが満載だったと思います。
Part1からPart4まではこれを説明するための長い前置きだったと言えるかもしれません。
それでは次回はNHibernateHelperクラスと書籍Facadeクラスの説明を行います。