Hatena::Groupcatalyst

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

2008-05-11

MacBook買いました!

MacBook買いました! - dann@catalyst を含むブックマーク はてなブックマーク - MacBook買いました! - dann@catalyst MacBook買いました! - dann@catalyst のブックマークコメント

switch中!後で書く!

Class::MOPとattributeでAOP

Class::MOPとattributeでAOP - dann@catalyst を含むブックマーク はてなブックマーク - Class::MOPとattributeでAOP - dann@catalyst Class::MOPとattributeでAOP - dann@catalyst のブックマークコメント

Mooseのmethod_modifierはAOP的な仕組みではあるのですが、横断的な関心毎が特定クラスに密に結びついてしまうために、Separation of Concernsを実現するためには少しいまいちな仕様です

また、MooseのRoleにしてMixinできるようにしても、例えばmethod_modifierで指定する指定するメソッド名が、特定のクラスを意識することになるため、これもまたAOP的なSeparation of Concernsを実現するための仕組みとしては不十分です。Roleが、consumeする側のことしっているという点で、AOP的な関心ごとの分離とは少し違う仕組みだといえます。


横断的関心事を実装するAOPのAdviceは、対象となるクラスのことを知らないようにし、任意のクラスに適用できるになっているのが魅力です。これを実現する方法を考えてみました。Perlでは以下のようにやるのがよいのではないかと思っています。

  • Adviceの指定する方法については、Perlのattributeでする
    • いつどこにAdviceを適用するのかをPerlのAttributeで指定するということです
  • Adviceのweavingは、DI Containerでする
    • Adviceの処理でラップしたメソッドでそのクラスのメソッドを置き換える必要があるわけですが、それはDIコンテナにやらせます。

該当のクラスには、以下のように、いつどんなAdviceを適用するのかを、PerlのAttributeで指定させるのは、以下のようにします。JavaのAnnotationの代りにattributesを使います。

sub witdraw :  Before(Debug) {
}

Adviceは別クラスにして、完全にもとのクラスには依存しないようにさせます。そうすると、そのAdviceは任意のクラスに対して適用できるようになるため、AOP的なSeparation of Concernsが実現できます。ここの例では、AOP::Advice::Debugとしています。

DI ContainerでAdviceを適用させるわけですが、その理由は、DIコンテナにやらせることで、そのクラスはDIコンテナ外ではAdviceには依存しないPOPOになるからです。PerlのAttributeに処理を直接書いてしまうと、そのAttributeの処理を切り離すことが難しくなるため、横断的な関心ごとを対象のクラスから分離するのが難しくなります。単体テストをする際にも、横断的な関心ごとを分離してやる必要があります。

以下が、その実装例です。

#!/usr/bin/env perl
use strict;
use warnings;

package Service;
use Moose;
use Data::Dumper;
use attributes;
use Attribute::Handlers;
use Perl6::Say;
has 'name' => ( 'is' => 'rw', default => sub {'service'} );

my %Attribute;

sub MODIFY_CODE_ATTRIBUTES {
    my ( $class, $code_ref, @attribute ) = @_;
    $Attribute{$code_ref} = \@attribute;
    return;
}

sub FETCH_CODE_ATTRIBUTES {
    my ( $class, $code_ref ) = @_;
    return @{ $Attribute{$code_ref} || [] };
}

sub Before : ATTR {
}

sub buisinessLogic : Before(Debug) {
    my ( $self, $args ) = @_;
    say '### BUSINESS LOGIC ###';
    say $args;
}

# 横断的な関心事を切り離す。
package AOP::Advice::Debug;
use Data::Dumper;
use Perl6::Say;

sub execute {
    my ( $self, $args ) = @_;
    say '### DEBUG ###';
    say Dumper $args;
}

# DIContainerで横断的関心毎をWeavingする
package DIContainer;
use Perl6::Say;
use Data::Dumper;

sub weave_advices {
    my ( $class, $service ) = @_;
    my $method_map = $service->meta->get_method_map;
    foreach my $method_name ( keys %$method_map ) {
        my $method_code_ref = $method_map->{$method_name}->body;
        foreach my $attribute_name ( attributes::get($method_code_ref) ) {
            if ( $attribute_name =~ /^Before\((.*?)\)/ ) {
                my $type = $1;
                $class->weave_before_advice( $service, $method_name, $type );
            }
        }
    }
}

sub weave_before_advice {
    my ( $class, $service, $method_name, $type ) = @_;
    if ( $type eq 'Debug' ) {
        $class->weave_log_before_advice( $service, $method_name );
    }
}

sub weave_log_before_advice {
    my ( $class, $service, $method_name ) = @_;
    $service->meta->add_before_method_modifier(
        "$method_name" => AOP::Advice::Debug->can('execute') );

}

sub main {
    my $service = Service->new;
    DIContainer->weave_advices($service);
    $service->buisinessLogic("Hello world");
}

main();

実行すると、以下のようになります。ServiceのbusinessLogicメソッドを実行する前に、DebugのAdviceがCallされていることがわかりますね。DebugのAdviceは、対象のクラスに依存していないので、任意のクラスで使うことができます。これで、AOP的なSeparation of Concernsも実現できそうだということがわかります。

### DEBUG ###
$VAR1 = 'Hello world';

### BUSINESS LOGIC ###
Hello world

DI Containerのところは、Bread::Boardに組み込んで使えるようにしてみようと思っています。Transactionのようにmethod単位で指定したいものや、TraceのInterceptorのようにパッケージ単位などで指定したいものなどもあると思うので、指定のさせ方はattribute以外にも複数用意できるようにしたいなとも思っています。

# というのを、PerlのattributeとClass::MOPを調べながら、Newbiethonで作ってみました。本当は、Bread::Boardに組み込むところまでもっていければよかったんですが、Bread::Boardのコードを読んでる途中で力尽きてしまいました。

http://d.hatena.ne.jp/tomyhero/20080503/1209830387

トラックバック - http://catalyst.g.hatena.ne.jp/dann/20080511