diff --git a/src/Events/AbstractEvent.php b/src/Events/AbstractEvent.php
new file mode 100644
index 0000000..016558f
--- /dev/null
+++ b/src/Events/AbstractEvent.php
@@ -0,0 +1,70 @@
+<?php
+/**
+ * Definition of AbstractEvent
+ *
+ * @author Marco Stoll <marco@fast-forward-encoding.de>
+ * @copyright 2019-forever Marco Stoll
+ * @filesource
+ */
+declare(strict_types=1);
+
+namespace FF\Events;
+
+use FF\Factories\ClassLocators\ClassIdentifierAwareInterface;
+
+/**
+ * Class AbstractEvent
+ *
+ * @package FF\Events
+ */
+abstract class AbstractEvent implements ClassIdentifierAwareInterface
+{
+    /**
+     * For use with the BaseNamespaceClassLocator of the EventsFactory
+     */
+    const COMMON_NS_SUFFIX = 'Events';
+
+    /**
+     * @var bool
+     */
+    protected $isCanceled = false;
+
+    /**
+     * @return bool
+     */
+    public function isCanceled(): bool
+    {
+        return $this->isCanceled;
+    }
+
+    /**
+     * @param bool $isCanceled
+     * @return $this
+     */
+    public function setIsCanceled(bool $isCanceled)
+    {
+        $this->isCanceled = $isCanceled;
+        return $this;
+    }
+
+    /**
+     * @return $this
+     */
+    public function cancel()
+    {
+        return $this->setIsCanceled(true);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public static function getClassIdentifier(): string
+    {
+        $className = get_called_class();
+        $needle = '\\' . self::COMMON_NS_SUFFIX . '\\';
+        $pos = strpos($className, $needle);
+        if ($pos === false) return $className;
+
+        return substr($className, $pos + strlen($needle));
+    }
+}
\ No newline at end of file
diff --git a/src/Services/EventBroker.php b/src/Services/EventBroker.php
new file mode 100644
index 0000000..aa9232a
--- /dev/null
+++ b/src/Services/EventBroker.php
@@ -0,0 +1,244 @@
+<?php
+/**
+ * Definition of EventBroker
+ *
+ * @author Marco Stoll <marco@fast-forward-encoding.de>
+ * @copyright 2019-forever Marco Stoll
+ * @filesource
+ */
+declare(strict_types=1);
+
+namespace FF\Services;
+
+use FF\DataStructures\IndexedCollection;
+use FF\DataStructures\OrderedCollection;
+use FF\Events\AbstractEvent;
+use FF\Factories\Exceptions\ClassNotFoundException;
+use FF\Services\Factories\EventsFactory;
+
+/**
+ * Class EventBroker
+ *
+ * @package FF\Services
+ */
+class EventBroker extends AbstractService
+{
+    /**
+     * @var IndexedCollection
+     */
+    protected $subscriptions;
+
+    /**
+     * @var EventsFactory
+     */
+    protected $eventsFactory;
+
+    /**
+     * {@inheritDoc}
+     */
+    protected function initialize(array $options)
+    {
+        parent::initialize($options);
+
+        $this->subscriptions = new IndexedCollection();
+        $this->eventsFactory = EventsFactory::getInstance();
+    }
+
+    /**
+     * @return EventsFactory
+     */
+    public function getEventsFactory(): EventsFactory
+    {
+        return $this->eventsFactory;
+    }
+
+    /**
+     * @return IndexedCollection
+     */
+    public function getSubscriptions(): IndexedCollection
+    {
+        return $this->subscriptions;
+    }
+
+    /**
+     * @param string $classIdentifier
+     * @return OrderedCollection
+     */
+    public function getSubscribers(string $classIdentifier): OrderedCollection
+    {
+        $this->initializeSubscribersCollection($classIdentifier);
+        return $this->subscriptions->get($classIdentifier);
+    }
+
+    /**
+     * Appends a listener to the subscribers list of an event
+     *
+     * Removes any previous subscriptions of the listener first to the named event.
+     *
+     * @param callable $listener
+     * @param string $classIdentifier
+     * @return $this
+     */
+    public function subscribe(callable $listener, string $classIdentifier)
+    {
+        $this->unsubscribe($listener, $classIdentifier);
+        $this->initializeSubscribersCollection($classIdentifier);
+
+        $this->subscriptions->get($classIdentifier)->push($listener);
+        return $this;
+    }
+
+    /**
+     * Prepends a listener to the subscriber list of an event
+     *
+     * Removes any previous subscriptions of the listener first to the named event.
+     *
+     * @param callable $listener
+     * @param string $classIdentifier
+     * @return $this
+     */
+    public function subscribeFirst(callable $listener, string $classIdentifier)
+    {
+        $this->unsubscribe($listener, $classIdentifier);
+        $this->initializeSubscribersCollection($classIdentifier);
+
+        $this->subscriptions->get($classIdentifier)->unshift($listener);
+        return $this;
+    }
+
+    /**
+     * Unsubscribes a listener
+     *
+     * If $name is omitted, the listener will be unsubscribed from each event it was subscribed to.
+     *
+     * @param callable $listener
+     * @param string $classIdentifier
+     * @return $this
+     */
+    public function unsubscribe(callable $listener, string $classIdentifier = null)
+    {
+        /** @var OrderedCollection $listenerCollection */
+        foreach ($this->subscriptions as $name => $listenerCollection) {
+            if (!is_null($classIdentifier) && $classIdentifier != $name) continue;
+
+            $index = $listenerCollection->search($listener, true);
+            if (is_null($index)) continue;
+
+            // remove listener from event
+            unset($listenerCollection[$index]);
+        }
+
+        return $this;
+    }
+
+    /**
+     * Removes all subscriptions for this event
+     *
+     * @param string $classIdentifier
+     * @return $this
+     */
+    public function unsubscribeAll(string $classIdentifier)
+    {
+        unset($this->subscriptions[$classIdentifier]);
+        return $this;
+    }
+
+    /**
+     * Checks whether any listeners where subscribed to the named event
+     *
+     * @param string $classIdentifier
+     * @return bool
+     */
+    public function hasSubscribers(string $classIdentifier): bool
+    {
+        return $this->subscriptions->has($classIdentifier) && !$this->subscriptions->get($classIdentifier)->isEmpty();
+    }
+
+    /**
+     * Checks of the listener has been subscribed to the given event
+     *
+     * @param callable $listener
+     * @param string $classIdentifier
+     * @return bool
+     */
+    public function isSubscribed(callable $listener, string $classIdentifier): bool
+    {
+        if (!$this->hasSubscribers($classIdentifier)) return false;
+
+        return !is_null($this->subscriptions->get($classIdentifier)->search($listener));
+    }
+
+    /**Notifies all listeners of events of the given type
+     *
+     * Listeners will be notified in the order of their subscriptions.
+     * Does nothing if no listeners subscribed to the type of the event.
+     *
+     * Creates an event instance and fires it.
+     * Does nothing if no suitable event model could be created.
+     *
+     * Any given $args will be passed to the constructor of the suitable event
+     * model class in the given order.
+     *
+     * @param string $classIdentifier
+     * @param mixed ...$args
+     * @return $this
+     */
+    public function fire(string $classIdentifier, ...$args)
+    {
+        $event = $this->createEvent($classIdentifier, ...$args);
+        if (is_null($event)) return $this;
+
+        foreach ($this->getSubscribers($classIdentifier) as $listener) {
+            $this->notify($listener, $event);
+
+            if ($event->isCanceled()) {
+                // stop notifying further listeners if event has been canceled
+                break;
+            }
+        }
+
+        return $this;
+    }
+
+    /**
+     * Initialize listener collection if necessary
+     *
+     * @param string $classIdentifier
+     */
+    protected function initializeSubscribersCollection(string $classIdentifier)
+    {
+        if ($this->subscriptions->has($classIdentifier)) return;
+
+        $this->subscriptions->set($classIdentifier, new OrderedCollection());
+    }
+
+    /**
+     * Create a fresh event instance
+     *
+     * @param string $classIdentifier
+     * @param mixed ...$args
+     * @return AbstractEvent|null
+     */
+    protected function createEvent(string $classIdentifier, ...$args): ?AbstractEvent
+    {
+        try {
+            return $this->eventsFactory->create($classIdentifier, ...$args);
+        } catch (ClassNotFoundException $e) {
+            return null;
+        }
+    }
+
+    /**
+     * Passes the event to the listener
+     *
+     * The listener will be invoked with the event as the first and only argument.
+     * Any return values of the listener will be discarded.
+     *
+     * @param callable $listener
+     * @param AbstractEvent $event
+     */
+    protected function notify(callable $listener, AbstractEvent $event)
+    {
+        call_user_func($listener, $event);
+    }
+}
\ No newline at end of file
diff --git a/src/Services/Factories/EventsFactory.php b/src/Services/Factories/EventsFactory.php
new file mode 100644
index 0000000..6f76a06
--- /dev/null
+++ b/src/Services/Factories/EventsFactory.php
@@ -0,0 +1,82 @@
+<?php
+/**
+ * Definition of EventsFactory
+ *
+ * @author Marco Stoll <marco@fast-forward-encoding.de>
+ * @copyright 2019-forever Marco Stoll
+ * @filesource
+ */
+declare(strict_types=1);
+
+namespace FF\Services\Factories;
+
+use FF\Events\AbstractEvent;
+use FF\Factories\AbstractFactory;
+use FF\Factories\ClassLocators\BaseNamespaceClassLocator;
+use FF\Factories\ClassLocators\ClassLocatorInterface;
+
+/**
+ * Class EventsFactory
+ *
+ * @package FF\Services\Factories
+ */
+class EventsFactory extends AbstractFactory
+{
+    /**
+     * @var EventsFactory
+     */
+    protected static $instance;
+
+    /**
+     * Declared protected to prevent external usage.
+     * Uses a BaseNamespaceClassLocator pre-configured with the 'Events' as common suffix and the FF namespace.
+     *
+     * @see \FF\Factories\ClassLocators\BaseNamespaceClassLocator
+     */
+    protected function __construct()
+    {
+        parent::__construct(new BaseNamespaceClassLocator(AbstractEvent::COMMON_NS_SUFFIX, 'FF'));
+    }
+
+    /**
+     * Declared protected to prevent external usage
+     */
+    protected function __clone()
+    {
+
+    }
+
+    /**
+     * {@inheritDoc}
+     * @return BaseNamespaceClassLocator
+     */
+    public function getClassLocator(): ClassLocatorInterface
+    {
+        return parent::getClassLocator();
+    }
+
+    /**
+     * Retrieves the singleton instance of this class
+     *
+     * @return EventsFactory
+     */
+    public static function getInstance(): EventsFactory
+    {
+        if (is_null(self::$instance)) {
+            self::$instance = new EventsFactory();
+        }
+
+        return self::$instance;
+    }
+
+    /**
+     * {@inheritdoc}
+     * @return AbstractEvent
+     */
+    public function create(string $classIdentifier, ...$args)
+    {
+        /** @var AbstractEvent $event */
+        $event = parent::create($classIdentifier, ...$args);
+        return $event;
+    }
+}
\ No newline at end of file
diff --git a/src/Services/Traits/EventEmitterTrait.php b/src/Services/Traits/EventEmitterTrait.php
new file mode 100644
index 0000000..d004f2b
--- /dev/null
+++ b/src/Services/Traits/EventEmitterTrait.php
@@ -0,0 +1,45 @@
+<?php
+/**
+ * Definition of EventEmitterTrait
+ *
+ * @author Marco Stoll <marco@fast-forward-encoding.de>
+ * @copyright 2019-forever Marco Stoll
+ * @filesource
+ */
+declare(strict_types=1);
+
+namespace FF\Services\Traits;
+
+use FF\Services\EventBroker;
+use FF\Services\Factories\SF;
+
+/**
+ * Trait EventEmitterTrait
+ *
+ * @package FF\Services\Traits
+ */
+trait EventEmitterTrait
+{
+    /**
+     * Creates an event instance and fires it
+     *
+     * Delegates the execution to the EventBroker provided by the ServiceFactory.
+     *
+     * @param string $classIdentifier
+     * @param mixed ...$args
+     * @return $this
+     */
+    protected function fire(string $classIdentifier, ...$args)
+    {
+        /** @var EventBroker $eventBroker */
+        static $eventBroker = null;
+
+        if (is_null($eventBroker)) {
+            $eventBroker = SF::i()->get('EventBroker');
+        }
+
+        $eventBroker->fire($classIdentifier, ...$args);
+
+        return $this;
+    }
+}
\ No newline at end of file
diff --git a/tests/Events/AbstractEventTest.php b/tests/Events/AbstractEventTest.php
new file mode 100644
index 0000000..075ff80
--- /dev/null
+++ b/tests/Events/AbstractEventTest.php
@@ -0,0 +1,68 @@
+<?php
+/**
+ * Definition of AbstractEventTest
+ *
+ * @author Marco Stoll <marco@fast-forward-encoding.de>
+ * @copyright 2019-forever Marco Stoll
+ * @filesource
+ */
+declare(strict_types=1);
+
+namespace FF\Tests\Events;
+
+use FF\Events\AbstractEvent;
+use PHPUnit\Framework\TestCase;
+
+/**
+ * Test AbstractEventTest
+ *
+ * @package FF\Tests
+ */
+class AbstractEventTest extends TestCase
+{
+    /**
+     * @var MyEvent
+     */
+    protected $uut;
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function setUp(): void
+    {
+        $this->uut = new MyEvent();
+    }
+
+    /**
+     * Tests the namesake method/feature
+     */
+    public function testSetGetIsCanceled()
+    {
+        $same = $this->uut->setIsCanceled(true);
+        $this->assertSame($this->uut, $same);
+        $this->assertTrue($this->uut->isCanceled());
+    }
+
+    /**
+     * Tests the namesake method/feature
+     */
+    public function testCancel()
+    {
+        $same = $this->uut->cancel();
+        $this->assertSame($this->uut, $same);
+        $this->assertTrue($this->uut->isCanceled());
+    }
+
+    /**
+     * Tests the namesake method/feature
+     */
+    public function testGetClassIdentifier()
+    {
+        $this->assertEquals('MyEvent', MyEvent::getClassIdentifier());
+    }
+}
+
+class MyEvent extends AbstractEvent
+{
+
+}
\ No newline at end of file
diff --git a/tests/Services/EventBrokerTest.php b/tests/Services/EventBrokerTest.php
new file mode 100644
index 0000000..f06f527
--- /dev/null
+++ b/tests/Services/EventBrokerTest.php
@@ -0,0 +1,287 @@
+<?php
+/**
+ * Definition of EventBrokerTest
+ *
+ * @author Marco Stoll <marco@fast-forward-encoding.de>
+ * @copyright 2019-forever Marco Stoll
+ * @filesource
+ */
+declare(strict_types=1);
+
+namespace FF\Tests\Services {
+
+    use FF\DataStructures\IndexedCollection;
+    use FF\DataStructures\OrderedCollection;
+    use FF\Services\EventBroker;
+    use FF\Services\Factories\EventsFactory;
+    use FF\Tests\Services\Events\EventA;
+    use FF\Tests\Services\Events\EventB;
+    use PHPUnit\Framework\TestCase;
+
+    /**
+     * Test EventBrokerTest
+     *
+     * @package FF\Tests
+     */
+    class EventBrokerTest extends TestCase
+    {
+        /**
+         * @var EventBroker
+         */
+        protected $uut;
+
+        /**
+         * {@inheritdoc}
+         */
+        protected function setUp(): void
+        {
+            $this->uut = new EventBroker();
+            $this->uut->getEventsFactory()
+                ->getClassLocator()
+                ->prependNamespaces(__NAMESPACE__);
+        }
+
+        /**
+         * {@inheritdoc}
+         */
+        protected function tearDown(): void
+        {
+            $this->uut->unsubscribeAll('EventA');
+        }
+
+        /**
+         * Tests the namesake method/feature
+         */
+        public function testGetEventsFactory()
+        {
+            $this->assertInstanceOf(EventsFactory::class, $this->uut->getEventsFactory());
+        }
+
+        /**
+         * Tests the namesake method/feature
+         */
+        public function testGetSubscriptions()
+        {
+            $this->assertInstanceOf(IndexedCollection::class, $this->uut->getSubscriptions());
+        }
+
+        /**
+         * Tests the namesake method/feature
+         */
+        public function testGetSubscribers()
+        {
+            $this->assertInstanceOf(OrderedCollection::class, $this->uut->getSubscribers('EventA'));
+        }
+
+        /**
+         * Tests the namesake method/feature
+         */
+        public function testSubscribe()
+        {
+            $callback = [new ListenerA(), 'shoutA'];
+
+            $same = $this->uut->subscribe($callback, 'EventA');
+            $this->assertSame($this->uut, $same);
+            $this->assertEquals(1, count($this->uut->getSubscribers('EventA')));
+            $this->assertSame($callback, $this->uut->getSubscribers('EventA')->getFirst());
+        }
+
+        /**
+         * Tests the namesake method/feature
+         */
+        public function testSubscribeRepeated()
+        {
+            $callback = [new ListenerA(), 'shoutA'];
+
+            $this->uut->subscribe($callback, 'EventA')->subscribe($callback, 'EventA');
+            $this->assertEquals(1, count($this->uut->getSubscribers('EventA')));
+        }
+
+        /**
+         * Tests the namesake method/feature
+         */
+        public function testSubscribeAppend()
+        {
+            $callback1 = [new ListenerA(), 'shoutA'];
+            $callback2 = [new ListenerA(), 'shoutA'];
+
+            $this->uut->subscribe($callback1, 'EventA')
+                ->subscribe($callback2, 'EventA');
+            $this->assertSame($callback1, $this->uut->getSubscribers('EventA')->get(0));
+            $this->assertSame($callback2, $this->uut->getSubscribers('EventA')->get(1));
+        }
+
+        /**
+         * Tests the namesake method/feature
+         */
+        public function testSubscribeFirst()
+        {
+            $callback1 = [new ListenerA(), 'shoutA'];
+            $callback2 = [new ListenerA(), 'shoutA'];
+
+            $same = $this->uut->subscribe($callback1, 'EventA')
+                ->subscribeFirst($callback2, 'EventA');
+            $this->assertSame($this->uut, $same);
+            $this->assertSame($callback1, $this->uut->getSubscribers('EventA')->get(1));
+            $this->assertSame($callback2, $this->uut->getSubscribers('EventA')->get(0));
+        }
+
+        /**
+         * Tests the namesake method/feature
+         */
+        public function testUnsubscribe()
+        {
+            $callback1 = [new ListenerA(), 'shoutA'];
+
+            $this->uut->subscribe($callback1, 'EventA')
+                ->subscribe($callback1, 'EventB')
+                ->unsubscribe($callback1);
+            $this->assertTrue($this->uut->getSubscribers('EventA')->isEmpty());
+            $this->assertTrue($this->uut->getSubscribers('EventB')->isEmpty());
+        }
+
+        /**
+         * Tests the namesake method/feature
+         */
+        public function testUnsubscribeNamed()
+        {
+            $callback1 = [new ListenerA(), 'shoutA'];
+            $callback2 = [new ListenerA(), 'shoutA'];
+
+            $same = $this->uut->subscribe($callback1, 'EventA')
+                ->subscribe($callback2, 'EventA')
+                ->unsubscribe($callback1, 'EventA');
+            $this->assertSame($this->uut, $same);
+            $this->assertEquals(1, count($this->uut->getSubscribers('EventA')));
+            $this->assertSame($callback2, $this->uut->getSubscribers('EventA')->get(0));
+        }
+
+        /**
+         * Tests the namesake method/feature
+         */
+        public function testUnsubscribeAll()
+        {
+            $callback1 = [new ListenerA(), 'shoutA'];
+            $callback2 = [new ListenerA(), 'shoutA'];
+
+            $same = $this->uut->subscribe($callback1, 'EventA')
+                ->subscribe($callback2, 'EventA')
+                ->unsubscribeAll('EventA');
+            $this->assertSame($this->uut, $same);
+            $this->assertTrue($this->uut->getSubscribers('EventA')->isEmpty());
+        }
+
+        /**
+         * Tests the namesake method/feature
+         */
+        public function testHasSubscribers()
+        {
+            $callback1 = [new ListenerA(), 'shoutA'];
+
+            $this->uut->subscribe($callback1, 'EventA');
+
+            $this->assertTrue($this->uut->hasSubscribers('EventA'));
+            $this->assertFalse($this->uut->hasSubscribers('EventB'));
+        }
+
+        /**
+         * Tests the namesake method/feature
+         */
+        public function testIsSubscribed()
+        {
+            $callback1 = [new ListenerA(), 'shoutA'];
+            $callback2 = [new ListenerA(), 'shoutB'];
+
+            $this->uut->subscribe($callback1, 'EventA');
+
+            $this->assertTrue($this->uut->isSubscribed($callback1, 'EventA'));
+            $this->assertFalse($this->uut->isSubscribed($callback1, 'EventB'));
+            $this->assertFalse($this->uut->isSubscribed($callback2, 'EventA'));
+            $this->assertFalse($this->uut->isSubscribed($callback2, 'EventB'));
+        }
+
+        /**
+         * Tests the namesake method/feature
+         */
+        public function testFire()
+        {
+            $same = $this->uut->fire('EventA', 'foo');
+            $this->assertSame($this->uut, $same);
+        }
+
+        /**
+         * Tests the namesake method/feature
+         */
+        public function testFireWithListener()
+        {
+            $this->expectOutputString('foo');
+
+            $this->uut->subscribe([new ListenerA(), 'shoutA'], 'EventA')
+                ->fire('EventA', 'foo');
+        }
+
+        /**
+         * Tests the namesake method/feature
+         */
+        public function testFireCancel()
+        {
+            $this->expectOutputString('foo'); // output would be 'foofoo' if event canceling does not work
+
+            $this->uut->subscribe([new ListenerA(), 'cancelA'], 'EventA')
+                ->subscribe([new ListenerB(), 'neverToReach'], 'EventA')
+                ->fire('EventA', 'foo');
+        }
+    }
+
+    class ListenerA
+    {
+        public function shoutA(EventA $event)
+        {
+            print $event->content;
+        }
+
+        public function shoutB(EventB $event)
+        {
+            print $event->content;
+        }
+
+        public function cancelA(EventA $event)
+        {
+            print $event->content;
+            $event->cancel();
+        }
+    }
+
+    class ListenerB extends ListenerA
+    {
+        public function shoutB(EventB $event)
+        {
+            print $event->content;
+        }
+
+        public function neverToReach(EventA $event)
+        {
+            print $event->content;
+        }
+    }
+}
+
+namespace FF\Tests\Services\Events {
+
+    use FF\Events\AbstractEvent;
+
+    class EventA extends AbstractEvent
+    {
+        public $content;
+
+        public function __construct(string $content)
+        {
+            $this->content = $content;
+        }
+    }
+
+    class EventB extends EventA
+    {
+
+    }
+}
\ No newline at end of file
diff --git a/tests/Services/Factories/EventsFactoryTest.php b/tests/Services/Factories/EventsFactoryTest.php
new file mode 100644
index 0000000..2b8aad7
--- /dev/null
+++ b/tests/Services/Factories/EventsFactoryTest.php
@@ -0,0 +1,41 @@
+<?php
+/**
+ * Definition of EventsFactoryTest
+ *
+ * @author Marco Stoll <marco@fast-forward-encoding.de>
+ * @copyright 2019-forever Marco Stoll
+ * @filesource
+ */
+declare(strict_types=1);
+
+namespace FF\Tests\Services\Factories;
+
+use FF\Factories\ClassLocators\BaseNamespaceClassLocator;
+use FF\Services\Factories\EventsFactory;
+use PHPUnit\Framework\TestCase;
+
+/**
+ * Test EventsFactoryTest
+ *
+ * @package FF\Tests
+ */
+class EventsFactoryTest extends TestCase
+{
+    /**
+     * Tests the namesake method/feature
+     */
+    public function testGetInstance()
+    {
+        $instance = EventsFactory::getInstance();
+        $this->assertInstanceOf(EventsFactory::class, $instance);
+        $this->assertSame($instance, EventsFactory::getInstance());
+    }
+
+    /**
+     * Tests the namesake method/feature
+     */
+    public function testGetClassLocator()
+    {
+        $this->assertInstanceOf(BaseNamespaceClassLocator::class, EventsFactory::getInstance()->getClassLocator());
+    }
+}
\ No newline at end of file