先日、弊社でBEAR.SundayというWebアプリケーションフレームワークを開発なされた郡山昭仁さんによる、BEAR.Sundayについての講演がおこなわれました。
こちらの記事では、その講演を元にBEAR.Sundayの肝の部分である、DI、AOP、Resourceについてご紹介していきたいと思います。
まずBEAR.Sundayとは、Ray.Di、Ray.Aop、BEAR.Resourceという3つのオブジェクトフレームワークにより構成されています。
ドット後を見れば分かるようにDI(依存性の注入)、AOP(アスペクト指向プログラミング)、**Resource(RESTリソース)**といった特徴を有するフレームワークです。
DI(依存性の注入)
初めに、DIとは、**Dependency Injection(依存性の注入)**というものです。
物事には必ず原理・原則というものがありますが、このDIにも原則、DIP(Dependency Inversion Principle)があります。
DIPとは、以下の様なことです。
- コード(クラス)というものは、それと同じか、あるいはそれより高レベルの抽象に依存すべきである。
- 高いレベルのポリシーは低レベルの細部に依存すべきではない。
この場合の依存性とは、クラス内などに固定化された定数、変数、インスタンスが入っており、それらに
依存していることを指しています。
このような依存による問題点としては、以下の様なことが挙げられます
- 値やインスタンスが固定化されているためテスト時にそれらを変更してのテストがしづらい
- フレキシビリティがないため容易にカスタマイズすることができない
以上のような問題を解決するための考えとして依存性を外部から注入してあげるというものが生まれました。
それを実装したのがRay.DIとなります。
では、実際の簡単なコードを見て、どのように依存性の注入をおこなうのか見ていきましょう。
1.注入するメソッドに@Injectをアノテートします
コンストラクタにアノテーション要らなかったです。
/**
* @Inject
*/
public function __construct(RenderInterface $renderer)
{
....
2.依存を必要とする場所に依存をどう渡すかを記述します。
class RendererModule extend AbstractModule
{
protected function configure()
{
$this->bind('RendererInterface')
->to('HalRenderer')
->in(Scope::SINGLETON);
}
3.インジェクターの作成
$injector = Inject::create([new RendererModule]);
$user = $injector->getInstance('UserInterface');
モジュールを使って作成したインジェクターは、どの依存が求められれば何を渡せばいいかを知っています。そのインジェクターを使ってUserInterfaceクラスを取得するとインジェクターは必要とされる依存をモジュールで決めたルールで渡し、**依存解決(dependency resolution)**が行われます。
以上のコードを見て分かるようにインスタンスが固定化されていないため、テストをする際も容易に変更を加えておこなうことができます。
AOP(アスペクト指向プログラミング)
AOPとは一言で言うと、「横断的な関心はコアな関心事から分けよう」というものです。
ここでいうコアな関心はビジネスロジックのことであり、横断的な関心がアスペクトになります。
まず、以下のコードを見て下さい。
class Post extends AppModel {
public function newest(){
$result = Cache::read('newest_posts', 'longterm');
if(!$result){
$result = $this->find('all');
Cache::write('newest_posts', $result, 'longterm');
}
return $result;
}
/**
* @Cache
*/
public function onGet($id)
{
// ...
$this->body = $stmt->fetchAll(PDO::FETCH_ASSOC);
return $this;
}
}
上記のコードを見て分かるようにキャッシュ管理とonGetというアクションが混在しています。これは、ビジネスロジックとアプリケーションロジックが混在していることを意味しています。
関心を上手く分けられておらず、テストがしにくい、読みにくい、リファクタリングがしづらいという問題が出てきます。
では、上記のようなコードを良くするにはどうすればよいでしょうか。
良くするとは、テストしやすく、読みやすく、リファクタリングしやすくするということです。
それを実現するのがAOPです。
横断的な関心(キャッシュ管理)をコアな関心から分けたものが、以下のコードになります。
class CacheInterceptor implements MethodInterceptor
{
public function invoke(MethodInvocation $invocation)
{
// 横断的な関心
$obj = $invocation->getThis();
$args = $invocation->getArguments();
$id = get_class($obj) . serialize($args);
$saved = $this->cache->fetch($id);
if($saved){
return $saved;
}
$result = $invocation->proceed(); //コアな関心
$this->cache->save($id, $result);
return $result;
}
}
以上のようなインターセプタを作成し、コアな関心にバインドすることにより、キャッシュ管理を経由し、コアな関心の処理を実行できるようになります。
RESTful Resource(HyperMedia)
オブジェクトをWebサービスのように扱う考えを実装したものが、BEAR.Resourceになります。
以下が、リソースオブジェクトです。URIがクラスにマップされます。
class Author extende ResourceObject
{
public $code = 200;
public $headers = [];
public $body = [];
/**
* @Link(rel="blog", href="app://self/blog/post?author_id={id}")
*/
public function onGet($id)
{
//...
return $this;
}
public onPost($name)
{
$this->code = 201;
return $this;
}
}
注目していただきたいのは、アノテーションの@Linkの部分です。
これは、URIテンプレートを使用し、リソース同士をリンクさせているのです。
このように、REST、API開発を中心に開発をおこなえるようになることで、webが成功した所以に則ることができるのです。
結び
今まで見てきことを整理して、結局BEAR.Sundayとは何であるのかというと以下のようになります。
Connecting frameworks
- DI - object as dependency
- AOP - domain logic to application logic
- Hypermedia - resource to resource
BEAR.Sundayは接続フレームワークであるということです。
DIはオブジェクト同士を依存性により接続し、AOPは本質的関心と横断的関心を繫ぎ、REST(Hypermedia)はリソース同士を繫ぎます。
Abstraction framework
- DSL
- Annotation
- URI
- Interface
- Aspects
- Hypermedia
BEAR.Sundayは実装を直接表すのではなく、抽象化された意図(intention)を表すフレームワークです。
Zen framework
BEAR.Sundayは、洗練された禅の庭のようなフレームワークです。シンプルではありますが、完璧な調和の取れたフレームワークです。
記事を書くにあたって参考にしたもの
- 郡山さんの講演
- BEAR.Sunday(https://bearsunday.github.io/)
- BEAR Blog(http://koriym.github.io/)
チュートリアルも充実しています。
日本語 -> (https://bearsunday.github.io/manuals/1.0/ja/tutorial.html)
English -> (https://bearsunday.github.io/manuals/1.0/en/tutorial.html)
初回投稿時からの修正
-
Bear.Sundayではなく、BEAR.Sundayでした。 - コンストラクタにアノテーションは要らない