免责声明:答案很长,但我认为值得一读
它的所有参考资料。恕我直言,它导致了一个确凿的答案。
过去几天我一直在努力解决这个问题,如果我没看错的话,答案是:
事件驱动!== 请求驱动
"[...] 我发现这是事件中最有趣的区别
协作,用 Jon Udell 的话说:请求驱动的软件会说话
说话时,事件驱动的软件会在有话要说时说话。
这样做的结果是管理状态的责任
转移。在请求合作中,您努力确保每一件
的数据有一个家,如果需要,您可以从该家查找
它。这个家负责数据的结构,多长
存储,如何访问它。在事件协作场景中
欢迎新数据的来源 忘记数据
传递给它的消息端点。”
马丁·福勒 - Event Collaboration (Queries section)
基于该断言 IIRC,现代 PHP 框架实现了观察者模式 + 拦截过滤器 + 信号和插槽,以便在请求周期内触发一些事件。
但是,尽管它采用了一些事件驱动架构的思想,但似乎并不支持整个框架是事件驱动的(即 Symfony2 是一个事件驱动的框架)。
我们习惯于将程序分成多个组件
一起合作。 (我在这里使用了模糊的“组件”一词
故意,因为在这种情况下,我的意思很多:包括
一个程序内的对象和多个跨进程通信的进程
网络。)使他们协作的最常见方式是
请求/响应样式。如果客户对象想要从
salesman 对象,它调用 salesman 对象上的一个方法来询问它
用于该数据。
另一种协作方式是事件协作。在这种风格
您永远不会有一个组件要求另一个组件做任何事情,而是
当任何事情发生变化时,每个组件都会发出一个事件信号。其他
组件侦听该事件并根据他们的意愿做出反应。这
著名的观察者模式就是事件协作的一个例子。
马丁·福勒 - Focus on events (section: Using events to
collaborate)
我认为 PHP 应用程序更接近于事件驱动而不是请求驱动只有在关注事件时。如果这些应用程序/框架仅将事件用于横切关注点 (AOP),那么它就不是事件驱动的。同样,您不会仅仅因为您有一些域对象和单元测试而将其称为测试驱动或域驱动。
现实世界的例子
我选择了一些示例来说明为什么这些框架不是完全由事件驱动的。尽管有 AOP 事件,但一切都是请求驱动的:
注意:虽然,它可以调整为事件驱动
Zend 框架 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);
}
那是事件驱动的:你不知道将使用哪个路由器、调度程序和视图渲染器的代码,你只知道这些事件将被触发。您可以挂钩几乎任何兼容的组件来侦听和处理事件。组件之间没有直接的通信。
但是,需要注意一件重要的事情:这是表示层(控制器 + 视图)。领域层确实可以是事件驱动的,但并非您看到的几乎所有应用程序都是如此。 **事件驱动和请求驱动之间存在混合:
// 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);
普拉多
我没有足够的时间研究源代码,但起初它似乎不是以SOLID 的方式构建的。无论哪种方式,什么是类似 MVC 的框架上的控制器,Prado 将其称为 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/decoupling_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
...等等