MCP Agents Guide: Laravel & Domain Driven Design (DDD) Rules

Agents.md — Domain-Driven Development Guide for Laravel

This document defines how engineers should design and implement agents (domains, actions, and data flows) following Domain-Driven Design (DDD) principles within a Laravel application.

Copy this into your project’s docs/agents/domain-driven-design.md and adapt as needed.

1. Purpose

An agent represents a coherent business capability — for example, Subscriber, Broadcast, Invoice, or Automation. Each agent encapsulates all logic, data, and rules that belong to its business area.

Goal: Align code with business language — your code should read like the product owner’s vocabulary.

2. Agent Structure

Every agent lives under app/Domain/{AgentName} and contains:

Domain/
 └── AgentName/
      ├── Actions/
      ├── DTOs/
      ├── Models/
      ├── Services/
      ├── ViewModels/
      ├── ValueObjects/
      └── Support/

Each folder has a clear purpose:

FolderResponsibility
Actions/Encapsulate one user story or business command
DTOs/Typed data objects for communication between layers
Models/Eloquent models representing entities
Services/Business logic or external API integration
ViewModels/Read/query models that prepare response data
ValueObjects/Immutable representations of scalar domain values
Support/Shared helpers, enums, states, or transitions

3. Core Rules

1. Business-First Naming

Use the same language the business uses. Rename technical terms:

User → Customer
Job → Broadcast
Task → AutomationStep

2. Value Objects (VO)

  • Represent immutable, ID-less business values.
  • Contain small, focused logic (formatting, normalization).
  • Never modify in place; always return new instances.
final class Percent
{
    public function __construct(public readonly float $value) {}

    public function formatted(): string => number_format($this->value * 100, 2).'%';
}

Rule: “If it represents a value, not an entity, make it a Value Object.”

3. Data Transfer Objects (DTO)

  • Structure untyped data (array $data) into objects.
  • Move data safely between layers (Controllers → Services → Actions).
  • Use spatie/laravel-data when possible.
final class CourseData extends Data
{
    public function __construct(
        public readonly string $title,
        public readonly string $description,
        /** @var Collection<LessonData> */
        public readonly Collection $lessons
    ) {}
}

Rule: “All communication between layers happens through DTOs.”

4. Actions

  • Describe a single user story.
  • Contain one public execute() or __invoke() method.
  • Return a DTO or Value Object, not raw models.
final class CreateSubscriberAction
{
    public static function execute(SubscriberData $data): SubscriberData
    {
        $subscriber = Subscriber::create($data->all());

        return SubscriberData::from($subscriber);
    }
}

Rule: “Each action = one business operation.”

5. Services

  • Implement reusable domain logic or integrate with external APIs.
  • Avoid overlapping with Actions — Services should not represent user stories.
final class ClientService
{
    public function getById(int $id): ClientData
    {
        return ClientData::from(Client::findOrFail($id));
    }
}

Rule: “Use Services for cross-domain or external concerns.”

6. ViewModels

  • Represent read-side data for views or APIs.
  • Contain only query logic — no writes.
  • Return arrays or DTOs.
final class GetDashboardViewModel extends ViewModel
{
    public function newSubscribersCount(): NewSubscribersCountData
    {
        return new NewSubscribersCountData(
            today: Subscriber::today()->count(),
            total: Subscriber::count()
        );
    }
}

Rule: “Every screen or API response = one ViewModel.”

7. CQRS

Separate writes (commands) and reads (queries) clearly:

  • Commands → Actions
  • Queries → ViewModels

Rule: “Never mix write and read responsibilities in one class.”

8. States & Transitions

Represent lifecycle logic explicitly.

abstract class OrderStatus {
    abstract public function canBeChanged(): bool;
}

final class PaidOrderStatus extends OrderStatus {
    public function canBeChanged(): bool => false;
}

Rule: “If you find yourself comparing strings for state, use state classes.”

4. Boundaries Between Agents

  • Never pass Eloquent models across domains.
  • Expose DTOs or Service APIs, not models.
  • Avoid foreign keys that cross domain boundaries unless absolutely required.
  • Keep each agent independently testable and deployable.

Rule: “Domains communicate through DTOs, not relationships.”

5. Recommended File Patterns

TypeNamingExample
Action{Verb}{Noun}ActionCreateInvoiceAction
Service{Noun}ServiceClientService
DTO{Noun}DataSubscriberData
ViewModelGet{Something}ViewModelGetDashboardViewModel
Value Object{Concept}Percent, Money
State{State}{Entity}StatusPaidOrderStatus

6. Practical Guidelines

  • Keep models thin, actions focused, DTOs explicit.
  • Avoid static helpers and global state.
  • Keep business rules in the domain, not the controller.
  • Prefer expressiveness over brevity.
  • Each domain should feel like a mini-app.

7. Example Agent Layout

app/Domain/Subscriber/
 ├── Actions/
 │    ├── CreateSubscriberAction.php
 │    └── UpdateSubscriberAction.php
 ├── DTOs/
 │    └── SubscriberData.php
 ├── Models/
 │    └── Subscriber.php
 ├── Services/
 │    └── ImportSubscriberService.php
 ├── ViewModels/
 │    └── GetSubscriberStatsViewModel.php
 ├── ValueObjects/
 │    └── EmailAddress.php
 └── Support/
      └── States/
          └── SubscriptionStatus.php

8. Checklist Before Shipping

  • Every public method maps to a business action
  • DTOs replace arrays in all inter-layer communication
  • No direct cross-domain Eloquent relationships
  • Domain folder names match business language
  • Value Objects cover reusable primitives
  • Tests target actions and view-models
  • Read/Write responsibilities are clearly separated

TL;DR — Agent Design Mantra

Speak the business language

Model reality, not databases.

Each action tells a story.

Each domain stands alone.