Weave Code
Code Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Ddd Core Laravel Package

becklyn/ddd-core

DDD/CQRS/event-sourcing core building blocks for PHP: entity identities, domain events, command handling, transactions, and an event store workflow. Framework-agnostic abstractions with Symfony/Doctrine/SimpleBus bridge packages available.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimal Steps to First Use

  1. Install the Core Package and Bridges

    composer require becklyn/ddd-core
    composer require becklyn/ddd-doctrine-bridge becklyn/ddd-symfony-bridge
    

    For Laravel, prefer becklyn/ddd-symfony-bridge (Symfony-compatible) and adapt bindings manually.

  2. Define a Domain Event

    namespace App\Domain\Events;
    
    use Becklyn\Ddd\Events\AbstractDomainEvent;
    
    class OrderCreated extends AbstractDomainEvent
    {
        public function __construct(
            public string $orderId,
            public string $customerId,
            public float $totalAmount
        ) {}
    }
    
  3. Create an Aggregate Root

    namespace App\Domain\Orders;
    
    use Becklyn\Ddd\Entities\AbstractAggregateId;
    use Becklyn\Ddd\Entities\EventSourcedProviderCapabilities;
    use Becklyn\Ddd\Events\DomainEvent;
    
    class OrderId extends AbstractAggregateId {}
    
    class Order
    {
        use EventSourcedProviderCapabilities;
    
        public function __construct(
            private OrderId $id,
            private string $customerId,
            private array $items = []
        ) {}
    
        public function addItem(string $productId, int $quantity): void
        {
            $this->items[] = [$productId, $quantity];
            $this->recordThat(new OrderItemAdded($this->id->value(), $productId, $quantity));
        }
    }
    
  4. Write a Command and Handler

    // Command
    namespace App\Domain\Orders\Commands;
    class AddItemToOrder implements CommandInterface
    {
        public function __construct(
            public OrderId $orderId,
            public string $productId,
            public int $quantity
        ) {}
    }
    
    // Handler
    namespace App\Domain\Orders\Handlers;
    use Becklyn\Ddd\Commands\CommandHandler;
    
    class AddItemToOrderHandler extends CommandHandler
    {
        public function execute(AddItemToOrder $command): ?Order
        {
            $order = $this->orderRepository->find($command->orderId);
            $order->addItem($command->productId, $command->quantity);
            return $order;
        }
    }
    
  5. Dispatch the Command

    use Becklyn\Ddd\Commands\CommandBus;
    
    $commandBus = app(CommandBus::class);
    $commandBus->dispatch(new AddItemToOrder(
        new OrderId('order-123'),
        'prod-456',
        2
    ));
    
  6. Configure Symfony/Laravel Bindings

    • For Symfony: Use becklyn/ddd-symfony-bridge’s CommandBus and EventBus services.
    • For Laravel: Bind interfaces manually in AppServiceProvider:
      $this->app->bind(CommandBus::class, function ($app) {
          return new SimpleBusCommandBus($app->make(CommandHandler::class));
      });
      

Implementation Patterns

Core Workflow Integration

  1. Command-Driven Architecture

    • Pattern: One command → One handler → One aggregate.
    • Laravel Tip: Use Illuminate\Bus\DispatchesCommands trait for controllers:
      use Illuminate\Bus\DispatchesCommands;
      use App\Domain\Orders\Commands\AddItemToOrder;
      
      class OrderController
      {
          use DispatchesCommands;
      
          public function addItem(OrderId $orderId, string $productId, int $quantity)
          {
              $this->dispatch(new AddItemToOrder($orderId, $productId, $quantity));
          }
      }
      
  2. Event Sourcing with Projections

    • Pattern: Rebuild aggregates from events via projections (e.g., Doctrine entities or read models).
    • Example:
      // Projection for read-optimized Order
      namespace App\Domain\Orders\Projections;
      use Becklyn\Ddd\EventSourcing\Projection;
      
      class OrderProjection implements Projection
      {
          public function apply(OrderCreated $event): void
          {
              // Update read-optimized Order entity
          }
      }
      
  3. Saga Orchestration

    • Pattern: Use event subscribers to chain commands for long-running workflows.
    • Example:
      namespace App\Domain\Orders\Subscribers;
      use Becklyn\Ddd\Events\EventSubscriber;
      use App\Domain\Orders\Commands\ProcessPayment;
      
      class PaymentSubscriber implements EventSubscriber
      {
          public static function subscribedTo(): array
          {
              return [OrderCreated::class];
          }
      
          public function handle(OrderCreated $event)
          {
              $this->commandBus->dispatch(new ProcessPayment(
                  new OrderId($event->orderId),
                  $event->totalAmount
              ));
          }
      }
      
  4. Transaction Boundaries

    • Pattern: Wrap command handling in a transaction (Doctrine bridge handles this automatically).
    • Laravel Note: Use DB::transaction() for custom logic:
      public function execute(Command $command)
      {
          DB::transaction(function () use ($command) {
              // Handle command
          });
      }
      
  5. Testing with BDD Traits

    • Pattern: Use provided traits for PHPUnit tests:
      use Becklyn\Ddd\Commands\Testing\CommandHandlerTestTrait;
      
      class AddItemToOrderHandlerTest
      {
          use CommandHandlerTestTrait;
      
          public function testAddItem()
          {
              $this->givenAnOrderExists('order-123');
              $this->whenHandling(new AddItemToOrder(
                  new OrderId('order-123'),
                  'prod-456',
                  2
              ));
              $this->thenAnEventWasRecorded(OrderItemAdded::class);
          }
      }
      

Gotchas and Tips

Pitfalls

  1. Aggregate Loading Performance

    • Issue: Loading aggregates via EventStore::getAggregateStream() is slow for large datasets.
    • Fix: Use projections or caching (e.g., Redis) for read-heavy workflows.
    • Example:
      $aggregate = $this->eventStore->getAggregateStream(Order::class, $orderId);
      // Cache result for 5 minutes
      Cache::put("order:$orderId", $aggregate, now()->addMinutes(5));
      
  2. Event Ordering in Subscribers

    • Issue: Subscribers may process events out of order if not using a queue.
    • Fix: Use Symfony’s SimpleBus with a queue (e.g., RabbitMQ) or Laravel’s queue system.
    • Config:
      # config/packages/simple_bus.yaml
      simple_bus:
          transports:
              default: 'doctrine://default'
      
  3. Correlation IDs in Commands

    • Issue: Forgetting to set correlationId on commands can break saga tracking.
    • Fix: Use a middleware to inject correlation IDs:
      namespace App\Domain\Commands\Middleware;
      use Becklyn\Ddd\Commands\CommandInterface;
      
      class CorrelationIdMiddleware
      {
          public function __invoke(CommandInterface $command)
          {
              $command->setCorrelationId(Uuid::generate());
              return $command;
          }
      }
      
  4. Event Replay Conflicts

    • Issue: Replaying events on an aggregate with stale state causes conflicts.
    • Fix: Implement EventSourcedProviderCapabilities::apply() to handle conflicts:
      protected function apply(DomainEvent $event): void
      {
          if ($event instanceof OrderItemAdded && $this->items[$event->productId] !== null) {
              throw new EventReplayConflictException();
          }
          // Apply event logic
      }
      
  5. Doctrine EntityManager Leaks

    • Issue: Forgetting to clear the EventManager after rollbacks can cause stale events.
    • Fix: Always call $eventManager->clear() in TransactionManager::rollback():
      public function rollback(): void
      {
          $this->entityManager->rollback();
          $this->eventManager->clear();
      }
      

Tips

  1. Laravel-Specific Optimizations

    • Tip: Use Laravel’s Queue for async event handling:
      namespace App\Domain\Events\Handlers;
      use Becklyn\Ddd\Events\EventSubscriber;
      use Illuminate\Bus\Queueable;
      
      class AsyncOrderSubscriber implements EventSubscriber, Queueable
      {
          // ...
      }
      
  2. Custom Event Store

    • Tip: Implement a lightweight event store for testing:
      namespace Tests\EventStore;
      use Becklyn\Ddd\EventSourcing\EventStore;
      
      class InMemoryEventStore implements EventStore
      {
          private array $streams = [];
      
          public function load(string $aggregateType, string $aggregateId): array
          {
              return $this
      
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
besmartand-pro/php-quality-config
sentix/ai-chatbot
codifyo/ts-generator-bundle
mintobit/jobqueue
a4sex/maintenance-bundle
a4sex/entity-date-update
a4sex/client-identifier
a4sex/base-utilites
a4sex/key-value-storage
a4sex/micro-status
chilldev/dependency-injection-extra
datinglibre/datinglibre-app-api
biberltd/corebundle
bricre/symfony-bundle-test
biberltd/logbundle
dominium/http-adapter-bundle
dominium/google-analytics
a4sex/auto-clean-entity
christhompsontldr/laravel-inky
spatie/mailcoach-vapor