今日も適当ダイアリー

PHP や Javascript や Symfony、BEAR.Sunday などのWeb周りのことを中心に。それ以外のことも気まぐれに投稿します。

BEAR.Sunday meetup #0 でRayについて話してきました #BEARSunday

こんばんわ。BEARでBEERな @madapaja です。

7/19に行われた BEAR.Sunday meetup #0 に参加して BEAR のオブジェクトフレームワークである Ray について発表させていただきました。

最初に、会場を提供していただいたVoyage Groupさん、ありがとうございました。そして、スピーカーの皆さん、参加者の皆さん、スタッフの皆さん、本当にお疲れ様でした! また、@koriym さんには発表前から後までフォローいただき非常にお世話になりました。ありがとうございました。

ということで、まだ開発途上のPHPフレームワーク「BEAR.Sunday」の勉強会ということでしたが、野心的でパワフルな方々が参加され、非常に面白い勉強会でした。
アットホームな雰囲気で、濃い話が聞けたのはとても良かったです。

私自身、BEAR.Sundayについて直接聞くのは、昨年末の前々回のSymfony勉強会、先月のSymfony勉強会に続いて3度目でしたが、やっとのことで、BEARの考え方がある程度理解できました。やっとスタートラインに立てたかな、などと思っています。
といっても、じゃあ他の人に説明してみて、と言われたらまだ出来ないので、これからも継続的にウォッチしたいとは思います。

Ray.Di / Ray.Aop コトハジメ

「Ray.Di / Ray.Aop コトハジメ」というタイトルで発表した内容は、スライドを見ればなんとなく分かるかと思いますが、スライドには書ききれなかったことを補足したいと思います。

 

当日使った資料とあわせてご覧ください。

BEAR.Sunday

以前、郡山さんからこんなDMをもらいました。

「ポイントは問題の解決に実装ではなく設計で挑もうとしてる点です。
これが開発リソースの細い細い僕が挑める唯一の方法です。」(@koriym)

この言葉にもある通り、BEAR.Sundayには多くの挑戦があり、多くの設計に関するキーワードがあります。

BEAR.Sundayを取り巻くコンセプトやアーキテクチャ、技術などは様々あります。(ここには書き切れませんし、説明し始めたら1日でも終わらないと…) その中で、BEAR.Sundayのコアフレームワークの一つである Ray の紹介を行いました。

Ray (rayphp)

Rayは、DI(依存性の注入)をサポートするRay.Diと、AOP(アスペクト指向プログラミング)をサポートするRay.Aopの2つのフレームワークから構成されています。

Rayの役割は、オブジェクトの面倒をみることです。 つまり、DIでオブジェクトの生成を行い、AOPによって振る舞いを変更することでオブジェクトを利用します。
このDIとAOPという2つのフレームワークによって、オブジェクトを「生成」して「利用」し「消滅」されるまでをサポートしているため、オブジェクトフレームワークと呼ばれます。

Ray.Di

発表では少しだけDIの使い方を紹介しました。 これは、DIの使い方を紹介したかった、というよりは、使い方からRay.Diの持っている価値観を見た方が理解しやすいのではないか、と思ったからです。

Symfony DI and Ray.Di Pattern A

最初に比較のために、SymfonyのDIコンポーネントと、それと似た形でのRay.Di(Ray.Di Pattern Aスライド)でのDIの使用方法を紹介しました。

この方法では、対象に全く触らずに依存関係を構築でき、依存関係はインジェクター側で全て持っている点も特徴です。

また、Ray.Diの場合、親クラスを指定した場合でも、その設定が継承され注入されます。(例ではMailerクラス自体ではなく、そのベースクラスであるMailerBaseクラスへDIしています)

ですが、BEARにおいては、この方法でのDIは特別な場合を除いて推奨されません。 BEAR.Sundayの標準の方法はアノテーションを使った方法です。

Ray.Di Pattern B (BEAR.Sunday standard, DI with annotation)

アノテーションを使ったDIでは文字通り、注記(=アノテーション)になりコードの読みやすさが向上されます。
SymfonyのDIコンポーネントや、Pattern Aで紹介した方法と比べて、NewsletterManager に対するDIの設定が行われていないのが分かるでしょうか。

これにより、「同じ依存が欲しいときにはモジュールに設定を追加する必要がなく、注入が欲しい箇所にアノテーション(@Inject)を加えるだけで済む」という特徴があります。

言い方を変えれば、この方法では“どの依存を用意するか”に注目し、依存中心に考えていることになります。 (もちろん、どちらが良いという話ではなく、BEAR.Sundayではそのような価値観を持って作られている、ということです)

モジュール

モジュールでは、複数のモジュールを合成することで、DIやAopの知識を構成します。
Ray.Diでは、そのモジュールの持つ知識を持ってオブジェクトグラフを構築することになります。

オブジェクトグラフとは、下記のようなイメージです。

この図では、Controllerが各ModelやEvent、Session、ACL、Viewなどの注入を必要としています。
また、SessionnではStrageの注入が、ViewではFromやTemplateの注入を必要としています。

このように、依存関係をDIによって再帰的に解決していき、必要なオブジェクトのすべてがDIによって注入されることになります。

(追記)記事公開時の説明に間違いがありましたので修正を行いました。間違っていた個所は以下の記述です。

また、LogやConfigは複数のオブジェクトによって必要とされています。 これは、特定のオブジェクトに依存するものではなく、複数のオブジェクトに渡って必要な、つまり横断的関心事(アスペクト)です。 この横断的関心事はAOPフレームワークであるRay.Aopを使って解決することになります。

複数のオブジェクトに同じオブジェクトが必要な場合の注入(早期束縛)は、DIで行われます。

AOPを利用するのは、横断的関心事(アスペクト)を解決したい場合です。 たとえば、ロギングしたい、とか、アクセス・コントロールしたい、とかを実現するために、Ray.Aopを使って振る舞いを変更します。

また、動的に利用するオブジェクトを変更したい場合(遅延束縛)、例えば、Select時はslave DBを使い、Insert時はmaster DBと使うようにする、とか、指定されたIDによってSelect/InsertするDBを変更したい、のような(動的に)依存を解決したい場合は、Ray.Aopによるランタイムインジェクションによって動的に必要なオブジェクトをインジェクトすることができます。(Object Framework - Ray.Aopの「ランタイムインジェクター」節を参照)

LogやConfigは複数のオブジェクトによって必要とされていますが、Ray.Diではそのオブジェクトが必要なクラスに、LogInterfaceやConfigInterfaceを受け取るコンストラクタ、もしくは、セッターを用意し「@Inject」アノテーションを加えるだけで、すべてのLog、Configが必要なクラスにそれぞれのオブジェクトをDIできます。

これは、Ray.DIが(アノテーションでの指定であれば)それぞれの(注入される側の)オブジェクトに対してDIを行うのではなく、インターフェース(もしくは名前)に基づいてDIを行うためで、モジュール側で一度、LogInterfaceに対してLogオブジェクトを注入するという指定を行えば、すべてのLogInterfaceが必要な個所にLogオブジェクトが注入されるためです。

(同じインターフェースに対して別々のオブジェクトの注入を行いたい場合、名前付き(@Named)で指定することで行えます)

Ray.Aop

AOPではインターセプタによって処理の前後に任意の処理を織り込むことができ、これによりオブジェクトのメソッドの利用にこれまでにない拡張性と機能性を与えます。
ここで注目したいのは下記に説明する2点です。(スライドのサンプルコードを参照)

1つめは、利用側($user->save())のコードを変更しなくても、インターセプタが適用されるということ。 つまり、getInstance した際には、Weaverされたインターセプタが織り込み済みのオブジェクトが返ってきているから。

2つめは、メソッドが呼び出す側(この例の場合、TimerIntercepter)は、$invocation->proceed()のコールによりオリジナルメソッドを実行しているつもりだけど、本当にオリジナルメソッドが呼ばれているかどうかはわからない、ということ。
つまり、インターセプタをチェーンすることで、メソッド実行がチェーンされるため、インターセプタ同士はどの位置のインターセプタかも知らないけど、最後のインターセプタだけが実際のメソッドを呼び出し、それ以外のメソッドは(オリジナルのメソッドを呼び出しているつもりで)次のインターセプタを呼び出している。
言い方を変えると、インターセプタによってアスペクトが織り込まれている、ということ。

サンプルコード内の利用部分で、Ray\Di\Injector::create([new DevModule]);というコードがありますが、これは、DevModuleではなくUserModuleの間違いです。

AOPを実現するのに裏で複雑な処理が行われているように思うかもしれませんが、実際に、Ray.Aopでのインターセプタの実装を見ると基本的なコードはこれだけです。(スライド: インターセプタのRay.Aop実装を参照)
インターセプタが空=残っていなければオリジナルメソッドを呼び、次のインターセプタがあれば、自分を引数としてinvokeメソッドを呼び出しているだけ。

たったこれだけで、インターセプタが実現されていることはちょっと驚きじゃないかと私はそう感じました。

まとめ

Ray では、オブジェクトを生成する(コンパイル)ための Ray.Di と、それを利用(ランタイム)するための Ray.Aop の2つを組み合わせることで、生成と利用を分離する、ひいては関心の分離を強力にサポートします。

また、生成と利用がはっきりと分離されていることにより、生成(コンパイル)されたオブジェクトグラフをそのまま再利用することができるようになります。
生成が再利用できることで、すべてのオブジェクトをキャッシュすることが可能で、パースやインジェクションのコストを原理的には0にすることが可能です。

説明した内容は Ray のもつ一部のコンセプトや機能ですが、興味を持ったら、是非使ってみてください!