feat(core): Implement DDD, CQRS, and Event Sourcing architecture
- Add project README and Composer configuration with PSR-4 autoloading - Implement domain layer with AggregateRoot base class for event sourcing - Create Carrier aggregate with CarrierRegistered domain event - Add application layer with RegisterCarrierCommand and RegisterCarrierHandler - Implement infrastructure layer with EventStore interface and CassandraEventStore - Add CommandBus and Router for request handling and routing - Create demo test file to showcase carrier registration workflow - Establish foundation for event-driven architecture with Cassandra persistence
This commit is contained in:
71
src/Infrastructure/CassandraEventStore.php
Normal file
71
src/Infrastructure/CassandraEventStore.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace DistributingCarriers\Infrastructure;
|
||||
|
||||
use Cassandra;
|
||||
|
||||
class CassandraEventStore implements EventStore
|
||||
{
|
||||
private $session;
|
||||
|
||||
public function __construct($session)
|
||||
{
|
||||
$this->session = $session;
|
||||
}
|
||||
|
||||
public function save(string $aggregateId, array $events, int $expectedVersion): void
|
||||
{
|
||||
// In a real implementation, we would check the version for concurrency control.
|
||||
// For this example, we'll just append.
|
||||
|
||||
$statement = $this->session->prepare(
|
||||
"INSERT INTO events (aggregate_id, version, event_type, payload, created_at) VALUES (?, ?, ?, ?, ?)"
|
||||
);
|
||||
|
||||
foreach ($events as $event) {
|
||||
$version = ++$expectedVersion;
|
||||
$payload = json_encode($event);
|
||||
$eventType = get_class($event);
|
||||
$createdAt = new Cassandra\Timestamp();
|
||||
|
||||
$this->session->execute($statement, [
|
||||
'arguments' => [
|
||||
$aggregateId,
|
||||
$version,
|
||||
$eventType,
|
||||
$payload,
|
||||
$createdAt
|
||||
]
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function getEventsForAggregate(string $aggregateId): array
|
||||
{
|
||||
$statement = $this->session->prepare(
|
||||
"SELECT event_type, payload FROM events WHERE aggregate_id = ?"
|
||||
);
|
||||
|
||||
$result = $this->session->execute($statement, ['arguments' => [$aggregateId]]);
|
||||
|
||||
$events = [];
|
||||
foreach ($result as $row) {
|
||||
$eventType = $row['event_type'];
|
||||
$payload = json_decode($row['payload'], true);
|
||||
|
||||
// Assuming we can reconstruct the event object from payload
|
||||
// This is a simplified version.
|
||||
if (class_exists($eventType)) {
|
||||
// In a real app, you might use a serializer/deserializer
|
||||
// Here we assume the event has a static fromArray or similar, or just public properties
|
||||
// For simplicity, let's assume we just return the data or a generic object if class doesn't handle it
|
||||
// But for the plan, let's try to instantiate.
|
||||
// We'll leave it as an array or basic object for now if complex reconstruction is needed.
|
||||
// Let's assume the event class has a constructor that takes the payload.
|
||||
$events[] = new $eventType($payload);
|
||||
}
|
||||
}
|
||||
|
||||
return $events;
|
||||
}
|
||||
}
|
||||
24
src/Infrastructure/CommandBus.php
Normal file
24
src/Infrastructure/CommandBus.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace DistributingCarriers\Infrastructure;
|
||||
|
||||
class CommandBus
|
||||
{
|
||||
private $handlers = [];
|
||||
|
||||
public function register(string $commandClass, callable $handler): void
|
||||
{
|
||||
$this->handlers[$commandClass] = $handler;
|
||||
}
|
||||
|
||||
public function dispatch(object $command): void
|
||||
{
|
||||
$commandClass = get_class($command);
|
||||
if (!isset($this->handlers[$commandClass])) {
|
||||
throw new \Exception("No handler registered for command: " . $commandClass);
|
||||
}
|
||||
|
||||
$handler = $this->handlers[$commandClass];
|
||||
$handler($command);
|
||||
}
|
||||
}
|
||||
9
src/Infrastructure/EventStore.php
Normal file
9
src/Infrastructure/EventStore.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace DistributingCarriers\Infrastructure;
|
||||
|
||||
interface EventStore
|
||||
{
|
||||
public function save(string $aggregateId, array $events, int $expectedVersion): void;
|
||||
public function getEventsForAggregate(string $aggregateId): array;
|
||||
}
|
||||
29
src/Infrastructure/Router.php
Normal file
29
src/Infrastructure/Router.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
|
||||
namespace DistributingCarriers\Infrastructure;
|
||||
|
||||
class Router
|
||||
{
|
||||
private $routes = [];
|
||||
|
||||
public function add(string $method, string $path, callable $handler): void
|
||||
{
|
||||
$this->routes[] = [
|
||||
'method' => $method,
|
||||
'path' => $path,
|
||||
'handler' => $handler
|
||||
];
|
||||
}
|
||||
|
||||
public function dispatch(string $method, string $uri)
|
||||
{
|
||||
foreach ($this->routes as $route) {
|
||||
if ($route['method'] === $method && $route['path'] === $uri) {
|
||||
return call_user_func($route['handler']);
|
||||
}
|
||||
}
|
||||
|
||||
http_response_code(404);
|
||||
echo "Not Found";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user