Loading...
Loading...
Domain-oriented architecture for Laravel without external package dependencies, based on concepts from "Laravel Beyond CRUD" (Spatie). Use this skill when the user wants to structure a Laravel project using DDD, domain-oriented architecture, or organize code by business concepts instead of technical properties. Also activate when the user asks about: Actions, Data Objects (DTOs), lean models, States with enums, ViewModels (Blade or API), HTTP Query builders, application layer, or scaling Laravel projects beyond simple CRUD. Activate for any mention of "domain layer", "application layer", "beyond CRUD", "domain-oriented", or requests to refactor a Laravel project into domain structure. Even if the user asks "how to organize a large Laravel project", this skill is relevant.
npx skill4agent add victoralbino/agent-skills laravel-domain-architecturereferences/domain-building-blocks.mdreferences/application-layer.mdapp/Domain/app/app/
├── Domain/ # Business logic (the "what")
│ ├── Invoice/
│ ├── Customer/
│ ├── Payment/
│ └── Shared/
├── Http/ # Standard Laravel HTTP layer
│ ├── Controllers/
│ │ ├── Api/
│ │ └── Web/
│ ├── Requests/
│ ├── Resources/
│ ├── Queries/
│ ├── ViewModels/
│ └── Middleware/
├── Jobs/
├── Listeners/
├── Notifications/
├── Providers/
└── Console/app/Domain/Invoice/
├── Invoice.php # Model
├── InvoiceLine.php # Model
├── InvoiceStatus.php # Enum
├── InvoiceData.php # DTO (only when justified)
├── CreateInvoiceAction.php # Action
├── MarkInvoiceAsPaidAction.php # Action
├── CancelInvoiceAction.php # Action
├── InvoiceQueryBuilder.php # Custom QueryBuilder
├── InvoiceLineCollection.php # Custom Collection
├── InvoiceCreatedEvent.php # Event
└── InvalidTransitionException.php # Exceptionapp/Domain/Invoice/
├── Actions/
│ ├── CreateInvoiceAction.php
│ ├── MarkInvoiceAsPaidAction.php
│ └── CancelInvoiceAction.php
├── Models/
│ ├── Invoice.php
│ └── InvoiceLine.php
├── Enums/
│ └── InvoiceStatus.php
├── Data/
│ └── InvoiceData.php
└── ...references/domain-building-blocks.mdapp/Http/Controllers/Api/
├── InvoiceController.php # Fine when there are few controllers
├── CustomerController.php
└── PaymentController.phpapp/Http/Controllers/Api/
├── Invoice/
│ ├── InvoiceController.php
│ └── InvoiceStatusController.php
├── Customer/
│ └── CustomerController.php
└── Payment/
└── PaymentController.phpapp/Http/Controllers/Api/InvoiceController.php # Single version — no prefixapp/Http/Controllers/Api/
├── V1/
│ └── InvoiceController.php # Move here only when V2 exists
└── V2/
└── InvoiceController.phproutes/api_v1.phproutes/api_v2.phpreferences/application-layer.md| Type | Suffix | Example |
|---|---|---|
| Action | | |
| Data Object | | |
| Model | — | |
| QueryBuilder | | |
| Collection | | |
| Enum | | |
| Event | | |
| Exception | | |
| Controller | | |
| HTTP Query | | |
| Resource | | |
| Request | | |
| Job | | |
execute()readonlycolor()label()canTransitionTo()handle()app/Domain/| Situation | Approach |
|---|---|
| Read a model/enum from another domain | Direct import |
| Validate a precondition from another domain | Direct call |
| Orchestrate actions atomically (transaction) | Direct call |
| Side effect (notify, log, sync) | Event |
| Async reaction (can fail without affecting the flow) | Event |
| Multiple domains react to the same fact | Event |
namespace App\Domain\Invoice;
use App\Domain\Customer\Customer;
class CreateInvoiceAction
{
public function execute(InvoiceData $data, Customer $customer): Invoice
{
if (! $customer->isActive()) {
throw new \App\Domain\Customer\InactiveCustomerException();
}
return Invoice::create([
'customer_id' => $customer->id,
'number' => $data->number,
]);
}
}class CheckoutController
{
public function store(CheckoutRequest $request)
{
return DB::transaction(function () use ($request) {
$invoice = app(CreateInvoiceAction::class)->execute($invoiceData);
$payment = app(ProcessPaymentAction::class)->execute($invoice, $paymentData);
return new CheckoutResource($invoice, $payment);
});
}
}class CreateInvoiceAction
{
public function execute(InvoiceData $data): Invoice
{
$invoice = Invoice::create([...]);
event(new InvoiceCreatedEvent($invoice));
return $invoice;
}
}
// Another domain reacts via listener — no direct coupling
class ReconcilePaymentListener
{
public function handle(InvoiceCreatedEvent $event): void
{
app(ReconcilePaymentAction::class)->execute($event->invoice);
}
}Domain/Shared/app/Providers/
├── InvoiceServiceProvider.php
└── CustomerServiceProvider.phpProcessInvoiceActionCreateInvoiceActionMarkInvoiceAsPaidActionShared/references/domain-building-blocks.mdreferences/domain-building-blocks.mdreferences/application-layer.md