免责声明:这是一个很长的答案,但我认为它的所有参考资料都值得一读。恕我直言,它导致了一个确凿的答案。
过去几天我一直在努力解决这个问题,如果我没看错的话,答案是:
事件驱动!== 请求驱动
“[...] 我发现这是事件协作中最有趣的区别,套用 Jon Udell 的话说:请求驱动的软件在说话时说话,事件驱动的软件在有话要说时说话。
这样做的结果是管理状态的责任发生了变化。在请求协作中,您努力确保每条数据都有一个归宿,如果需要,您可以从该归宿进行查找。这个家负责数据的结构,存储多长时间,如何访问它。在事件协作场景中,欢迎新数据源在传递到其消息端点的第二秒忘记数据。”
Martin Fowler -活动协作(查询部分)
基于该断言,IIRC,现代 PHP 框架实现了观察者模式 + 拦截过滤器 + 信号和插槽,以便在请求周期中触发一些事件。
但是,尽管它采用了一些事件驱动架构的思想,但似乎并不支持整个框架是事件驱动的(即 Symfony2 是一个事件驱动的框架)。
我们习惯于将程序分成多个协作的组件。(我在这里故意使用模糊的“组件”一词,因为在这种情况下,我的意思很多:包括程序中的对象和通过网络进行通信的多个进程。)使它们协作的最常见方式是请求/响应样式. 如果客户对象想要来自销售员对象的一些数据,它会调用销售员对象上的一个方法来向它询问该数据。
另一种协作方式是事件协作。在这种风格
中,您永远不会有一个组件要求另一个组件做任何事情,而是当任何事情发生变化时,每个组件都会发出一个事件。其他组件侦听该事件并根据他们的意愿做出反应。著名的观察者模式是事件协作的一个例子。
Martin Fowler -关注事件(部分:使用事件进行协作)
我认为 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 Application/FrontController 上检查相同的内容:\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
... 等等