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:
| Folder | Responsibility |
|---|---|
| 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-datawhen 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
| Type | Naming | Example |
|---|---|---|
| Action | {Verb}{Noun}Action | CreateInvoiceAction |
| Service | {Noun}Service | ClientService |
| DTO | {Noun}Data | SubscriberData |
| ViewModel | Get{Something}ViewModel | GetDashboardViewModel |
| Value Object | {Concept} | Percent, Money |
| State | {State}{Entity}Status | PaidOrderStatus |
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.
