Skip to content

Commit de40b26

Browse files
authoredNov 20, 2023
chore: add cdc pattern example (#369)
* chore: add mutations table * refactor: use codely for commands instead of codelytv * chore: remove symfony deprecations * chore: add command to publish mutations as domain events * fix: no finalize kernel classes
1 parent ecf8d27 commit de40b26

16 files changed

+297
-70
lines changed
 

‎apps/backoffice/backend/src/BackofficeBackendKernel.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public function getProjectDir(): string
3636
protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader): void
3737
{
3838
$container->addResource(new FileResource($this->getProjectDir() . '/config/bundles.php'));
39-
$container->setParameter('container.dumper.inline_class_loader', true);
39+
$container->setParameter('.container.dumper.inline_class_loader', true);
4040
$confDir = $this->getProjectDir() . '/config';
4141

4242
$loader->load($confDir . '/services' . self::CONFIG_EXTS, 'glob');

‎apps/backoffice/frontend/src/BackofficeFrontendKernel.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public function getProjectDir(): string
3636
protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader): void
3737
{
3838
$container->addResource(new FileResource($this->getProjectDir() . '/config/bundles.php'));
39-
$container->setParameter('container.dumper.inline_class_loader', true);
39+
$container->setParameter('.container.dumper.inline_class_loader', true);
4040
$confDir = $this->getProjectDir() . '/config';
4141

4242
$loader->load($confDir . '/services' . self::CONFIG_EXTS, 'glob');

‎apps/mooc/backend/src/Command/DomainEvents/MySql/ConsumeMySqlDomainEventsCommand.php

+3-5
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,17 @@
88
use CodelyTv\Shared\Infrastructure\Bus\Event\DomainEventSubscriberLocator;
99
use CodelyTv\Shared\Infrastructure\Bus\Event\MySql\MySqlDoctrineDomainEventsConsumer;
1010
use CodelyTv\Shared\Infrastructure\Doctrine\DatabaseConnections;
11+
use Symfony\Component\Console\Attribute\AsCommand;
1112
use Symfony\Component\Console\Command\Command;
1213
use Symfony\Component\Console\Input\InputArgument;
1314
use Symfony\Component\Console\Input\InputInterface;
1415
use Symfony\Component\Console\Output\OutputInterface;
1516

1617
use function Lambdish\Phunctional\pipe;
1718

19+
#[AsCommand(name: 'codely:domain-events:mysql:consume', description: 'Consume domain events from MySql',)]
1820
final class ConsumeMySqlDomainEventsCommand extends Command
1921
{
20-
protected static $defaultName = 'codelytv:domain-events:mysql:consume';
21-
2222
public function __construct(
2323
private readonly MySqlDoctrineDomainEventsConsumer $consumer,
2424
private readonly DatabaseConnections $connections,
@@ -29,9 +29,7 @@ public function __construct(
2929

3030
protected function configure(): void
3131
{
32-
$this
33-
->setDescription('Consume domain events from MySql')
34-
->addArgument('quantity', InputArgument::REQUIRED, 'Quantity of events to process');
32+
$this->addArgument('quantity', InputArgument::REQUIRED, 'Quantity of events to process');
3533
}
3634

3735
protected function execute(InputInterface $input, OutputInterface $output): int
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace CodelyTv\Apps\Mooc\Backend\Command\DomainEvents;
6+
7+
use CodelyTv\Mooc\Courses\Infrastructure\Cdc\DatabaseMutationToCourseCreatedDomainEvent;
8+
use CodelyTv\Shared\Domain\Bus\Event\EventBus;
9+
use CodelyTv\Shared\Infrastructure\Cdc\DatabaseMutationAction;
10+
use CodelyTv\Shared\Infrastructure\Cdc\DatabaseMutationToDomainEvent;
11+
use Doctrine\ORM\EntityManager;
12+
use RuntimeException;
13+
use Symfony\Component\Console\Attribute\AsCommand;
14+
use Symfony\Component\Console\Command\Command;
15+
use Symfony\Component\Console\Input\InputArgument;
16+
use Symfony\Component\Console\Input\InputInterface;
17+
use Symfony\Component\Console\Output\OutputInterface;
18+
19+
#[AsCommand(
20+
name: 'codely:domain-events:generate-from-mutations',
21+
description: 'Publish domain events from mutations',
22+
)]
23+
final class PublishDomainEventsFromMutationsCommand extends Command
24+
{
25+
private array $transformers;
26+
27+
public function __construct(
28+
private readonly EntityManager $entityManager,
29+
private readonly EventBus $eventBus
30+
) {
31+
parent::__construct();
32+
33+
$this->transformers = [
34+
'courses' => [
35+
DatabaseMutationAction::INSERT->value => DatabaseMutationToCourseCreatedDomainEvent::class,
36+
DatabaseMutationAction::UPDATE->value => null,
37+
DatabaseMutationAction::DELETE->value => null,
38+
],
39+
];
40+
}
41+
42+
protected function configure(): void
43+
{
44+
$this->addArgument('quantity', InputArgument::REQUIRED, 'Quantity of mutations to process');
45+
}
46+
47+
protected function execute(InputInterface $input, OutputInterface $output): int
48+
{
49+
$totalMutations = (int) $input->getArgument('quantity');
50+
51+
$this->entityManager->wrapInTransaction(function (EntityManager $entityManager) use ($totalMutations) {
52+
$mutations = $entityManager->getConnection()
53+
->executeQuery("SELECT * FROM mutations ORDER BY id ASC LIMIT $totalMutations FOR UPDATE")
54+
->fetchAllAssociative();
55+
56+
foreach ($mutations as $mutation) {
57+
$transformer = $this->findTransformer($mutation['table_name'], $mutation['operation']);
58+
59+
if ($transformer === null) {
60+
echo sprintf("Ignoring %s %s\n", $mutation['table_name'], $mutation['operation']);
61+
continue;
62+
}
63+
64+
$domainEvents = $transformer->transform($mutation);
65+
66+
$this->eventBus->publish(...$domainEvents);
67+
}
68+
69+
$entityManager->getConnection()->executeStatement(
70+
sprintf('DELETE FROM mutations WHERE id IN (%s)', implode(',', array_column($mutations, 'id')))
71+
);
72+
});
73+
74+
return 0;
75+
}
76+
77+
private function findTransformer(string $tableName, string $operation): ?DatabaseMutationToDomainEvent
78+
{
79+
if (!array_key_exists($tableName, $this->transformers) && array_key_exists(
80+
$operation,
81+
$this->transformers[$tableName]
82+
)) {
83+
throw new RuntimeException("Transformer not found for table $tableName and operation $operation");
84+
}
85+
86+
/** @var class-string<DatabaseMutationToDomainEvent>|null $class */
87+
$class = $this->transformers[$tableName][$operation];
88+
89+
return $class ? new $class() : null;
90+
}
91+
}

‎apps/mooc/backend/src/Command/DomainEvents/RabbitMq/ConfigureRabbitMqCommand.php

+5-7
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,18 @@
55
namespace CodelyTv\Apps\Mooc\Backend\Command\DomainEvents\RabbitMq;
66

77
use CodelyTv\Shared\Infrastructure\Bus\Event\RabbitMq\RabbitMqConfigurer;
8+
use Symfony\Component\Console\Attribute\AsCommand;
89
use Symfony\Component\Console\Command\Command;
910
use Symfony\Component\Console\Input\InputInterface;
1011
use Symfony\Component\Console\Output\OutputInterface;
1112
use Traversable;
1213

14+
#[AsCommand(
15+
name: 'codely:domain-events:rabbitmq:configure',
16+
description: 'Configure the RabbitMQ to allow publish & consume domain events',
17+
)]
1318
final class ConfigureRabbitMqCommand extends Command
1419
{
15-
protected static $defaultName = 'codelytv:domain-events:rabbitmq:configure';
16-
1720
public function __construct(
1821
private readonly RabbitMqConfigurer $configurer,
1922
private readonly string $exchangeName,
@@ -22,11 +25,6 @@ public function __construct(
2225
parent::__construct();
2326
}
2427

25-
protected function configure(): void
26-
{
27-
$this->setDescription('Configure the RabbitMQ to allow publish & consume domain events');
28-
}
29-
3028
protected function execute(InputInterface $input, OutputInterface $output): int
3129
{
3230
$this->configurer->configure($this->exchangeName, ...iterator_to_array($this->subscribers));

‎apps/mooc/backend/src/Command/DomainEvents/RabbitMq/ConsumeRabbitMqDomainEventsCommand.php

+5-3
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,20 @@
77
use CodelyTv\Shared\Infrastructure\Bus\Event\DomainEventSubscriberLocator;
88
use CodelyTv\Shared\Infrastructure\Bus\Event\RabbitMq\RabbitMqDomainEventsConsumer;
99
use CodelyTv\Shared\Infrastructure\Doctrine\DatabaseConnections;
10+
use Symfony\Component\Console\Attribute\AsCommand;
1011
use Symfony\Component\Console\Command\Command;
1112
use Symfony\Component\Console\Input\InputArgument;
1213
use Symfony\Component\Console\Input\InputInterface;
1314
use Symfony\Component\Console\Output\OutputInterface;
1415

1516
use function Lambdish\Phunctional\repeat;
1617

18+
#[AsCommand(
19+
name: 'codely:domain-events:rabbitmq:consume',
20+
description: 'Consume domain events from the RabbitMQ',
21+
)]
1722
final class ConsumeRabbitMqDomainEventsCommand extends Command
1823
{
19-
protected static $defaultName = 'codelytv:domain-events:rabbitmq:consume';
20-
2124
public function __construct(
2225
private readonly RabbitMqDomainEventsConsumer $consumer,
2326
private readonly DatabaseConnections $connections,
@@ -29,7 +32,6 @@ public function __construct(
2932
protected function configure(): void
3033
{
3134
$this
32-
->setDescription('Consume domain events from the RabbitMQ')
3335
->addArgument('queue', InputArgument::REQUIRED, 'Queue name')
3436
->addArgument('quantity', InputArgument::REQUIRED, 'Quantity of events to process');
3537
}

‎apps/mooc/backend/src/Command/DomainEvents/RabbitMq/GenerateSupervisorRabbitMqConsumerFilesCommand.php

+7-5
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,23 @@
77
use CodelyTv\Shared\Domain\Bus\Event\DomainEventSubscriber;
88
use CodelyTv\Shared\Infrastructure\Bus\Event\DomainEventSubscriberLocator;
99
use CodelyTv\Shared\Infrastructure\Bus\Event\RabbitMq\RabbitMqQueueNameFormatter;
10+
use Symfony\Component\Console\Attribute\AsCommand;
1011
use Symfony\Component\Console\Command\Command;
1112
use Symfony\Component\Console\Input\InputArgument;
1213
use Symfony\Component\Console\Input\InputInterface;
1314
use Symfony\Component\Console\Output\OutputInterface;
1415

1516
use function Lambdish\Phunctional\each;
1617

18+
#[AsCommand(
19+
name: 'codely:domain-events:rabbitmq:generate-supervisor-files',
20+
description: 'Generate the supervisor configuration for every RabbitMQ subscriber',
21+
)]
1722
final class GenerateSupervisorRabbitMqConsumerFilesCommand extends Command
1823
{
1924
private const EVENTS_TO_PROCESS_AT_TIME = 200;
2025
private const NUMBERS_OF_PROCESSES_PER_SUBSCRIBER = 1;
2126
private const SUPERVISOR_PATH = __DIR__ . '/../../../../build/supervisor';
22-
protected static $defaultName = 'codelytv:domain-events:rabbitmq:generate-supervisor-files';
2327

2428
public function __construct(private readonly DomainEventSubscriberLocator $locator)
2529
{
@@ -28,9 +32,7 @@ public function __construct(private readonly DomainEventSubscriberLocator $locat
2832

2933
protected function configure(): void
3034
{
31-
$this
32-
->setDescription('Generate the supervisor configuration for every RabbitMQ subscriber')
33-
->addArgument('command-path', InputArgument::OPTIONAL, 'Path on this is gonna be deployed', '/var/www');
35+
$this->addArgument('command-path', InputArgument::OPTIONAL, 'Path on this is gonna be deployed', '/var/www');
3436
}
3537

3638
protected function execute(InputInterface $input, OutputInterface $output): int
@@ -68,7 +70,7 @@ private function template(): string
6870
{
6971
return <<<EOF
7072
[program:codelytv_{queue_name}]
71-
command = {path}/apps/mooc/backend/bin/console codelytv:domain-events:rabbitmq:consume --env=prod {queue_name} {events_to_process}
73+
command = {path}/apps/mooc/backend/bin/console codely:domain-events:rabbitmq:consume --env=prod {queue_name} {events_to_process}
7274
process_name = %(program_name)s_%(process_num)02d
7375
numprocs = {processes}
7476
startsecs = 1

‎apps/mooc/backend/src/MoocBackendKernel.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public function getProjectDir(): string
3636
protected function configureContainer(ContainerBuilder $container, LoaderInterface $loader): void
3737
{
3838
$container->addResource(new FileResource($this->getProjectDir() . '/config/bundles.php'));
39-
$container->setParameter('container.dumper.inline_class_loader', true);
39+
$container->setParameter('.container.dumper.inline_class_loader', true);
4040
$confDir = $this->getProjectDir() . '/config';
4141

4242
$loader->load($confDir . '/services' . self::CONFIG_EXTS, 'glob');

‎ecs.php

+14-18
Original file line numberDiff line numberDiff line change
@@ -7,24 +7,20 @@
77
use Symplify\EasyCodingStandard\Config\ECSConfig;
88

99
return function (ECSConfig $ecsConfig): void {
10-
$ecsConfig->paths([
11-
__DIR__ . '/apps',
12-
__DIR__ . '/src',
13-
__DIR__ . '/tests',
14-
]);
10+
$ecsConfig->paths([__DIR__ . '/apps', __DIR__ . '/src', __DIR__ . '/tests', ]);
1511

16-
$ecsConfig->sets([CodingStyle::DEFAULT]);
12+
$ecsConfig->sets([CodingStyle::DEFAULT]);
1713

18-
$ecsConfig->skip([
19-
FinalClassFixer::class => [
20-
__DIR__ . '/apps/backoffice/backend/src/BackofficeBackendKernel.php',
21-
__DIR__ . '/apps/backoffice/frontend/src/BackofficeFrontendKernel.php',
22-
__DIR__ . '/apps/mooc/backend/src/MoocBackendKernel.php',
23-
__DIR__ . '/src/Shared/Infrastructure/Bus/Event/InMemory/InMemorySymfonyEventBus.php',
24-
],
25-
__DIR__ . '/apps/backoffice/backend/var',
26-
__DIR__ . '/apps/backoffice/frontend/var',
27-
__DIR__ . '/apps/mooc/backend/var',
28-
__DIR__ . '/apps/mooc/frontend/var',
29-
]);
14+
$ecsConfig->skip([
15+
FinalClassFixer::class => [
16+
__DIR__ . '/apps/backoffice/backend/src/BackofficeBackendKernel.php',
17+
__DIR__ . '/apps/backoffice/frontend/src/BackofficeFrontendKernel.php',
18+
__DIR__ . '/apps/mooc/backend/src/MoocBackendKernel.php',
19+
__DIR__ . '/src/Shared/Infrastructure/Bus/Event/InMemory/InMemorySymfonyEventBus.php',
20+
],
21+
__DIR__ . '/apps/backoffice/backend/var',
22+
__DIR__ . '/apps/backoffice/frontend/var',
23+
__DIR__ . '/apps/mooc/backend/var',
24+
__DIR__ . '/apps/mooc/frontend/var',
25+
]);
3026
};

‎etc/databases/mooc.sql

+88-24
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,95 @@
1+
/* -------------------------
2+
MOOC CONTEXT
3+
---------------------------- */
4+
5+
-- Generic tables
6+
7+
CREATE TABLE `mutations` (
8+
`id` BIGINT AUTO_INCREMENT PRIMARY KEY,
9+
`table_name` VARCHAR(255) NOT NULL,
10+
`operation` ENUM ('INSERT', 'UPDATE', 'DELETE') NOT NULL,
11+
`old_value` JSON NULL,
12+
`new_value` JSON NULL,
13+
`mutation_timestamp` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
14+
) ENGINE = InnoDB
15+
DEFAULT CHARSET = utf8mb4
16+
COLLATE = utf8mb4_unicode_ci;
17+
18+
CREATE TABLE `domain_events` (
19+
`id` CHAR(36) NOT NULL,
20+
`aggregate_id` CHAR(36) NOT NULL,
21+
`name` VARCHAR(255) NOT NULL,
22+
`body` JSON NOT NULL,
23+
`occurred_on` TIMESTAMP NOT NULL,
24+
PRIMARY KEY (`id`)
25+
) ENGINE = InnoDB
26+
DEFAULT CHARSET = utf8mb4
27+
COLLATE = utf8mb4_unicode_ci;
28+
29+
-- Aggregates tables
30+
131
CREATE TABLE `courses` (
2-
`id` CHAR(36) NOT NULL,
3-
`name` VARCHAR(255) NOT NULL,
4-
`duration` VARCHAR(255) NOT NULL,
5-
PRIMARY KEY (`id`)
6-
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
32+
`id` CHAR(36) NOT NULL,
33+
`name` VARCHAR(255) NOT NULL,
34+
`duration` VARCHAR(255) NOT NULL,
35+
PRIMARY KEY (`id`)
36+
) ENGINE = InnoDB
37+
DEFAULT CHARSET = utf8mb4
38+
COLLATE = utf8mb4_unicode_ci;
39+
40+
CREATE TRIGGER after_courses_insert
41+
AFTER INSERT
42+
ON courses
43+
FOR EACH ROW
44+
BEGIN
45+
INSERT INTO mutations (table_name, operation, new_value, mutation_timestamp)
46+
VALUES ('courses', 'INSERT', JSON_OBJECT('id', new.id, 'name', new.name, 'duration', new.duration), NOW());
47+
END;
48+
49+
CREATE TRIGGER after_courses_update
50+
AFTER UPDATE
51+
ON courses
52+
FOR EACH ROW
53+
BEGIN
54+
INSERT INTO mutations (table_name, operation, old_value, new_value, mutation_timestamp)
55+
VALUES ('courses',
56+
'UPDATE',
57+
JSON_OBJECT('id', old.id, 'name', old.name, 'duration', old.duration),
58+
JSON_OBJECT('id', new.id, 'name', new.name, 'duration', new.duration),
59+
NOW());
60+
END;
61+
62+
CREATE TRIGGER after_courses_delete
63+
AFTER DELETE
64+
ON courses
65+
FOR EACH ROW
66+
BEGIN
67+
INSERT INTO mutations (table_name, operation, old_value, mutation_timestamp)
68+
VALUES ('courses', 'DELETE', JSON_OBJECT('id', old.id, 'name', old.name, 'duration', old.duration), NOW());
69+
END;
770

871
CREATE TABLE `courses_counter` (
9-
`id` CHAR(36) NOT NULL,
10-
`total` INT NOT NULL,
11-
`existing_courses` JSON NOT NULL,
12-
PRIMARY KEY (`id`)
13-
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
72+
`id` CHAR(36) NOT NULL,
73+
`total` INT NOT NULL,
74+
`existing_courses` JSON NOT NULL,
75+
PRIMARY KEY (`id`)
76+
) ENGINE = InnoDB
77+
DEFAULT CHARSET = utf8mb4
78+
COLLATE = utf8mb4_unicode_ci;
1479

15-
INSERT INTO `courses_counter` VALUES ("cdf26d7d-3deb-4e8c-9f73-4ac085a8d6f3", 0, "[]");
80+
INSERT INTO `courses_counter`
81+
VALUES ("cdf26d7d-3deb-4e8c-9f73-4ac085a8d6f3", 0, "[]");
1682

17-
CREATE TABLE `domain_events` (
18-
`id` CHAR(36) NOT NULL,
19-
`aggregate_id` CHAR(36) NOT NULL,
20-
`name` VARCHAR(255) NOT NULL,
21-
`body` JSON NOT NULL,
22-
`occurred_on` timestamp NOT NULL,
23-
PRIMARY KEY (`id`)
24-
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
83+
84+
/* -------------------------
85+
BACKOFFICE CONTEXT
86+
---------------------------- */
2587

2688
CREATE TABLE `backoffice_courses` (
27-
`id` CHAR(36) NOT NULL,
28-
`name` VARCHAR(255) NOT NULL,
29-
`duration` VARCHAR(255) NOT NULL,
30-
PRIMARY KEY (`id`)
31-
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
89+
`id` CHAR(36) NOT NULL,
90+
`name` VARCHAR(255) NOT NULL,
91+
`duration` VARCHAR(255) NOT NULL,
92+
PRIMARY KEY (`id`)
93+
) ENGINE = InnoDB
94+
DEFAULT CHARSET = utf8mb4
95+
COLLATE = utf8mb4_unicode_ci;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace CodelyTv\Mooc\Courses\Infrastructure\Cdc;
6+
7+
use CodelyTv\Mooc\Courses\Domain\CourseCreatedDomainEvent;
8+
use CodelyTv\Shared\Domain\Bus\Event\DomainEvent;
9+
use CodelyTv\Shared\Domain\Utils;
10+
use CodelyTv\Shared\Infrastructure\Cdc\DatabaseMutationAction;
11+
use CodelyTv\Shared\Infrastructure\Cdc\DatabaseMutationToDomainEvent;
12+
13+
final class DatabaseMutationToCourseCreatedDomainEvent implements DatabaseMutationToDomainEvent
14+
{
15+
/** @return DomainEvent[] */
16+
public function transform(array $data): array
17+
{
18+
$mutation = Utils::jsonDecode($data['new_value']);
19+
20+
return [
21+
new CourseCreatedDomainEvent(
22+
$mutation['id'],
23+
$mutation['name'],
24+
$mutation['duration'],
25+
null,
26+
$data['mutation_timestamp'],
27+
),
28+
];
29+
}
30+
31+
public function tableName(): string
32+
{
33+
return 'courses';
34+
}
35+
36+
public function mutationAction(): DatabaseMutationAction
37+
{
38+
return DatabaseMutationAction::INSERT;
39+
}
40+
}

‎src/Mooc/CoursesCounter/Infrastructure/Persistence/Doctrine/CourseIdsType.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,12 @@ public function getName(): string
2323
return self::customTypeName();
2424
}
2525

26-
public function convertToDatabaseValue($value, AbstractPlatform $platform)
26+
public function convertToDatabaseValue($value, AbstractPlatform $platform): ?string
2727
{
2828
return parent::convertToDatabaseValue(map(fn (CourseId $id): string => $id->value(), $value), $platform);
2929
}
3030

31-
public function convertToPHPValue($value, AbstractPlatform $platform)
31+
public function convertToPHPValue($value, AbstractPlatform $platform): array
3232
{
3333
$scalars = parent::convertToPHPValue($value, $platform);
3434

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace CodelyTv\Shared\Infrastructure\Cdc;
6+
7+
enum DatabaseMutationAction: string
8+
{
9+
case INSERT = 'INSERT';
10+
case UPDATE = 'UPDATE';
11+
case DELETE = 'DELETE';
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace CodelyTv\Shared\Infrastructure\Cdc;
6+
7+
use CodelyTv\Shared\Domain\Bus\Event\DomainEvent;
8+
9+
interface DatabaseMutationToDomainEvent
10+
{
11+
/** @return DomainEvent[] */
12+
public function transform(array $data): array;
13+
14+
public function tableName(): string;
15+
16+
public function mutationAction(): DatabaseMutationAction;
17+
}

‎src/Shared/Infrastructure/Doctrine/DoctrineEntityManagerFactory.php

+8-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
use Doctrine\Common\EventManager;
99
use Doctrine\DBAL\DriverManager;
1010
use Doctrine\DBAL\Platforms\MariaDBPlatform;
11+
use Doctrine\DBAL\Schema\DefaultSchemaManagerFactory;
1112
use Doctrine\DBAL\Schema\MySQLSchemaManager;
1213
use Doctrine\ORM\Configuration;
1314
use Doctrine\ORM\EntityManager;
@@ -37,8 +38,13 @@ public static function create(
3738
DbalCustomTypesRegistrar::register($dbalCustomTypesClasses);
3839

3940
$config = self::createConfiguration($contextPrefixes, $isDevMode);
41+
$eventManager = new EventManager();
4042

41-
return new EntityManager(DriverManager::getConnection($parameters, $config, new EventManager()), $config);
43+
return new EntityManager(
44+
DriverManager::getConnection($parameters, $config, $eventManager),
45+
$config,
46+
$eventManager
47+
);
4248
}
4349

4450
private static function generateDatabaseIfNotExists(array $parameters, string $schemaFile): void
@@ -78,6 +84,7 @@ private static function createConfiguration(array $contextPrefixes, bool $isDevM
7884
$config = ORMSetup::createConfiguration($isDevMode);
7985

8086
$config->setMetadataDriverImpl(new SimplifiedXmlDriver(array_merge(self::$sharedPrefixes, $contextPrefixes)));
87+
$config->setSchemaManagerFactory(new DefaultSchemaManagerFactory());
8188

8289
return $config;
8390
}

‎src/Shared/Infrastructure/Persistence/Doctrine/UuidType.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,14 @@ final public function getName(): string
2626
return self::customTypeName();
2727
}
2828

29-
final public function convertToPHPValue($value, AbstractPlatform $platform)
29+
final public function convertToPHPValue($value, AbstractPlatform $platform): mixed
3030
{
3131
$className = $this->typeClassName();
3232

3333
return new $className($value);
3434
}
3535

36-
final public function convertToDatabaseValue($value, AbstractPlatform $platform)
36+
final public function convertToDatabaseValue($value, AbstractPlatform $platform): string
3737
{
3838
/** @var Uuid $value */
3939
return $value->value();

0 commit comments

Comments
 (0)
Please sign in to comment.