免責事項:それは長い答えですが、すべての参照とともに読む価値があると思います。そして私見それは決定的な答えにつながります。
私はこの数日間、このテーマに苦労してきました。すべてを正しく読んだ場合、答えは次のとおりです。
イベント駆動型!==リクエスト駆動型
「[...]これはイベントコラボレーションの最も興味深い違いだと思います。言い換えると、Jon Udell:要求駆動型ソフトウェアは話しかけられたときに話し、イベント駆動型ソフトウェアは何か言いたいことがあるときに話します。
この結果、州の管理責任が変化します。リクエストコラボレーションでは、すべてのデータに1つのホームがあることを確認し、必要に応じてそのホームから検索します。この家は、データの構造、データの保存期間、データへのアクセス方法を担当します。イベントコラボレーションのシナリオでは、新しいデータのソースは、メッセージエンドポイントに渡された2番目のデータを忘れることができます。」
マーティンファウラー-イベントコラボレーション(クエリセクション)
そのアサーション、IIRCに基づいて、最新のPHPフレームワークは、リクエストサイクル中にいくつかのイベントをトリガーするために、オブザーバーパターン+インターセプトフィルター+SingalおよびSlotsを実装します。
しかし、イベント駆動型アーキテクチャのいくつかのアイデアを採用しているという事実にもかかわらず、フレームワーク全体がイベント駆動型であることをサポートしていないようです(つまり、Symfony2はイベント駆動型フレームワークです)。
私たちは、プログラムを複数のコンポーネントに分割し、それらを連携させることに慣れています。(ここでは、あいまいな「コンポーネント」という言葉を意図的に使用しています。これは、プログラム内のオブジェクトやネットワークを介して通信する複数のプロセスなど、多くのことを意味するためです。)それらを連携させる最も一般的な方法は、要求/応答スタイルです。 。顧客オブジェクトがセールスマンオブジェクトからのデータを必要とする場合、セールスマンオブジェクトのメソッドを呼び出して、そのデータを要求します。
コラボレーションのもう1つのスタイルは、イベントコラボレーションです。このスタイル
では、あるコンポーネントが別のコンポーネントに何かを要求することはありません。代わりに、各コンポーネントは、何かが変更されたときにイベントを通知します。他のコンポーネントはそのイベントをリッスンし、希望どおりに反応します。よく知られているオブザーバーパターンは、イベントコラボレーションの例です。
マーティンファウラー-イベントに焦点を当てる(セクション:イベントを使用したコラボレーション)
PHPアプリケーションは、イベントに焦点が当てられている場合にのみ、要求駆動型よりもイベント駆動型に近いと思います。それらのアプリケーション/フレームワークが横断的関心事(AOP)のイベントのみを使用している場合、それはイベント駆動型ではありません。同様に、いくつかのドメインオブジェクトと単体テストがあるという理由だけで、テスト駆動またはドメイン駆動とは呼ばないでしょう。
実例
これらのフレームワークが完全にイベント駆動型ではない理由を示すために、いくつかの例を選びました。AOPイベントにもかかわらず、すべてがリクエスト駆動型です:
注:ただし、イベント駆動型に適合させることができます
Zend Framework 2
\ Zend \ Mvc\Applicationコンポーネントを調べてみましょう。
\ Zend \ EventManager \ EventManagerAwareInterfaceを実装し、可能なイベントを説明する\ Zend \ Mvc\MvcEventに依存します。
class MvcEvent extends Event
{
/**#@+
* Mvc events triggered by eventmanager
*/
const EVENT_BOOTSTRAP = 'bootstrap';
const EVENT_DISPATCH = 'dispatch';
const EVENT_DISPATCH_ERROR = 'dispatch.error';
const EVENT_FINISH = 'finish';
const EVENT_RENDER = 'render';
const EVENT_ROUTE = 'route';
// [...]
}
\ Zend \ Mvc \ Applicationコンポーネント自体は、他のコンポーネントと直接通信しないため、イベント駆動型ですが、代わりに、イベントをトリガーするだけです。
/**
* Run the application
*
* @triggers route(MvcEvent)
* Routes the request, and sets the RouteMatch object in the event.
* @triggers dispatch(MvcEvent)
* Dispatches a request, using the discovered RouteMatch and
* provided request.
* @triggers dispatch.error(MvcEvent)
* On errors (controller not found, action not supported, etc.),
* populates the event with information about the error type,
* discovered controller, and controller class (if known).
* Typically, a handler should return a populated Response object
* that can be returned immediately.
* @return ResponseInterface
*/
public function run()
{
$events = $this->getEventManager();
$event = $this->getMvcEvent();
// Define callback used to determine whether or not to short-circuit
$shortCircuit = function ($r) use ($event) {
if ($r instanceof ResponseInterface) {
return true;
}
if ($event->getError()) {
return true;
}
return false;
};
// Trigger route event
$result = $events->trigger(MvcEvent::EVENT_ROUTE, $event, $shortCircuit);
if ($result->stopped()) {
$response = $result->last();
if ($response instanceof ResponseInterface) {
$event->setTarget($this);
$events->trigger(MvcEvent::EVENT_FINISH, $event);
return $response;
}
if ($event->getError()) {
return $this->completeRequest($event);
}
return $event->getResponse();
}
if ($event->getError()) {
return $this->completeRequest($event);
}
// Trigger dispatch event
$result = $events->trigger(MvcEvent::EVENT_DISPATCH, $event, $shortCircuit);
// Complete response
$response = $result->last();
if ($response instanceof ResponseInterface) {
$event->setTarget($this);
$events->trigger(MvcEvent::EVENT_FINISH, $event);
return $response;
}
$response = $this->getResponse();
$event->setResponse($response);
return $this->completeRequest($event);
}
これはイベント駆動型です。どのルーター、ディスパッチャー、ビューレンダラーが使用されるかを確認する手がかりはなく、これらのイベントがトリガーされることだけがわかります。互換性のあるほとんどすべてのコンポーネントをフックして、イベントをリッスンして処理できます。コンポーネント間の直接通信はありません。
ただし、注意すべき重要な点が1つあります。これは、プレゼンテーション層(Controller + View)です。ドメイン層は確かにイベント駆動型にすることができますが、そこにあるほとんどすべてのアプリケーションには当てはまりません。**イベント駆動型とリクエスト駆動型の間には混合があります:
// albums controller
public function indexAction()
{
return new ViewModel(array(
'albums' => $this->albumsService->getAlbumsFromArtist('Joy Division'),
));
}
コントローラコンポーネントはイベント駆動型ではありません。サービスコンポーネントと直接通信します。代わりに、サービスは、プレゼンテーション層の一部であるコントローラーによって発生したイベントをサブスクライブする必要があります。(この回答の最後に、イベント駆動型ドメインモデルとなるものについての参照を指摘します)。
Symfony 2
それでは、Symfony2アプリケーション/フロントコントローラーで同じことを調べてみましょう:\ Symfony \ Component \ HttpKernel \ HttpKernel
それは確かにリクエスト中にメインイベントを持っています:Symfony \ Component \ HttpKernel \ KernelEvents
/**
* Handles a request to convert it to a response.
*
* Exceptions are not caught.
*
* @param Request $request A Request instance
* @param integer $type The type of the request (one of HttpKernelInterface::MASTER_REQUEST or HttpKernelInterface::SUB_REQUEST)
*
* @return Response A Response instance
*
* @throws \LogicException If one of the listener does not behave as expected
* @throws NotFoundHttpException When controller cannot be found
*/
private function handleRaw(Request $request, $type = self::MASTER_REQUEST)
{
// request
$event = new GetResponseEvent($this, $request, $type);
$this->dispatcher->dispatch(KernelEvents::REQUEST, $event);
if ($event->hasResponse()) {
return $this->filterResponse($event->getResponse(), $request, $type);
}
// load controller
if (false === $controller = $this->resolver->getController($request)) {
throw new NotFoundHttpException(sprintf('Unable to find the controller for path "%s". Maybe you forgot to add the matching route in your routing configuration?', $request->getPathInfo()));
}
$event = new FilterControllerEvent($this, $controller, $request, $type);
$this->dispatcher->dispatch(KernelEvents::CONTROLLER, $event);
$controller = $event->getController();
// controller arguments
$arguments = $this->resolver->getArguments($request, $controller);
// call controller
$response = call_user_func_array($controller, $arguments);
// view
if (!$response instanceof Response) {
$event = new GetResponseForControllerResultEvent($this, $request, $type, $response);
$this->dispatcher->dispatch(KernelEvents::VIEW, $event);
if ($event->hasResponse()) {
$response = $event->getResponse();
}
if (!$response instanceof Response) {
$msg = sprintf('The controller must return a response (%s given).', $this->varToString($response));
// the user may have forgotten to return something
if (null === $response) {
$msg .= ' Did you forget to add a return statement somewhere in your controller?';
}
throw new \LogicException($msg);
}
}
return $this->filterResponse($response, $request, $type);
}
ただし、「イベント対応」であることに加えて、ControllerResolverコンポーネントと直接通信するため、一部のイベントをトリガーし、一部のコンポーネントをプラグ可能にすることはできますが、リクエストプロセスの開始以降、完全にイベント駆動型ではありません(そうではありません)。コンストラクターパラメーターとして挿入されたControllerResolverの)。
代わりに、完全にイベント駆動型のコンポーネントであるためには、ZF2アプリケーションコンポーネントのようにする必要があります。
// Trigger dispatch event
$result = $events->trigger(MvcEvent::EVENT_DISPATCH, $event, $shortCircuit);
プラド
ソースコードを調べる時間がありませんが、最初はしっかりした方法で構築されていないようです。いずれにせよ、MVCに似たフレームワークのコントローラーとは、プラドはそれをTPageと呼んでいます(まだわかりません)。
http://www.pradosoft.com/demos/blog-tutorial/?page=Day3.CreateNewUser
そしてそれは確かにコンポーネントと直接通信します:
class NewUser extends TPage
{
/**
* Checks whether the username exists in the database.
* This method responds to the OnServerValidate event of username's custom validator.
* @param mixed event sender
* @param mixed event parameter
*/
public function checkUsername($sender,$param)
{
// valid if the username is not found in the database
$param->IsValid=UserRecord::finder()->findByPk($this->Username->Text)===null;
}
[...]
}
TPage
はイベントリスナーであり、プラグイン可能であることを理解しています。ただし、ドメインモデルをイベント駆動型にするわけではありません。ですから、ある程度、ZF2の提案に近いと思います。
イベント駆動型の例
この長い答えを完成させるために、本格的なイベント駆動型アプリケーションは次のようになります。
ドメインイベントを使用したアプリケーションのデカップリング
http://www.whitewashing.de/2012/08/25/decouple_applications_with_domain_events.html
イベントソーシング
http://martinfowler.com/eaaDev/EventSourcing.html
ドメインイベントパターン
http://martinfowler.com/eaaDev/DomainEvent.html
イベントコラボレーション
http://martinfowler.com/eaaDev/EventCollaboration.html
イベントインターセプト
http://martinfowler.com/bliki/EventInterception.html
メッセージエンドポイント
http://www.enterpriseintegrationpatterns.com/MessageEndpoint.html
... 等々