creativecrafts/laravel-domain-driven-design-lite

Domain‑Driven Design (DDD)‑lite scaffolding for Laravel. This package generates a lightweight, opinionated module structure under Modules/[Module] and provides Artisan commands to scaffold common artifacts (Actions, Queries, DTOs, Repositories, Models, Controllers, Requests, Mappers, Migrations, Fac

Fund package maintenance!
CreativeCrafts

Installs: 31

Dependents: 0

Suggesters: 0

Security: 0

Stars: 1

Watchers: 0

Forks: 0

Open Issues: 1

pkg:composer/creativecrafts/laravel-domain-driven-design-lite

0.0.1 2025-11-25 12:35 UTC

This package is auto-updated.

Last update: 2025-11-25 14:33:10 UTC


README

Latest Version on Packagist GitHub Tests Action Status GitHub Code Style Action Status Total Downloads

Pragmatic, Laravel-native DDD modules with generators, safety rails, and CI helpers – without drowning you in ceremony.

🧭 What is DDD-Lite?

DDD-Lite is a developer-tooling package that helps you organise your Laravel 12+ application into modular, domain-oriented boundaries.

It gives you:

  • A module structure (modules/<ModuleName>) with a clear split between:
    • App/ – adapters & Laravel-specific glue (controllers, requests, models, providers, repositories)
    • Domain/ – pure PHP domain code (DTOs, actions, contracts, queries, value objects)
  • Generators for:
    • DTOs, Actions, Contracts, Repositories, Value Objects
    • Queries and Query Builders
    • Aggregate Roots
    • Controllers, Requests, Models, Migrations, Providers, Routes
  • A conversion engine to move existing app/* code into modules:
    • Discovers move candidates (controllers, models, requests, actions, DTOs, contracts)
    • Applies moves with AST-based namespace rewrites
  • Safety rails for all file operations:
    • Every run produces a Manifest (with creates/updates/deletes/moves/mkdirs)
    • --dry-run on everything
    • Rollback by manifest id
  • Quality & CI tooling:
    • Publishable PHPStan & Deptrac configs
    • Optional Pest architecture tests
    • ddd-lite:doctor / ddd-lite:doctor:domain / ddd-lite:doctor-ci to keep modules healthy

The goal is: clean seams, safer refactors, better testability – without requiring you to rewrite your entire app in one go.

🧱 Architecture Overview

A DDD-Lite module lives under modules/<ModuleName>:

modules/<ModuleName>/
├─ App/
│  ├─ Http/
│  │  ├─ Controllers/
│  │  └─ Requests/
│  ├─ Models/
│  ├─ Providers/
│  └─ Repositories/
├─ Domain/
│  ├─ Actions/
│  ├─ Contracts/
│  ├─ DTO/
│  ├─ Queries/
│  └─ ValueObjects/
├─ Database/
│  └─ migrations/
├─ Routes/
│  ├─ api.php
│  └─ web.php
└─ tests/
   ├─ Feature/
   └─ Unit/
└─ Unit/

✅ Rules of thumb

Domain:

  • Pure PHP, no hard dependency on Laravel.
  • Orchestrates use cases with Actions (e.g. CreateTripAction).
  • Talks to the outside world through Contracts (e.g. TripRepositoryContract).
  • Uses DTOs and Value Objects for data.

App:

  • Typical Laravel adapters (controllers, form requests, models).
  • Implements contracts using Eloquent (e.g. TripRepository).
  • Wires things together using module service providers.

⚙️ Requirements

  • PHP: ^8.3
  • Laravel (Illuminate components): ^12.0
  • Composer

Recommended dev dependencies in your app (for quality tooling integration):

  • larastan/larastan
  • deptrac/deptrac
  • pestphp/pest
  • pestphp/pest-plugin-laravel
  • pestphp/pest-plugin-arch

⚙️ Installation

Require the package in your Laravel app (usually as a dev dependency):

composer require creativecrafts/laravel-domain-driven-design-lite --dev

This package is primarily a developer tool (scaffolding, conversion, quality helpers), so installing under --dev is recommended. Laravel’s package discovery will automatically register the service provider.

📦 Provider & Publishing

DDD-Lite ships with stubs and quality configs you can copy into your app.

Stubs (module scaffolding & generators)

To publish the stubs:

php artisan vendor:publish --tag=ddd-lite
php artisan vendor:publish --tag=ddd-lite-stubs

This will create:

  • stubs/ddd-lite/*.stub – templates for:
  • DTOs, Actions, Contracts, Repositories, Value Objects, Aggregates
  • Controllers (including an --inertia variant)
  • Requests
  • Models & migrations
  • Module providers and route/event providers
  • Routes (web & api)

You typically don’t need to touch these unless you want to customise the generated code style.

Quality tooling

To seed PHPStan, Deptrac and Pest architecture tests into your application:

php artisan ddd-lite:publish:quality --target=all

This will (in your app):

  • Create phpstan.app.neon with sensible defaults for app/ + modules/
  • Create deptrac.app.yaml describing layer boundaries (Http, Models, Domain, Modules, etc.)
  • Add tests/ArchitectureTest.php with some baseline rules:
  • No debug helpers (dd, dump, var_dump, …)
  • No stray env() calls
  • Enforce strict types

You can also publish selectively:

php artisan ddd-lite:publish:quality --target=phpstan
php artisan ddd-lite:publish:quality --target=deptrac
php artisan ddd-lite:publish:quality --target=pest-arch

🚀 Getting Started (QuickStart)

We’ll build a simple Planner module with a Trip aggregate.

1) Scaffold a module

php artisan ddd-lite:module Planner --aggregate=Trip

This creates modules/Planner with:

  • Providers (module, route, event) under App/Providers
  • Routes in Routes/api.php and Routes/web.php
  • A ULID Eloquent model (App/Models/Trip.php) plus migrations in Database/migrations
  • Domain DTOs and a repository contract
  • Tests folders

Core flags:

  • --dry-run – preview actions without writing
  • --fix-psr4 – auto-rename lowercased module folders to proper PascalCase
  • --rollback= – undo a previous scaffold
  • --force – overwrite content when needed (with backups)

For full details see: docs/module-scaffold.md

2) Generate a DTO

php artisan ddd-lite:make:dto Planner CreateTripData \
  --props="id:Ulid|nullable,title:string,startsAt:CarbonImmutable,endsAt:CarbonImmutable"

This generates modules/Planner/Domain/DTO/CreateTripData.php:

  • Properly typed constructor
  • readonly by default
  • Optional unit test (unless --no-test is passed)

3) Generate a domain Action

php artisan ddd-lite:make:action Planner CreateTrip \
  --in=Trip \
  --input=FQCN --param=data \
  --returns=ulid

This creates Domain/Actions/Trip/CreateTripAction.php similar to:

namespace Modules\Planner\Domain\Actions\Trip;

use Modules\Planner\Domain\Contracts\TripRepositoryContract;
use Modules\Planner\Domain\DTO\CreateTripData;

final class CreateTripAction
{
    public function __construct(
        private TripRepositoryContract $repo,
    ) {}

    public function __invoke(CreateTripData $data): string
    {
        // Domain invariants live here
        return $this->repo->create($data);
    }
}

4) Implement the repository & bind it Create an Eloquent repository in modules/Planner/App/Repositories/TripRepository.php (or let ddd-lite:make:repository scaffold it):

php artisan ddd-lite:make:repository Planner Trip

Then wire the contract to the implementation:

php artisan ddd-lite:bind Planner TripRepositoryContract TripRepository

ddd-lite:bind edits your module provider so that:

$this->app->bind(
    TripRepositoryContract::class,
    TripRepository::class,
);

is registered.

5) Expose via HTTP Generate a controller + request:

php artisan ddd-lite:make:controller Planner Trip --resource
php artisan ddd-lite:make:request Planner StoreTrip

🧠 DDD-Lite in Practice: Example Flow

A typical “vertical slice” in a module:

  • DTO: CreateTripData – validated input from HTTP or CLI.
  • Action: CreateTripAction – orchestrates creation, enforces invariants.
  • Contract: TripRepositoryContract – interface for persistence.
  • Repository: TripRepository (Eloquent) – implements contract.
  • Controller: TripController@store – adapts HTTP to the action.

Generators help you keep this shape consistent across modules without hand-rolling boilerplate every time.

🧰 Command Reference

All commands share a consistent UX:

  • --dry-run – print what would happen; no files written; no manifest saved.
  • --force – overwrite when content changes (backups are tracked in manifests).
  • --rollback= – revert a previous run (see Manifest section below).

1. Module scaffolding & conversion

ddd-lite:module Scaffold a new module skeleton:

php artisan ddd-lite:module Planner

Key flags:

  • name (required) – module name in PascalCase.
  • --dry-run – preview only.
  • --force – overwrite files if they exist.
  • --fix-psr4 – rename existing lowercased module folders to PSR-4 PascalCase.
  • --rollback= – rollback a previous scaffold.

See docs/module-scaffold.md for details.

ddd-lite:convert Discover and optionally apply moves from app/* into modules:

php artisan ddd-lite:convert Planner \
  --plan-moves \
  --paths=app/Http/Controllers,app/Models

To use the namespace rewriting and AST-based moves, install nikic/php-parser:

composer require --dev nikic/php-parser

Important options:

  • module – target module name.
  • --plan-moves – discover move candidates and print a plan (no writes).
  • --apply-moves – actually apply moves (AST-safe namespace rewrites).
  • --review – interactive confirmation per move (with --apply-moves).
  • --all – apply all moves without prompts.
  • --only=controllers,models,requests,actions,dto,contracts – include kinds.
  • --except=... – exclude kinds.
  • --paths=... – comma-separated paths to scan.
  • --with-shims – include shim suggestions in the printed plan.
  • --export-plan=path.json – write discovered move plan to JSON.
  • --dry-run, --force, --rollback=.

Use this to gradually migrate a legacy app into DDD-Lite modules.

2. Domain generators

ddd-lite:make:dto Generate a DTO under Domain/DTO:

php artisan ddd-lite:make:dto Planner CreateTripData \
  --in=Trip \
  --props="id:Ulid|nullable,title:string,startsAt:CarbonImmutable"
  • module – module name.
  • name – DTO class name.
  • --in= – optional subnamespace inside Domain/DTO.
  • --props= – name:type[|nullable] comma-separated.
  • --readonly – enforce readonly class (default: true).
  • --no-test – skip generating a test.

ddd-lite:make:action Generate a domain action in Domain/Actions:

php artisan ddd-lite:make:action Planner CreateTrip \
  --in=Trip \
  --input=FQCN --param=data \
  --returns=ulid
  • --in= – optional subnamespace.
  • --method= – method name (default __invoke).
  • --input= – parameter type preset: none|ulid|FQCN.
  • --param= – parameter variable name.
  • --returns= – void|ulid|FQCN.
  • --no-test – skip test.

ddd-lite:make:contract Generate a domain contract:

php artisan ddd-lite:make:contract Planner TripRepository \
  --in=Trip \
  --methods="find:TripData|null(id:Ulid); create:TripData(data:TripCreateData)"
  • --methods= – semi-colon separated: name:ReturnType(args...).
  • --with-fake – generate a Fake implementation under tests/Unit/fakes.
  • --no-test – skip the contract test.

ddd-lite:make:repository Generate an Eloquent repository for an aggregate:

php artisan ddd-lite:make:repository Planner Trip

Creates:

  • App/Repositories/TripRepository.php
  • Optional tests (unless --no-test).

ddd-lite:make:value-object Generate a value object:

php artisan ddd-lite:make:value-object Planner Email \
  --scalar=string
  • --scalar= – backing scalar type: string|int|float|bool.

ddd-lite:make:aggregate-root Generate an aggregate root base:

php artisan ddd-lite:make:aggregate-root Planner Trip

Useful for richer domain modelling around key aggregates.

Query side

  • ddd-lite:make:query – generate a Query class in Domain/Queries.
  • ddd-lite:make:query-builder – generate a QueryBuilder helper.
  • ddd-lite:make:aggregator – generate an Aggregator to combine queries.

Example:

php artisan ddd-lite:make:query Planner TripIndexQuery
php artisan ddd-lite:make:query-builder Planner Trip
php artisan ddd-lite:make:aggregator Planner TripIndexAggregator

3. App-layer generators

ddd-lite:make:model Generate an Eloquent model in App/Models:

php artisan ddd-lite:make:model Planner Trip \
  --table=trips \
  --fillable="title,starts_at,ends_at"

Options:

  • --table=, --fillable=, --guarded=
  • --soft-deletes
  • --no-timestamps

ddd-lite:make:migration Generate a migration under Database/migrations:

php artisan ddd-lite:make:migration Planner create_trips_table --create=trips
  • module? – module name (optional).
  • name? – migration base name.
  • --table= – table name.
  • --create= – shortcut for table creation.
  • --path= – override path (defaults to module migrations).
  • --force, --dry-run, --rollback=.

ddd-lite:make:controller Generate a controller:

php artisan ddd-lite:make:controller Planner Trip --resource
  • --resource – standard Laravel resource methods.
  • --inertia – generate methods that return Inertia pages.
  • --suffix= – class suffix (default Controller).

ddd-lite:make:request Generate a form request:

php artisan ddd-lite:make:request Planner StoreTrip
  • --suffix= – class suffix (default Request).

4. Binding & wiring

ddd-lite:bind Bind a domain contract to an implementation in the module provider:

php artisan ddd-lite:bind Planner TripRepositoryContract TripRepository
  • module – module name.
  • contract – contract short name or FQCN.
  • implementation – implementation short name or FQCN.
  • --force – skip class existence checks (e.g. when generating ahead of time).

5. Manifest commands Every write operation (scaffolding, generate, convert, publish, doctor fixes) is tracked via a Manifest.

ddd-lite:manifest:list List manifests:

php artisan ddd-lite:manifest:list --module=Planner --type=create --json

Options:

  • --module= – filter by module.
  • --type= – mkdir|create|update|delete|move.
  • --after=, --before= – ISO8601 bounds for created_at.
  • --json – machine-readable output.

ddd-lite:manifest:show Inspect a single manifest:

php artisan ddd-lite:manifest:show 2025-11-24-13-54-01 --json

Shows the tracked operations for that run (created files, backups, moves, deletions, etc.).

6. Doctor & Quality commands

ddd-lite:publish:quality (Described earlier) – publishes PHPStan, Deptrac, and Pest Arch configuration/stubs into your app.

ddd-lite:doctor Run structural checks on your modules and wiring:

php artisan ddd-lite:doctor --module=Planner --json

Checks things like:

  • Module provider registration
  • Route/service provider wiring
  • PSR-4 inconsistencies
  • Missing or mis-wired module components

Flags:

  • --module= – limit to a specific module.
  • --fix – attempt automatic fixes (provider edits, PSR-4 renames, etc.).
  • --json – JSON report for tooling.
  • --prefer=file|class – strategy when class and filename mismatch.
  • --rollback= – undo fixes.

ddd-lite:doctor:domain Run domain purity checks via Deptrac:

php artisan ddd-lite:doctor:domain \
  --config=deptrac.app.yaml \
  --bin=vendor/bin/deptrac \
  --json \
  --fail-on=violations

Options:

  • --config= – Deptrac YAML config.
  • --bin= – path to deptrac executable.
  • --json – JSON summary.
  • --strict – treat uncovered as failure.
  • --stdin-report= – use pre-generated Deptrac JSON report.
  • --fail-on= – violations|errors|uncovered|any.

ddd-lite:doctor-ci Run both structural and domain checks in CI:

php artisan ddd-lite:doctor-ci --json --fail-on=error
  • --paths= – paths to scan (defaults to modules/ and bootstrap/app.php).
  • --fail-on=none|any|error – CI failure policy.
  • --json – CI-friendly JSON result.

Use this in your CI pipeline to enforce module health.

🧪 Safety Rails: Manifest & Rollback

DDD-Lite never silently edits your app.

For each command run that changes files:

  • A Manifest is written with:
  • mkdir, create, update, delete, move records
  • Backups of overwritten files
  • You can inspect manifests with:
  • ddd-lite:manifest:list
  • ddd-lite:manifest:show {id}
  • You can revert a run by passing --rollback= to the original command (e.g. ddd-lite:module, ddd-lite:convert, ddd-lite:publish:quality, ddd-lite:doctor).

This makes DDD-Lite safe to use on large, existing codebases.

🧮 Package Quality: PHPStan, Deptrac & Pest

Inside this package:

  • phpstan.neon.dist – strict rules for the package itself.
  • deptrac.package.yaml – package-level dependency rules.
  • tests/ArchTest.php – baseline architecture checks via Pest.

In your application, use:

php artisan ddd-lite:publish:quality --target=all

and then:

# In your app
vendor/bin/phpstan analyse -c phpstan.app.neon
vendor/bin/deptrac --config=deptrac.app.yaml
php artisan test tests/ArchitectureTest.php

Combine this with ddd-lite:doctor-ci in CI for a tight feedback loop.

🧩 Common Workflows

Greenfield project

  • Install DDD-Lite.
  • Scaffold your first module: ddd-lite:module.
  • Generate DTOs, Actions, Contracts, Repositories, Controllers, Requests.
  • Set up quality tooling with ddd-lite:publish:quality.
  • Wire ddd-lite:doctor-ci into your CI.

Migrating a legacy app

  • Install DDD-Lite.
  • Scaffold a module for a coherent slice (e.g. Planner, Billing, Users).
  • Use ddd-lite:convert with --plan-moves on a subset of app/*.
  • Iterate with --apply-moves and --review, keeping an eye on manifests.
  • Introduce contracts + repositories for areas you want to harden.
  • Run ddd-lite:doctor and ddd-lite:doctor:domain regularly during the migration.

🧪 Testing Philosophy

The package itself is tested with:

  • Pest for:
    • Feature tests of console commands
    • Unit tests for internals (filesystem, manifests, planners)
  • Architecture tests to protect boundaries.

You’re encouraged to:

  • Keep module tests close to modules (under modules//tests).
  • Use the provided stubs for DTO / Action / Contract / Repository tests to keep patterns consistent.

🔒 Design Principles

  • Domain purity – Domain/ should know nothing about Laravel.
  • Explicit boundaries – Domain <-> App contracts are interfaces, not facades.
  • Safety first – manifests, backups, --dry-run, --rollback.
  • Deterministic generators – running a command twice should be safe and idempotent.
  • CI-friendly – all checks and reports can be consumed by automation via JSON / exit codes.

🧰 Troubleshooting

  • “Nothing seems to happen when I run a command”
    • Check if you passed --dry-run.
    • Inspect manifests using ddd-lite:manifest:list.
  • “I messed up my module structure”
    • Find the relevant manifest id: ddd-lite:manifest:list.
    • Rerun the original command with --rollback=.
  • “Deptrac or PHPStan fail after publishing quality configs”
    • Make sure you installed the suggested dev dependencies in your app.
    • Tweak phpstan.app.neon / deptrac.app.yaml to match your project’s structure.

Changelog

Please see CHANGELOG for more information on what has changed recently.

Security

Please review our security policy on how to report security vulnerabilities.

🙌 Credits

📄 License

The MIT License (MIT). Please see License File for more information.