smorken/domain
A Laravel package for organizing applications around domain-driven design, giving you a simple foundation for structuring domain logic and keeping business rules cleanly separated from framework and infrastructure concerns.
The package is small and lightly adopted (no dependents, minimal stars), so treat the source as the documentation. Start by reading the service provider and config file before writing any code — the whole package is likely a few hundred lines.
Install and inspect
composer require smorken/domain
php artisan vendor:publish --provider="Smorken\Domain\DomainServiceProvider"
If the provider isn't auto-discovered, add it manually to config/app.php. Then read vendor/smorken/domain/src top to bottom — you need to know exactly which conventions it enforces (namespace layout, config keys, whether it registers bindings automatically) because there is no community knowledge base to fall back on.
Look at the config first. Any DDD helper package is opinionated about where domain code lives. Check config/domain.php (or similarly named) for the base namespace, path, and any repository/action binding settings. This determines your directory structure for the rest of the project.
First use case: extract one bounded context. Don't retrofit the whole app. Pick a single, self-contained slice of behavior — say Billing or Onboarding — and move its models, business rules, and persistence behind a repository interface. The value of a DDD helper package shows up the moment you can swap or fake an implementation in a test.
Verify the wiring with one test. Write an integration test that resolves the repository interface from the container and asserts it returns the concrete class. If that passes, the package's registration machinery is doing its job; if it fails, you've found the one thing you need to configure.
Domain-per-directory with a matching service provider. The standard shape is:
src/Domain/Billing/
Actions/
Models/
Repositories/
BillingRepository.php (interface)
EloquentBillingRepository.php (implementation)
BillingServiceProvider.php
Register each domain's provider in config/app.php (or let the package auto-discover if it supports that). Keep the provider tiny — bindings only, no business logic.
Interface-first repositories. Define the contract in the domain layer, implement it with Eloquent in an infrastructure layer (or alongside it if the package doesn't split layers). Bind in the provider:
$this->app->bind(
\Domain\Billing\Repositories\BillingRepository::class,
\Domain\Billing\Repositories\EloquentBillingRepository::class
);
Day-to-day, controllers and jobs type-hint the interface; they never touch Model::query(). This is the single highest-leverage pattern the package enables.
Actions as the unit of work. Put each meaningful business operation in a single-purpose class with an execute() or __invoke() method (CreateInvoice, SuspendAccount). Controllers become thin: validate → build a DTO → call the action → return a resource. Actions receive repositories via constructor injection, which the container resolves for you.
Cross-domain communication via events, not direct calls. When Billing needs to react to something in Accounts, listen to a domain event rather than importing the other domain's models. This keeps the bounded contexts separable and is the main reason to bother with the structure at all.
Integration tips:
AppServiceProvider — it keeps the wiring next to the code it wires.Facade-free code throughout. Everything injectable means everything testable, and DDD helpers generally assume constructor injection.Testing workflow. Because everything sits behind interfaces, tests become straightforward:
$this->app->instance(BillingRepository::class, new InMemoryBillingRepository);
Swap the binding in a test case and exercise the action with no database at all. Keep a small set of integration tests that use the real Eloquent binding to catch mapping bugs.
Autoloading is the most common failure. If the package expects Domain\ (or similar) to map to a specific path, add it to composer.json autoload.psr-4 and run composer dump-autoload. Class-not-found errors here look like package bugs but are almost always namespace mismatches.
Config caching bites during development. Bindings driven by config values won't refresh until you run php artisan config:clear. If a newly added domain isn't registering, clear config and route caches before debugging anything else.
Binding order matters for decorated services. If you wrap a repository (caching, logging), bind the decorator after the base binding, and prefer extend() over a second bind() so you don't accidentally shadow the original.
Don't expect auto-resolution of interfaces. Laravel resolves concrete classes automatically but never interfaces. Every repository contract needs an explicit binding — a missing one produces a Target [X] is not instantiable error at runtime, often deep in a request. Add a smoke test that resolves each interface.
Debug the container directly. When injection fails, use php artisan tinker and app()->make(YourInterface::class) to confirm what's bound. app()->getBindings() shows the full registry if you suspect a duplicate registration.
Treat this as a low-bus-factor dependency. With no dependents and minimal community usage, there's no Stack Overflow safety net and no guarantee of timely fixes. Pin the version in composer.json, and be prepared to vendor or fork it. Before committing, skim the source for Laravel-version compatibility — a package this small may not have been updated for recent framework releases.
The extension point is the service provider. If the package's conventions don't fit (e.g., you want repositories in a separate Infrastructure namespace), don't fight it — extend or re-implement its base provider. The abstraction surface is thin enough that forking a single class is a reasonable maintenance strategy.
Measure the payoff early. DDD scaffolding is overhead until you have more than one consumer of a domain. If after a sprint you haven't written a second implementation or a fake of a repository, the indirection isn't earning its keep — keep the directory structure, drop the ceremony.
How can I help you explore Laravel packages today?