Hatena::Groupcatalyst

dann@catalyst このページをアンテナに追加 RSSフィード

2008-04-02

AOPによる宣言トランザクションとDBIx::Class::Service

01:56 |  AOPによる宣言トランザクションとDBIx::Class::Service - dann@catalyst を含むブックマーク はてなブックマーク -  AOPによる宣言トランザクションとDBIx::Class::Service - dann@catalyst  AOPによる宣言トランザクションとDBIx::Class::Service - dann@catalyst のブックマークコメント

DBIx::Class::Service

http://d.hatena.ne.jp/ZIGOROu/20080401/1207037478

DBIx::Class::Serviceの仕組みは、JavaAOPによる宣言トランザクションの仕組みにとても近いです。クラス登録時にTransationアトリビュートがついていたら、トランザクションの開始・終了を行うようなProxyクラスでラッピングしてやるわけです。

Javaではこれをバイトコードレベルでそういうコードを差し込む形になっています。AOPでTransation用のInterceptorを設定してやると、そのメソッドはトランザクショナルなメソッドになります。このときに、サービスのクラスには一切トランザクションのコードが埋まっていないわけです。

DBIx::Class::Serviceと、JavaでのAOPの宣言トランザクションの仕組みはとても似ていますが、仕組み以外では少し違うところがあります。Javaでいう宣言トランザクションの仕組みは、ORマッパには全く依存していません。トランザクションのAPIJTAAPI)にしか依存していません。そこが大きな違いです。

ただ、将来的に同じような仕組みで実現されていくのではないかと思います。Javaと同じように以下の形になっていくのではないかと思います。

  • DIコンテナがサービス登録時に、トランザクショナルなProxyを介してトランザクショナルなメソッドに変換するのはする
  • 宣言トランザクションはメソッドのアトリビュートとして指定する
  • トランザクションの開始・終了は、ORマッパに依存させない
  • サービスというクラスは、ORマッパの外にある Service->ORマッパ といった構成になる。

Bread::Boardを使った場合には、

「serviceの定義にtransactional_methodsというトランザクショナルなメソッドを指定するものを用意して、そこにメソッドが指定されていた場合には、before, afterでトランザクション開始終了を行うようなServiceとして登録する」

といったような形での実装になっていくのではないかと思います。

ただ、DBIx::Class::Storage::DBIの実装を見ると、切り離すのは少し面倒そうだなぁという気はします。

何故宣言トランザクションがJava界隈でもてはやされているかというと、宣言トランザクションの仕組みがあれば、サービスのクラスのビジネスロジックがトランザクションの管理から解放され、それがPOJOとして扱えるからです。

これはTestabilityの点で望ましいということになります。これについては、PerlでもJavaでも変わらないかなとは思います。

ただ、以前も書きましたが、DBIx::Classだけを用いる場合でも、テスト時にtxn_doメソッドをMonkey Patchingでトランザクショナルなメソッドにしないようにしてしまってもいいわけで、労力をかけて頑張ろうという気になれないのも確かです。

CatalystConでの発表内容

00:09 |  CatalystConでの発表内容 - dann@catalyst を含むブックマーク はてなブックマーク -  CatalystConでの発表内容 - dann@catalyst  CatalystConでの発表内容 - dann@catalyst のブックマークコメント

以下のような構成で話そうかと思ってます

ただ、DIとDIコンテナについての説明をいれると、時間的に入らなそうなので、それについてはこの日記で書いていこうと思います。

Dependency Injection

00:09 |  Dependency Injection - dann@catalyst を含むブックマーク はてなブックマーク -  Dependency Injection - dann@catalyst  Dependency Injection - dann@catalyst のブックマークコメント

外部から依存関係を挿入する。ただ、それだけのことです。

Dependency Injectionのパターンを使うことで何が嬉しくなるのか?

依存関係が挿入されるため、依存するオブジェクトの詳細が、その依存関係を挿入されるクラスから切り離されます。要するに、単体テストが簡単になるということです。

では、具体的な例を見てみましょう。

MyApplicationが何らかのLoggerに依存していたとします。仮にLoggerが以下のように依存関係があったとします。

package MyApplication;
use Moose;

sub run {
    my $self = shift;
    my $logger = FileLogger->new(log_file_name => 'hoge.log');
    $logger->log();
}

1;

このように依存関係がハードコードされている場合、FileLoggerを違うLoggerに変えてテストをすることが難しくなってしまいます。例えば、依存先がDBに依存していたりすれば、DBなくしてはテストができなくなってしまいます(Monkey Pachingで回避できないことはないですが)

では、どうすれば、MyApplicationからこのloggerの実装への依存を断ち切れるでしょうか。こんなときに依存関係を断ち切るために使われるのが、Dependency Injection(IoCパターン)です。

以下のようにします。要するに、loggerの依存関係をMyApplicationの外に出してしまいます。MyApplicationに任意のLoggerを設定すれば、MyApplicationは任意のLoggerでrunメソッドを実行できるようになります。外からLoggerへの依存関係を注入することになるので、Dependency Injectionといわれています。

Dependency Injectionそのものは、とてもシンプルなアイデアですが、Testabilityを高めるための重要なテクニックの一つです。他にも依存関係を切る方法はあるのですが、ここではDependency Injectionのパターンだけを紹介することにします。

package MyApplication;
use Moose;
has 'logger' => (is =>'ro', required => 1);

sub run {
    my $self = shift;
    $self->logger->log();
}

1;

では、このDependencyは誰がInjectしてくれるのでしょうか。もっと簡単に言えば、誰がMyApplicationのコンストラクタに、FileLoggerというLoggerの実装のインスタンスを設定してくれるのでしょうか。

そこで出てくるのがDIコンテナです。手動でFileLoggerをnewして設定する方法もありますが、それだとMyApplicationを利用するクラスが実装に依存してしまうことになります。結局それはテスタビリティを損なうことになるかもしれません。

DIコンテナ

00:09 |  DIコンテナ - dann@catalyst を含むブックマーク はてなブックマーク -  DIコンテナ - dann@catalyst  DIコンテナ - dann@catalyst のブックマークコメント

DIコンテナの一つの役割は、オブジェクトの依存関係をWiringして、上記のような依存関係を解決することです。以下にBread::Boardを利用したDIの例を説明します。

package MyApplication;
use Moose;
has 'logger' => (is =>'ro', required => 1);

sub run {
    my $self = shift;
    $self->logger->log();
}

1;
package FileLogger;
use Moose;
has 'log_file_name' => (is=> 'ro', required => 1);

sub log {
    my $self = shift;
    print "log\n";
}

1;

実行するコードは、以下のようになります。下記の例は、Bread::BoardというPerlのDIコンテナを利用したものです。

my $c = container 'MyApp' => as {

    service 'log_file_name' => "logfile.log";

    service 'logger' => (
        class        => 'FileLogger',
        lifecycle    => 'Singleton',
        dependencies => [ depends_on('log_file_name'), ]
    );

    service 'application' => (
        class        => 'MyApplication',
        dependencies => {
            logger => depends_on('logger'),
        }
    );

};

$c->fetch('application')->get->run;

applicationサービスを取得すると、MyApplicationのオブジェクトはFileLoggerオブジェクトが設定された状態(依存関係がwiringされた状態)で取得されるため、MyApplicationのrunメソッドを実行すると、Loggerのlogメソッドが呼ばれます。

このようにDIコンテナが外から依存関係を注入してくれるため、各種サービスは依存関係のあるクラスの実装の詳細を知ることがないため、互いに疎に結合されます。それによって、各サービスは単体でテストができるようになります。

MccadeMccade2011/08/02 12:38This forum needed sakhing up and you've just done that. Great post!

kmeyilypzkmeyilypz2011/08/03 20:4915ggJ4 , [url=http://qeznvwezdxfs.com/]qeznvwezdxfs[/url], [link=http://nuwhqfxgeivl.com/]nuwhqfxgeivl[/link], http://srnfyjntrpcw.com/

hvqabaqxdhvqabaqxd2011/08/05 22:13fSEfDn , [url=http://hitejlqemnsf.com/]hitejlqemnsf[/url], [link=http://ldrpnarvjijm.com/]ldrpnarvjijm[/link], http://mvfzvlgrqjjn.com/