Skip to content

Commit

Permalink
Working on the tests
Browse files Browse the repository at this point in the history
  • Loading branch information
floriankraemer committed Jun 30, 2024
1 parent 79f3882 commit ae4a5e3
Show file tree
Hide file tree
Showing 9 changed files with 238 additions and 17 deletions.
12 changes: 0 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,6 @@ It features different ways of extracting information from your aggregates, pick
* Via Interfaces
* Via Reflection

## What is Event Sourcing?

Event sourcing is a software architecture pattern that emphasizes capturing and persisting the state of an application as a sequence of events rather than storing the current state directly. In event sourcing, every state-changing operation, or event, is stored in an append-only log. The current state of an entity is reconstructed by replaying these events in sequence.

This approach provides a comprehensive audit trail of all changes, enabling traceability, versioning, and the ability to rebuild the system's state at any point in time. Event sourcing promotes a decentralized and scalable model, facilitating event-driven architectures and supporting the evolution of domain models over time, making it particularly suitable for complex business domains and systems where temporal aspects and historical data are crucial.

## When to NOT use it

Event sourcing comes with additional complexity. You should NOT use event sourcing when you don't need it. It is a powerful tool, but it is not a silver bullet. It is not a one-size-fits-all solution. Event sourcing is a good solution for scenarious like audit logging, undo/redo functionality, and complex business rules.

If you have no good reason to use it, then don't.

## Installation

```sh
Expand Down
11 changes: 11 additions & 0 deletions docs/Event-Sourcing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
## What is Event Sourcing?

Event sourcing is a software architecture pattern that emphasizes capturing and persisting the state of an application as a sequence of events rather than storing the current state directly. In event sourcing, every state-changing operation, or event, is stored in an append-only log. The current state of an entity is reconstructed by replaying these events in sequence.

This approach provides a comprehensive audit trail of all changes, enabling traceability, versioning, and the ability to rebuild the system's state at any point in time. Event sourcing promotes a decentralized and scalable model, facilitating event-driven architectures and supporting the evolution of domain models over time, making it particularly suitable for complex business domains and systems where temporal aspects and historical data are crucial.

## When to NOT use it

Event sourcing comes with additional complexity. You should NOT use event sourcing when you don't need it. It is a powerful tool, but it is not a silver bullet. It is not a one-size-fits-all solution. Event sourcing is a good solution for scenarious like audit logging, undo/redo functionality, and complex business rules.

If you have no good reason to use it, then don't.
5 changes: 5 additions & 0 deletions src/Repository/EventPublisher/EventLoggerMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,9 @@ public function handle(object $event): void
{
$this->logger->info(sprintf('Event %s emitted.', get_class($event)));
}

public function isInterrupting(): bool
{
return false;
}
}
23 changes: 18 additions & 5 deletions src/Repository/EventPublisher/EventPublisher.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,29 @@
*/
class EventPublisher implements EventPublisherInterface
{
/**
* @var array<int, EventPublisherMiddlewareInterface> $middlewares
*/
protected array $middlewares = [];

/**
* @param array<int, EventPublisherMiddlewareInterface> $middlewares
*/
public function __construct(
protected array $middlewares = [],
array $middlewares = []
) {
foreach ($middlewares as $middleware) {
$this->addMiddleware($middleware);
}

$this->assertNonEmptyMiddlewareStack();
}

public function addMiddleware(EventPublisherMiddlewareInterface $middleware): void
{
$this->middlewares[] = $middleware;
}

protected function assertNonEmptyMiddlewareStack(): void
{
if (empty($this->middlewares)) {
Expand All @@ -31,11 +45,10 @@ protected function assertNonEmptyMiddlewareStack(): void
public function emitEvent(object $event): void
{
foreach ($this->middlewares as $middleware) {
if (!is_callable($middleware)) {
throw new RuntimeException('Non-callable subscriber!');
$middleware->handle($event);
if ($middleware->isInterrupting()) {
break;
}

$middleware($event);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@
interface EventPublisherMiddlewareInterface
{
public function handle(object $event): void;

public function isInterrupting(): bool;
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,9 @@ public function handle(object $event): void
{
$this->eventBus->dispatch($event);
}

public function isInterrupting(): bool
{
return false;
}
}
40 changes: 40 additions & 0 deletions tests/Repository/EventPublisher/EventLoggerMiddlewareTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

declare(strict_types=1);

namespace Phauthentic\EventSourcing\Test\Repository\EventPublisher;

use Phauthentic\EventSourcing\Repository\EventPublisher\EventLoggerMiddleware;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;

/**
*
*/
class EventLoggerMiddlewareTest extends TestCase
{
public function testHandleLogsEventInfo(): void
{
$mockLogger = $this->createMock(LoggerInterface::class);

$middleware = new EventLoggerMiddleware($mockLogger);

$dummyEvent = new class () {
};

$mockLogger->expects($this->once())
->method('info')
->with($this->equalTo(sprintf('Event %s emitted.', get_class($dummyEvent))));

$middleware->handle($dummyEvent);
}

public function testIsInterrupting(): void
{
$mockLogger = $this->createMock(LoggerInterface::class);

$middleware = new EventLoggerMiddleware($mockLogger);

$this->assertFalse($middleware->isInterrupting());
}
}
115 changes: 115 additions & 0 deletions tests/Repository/EventPublisher/EventPublisherTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
<?php

declare(strict_types=1);

namespace Phauthentic\EventSourcing\Test\Repository\EventPublisher;

use Phauthentic\EventSourcing\Repository\EventPublisher\EventPublisher;
use Phauthentic\EventSourcing\Repository\EventPublisher\EventPublisherMiddlewareInterface;
use PHPUnit\Framework\TestCase;
use ReflectionClass;
use RuntimeException;

/**
*
*/
class EventPublisherTest extends TestCase
{
public function testConstructorWithEmptyMiddlewareArrayThrowsException(): void
{
$this->expectException(RuntimeException::class);
$this->expectExceptionMessage('No middleware registered!');

new EventPublisher([]);
}

public function testAddMiddleware(): void
{
$publisher = new EventPublisher([
$this->createMock(EventPublisherMiddlewareInterface::class)
]);

$newMiddleware = $this->createMock(EventPublisherMiddlewareInterface::class);
$publisher->addMiddleware($newMiddleware);

$reflectionClass = new ReflectionClass($publisher);
$this->assertCount(2, $reflectionClass->getProperty('middlewares')->getValue($publisher));
}

public function testEmitEventCallsAllMiddlewares(): void
{
$middleware1 = $this->createMock(EventPublisherMiddlewareInterface::class);
$middleware2 = $this->createMock(EventPublisherMiddlewareInterface::class);

$middleware1->method('isInterrupting')->willReturn(false);
$middleware2->method('isInterrupting')->willReturn(false);

$publisher = new EventPublisher([$middleware1, $middleware2]);

$event = new \stdClass();

$middleware1->expects($this->once())->method('handle')->with($event);
$middleware2->expects($this->once())->method('handle')->with($event);

$publisher->emitEvent($event);
}

public function testEmitEventStopsOnInterruptingMiddleware(): void
{
$middleware1 = $this->createMock(EventPublisherMiddlewareInterface::class);
$middleware2 = $this->createMock(EventPublisherMiddlewareInterface::class);

$middleware1->method('isInterrupting')->willReturn(true);

$publisher = new EventPublisher([$middleware1, $middleware2]);

$event = new \stdClass();

$middleware1->expects($this->once())->method('handle')->with($event);
$middleware2->expects($this->never())->method('handle');

$publisher->emitEvent($event);
}

public function testEmitEventsCallsEmitEventForEachEvent(): void
{
$middleware = $this->createMock(EventPublisherMiddlewareInterface::class);
$middleware->method('isInterrupting')->willReturn(false);
$publisher = new EventPublisher([$middleware]);

$events = [new \stdClass(), new \stdClass(), new \stdClass()];

$middleware->expects($this->exactly(3))->method('handle');

$publisher->emitEvents($events);
}

public function testEmitEventsWorksWithIterator(): void
{
$middleware = $this->createMock(EventPublisherMiddlewareInterface::class);
$middleware->method('isInterrupting')->willReturn(false);
$publisher = new EventPublisher([$middleware]);

$events = new \ArrayIterator([new \stdClass(), new \stdClass()]);

$middleware->expects($this->exactly(2))->method('handle');

$publisher->emitEvents($events);
}

public function testEmitEventsWorksWithGenerator(): void
{
$middleware = $this->createMock(EventPublisherMiddlewareInterface::class);
$middleware->method('isInterrupting')->willReturn(false);
$publisher = new EventPublisher([$middleware]);

$events = (function () {
yield new \stdClass();
yield new \stdClass();
})();

$middleware->expects($this->exactly(2))->method('handle');

$publisher->emitEvents($events);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php

declare(strict_types=1);

namespace Phauthentic\EventSourcing\Test\Repository\EventPublisher;

use Phauthentic\EventSourcing\Repository\EventPublisher\SymfonyMessageBusConnectorMiddleware;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\MessageBus;

/**
*
*/
class SymfonyMessageBusConnectorMiddlewareTest extends TestCase
{
public function testHandleDispatchesEventToMessageBus(): void
{
$mockMessageBus = $this->createMock(MessageBus::class);

$middleware = new SymfonyMessageBusConnectorMiddleware($mockMessageBus);

$dummyEvent = new class () {
};

$mockMessageBus->expects($this->once())
->method('dispatch')
->with($this->identicalTo($dummyEvent))
->willReturn(new Envelope($dummyEvent));

$middleware->handle($dummyEvent);
}

public function testIsInterrupting(): void
{
$mockMessageBus = $this->createMock(MessageBus::class);

$middleware = new SymfonyMessageBusConnectorMiddleware($mockMessageBus);

$this->assertFalse($middleware->isInterrupting());
}
}

0 comments on commit ae4a5e3

Please sign in to comment.