Skip to content

Commit

Permalink
Merge branch 'release/1.2.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
lindyhopchris committed Apr 5, 2024
2 parents 382eb2e + 6f2734b commit bceb347
Show file tree
Hide file tree
Showing 14 changed files with 667 additions and 96 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ jobs:
command: composer update --prefer-dist --no-interaction --no-progress

- name: Execute style
run: composer run style
run: composer run style -- --bail

- name: Execute static analysis
run: composer run static
Expand Down
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,25 @@ All notable changes to this project will be documented in this file. This projec

## Unreleased

## [1.2.0] - 2024-04-05

### Added

- New integration event middleware:
- `NotifyInUnitOfWork` for notifiers that need to be executed in a unit of work. Note that the documentation for
Integration Events incorrectly showed the `ExecuteInUnitOfWork` command middleware being used.
- `SetupBeforeEvent` for doing setup work before an integration event is published or notified, and optionally
teardown work after.
- `TeardownAfterEvent` for doing teardown work after an integration event is published or notified.
- `LogInboundEvent` for logging that an integration event is being received.
- `LogOutboundEvent` for logging that an integration event is being published.

### Deprecated

- The following integration event middleware are deprecated and will be removed in 2.0:
- `LogInboundIntegrationEvent`: use `LogInboundEvent` instead.
- `LogOutboundIntegrationEvent`: use `LogOutboundEvent` instead.

## [1.1.0] - 2024-03-14

### Added
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
"ramsey/uuid": "^4.7"
},
"require-dev": {
"laravel/pint": "^1.13",
"laravel/pint": "^1.15",
"phpstan/phpstan": "^1.10",
"phpunit/phpunit": "^10.4"
},
Expand Down
121 changes: 105 additions & 16 deletions docs/guide/application/events.md
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ use App\Modules\EventManagement\Shared\IntegrationEvents\{
};
use CloudCreativity\Modules\EventBus\{
IntegrationEventHandlerContainer,
Middleware\LogOutboundIntegrationEvent,
Middleware\LogOutboundEvent,
Outbound\Publisher,
};
use CloudCreativity\Modules\Toolkit\Pipeline\PipeContainer;
Expand Down Expand Up @@ -334,15 +334,15 @@ final class EventManagementApplication implements EventManagementEventBusInterfa

/** Bind middleware factories */
$middleware->bind(
LogOutboundIntegrationEvent::class,
fn () => new LogOutboundIntegrationEvent(
LogOutboundEvent::class,
fn () => new LogOutboundEvent(
$this->dependencies->getLogger(),
),
);

/** Attach middleware that runs for all events */
$bus->through([
LogOutboundIntegrationEvent::class,
LogOutboundEvent::class,
]);

return $publisher;
Expand Down Expand Up @@ -448,7 +448,7 @@ use App\Modules\Ordering\Shared\IntegrationEvents\OrderWasFulfilled;
use CloudCreativity\Modules\EventBus\{
IntegrationEventHandlerContainer,
Inbound\Notifier,
Middleware\LogInboundIntegrationEvent,
Middleware\LogInboundEvent,
};
use CloudCreativity\Modules\Toolkit\Pipeline\PipeContainer;

Expand Down Expand Up @@ -480,15 +480,15 @@ final class EventManagementApplication implements EventManagementEventBusInterfa

/** Bind middleware factories */
$middleware->bind(
LogInboundIntegrationEvent::class,
fn () => new LogInboundIntegrationEvent(
LogInboundEvent::class,
fn () => new LogInboundEvent(
$this->dependencies->getLogger(),
),
);

/** Attach middleware that runs for all events */
$bus->through([
LogInboundIntegrationEvent::class,
LogInboundEvent::class,
]);

return $notifier;
Expand Down Expand Up @@ -566,8 +566,8 @@ namespace App\Modules\EventManagement\BoundedContext\Application\IntegrationEven

use App\Modules\EventManagement\BoundedContext\Domain\Events\SalesAtEventDidChange;
use App\Modules\Ordering\Shared\IntegrationEvents\OrderWasFulfilled;
use CloudCreativity\Modules\Bus\Middleware\ExecuteInUnitOfWork;
use CloudCreativity\Modules\Domain\Events\DispatcherInterface;
use CloudCreativity\Modules\EventBus\Middleware\NotifyInUnitOfWork;
use CloudCreativity\Modules\Toolkit\Messages\DispatchThroughMiddleware;

final readonly class OrderWasFulfilledHandler implements
Expand All @@ -588,7 +588,7 @@ final readonly class OrderWasFulfilledHandler implements
public function middleware(): array
{
return [
ExecuteInUnitOfWork::class,
NotifyInUnitOfWork::class,
];
}
}
Expand Down Expand Up @@ -669,21 +669,110 @@ the order they should be executed. Handler middleware are always executed _after
This package provides several useful middleware, which are described below. Additionally, you can write your own
middleware to suit your specific needs.

### Setup and Teardown

Our `SetupBeforeEvent` middleware allows your to run setup work before the event is published or notified, and
optionally teardown work after.

This allows you to set up any state, services or singletons - and guarantee that these are cleaned up, regardless of
whether the notifying or publishing completes or throws an exception.

For example:

```php
use App\Modules\EventManagement\BoundedContext\Domain\Services;
use CloudCreativity\Modules\EventBus\Middleware\SetupBeforeEvent;

$middleware->bind(
SetupBeforeEvent::class,
fn () => new SetupBeforeEvent(function (): Closure {
// setup singletons, dependencies etc here.
return function (): void {
// teardown singletons, dependencies etc here.
// returning a teardown closure is optional.
};
}),
);

$bus->through([
LogInboundEvent::class,
SetupBeforeEvent::class,
]);
```

Here our setup middleware takes a setup closure as its only constructor argument. This setup closure can optionally
return a closure to do any teardown work. The teardown callback is guaranteed to always be executed - i.e. it will run
even if an exception is thrown.

If you only need to do any teardown work, use the `TeardownAfterEvent` middleware instead. This takes a single teardown
closure as its only constructor argument:

```php
use CloudCreativity\Modules\EventBus\Middleware\TearDownAfterEvent;

$middleware->bind(
TearDownAfterEvent::class,
fn () => new TearDownAfterEvent(function (): Closure {
// teardown here
}),
);

$bus->through([
LogInboundEvent::class,
TearDownAfterEvent::class,
]);
```

### Unit of Work

Ideally notifiers that are not dispatching commands should always be executed in a unit of work.
We cover this in detail in the [Units of Work chapter.](../infrastructure/units-of-work)

:::tip
If your notifier only dispatches a command, then it will not need to be wrapped in a unit of work. This is because the
command itself should use a unit of work.
:::

To notify an event in a unit of work, you will need to use our `NotifyInUnitOfWork` middleware. You should always
implement this as handler middleware - because typically you need it to be the final middleware that runs before a
handler is invoked. It also makes it clear to developers looking at the handler that it is expected to run
in a unit of work. The example `OrderWasFulfilledHandler` above demonstrates this.

An example binding for this middleware is:

```php
use CloudCreativity\Modules\EventBus\Middleware\NotifyInUnitOfWork;

$middleware->bind(
NotifyInUnitOfWork::class,
fn () => new NotifyInUnitOfWork($this->getUnitOfWorkManager()),
);
```

:::warning
If you're using a unit of work, you should be combining this with our "unit of work domain event dispatcher".
One really important thing to note is that you **must inject both the middleware and the domain event dispatcher with
exactly the same instance of the unit of work manager.**

I.e. use a singleton instance of the unit of work manager. Plus use the teardown middleware (described above) to dispose
of the singleton instance once the handler has been executed.
:::

### Logging

Use our `LogInboundIntegrationEvent` or `LogOutboundIntegrationEvent` middleware to log the receiving or publishing
an integration event. Both take a [PSR Logger](https://php-fig.org/psr/psr-3/).
Use our `LogInboundEvent` or `LogOutboundEvent` middleware to log when an integration event is received or published.
Both take a [PSR Logger](https://php-fig.org/psr/psr-3/).

The only difference between these two middleware is they log a different message that makes it clear whether the
integration event is inbound or outbound. Make sure you use the correct one for the publisher or notifier! The publisher
needs to use `LogOutboundIntegrationEvent` and the notifier needs to use `LogInboundIntegrationEvent`.
needs to use `LogOutboundEvent` and the notifier needs to use `LogInboundEvent`.

```php
use CloudCreativity\Modules\EventBus\Middleware\LogMessageDispatch;
use CloudCreativity\Modules\EventBus\Middleware\LogInboundEvent;

$middleware->bind(
LogMessageDispatch::class,
fn (): LogMessageDispatch => new LogMessageDispatch(
LogInboundEvent::class,
fn () => new LogInboundEvent(
$this->dependencies->getLogger(),
),
);
Expand Down
54 changes: 54 additions & 0 deletions src/EventBus/Middleware/LogInboundEvent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php
/*
* Copyright 2024 Cloud Creativity Limited
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/

declare(strict_types=1);

namespace CloudCreativity\Modules\EventBus\Middleware;

use Closure;
use CloudCreativity\Modules\Infrastructure\Log\ObjectContext;
use CloudCreativity\Modules\Toolkit\Messages\IntegrationEventInterface;
use CloudCreativity\Modules\Toolkit\ModuleBasename;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;

class LogInboundEvent implements IntegrationEventMiddlewareInterface
{
/**
* LogInboundEvent constructor.
*
* @param LoggerInterface $log
* @param string $publishLevel
* @param string $publishedLevel
*/
public function __construct(
private readonly LoggerInterface $log,
private readonly string $publishLevel = LogLevel::DEBUG,
private readonly string $publishedLevel = LogLevel::INFO,
) {
}

/**
* @inheritDoc
*/
public function __invoke(IntegrationEventInterface $event, Closure $next): void
{
$name = ModuleBasename::tryFrom($event)?->toString() ?? $event::class;

$this->log->log(
$this->publishLevel,
"Receiving integration event {$name}.",
$context = ObjectContext::from($event)->context(),
);

$next($event);

$this->log->log($this->publishedLevel, "Received integration event {$name}.", $context);
}
}
43 changes: 4 additions & 39 deletions src/EventBus/Middleware/LogInboundIntegrationEvent.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,44 +11,9 @@

namespace CloudCreativity\Modules\EventBus\Middleware;

use Closure;
use CloudCreativity\Modules\Infrastructure\Log\ObjectContext;
use CloudCreativity\Modules\Toolkit\Messages\IntegrationEventInterface;
use CloudCreativity\Modules\Toolkit\ModuleBasename;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;

final class LogInboundIntegrationEvent implements IntegrationEventMiddlewareInterface
/**
* @deprecated 2.0
*/
final class LogInboundIntegrationEvent extends LogInboundEvent
{
/**
* LogInboundIntegrationEvent constructor.
*
* @param LoggerInterface $log
* @param string $publishLevel
* @param string $publishedLevel
*/
public function __construct(
private readonly LoggerInterface $log,
private readonly string $publishLevel = LogLevel::DEBUG,
private readonly string $publishedLevel = LogLevel::INFO,
) {
}

/**
* @inheritDoc
*/
public function __invoke(IntegrationEventInterface $event, Closure $next): void
{
$name = ModuleBasename::tryFrom($event)?->toString() ?? $event::class;

$this->log->log(
$this->publishLevel,
"Receiving integration event {$name}.",
$context = ObjectContext::from($event)->context(),
);

$next($event);

$this->log->log($this->publishedLevel, "Received integration event {$name}.", $context);
}
}
54 changes: 54 additions & 0 deletions src/EventBus/Middleware/LogOutboundEvent.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php
/*
* Copyright 2024 Cloud Creativity Limited
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/

declare(strict_types=1);

namespace CloudCreativity\Modules\EventBus\Middleware;

use Closure;
use CloudCreativity\Modules\Infrastructure\Log\ObjectContext;
use CloudCreativity\Modules\Toolkit\Messages\IntegrationEventInterface;
use CloudCreativity\Modules\Toolkit\ModuleBasename;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;

class LogOutboundEvent implements IntegrationEventMiddlewareInterface
{
/**
* LogOutboundEvent constructor.
*
* @param LoggerInterface $log
* @param string $publishLevel
* @param string $publishedLevel
*/
public function __construct(
private readonly LoggerInterface $log,
private readonly string $publishLevel = LogLevel::DEBUG,
private readonly string $publishedLevel = LogLevel::INFO,
) {
}

/**
* @inheritDoc
*/
public function __invoke(IntegrationEventInterface $event, Closure $next): void
{
$name = ModuleBasename::tryFrom($event)?->toString() ?? $event::class;

$this->log->log(
$this->publishLevel,
"Publishing integration event {$name}.",
$context = ObjectContext::from($event)->context(),
);

$next($event);

$this->log->log($this->publishedLevel, "Published integration event {$name}.", $context);
}
}
Loading

0 comments on commit bceb347

Please sign in to comment.