This bundle integrates the Orkestra Framework with Symfony 5.
Open a command console, enter your project directory and execute:
$ composer require morebec/orkestra-symfony-bundle
Open a command console, enter your project directory and execute the following command to download the latest stable version of this bundle:
$ composer require morebec/orkestra-symfony-bundle
This command requires you to have Composer installed globally, as explained in the installation chapter of the Composer documentation.
Then, enable the bundle by adding it to the list of registered bundles
in the config/bundles.php
file of your project:
return [
// ...
OrkestraSymfonyBundle::class => ['all' => true]
];
For persistence and infrastructure concerns, Orkestra requires adapters.
Install one of the adapters and register the classes of the adapter as services in a Module Configurator (see below for more information).
A Module is a logical separation of the source code. This is usually linked to the separations of DDD Bounded Contexts according to the context map. Although Symfony provides a Bundle System, This bundle's Module System is tailored for the dependency injection needs of Orkestra based application. It provides ways to configure services using pure PHP with a fluent API which simplifies greatly this process while still allowing all the power of Symfony.
- Create a directory under
src
with the name of your Module. E.g. `Shipping'. - Inside this directory, create a class implementing the
SymfonyOrkestraModuleConfiguratorInterface
. This class will be used by the bundle to register the service dependencies of the module with Symfony's service container as well as the controller routes with the Symfony Route (not to be confused withDomainMessageRoutes
).
class SandboxModuleConfiguratorConfigurator implements SymfonyOrkestraModuleConfiguratorInterface
{
public function configureContainer(ContainerConfigurator $container): void
{
$conf = new SymfonyOrkestraModuleContainerConfigurator($container);
$conf->services()
->defaults()
->autowire()
->autoconfigure()
;
$conf->commandHandler(SandBoxMessageHandler::class)
->autoroute()
->disableMethodRoute('__invoke')
;
$conf->consoleCommand(SandboxConsoleCommand::class);
}
public function configureRoutes(RoutingConfigurator $routes): void
{
}
}
Note: The bundle provides a class
SymfonyOrkestraModuleContainerConfigurator
that allows to fluently define services. With a language closer to the technical requirements of Orkestra (eventHandler, commandHandler, queryHandler projectors etc.)
Then, enable the module by adding its Configurator to the list of registered Module Configurators in the config/modules.php
file of your project:
return [
// ...
SandboxModuleConfiguratorConfigurator::class => ['all' => true],
];
Module Configurations are registered just like Symfony Bundles allowing you to provide the environment in which they should exist. If you need a different configurator on a per environment basis, you can simply check for the environment using
$_ENV['APP_ENV]
in the configurators code.
As previously explained Orkestra requires adapters in order to support infrastructure concerns. There adapters are not configured by this bundle since they might be various. Also, it does not forces to use an Event Store by default, meaning this should be configured.
Here's an example configuration for the MongoDB Adapter:
$configurator = new SymfonyOrkestraModuleContainerConfigurator($container);
$configurator->service(MongoDbClient::class)->args(['%env(MONGO_URL)%', '%env(MONGO_DATABASE)%']);
$configurator->service(MongoDbTransactionMiddleware::class);
$configurator->service(DomainMessageSchedulerStorageInterface::class, MongoDbDomainMessageSchedulerStorage::class);
$configurator->service(PersonalInformationStoreInterface::class, MongoDbPersonalInformationStore::class);
$configurator->service(EventStoreInterface::class, SimpleEventStore::class);
$configurator->service(SimpleEventStorageReaderInterface::class, MongoDbSimpleEventStoreStorage::class);
$configurator->service(SimpleEventStorageWriterInterface::class, MongoDbSimpleEventStoreStorage::class);
$configurator->service(ProjectorStateStorageInterface::class, MongoDbProjectorStateStorage::class);
Note that the use of the
SymfonyOrkestraModuleContainerConfigurator
is optional and was just for demonstration purposes.
The module configurator do not have a specific method to add compiler passes, instead, you can rely on the ContainerConfigurator
to register custom Container Extensions
. For more information on this please refer to the Official Symfony Documentation.
The DomainMessageBusInterface
can be configured to receive more Middleware.
This Bundle provides a DefaultDomainMessageBusConfigurator
that sets up the default Orkestra Middleware on the domain message bus.
To change the way it is wired, you can simply extend this class and register it in your dependency injection service configuration:
// ...
$container
->services()
->set(DomainMessageBusConfiguratorInterface::class, YourCustomConfigurator::class);
Configuring the router allows you to manually add new routes to the DomainMessageRouterInterface
.
Simply create a new class implementing DomainMessageRouterConfiguratorInterface
registered with the service container
and add your routes in the configure
method.
If you use the SymfonyOrkestraModuleContainerConfigurator
routes can be automatically added to the bus while registering
message handlers.
The message bus can be configured to receive more NormalizerInterface
and DenormalizerInterface
as per your needs.
Simply create a new class implementing DomainMessageNormalizerConfiguratorInterface
registered with the service container
and add your NormalizerInterface
and DenormalizerInterface
in the configure
method.
The SymfonyOrkestraModuleContainerConfigurator
provides a method to automatically register validators:
SymfonyOrkestraModuleContainerConfigurator::messageValidator
.
This is done behind the scene through tags and autowiring of tagged services.
Orkestra does not provide a default implementation of a Validation library simply a set of interfaces. Here's an example of a validator using Symfony's Validator:
class SandboxCommandValidator implements DomainMessageValidatorInterface
{
/**
* @var ValidatorInterface
*/
private $validator;
public function __construct(ValidatorInterface $validator)
{
$this->validator = $validator;
}
public function validate(DomainMessageInterface $domainMessage, DomainMessageHeaders $headers): DomainMessageValidationErrorList
{
/** @var SandboxCommand $command */
$command = $domainMessage;
$metadata = $this->validator->getMetadataFor(SandboxCommand::class);
$metadata->addPropertyConstraint('userId', new Assert\NotBlank());
$errors = $this->validator->validate($command);
$c = (new Collection($errors))
->map(static function (ConstraintViolation $v) {
return new DomainMessageValidationError($v->getMessage(), $v->getPropertyPath(), $v->getInvalidValue());
})
->flatten()
;
return new DomainMessageValidationErrorList($c);
}
public function supports(DomainMessageInterface $domainMessage, DomainMessageHeaders $headers): bool
{
return $domainMessage instanceof SandboxCommand;
}
}