Decoupling your (event) system

Posted by Unknown on Tuesday, August 26, 2014

About interface segregation, dependency inversion and package stability


You are creating a nice reusable package. Inside the package you want to use events to allow others to hook into your own code. You look at several event managers that are available. Since you are somewhat familiar with the Symfony EventDispatcher component already, you decide to add it to your package's composer.json:



{
"name": "my/package"
"require": {
"symfony/event-dispatcher": "~2.5"
}
}


Your dependency graph now looks like this:


Dependency graph


Introducing this dependency is not without any problem: everybody who uses my/package in their project will also pull in the symfony/event-dispatcher package, meaning they will now have yet another event dispatcher available in their project (a Laravel one, a Doctrine one, a Symfony one, etc.). This doesn't make sense, especially because event dispatchers all do (or can do) more or less the same thing.


Though this may be a minor inconvenience to most developers, having just this one extra dependency may cause some more serious problems when Composer tries to resolve version constraints. Maybe a project or one of its dependencies already has a dependency on symfony/event-dispatcher, but with version constraint >=2.3,<2.5...


Some drawbacks of the Symfony EventDispatcher


On top of these usability issues, when it comes to design principles, depending on a concrete library like the Symfony EventDispatcher is not such a good choice either. The EventDispatcherInterface which you will likely use in your code is quite bloated:



namespace Symfony\Component\EventDispatcher;

interface EventDispatcherInterface
{
public function dispatch($eventName, Event $event = null);

public function addListener($eventName, $listener, $priority = 0);
public function addSubscriber(EventSubscriberInterface $subscriber);
public function removeListener($eventName, $listener);
public function removeSubscriber(EventSubscriberInterface $subscriber);
public function getListeners($eventName = null);
public function hasListeners($eventName = null);
}


The interface basically violates the Interface Segregation Principle , which means that it serves too many different types of clients: most clients will only use the dispatch() method to actually dispatch an event, and other clients will only use addListener() and addSubscriber(). The remaining methods are probably not even used by any of the clients, or only by clients that help you to debug your application (a quick search in the Symfony code-base confirms this suspicion).


Personally, I think it's not really nice that events need to be objects extending the Event class. I understand why Symfony does it (it's basically because that class has the stopPropagation() and isPropagationStopped() methods which enables event listeners to stop the dispatcher from notifying the remaining listeners. I've never liked that, nor wanted it to happen in my code. Just like the original Observer pattern prescribes, all listeners (observers) should be able to respond to the current situation.


I also don't like the fact that the same event class can be used for different events (differentiated by just their name, which is the first argument of the dispatch() method). I prefer each type of event to have its own class. Therefore, to me it would make sense to pass just an event object to the dispatch() method, allowing it to return its own name, which will always be the same anyway. The name returned by the event itself can then be used to determine which listeners need to be notified.


According to these objections to the design of the Symfony EventDispatcher classes, we'd be better off with the following clean interfaces:



namespace My\Package;

interface Event
{
public function getName();
}

interface EventDispatcher
{
public function dispatch(Event $event);
}


It would be really great to be able to just use these two interface in my/package!


Symfony EventDispatcher: the good parts


Still, we also want to use the Symfony E


Truncated by Planet PHP, read more at the original (another 6031 bytes)




more

{ 0 comments... » Decoupling your (event) system read them below or add one }

Post a Comment

Popular Posts