
Domain Events in Laravel: Decoupling Business Rules the Smart Way
One of the biggest challenges when an application grows is keeping the code decoupled. Especially when many business rules must react to the same action, the code can become full of conditions, service calls, and direct dependencies.
This is where Domain Events help: they allow us to organize the code so that domain events trigger reactions without creating unnecessary dependencies between modules.
The problem: coupled business rules
Imagine a user registration flow. When a new user is created, several things must happen:
- Send a welcome email
- Create a profile in the CRM
- Notify the support team on Slack
- Start an identity verification process
Without events, your code might look like this:
public function register(UserData $data)
{
$user = User::create($data->toArray());
Mail::to($user->email)->send(new WelcomeMail($user));
$this->crmService->createProfile($user);
$this->slackNotifier->send("New user: {$user->email}");
$this->kycService->startVerification($user);
return $user;
}
It works, but the registration method now knows too much. Any change in these integrations forces you to modify the registration logic.
The solution: domain events
With Domain Events, we remove these responsibilities. The registration only needs to “say” that an event happened:
public function register(UserData $data)
{
$user = User::create($data->toArray());
event(new UserRegistered($user));
return $user;
}
Now, the method does not need to know about email, CRM, or Slack. It only triggers a domain event: UserRegistered.
Creating a domain event in Laravel
An event is just a simple class:
namespace App\Domain\Events;
use App\Models\User;
class UserRegistered
{
public function __construct(public User $user) {}
}
Listeners: reacting to events
Each business rule that should run after registration lives in its own listener:
class SendWelcomeEmail
{
public function handle(UserRegistered $event): void
{
Mail::to($event->user->email)->send(new WelcomeMail($event->user));
}
}
class CreateCrmProfile
{
public function handle(UserRegistered $event): void
{
app(CrmService::class)->createProfile($event->user);
}
}
class NotifySlack
{
public function handle(UserRegistered $event): void
{
app(SlackNotifier::class)->send("New user: {$event->user->email}");
}
}
Registering the listeners
In Laravel, you can register listeners inside the EventServiceProvider:
protected $listen = [
\App\Domain\Events\UserRegistered::class => [
\App\Domain\Listeners\SendWelcomeEmail::class,
\App\Domain\Listeners\CreateCrmProfile::class,
\App\Domain\Listeners\NotifySlack::class,
],
];
Benefits of using Domain Events
- Decoupling → the registration does not depend on email, CRM, or Slack.
- Scalability → adding new reactions (for example, analytics) does not require changing the registration code.
- Testability → each listener can be tested separately.
- Domain organization → events reflect the business language (“a user was registered”), not technical details.
When to use (and when not to use)
✅ Use when an action needs to generate multiple independent effects.
✅ Use when you want to keep the domain clean and free of external dependencies.
⚠️ Do not use for simple sequential actions that always happen together (for example, creating a user and creating a mandatory address). In these cases, it may be overengineering.
Conclusion
Domain Events are a simple but powerful way to apply the Dependency Inversion principle in Laravel. They help keep your code clean, testable, and ready to evolve.
Start small: identify points where a single action produces multiple consequences and extract them into events. Over time, your application will become more organized and resilient.
Published on August 20, 2025