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-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
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.