From 970c3df6e831e546a98d9b7cde694defd96f60cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=20G=C3=B3mez?= Date: Mon, 20 Nov 2023 12:26:20 +0100 Subject: [PATCH 1/5] chore: add mutations table --- etc/databases/mooc.sql | 114 ++++++++++++++++++++++++++++++++--------- 1 file changed, 90 insertions(+), 24 deletions(-) diff --git a/etc/databases/mooc.sql b/etc/databases/mooc.sql index e236cbc46..269362111 100644 --- a/etc/databases/mooc.sql +++ b/etc/databases/mooc.sql @@ -1,31 +1,97 @@ +/* ------------------------- + MOOC CONTEXT +---------------------------- */ + +-- Generic tables + +CREATE TABLE `mutations` ( + `id` BIGINT AUTO_INCREMENT PRIMARY KEY, + `table_name` VARCHAR(255) NOT NULL, + `operation` ENUM ('INSERT', 'UPDATE', 'DELETE') NOT NULL, + `old_value` JSON NULL, + `new_value` JSON NULL, + `mutation_timestamp` TIMESTAMP DEFAULT CURRENT_TIMESTAMP +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_unicode_ci; + +CREATE TABLE `domain_events` ( + `id` CHAR(36) NOT NULL, + `aggregate_id` CHAR(36) NOT NULL, + `name` VARCHAR(255) NOT NULL, + `body` JSON NOT NULL, + `occurred_on` TIMESTAMP NOT NULL, + PRIMARY KEY (`id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_unicode_ci; + +-- Aggregates tables + CREATE TABLE `courses` ( - `id` CHAR(36) NOT NULL, - `name` VARCHAR(255) NOT NULL, - `duration` VARCHAR(255) NOT NULL, - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + `id` CHAR(36) NOT NULL, + `name` VARCHAR(255) NOT NULL, + `duration` VARCHAR(255) NOT NULL, + PRIMARY KEY (`id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_unicode_ci; + +CREATE TRIGGER after_courses_insert + AFTER INSERT + ON courses + FOR EACH ROW +BEGIN + INSERT INTO mutations (table_name, operation, new_value, mutation_timestamp) + VALUES ('courses', 'INSERT', JSON_OBJECT('id', new.id, 'name', new.name, 'duration', new.duration), NOW()); +END; + + +CREATE TRIGGER after_courses_update + AFTER UPDATE + ON courses + FOR EACH ROW +BEGIN + INSERT INTO mutations (table_name, operation, old_value, new_value, mutation_timestamp) + VALUES ('courses', + 'UPDATE', + JSON_OBJECT('id', old.id, 'name', old.name, 'duration', old.duration), + JSON_OBJECT('id', new.id, 'name', new.name, 'duration', new.duration), + NOW()); +END; + + +CREATE TRIGGER after_courses_delete + AFTER DELETE + ON courses + FOR EACH ROW +BEGIN + INSERT INTO mutations (table_name, operation, old_value, mutation_timestamp) + VALUES ('courses', 'DELETE', JSON_OBJECT('id', old.id, 'name', old.name, 'duration', old.duration), NOW()); +END; CREATE TABLE `courses_counter` ( - `id` CHAR(36) NOT NULL, - `total` INT NOT NULL, - `existing_courses` JSON NOT NULL, - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + `id` CHAR(36) NOT NULL, + `total` INT NOT NULL, + `existing_courses` JSON NOT NULL, + PRIMARY KEY (`id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_unicode_ci; -INSERT INTO `courses_counter` VALUES ("cdf26d7d-3deb-4e8c-9f73-4ac085a8d6f3", 0, "[]"); +INSERT INTO `courses_counter` +VALUES ("cdf26d7d-3deb-4e8c-9f73-4ac085a8d6f3", 0, "[]"); -CREATE TABLE `domain_events` ( - `id` CHAR(36) NOT NULL, - `aggregate_id` CHAR(36) NOT NULL, - `name` VARCHAR(255) NOT NULL, - `body` JSON NOT NULL, - `occurred_on` timestamp NOT NULL, - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +/* ------------------------- + BACKOFFICE CONTEXT +---------------------------- */ CREATE TABLE `backoffice_courses` ( - `id` CHAR(36) NOT NULL, - `name` VARCHAR(255) NOT NULL, - `duration` VARCHAR(255) NOT NULL, - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + `id` CHAR(36) NOT NULL, + `name` VARCHAR(255) NOT NULL, + `duration` VARCHAR(255) NOT NULL, + PRIMARY KEY (`id`) +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4 + COLLATE = utf8mb4_unicode_ci; From 637738194ee20d2cdc7e5cc31448215f8112fd99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=20G=C3=B3mez?= Date: Mon, 20 Nov 2023 12:37:17 +0100 Subject: [PATCH 2/5] refactor: use codely for commands instead of codelytv --- .../DomainEvents/MySql/ConsumeMySqlDomainEventsCommand.php | 2 +- .../DomainEvents/RabbitMq/ConfigureRabbitMqCommand.php | 2 +- .../RabbitMq/ConsumeRabbitMqDomainEventsCommand.php | 2 +- .../GenerateSupervisorRabbitMqConsumerFilesCommand.php | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/mooc/backend/src/Command/DomainEvents/MySql/ConsumeMySqlDomainEventsCommand.php b/apps/mooc/backend/src/Command/DomainEvents/MySql/ConsumeMySqlDomainEventsCommand.php index 359264a69..febaf4b79 100644 --- a/apps/mooc/backend/src/Command/DomainEvents/MySql/ConsumeMySqlDomainEventsCommand.php +++ b/apps/mooc/backend/src/Command/DomainEvents/MySql/ConsumeMySqlDomainEventsCommand.php @@ -17,7 +17,7 @@ final class ConsumeMySqlDomainEventsCommand extends Command { - protected static $defaultName = 'codelytv:domain-events:mysql:consume'; + protected static $defaultName = 'codely:domain-events:mysql:consume'; public function __construct( private readonly MySqlDoctrineDomainEventsConsumer $consumer, diff --git a/apps/mooc/backend/src/Command/DomainEvents/RabbitMq/ConfigureRabbitMqCommand.php b/apps/mooc/backend/src/Command/DomainEvents/RabbitMq/ConfigureRabbitMqCommand.php index e42c35ba6..6f8913ea4 100644 --- a/apps/mooc/backend/src/Command/DomainEvents/RabbitMq/ConfigureRabbitMqCommand.php +++ b/apps/mooc/backend/src/Command/DomainEvents/RabbitMq/ConfigureRabbitMqCommand.php @@ -12,7 +12,7 @@ final class ConfigureRabbitMqCommand extends Command { - protected static $defaultName = 'codelytv:domain-events:rabbitmq:configure'; + protected static $defaultName = 'codely:domain-events:rabbitmq:configure'; public function __construct( private readonly RabbitMqConfigurer $configurer, diff --git a/apps/mooc/backend/src/Command/DomainEvents/RabbitMq/ConsumeRabbitMqDomainEventsCommand.php b/apps/mooc/backend/src/Command/DomainEvents/RabbitMq/ConsumeRabbitMqDomainEventsCommand.php index 856f40b04..25435c565 100644 --- a/apps/mooc/backend/src/Command/DomainEvents/RabbitMq/ConsumeRabbitMqDomainEventsCommand.php +++ b/apps/mooc/backend/src/Command/DomainEvents/RabbitMq/ConsumeRabbitMqDomainEventsCommand.php @@ -16,7 +16,7 @@ final class ConsumeRabbitMqDomainEventsCommand extends Command { - protected static $defaultName = 'codelytv:domain-events:rabbitmq:consume'; + protected static $defaultName = 'codely:domain-events:rabbitmq:consume'; public function __construct( private readonly RabbitMqDomainEventsConsumer $consumer, diff --git a/apps/mooc/backend/src/Command/DomainEvents/RabbitMq/GenerateSupervisorRabbitMqConsumerFilesCommand.php b/apps/mooc/backend/src/Command/DomainEvents/RabbitMq/GenerateSupervisorRabbitMqConsumerFilesCommand.php index f806d57da..dda29abb5 100644 --- a/apps/mooc/backend/src/Command/DomainEvents/RabbitMq/GenerateSupervisorRabbitMqConsumerFilesCommand.php +++ b/apps/mooc/backend/src/Command/DomainEvents/RabbitMq/GenerateSupervisorRabbitMqConsumerFilesCommand.php @@ -19,7 +19,7 @@ final class GenerateSupervisorRabbitMqConsumerFilesCommand extends Command private const EVENTS_TO_PROCESS_AT_TIME = 200; private const NUMBERS_OF_PROCESSES_PER_SUBSCRIBER = 1; private const SUPERVISOR_PATH = __DIR__ . '/../../../../build/supervisor'; - protected static $defaultName = 'codelytv:domain-events:rabbitmq:generate-supervisor-files'; + protected static $defaultName = 'codely:domain-events:rabbitmq:generate-supervisor-files'; public function __construct(private readonly DomainEventSubscriberLocator $locator) { @@ -68,7 +68,7 @@ private function template(): string { return << Date: Mon, 20 Nov 2023 13:08:15 +0100 Subject: [PATCH 3/5] chore: remove symfony deprecations --- .../backend/src/BackofficeBackendKernel.php | 4 +-- .../frontend/src/BackofficeFrontendKernel.php | 4 +-- .../MySql/ConsumeMySqlDomainEventsCommand.php | 8 ++--- .../RabbitMq/ConfigureRabbitMqCommand.php | 12 +++---- .../ConsumeRabbitMqDomainEventsCommand.php | 8 +++-- ...SupervisorRabbitMqConsumerFilesCommand.php | 10 +++--- apps/mooc/backend/src/MoocBackendKernel.php | 4 +-- ecs.php | 32 ++++++++----------- 8 files changed, 39 insertions(+), 43 deletions(-) diff --git a/apps/backoffice/backend/src/BackofficeBackendKernel.php b/apps/backoffice/backend/src/BackofficeBackendKernel.php index 793ede8c0..f3765c268 100644 --- a/apps/backoffice/backend/src/BackofficeBackendKernel.php +++ b/apps/backoffice/backend/src/BackofficeBackendKernel.php @@ -12,7 +12,7 @@ use function dirname; -class BackofficeBackendKernel extends Kernel +final class BackofficeBackendKernel extends Kernel { use MicroKernelTrait; @@ -36,7 +36,7 @@ public function getProjectDir(): string protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader): void { $container->addResource(new FileResource($this->getProjectDir() . '/config/bundles.php')); - $container->setParameter('container.dumper.inline_class_loader', true); + $container->setParameter('.container.dumper.inline_class_loader', true); $confDir = $this->getProjectDir() . '/config'; $loader->load($confDir . '/services' . self::CONFIG_EXTS, 'glob'); diff --git a/apps/backoffice/frontend/src/BackofficeFrontendKernel.php b/apps/backoffice/frontend/src/BackofficeFrontendKernel.php index 28852a38a..9411cb2bc 100644 --- a/apps/backoffice/frontend/src/BackofficeFrontendKernel.php +++ b/apps/backoffice/frontend/src/BackofficeFrontendKernel.php @@ -12,7 +12,7 @@ use function dirname; -class BackofficeFrontendKernel extends Kernel +final class BackofficeFrontendKernel extends Kernel { use MicroKernelTrait; @@ -36,7 +36,7 @@ public function getProjectDir(): string protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader): void { $container->addResource(new FileResource($this->getProjectDir() . '/config/bundles.php')); - $container->setParameter('container.dumper.inline_class_loader', true); + $container->setParameter('.container.dumper.inline_class_loader', true); $confDir = $this->getProjectDir() . '/config'; $loader->load($confDir . '/services' . self::CONFIG_EXTS, 'glob'); diff --git a/apps/mooc/backend/src/Command/DomainEvents/MySql/ConsumeMySqlDomainEventsCommand.php b/apps/mooc/backend/src/Command/DomainEvents/MySql/ConsumeMySqlDomainEventsCommand.php index febaf4b79..16d96c2a6 100644 --- a/apps/mooc/backend/src/Command/DomainEvents/MySql/ConsumeMySqlDomainEventsCommand.php +++ b/apps/mooc/backend/src/Command/DomainEvents/MySql/ConsumeMySqlDomainEventsCommand.php @@ -8,6 +8,7 @@ use CodelyTv\Shared\Infrastructure\Bus\Event\DomainEventSubscriberLocator; use CodelyTv\Shared\Infrastructure\Bus\Event\MySql\MySqlDoctrineDomainEventsConsumer; use CodelyTv\Shared\Infrastructure\Doctrine\DatabaseConnections; +use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; @@ -15,10 +16,9 @@ use function Lambdish\Phunctional\pipe; +#[AsCommand(name: 'codely:domain-events:mysql:consume', description: 'Consume domain events from MySql',)] final class ConsumeMySqlDomainEventsCommand extends Command { - protected static $defaultName = 'codely:domain-events:mysql:consume'; - public function __construct( private readonly MySqlDoctrineDomainEventsConsumer $consumer, private readonly DatabaseConnections $connections, @@ -29,9 +29,7 @@ public function __construct( protected function configure(): void { - $this - ->setDescription('Consume domain events from MySql') - ->addArgument('quantity', InputArgument::REQUIRED, 'Quantity of events to process'); + $this->addArgument('quantity', InputArgument::REQUIRED, 'Quantity of events to process'); } protected function execute(InputInterface $input, OutputInterface $output): int diff --git a/apps/mooc/backend/src/Command/DomainEvents/RabbitMq/ConfigureRabbitMqCommand.php b/apps/mooc/backend/src/Command/DomainEvents/RabbitMq/ConfigureRabbitMqCommand.php index 6f8913ea4..72801af71 100644 --- a/apps/mooc/backend/src/Command/DomainEvents/RabbitMq/ConfigureRabbitMqCommand.php +++ b/apps/mooc/backend/src/Command/DomainEvents/RabbitMq/ConfigureRabbitMqCommand.php @@ -5,15 +5,18 @@ namespace CodelyTv\Apps\Mooc\Backend\Command\DomainEvents\RabbitMq; use CodelyTv\Shared\Infrastructure\Bus\Event\RabbitMq\RabbitMqConfigurer; +use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; use Traversable; +#[AsCommand( + name: 'codely:domain-events:rabbitmq:configure', + description: 'Configure the RabbitMQ to allow publish & consume domain events', +)] final class ConfigureRabbitMqCommand extends Command { - protected static $defaultName = 'codely:domain-events:rabbitmq:configure'; - public function __construct( private readonly RabbitMqConfigurer $configurer, private readonly string $exchangeName, @@ -22,11 +25,6 @@ public function __construct( parent::__construct(); } - protected function configure(): void - { - $this->setDescription('Configure the RabbitMQ to allow publish & consume domain events'); - } - protected function execute(InputInterface $input, OutputInterface $output): int { $this->configurer->configure($this->exchangeName, ...iterator_to_array($this->subscribers)); diff --git a/apps/mooc/backend/src/Command/DomainEvents/RabbitMq/ConsumeRabbitMqDomainEventsCommand.php b/apps/mooc/backend/src/Command/DomainEvents/RabbitMq/ConsumeRabbitMqDomainEventsCommand.php index 25435c565..c29c056d8 100644 --- a/apps/mooc/backend/src/Command/DomainEvents/RabbitMq/ConsumeRabbitMqDomainEventsCommand.php +++ b/apps/mooc/backend/src/Command/DomainEvents/RabbitMq/ConsumeRabbitMqDomainEventsCommand.php @@ -7,6 +7,7 @@ use CodelyTv\Shared\Infrastructure\Bus\Event\DomainEventSubscriberLocator; use CodelyTv\Shared\Infrastructure\Bus\Event\RabbitMq\RabbitMqDomainEventsConsumer; use CodelyTv\Shared\Infrastructure\Doctrine\DatabaseConnections; +use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; @@ -14,10 +15,12 @@ use function Lambdish\Phunctional\repeat; +#[AsCommand( + name: 'codely:domain-events:rabbitmq:consume', + description: 'Consume domain events from the RabbitMQ', +)] final class ConsumeRabbitMqDomainEventsCommand extends Command { - protected static $defaultName = 'codely:domain-events:rabbitmq:consume'; - public function __construct( private readonly RabbitMqDomainEventsConsumer $consumer, private readonly DatabaseConnections $connections, @@ -29,7 +32,6 @@ public function __construct( protected function configure(): void { $this - ->setDescription('Consume domain events from the RabbitMQ') ->addArgument('queue', InputArgument::REQUIRED, 'Queue name') ->addArgument('quantity', InputArgument::REQUIRED, 'Quantity of events to process'); } diff --git a/apps/mooc/backend/src/Command/DomainEvents/RabbitMq/GenerateSupervisorRabbitMqConsumerFilesCommand.php b/apps/mooc/backend/src/Command/DomainEvents/RabbitMq/GenerateSupervisorRabbitMqConsumerFilesCommand.php index dda29abb5..646392bf3 100644 --- a/apps/mooc/backend/src/Command/DomainEvents/RabbitMq/GenerateSupervisorRabbitMqConsumerFilesCommand.php +++ b/apps/mooc/backend/src/Command/DomainEvents/RabbitMq/GenerateSupervisorRabbitMqConsumerFilesCommand.php @@ -7,6 +7,7 @@ use CodelyTv\Shared\Domain\Bus\Event\DomainEventSubscriber; use CodelyTv\Shared\Infrastructure\Bus\Event\DomainEventSubscriberLocator; use CodelyTv\Shared\Infrastructure\Bus\Event\RabbitMq\RabbitMqQueueNameFormatter; +use Symfony\Component\Console\Attribute\AsCommand; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; @@ -14,12 +15,15 @@ use function Lambdish\Phunctional\each; +#[AsCommand( + name: 'codely:domain-events:rabbitmq:generate-supervisor-files', + description: 'Generate the supervisor configuration for every RabbitMQ subscriber', +)] final class GenerateSupervisorRabbitMqConsumerFilesCommand extends Command { private const EVENTS_TO_PROCESS_AT_TIME = 200; private const NUMBERS_OF_PROCESSES_PER_SUBSCRIBER = 1; private const SUPERVISOR_PATH = __DIR__ . '/../../../../build/supervisor'; - protected static $defaultName = 'codely:domain-events:rabbitmq:generate-supervisor-files'; public function __construct(private readonly DomainEventSubscriberLocator $locator) { @@ -28,9 +32,7 @@ public function __construct(private readonly DomainEventSubscriberLocator $locat protected function configure(): void { - $this - ->setDescription('Generate the supervisor configuration for every RabbitMQ subscriber') - ->addArgument('command-path', InputArgument::OPTIONAL, 'Path on this is gonna be deployed', '/var/www'); + $this->addArgument('command-path', InputArgument::OPTIONAL, 'Path on this is gonna be deployed', '/var/www'); } protected function execute(InputInterface $input, OutputInterface $output): int diff --git a/apps/mooc/backend/src/MoocBackendKernel.php b/apps/mooc/backend/src/MoocBackendKernel.php index afc22aca7..36c5be57e 100644 --- a/apps/mooc/backend/src/MoocBackendKernel.php +++ b/apps/mooc/backend/src/MoocBackendKernel.php @@ -12,7 +12,7 @@ use function dirname; -class MoocBackendKernel extends Kernel +final class MoocBackendKernel extends Kernel { use MicroKernelTrait; @@ -36,7 +36,7 @@ public function getProjectDir(): string protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader): void { $container->addResource(new FileResource($this->getProjectDir() . '/config/bundles.php')); - $container->setParameter('container.dumper.inline_class_loader', true); + $container->setParameter('.container.dumper.inline_class_loader', true); $confDir = $this->getProjectDir() . '/config'; $loader->load($confDir . '/services' . self::CONFIG_EXTS, 'glob'); diff --git a/ecs.php b/ecs.php index 3e87e64ce..e3ed961ba 100644 --- a/ecs.php +++ b/ecs.php @@ -7,24 +7,20 @@ use Symplify\EasyCodingStandard\Config\ECSConfig; return function (ECSConfig $ecsConfig): void { - $ecsConfig->paths([ - __DIR__ . '/apps', - __DIR__ . '/src', - __DIR__ . '/tests', - ]); + $ecsConfig->paths([__DIR__ . '/apps', __DIR__ . '/src', __DIR__ . '/tests', ]); - $ecsConfig->sets([CodingStyle::DEFAULT]); + $ecsConfig->sets([CodingStyle::DEFAULT]); - $ecsConfig->skip([ - FinalClassFixer::class => [ - __DIR__ . '/apps/backoffice/backend/src/BackofficeBackendKernel.php', - __DIR__ . '/apps/backoffice/frontend/src/BackofficeFrontendKernel.php', - __DIR__ . '/apps/mooc/backend/src/MoocBackendKernel.php', - __DIR__ . '/src/Shared/Infrastructure/Bus/Event/InMemory/InMemorySymfonyEventBus.php', - ], - __DIR__ . '/apps/backoffice/backend/var', - __DIR__ . '/apps/backoffice/frontend/var', - __DIR__ . '/apps/mooc/backend/var', - __DIR__ . '/apps/mooc/frontend/var', - ]); + $ecsConfig->skip([ + FinalClassFixer::class => [ + __DIR__ . '/apps/backoffice/backend/src/BackofficeBackendKernel.php', + __DIR__ . '/apps/backoffice/frontend/src/BackofficeFrontendKernel.php', + __DIR__ . '/apps/mooc/backend/src/MoocBackendKernel.php', + __DIR__ . '/src/Shared/Infrastructure/Bus/Event/InMemory/InMemorySymfonyEventBus.php', + ], + __DIR__ . '/apps/backoffice/backend/var', + __DIR__ . '/apps/backoffice/frontend/var', + __DIR__ . '/apps/mooc/backend/var', + __DIR__ . '/apps/mooc/frontend/var', + ]); }; From 1c5a6b499118b68511546e7a3db9a43051906915 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=20G=C3=B3mez?= Date: Mon, 20 Nov 2023 16:35:59 +0100 Subject: [PATCH 4/5] chore: add command to publish mutations as domain events --- ...ublishDomainEventsFromMutationsCommand.php | 90 +++++++++++++++++++ etc/databases/mooc.sql | 2 - ...baseMutationToCourseCreatedDomainEvent.php | 40 +++++++++ .../Persistence/Doctrine/CourseIdsType.php | 4 +- .../Cdc/DatabaseMutationAction.php | 12 +++ .../Cdc/DatabaseMutationToDomainEvent.php | 17 ++++ .../Doctrine/DoctrineEntityManagerFactory.php | 9 +- .../Persistence/Doctrine/UuidType.php | 4 +- 8 files changed, 171 insertions(+), 7 deletions(-) create mode 100644 apps/mooc/backend/src/Command/DomainEvents/PublishDomainEventsFromMutationsCommand.php create mode 100644 src/Mooc/Courses/Infrastructure/Cdc/DatabaseMutationToCourseCreatedDomainEvent.php create mode 100644 src/Shared/Infrastructure/Cdc/DatabaseMutationAction.php create mode 100644 src/Shared/Infrastructure/Cdc/DatabaseMutationToDomainEvent.php diff --git a/apps/mooc/backend/src/Command/DomainEvents/PublishDomainEventsFromMutationsCommand.php b/apps/mooc/backend/src/Command/DomainEvents/PublishDomainEventsFromMutationsCommand.php new file mode 100644 index 000000000..91dc36da2 --- /dev/null +++ b/apps/mooc/backend/src/Command/DomainEvents/PublishDomainEventsFromMutationsCommand.php @@ -0,0 +1,90 @@ +transformers = [ + 'courses' => [ + DatabaseMutationAction::INSERT->value => DatabaseMutationToCourseCreatedDomainEvent::class, + DatabaseMutationAction::UPDATE->value => null, + DatabaseMutationAction::DELETE->value => null, + ], + ]; + } + + protected function configure(): void + { + $this->addArgument('quantity', InputArgument::REQUIRED, 'Quantity of mutations to process'); + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $totalMutations = (int) $input->getArgument('quantity'); + + $this->entityManager->wrapInTransaction(function (EntityManager $entityManager) use ($totalMutations) { + $mutations = $entityManager->getConnection() + ->executeQuery("SELECT * FROM mutations ORDER BY id ASC LIMIT $totalMutations FOR UPDATE") + ->fetchAllAssociative(); + + foreach ($mutations as $mutation) { + $transformer = $this->findTransformer($mutation['table_name'], $mutation['operation']); + + if ($transformer === null) { + echo sprintf("Ignoring %s %s\n", $mutation['table_name'], $mutation['operation']); + continue; + } + + $domainEvents = $transformer->transform($mutation); + + $this->eventBus->publish(...$domainEvents); + } + + $entityManager->getConnection()->executeStatement( + sprintf('DELETE FROM mutations WHERE id IN (%s)', implode(',', array_column($mutations, 'id'))) + ); + }); + + return 0; + } + + private function findTransformer(string $tableName, string $operation): ?DatabaseMutationToDomainEvent + { + if (!array_key_exists($tableName, $this->transformers) && array_key_exists( + $operation, + $this->transformers[$tableName] + )) { + throw new RuntimeException("Transformer not found for table $tableName and operation $operation"); + } + + $class = $this->transformers[$tableName][$operation]; + + return $class ? new $class() : null; + } +} diff --git a/etc/databases/mooc.sql b/etc/databases/mooc.sql index 269362111..a999ef78e 100644 --- a/etc/databases/mooc.sql +++ b/etc/databases/mooc.sql @@ -46,7 +46,6 @@ BEGIN VALUES ('courses', 'INSERT', JSON_OBJECT('id', new.id, 'name', new.name, 'duration', new.duration), NOW()); END; - CREATE TRIGGER after_courses_update AFTER UPDATE ON courses @@ -60,7 +59,6 @@ BEGIN NOW()); END; - CREATE TRIGGER after_courses_delete AFTER DELETE ON courses diff --git a/src/Mooc/Courses/Infrastructure/Cdc/DatabaseMutationToCourseCreatedDomainEvent.php b/src/Mooc/Courses/Infrastructure/Cdc/DatabaseMutationToCourseCreatedDomainEvent.php new file mode 100644 index 000000000..59f78fe54 --- /dev/null +++ b/src/Mooc/Courses/Infrastructure/Cdc/DatabaseMutationToCourseCreatedDomainEvent.php @@ -0,0 +1,40 @@ + $id->value(), $value), $platform); } - public function convertToPHPValue($value, AbstractPlatform $platform) + public function convertToPHPValue($value, AbstractPlatform $platform): array { $scalars = parent::convertToPHPValue($value, $platform); diff --git a/src/Shared/Infrastructure/Cdc/DatabaseMutationAction.php b/src/Shared/Infrastructure/Cdc/DatabaseMutationAction.php new file mode 100644 index 000000000..fdad67057 --- /dev/null +++ b/src/Shared/Infrastructure/Cdc/DatabaseMutationAction.php @@ -0,0 +1,12 @@ +setMetadataDriverImpl(new SimplifiedXmlDriver(array_merge(self::$sharedPrefixes, $contextPrefixes))); + $config->setSchemaManagerFactory(new DefaultSchemaManagerFactory()); return $config; } diff --git a/src/Shared/Infrastructure/Persistence/Doctrine/UuidType.php b/src/Shared/Infrastructure/Persistence/Doctrine/UuidType.php index f90ebbe32..1ef8213bc 100644 --- a/src/Shared/Infrastructure/Persistence/Doctrine/UuidType.php +++ b/src/Shared/Infrastructure/Persistence/Doctrine/UuidType.php @@ -26,14 +26,14 @@ final public function getName(): string return self::customTypeName(); } - final public function convertToPHPValue($value, AbstractPlatform $platform) + final public function convertToPHPValue($value, AbstractPlatform $platform): mixed { $className = $this->typeClassName(); return new $className($value); } - final public function convertToDatabaseValue($value, AbstractPlatform $platform) + final public function convertToDatabaseValue($value, AbstractPlatform $platform): string { /** @var Uuid $value */ return $value->value(); From b74862f30f1e937482211f226c1eb1b75bb87d46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=20G=C3=B3mez?= Date: Mon, 20 Nov 2023 16:39:40 +0100 Subject: [PATCH 5/5] fix: no finalize kernel classes --- apps/backoffice/backend/src/BackofficeBackendKernel.php | 2 +- apps/backoffice/frontend/src/BackofficeFrontendKernel.php | 2 +- .../DomainEvents/PublishDomainEventsFromMutationsCommand.php | 1 + apps/mooc/backend/src/MoocBackendKernel.php | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/backoffice/backend/src/BackofficeBackendKernel.php b/apps/backoffice/backend/src/BackofficeBackendKernel.php index f3765c268..6ed60b1be 100644 --- a/apps/backoffice/backend/src/BackofficeBackendKernel.php +++ b/apps/backoffice/backend/src/BackofficeBackendKernel.php @@ -12,7 +12,7 @@ use function dirname; -final class BackofficeBackendKernel extends Kernel +class BackofficeBackendKernel extends Kernel { use MicroKernelTrait; diff --git a/apps/backoffice/frontend/src/BackofficeFrontendKernel.php b/apps/backoffice/frontend/src/BackofficeFrontendKernel.php index 9411cb2bc..dd793f7ab 100644 --- a/apps/backoffice/frontend/src/BackofficeFrontendKernel.php +++ b/apps/backoffice/frontend/src/BackofficeFrontendKernel.php @@ -12,7 +12,7 @@ use function dirname; -final class BackofficeFrontendKernel extends Kernel +class BackofficeFrontendKernel extends Kernel { use MicroKernelTrait; diff --git a/apps/mooc/backend/src/Command/DomainEvents/PublishDomainEventsFromMutationsCommand.php b/apps/mooc/backend/src/Command/DomainEvents/PublishDomainEventsFromMutationsCommand.php index 91dc36da2..945784311 100644 --- a/apps/mooc/backend/src/Command/DomainEvents/PublishDomainEventsFromMutationsCommand.php +++ b/apps/mooc/backend/src/Command/DomainEvents/PublishDomainEventsFromMutationsCommand.php @@ -83,6 +83,7 @@ private function findTransformer(string $tableName, string $operation): ?Databas throw new RuntimeException("Transformer not found for table $tableName and operation $operation"); } + /** @var class-string|null $class */ $class = $this->transformers[$tableName][$operation]; return $class ? new $class() : null; diff --git a/apps/mooc/backend/src/MoocBackendKernel.php b/apps/mooc/backend/src/MoocBackendKernel.php index 36c5be57e..cc8f8ed58 100644 --- a/apps/mooc/backend/src/MoocBackendKernel.php +++ b/apps/mooc/backend/src/MoocBackendKernel.php @@ -12,7 +12,7 @@ use function dirname; -final class MoocBackendKernel extends Kernel +class MoocBackendKernel extends Kernel { use MicroKernelTrait;