Laravel Modular Monolith: Bounded Contexts Guide | Mohamed Said       [Skip to content](#main)  [ ![](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png) Mohamed SaidLaravel Backend Engineer ](https://www.msaied.com/public) - [Home](https://www.msaied.com/public)
- [Projects](https://www.msaied.com/public/projects)
- [Articles](https://www.msaied.com/public/articles)
- [Certificates](https://www.msaied.com/public/certificates)
- [About](https://www.msaied.com/public#about)

           [  Contact](https://www.msaied.com/public#contact) Menu 

Menu
----

Close 

 - [HomeStart here](https://www.msaied.com/public)
- [ProjectsCase studies](https://www.msaied.com/public/projects)
- [ArticlesEngineering notes](https://www.msaied.com/public/articles)
- [CertificatesCredentials](https://www.msaied.com/public/certificates)
- [AboutHow I work](https://www.msaied.com/public#about)
- [ContactGet in touch](https://www.msaied.com/public#contact)

  [Start a conversation](https://www.msaied.com/public#contact) [WhatsApp](https://wa.me/201094619204) [Email](mailto:hello@msaied.com) 

 1. [Home](https://www.msaied.com/public)
2. /
3. [Articles](https://www.msaied.com/public/articles)
4. /
5. Modular Monolith in Laravel: Enforcing Bounded Contexts Without a Microservices Tax

 Modular Monolith in Laravel: Enforcing Bounded Contexts Without a Microservices Tax
====================================================================================

 Learn how to slice a Laravel application into cohesive bounded contexts using modules, internal contracts, and cross-module event dispatch — without splitting into microservices or fighting the framework.

 ![](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp) [Mohamed Said](https://www.msaied.com/public#person) Published 28 Jun 2026 · Updated 28 Jun 2026 · 3 min read

ShareCopy linkCopied

 ![Modular Monolith in Laravel: Enforcing Bounded Contexts Without a Microservices Tax](https://cdn.msaied.com/316/45b214e7656a6fe4e1c42a427f394287.png) 

  On this page +1. [Why a Modular Monolith?](#why-a-modular-monolith)
2. [Directory Layout](#directory-layout)
3. [Internal Contracts: The Only Crossing Point](#internal-contracts-the-only-crossing-point)
4. [Cross-Module Communication via Domain Events](#cross-module-communication-via-domain-events)
5. [Enforcing Boundaries with Deptrac](#enforcing-boundaries-with-deptrac)
6. [Shared Kernel: What Belongs There](#shared-kernel-what-belongs-there)
7. [Key Takeaways](#key-takeaways)

 Why a Modular Monolith?
-----------------------

Microservices promise isolation but deliver operational complexity. A well-structured modular monolith gives you the same conceptual boundaries — bounded contexts, explicit contracts, independent domain logic — while keeping deployment, transactions, and debugging simple.

The goal is not to split the codebase into packages. It is to enforce rules about *who may call whom* and *how* at the PHP level, so the boundaries are real rather than aspirational.

---

Directory Layout
----------------

Each bounded context lives under `src/Modules/`. The framework's `app/` directory becomes a thin bootstrap layer.

```javascript
src/
  Modules/
    Billing/
      Actions/
      Data/
      Events/
      Http/
      Models/
      Contracts/
        BillingServiceInterface.php
      BillingServiceProvider.php
    Catalog/
      ...
    Identity/
      ...
app/
  Providers/
    AppServiceProvider.php   ← registers module providers only

```

Each module registers itself via its own `ServiceProvider`. `AppServiceProvider` does nothing but call `$this->app->register(BillingServiceProvider::class)` for each module.

---

Internal Contracts: The Only Crossing Point
-------------------------------------------

Modules must never import each other's concrete classes. They communicate through interfaces declared in their own `Contracts/` namespace.

```php
// src/Modules/Billing/Contracts/BillingServiceInterface.php
namespace Modules\Billing\Contracts;

interface BillingServiceInterface
{
    public function charge(string $customerId, int $amountCents): ChargeResult;
}

```

The `Catalog` module depends on this interface, not on `Modules\Billing\Services\StripeService`.

```php
// src/Modules/Catalog/Actions/PublishProduct.php
namespace Modules\Catalog\Actions;

use Modules\Billing\Contracts\BillingServiceInterface;

final class PublishProduct
{
    public function __construct(
        private readonly BillingServiceInterface $billing
    ) {}

    public function handle(Product $product): void
    {
        $result = $this->billing->charge($product->owner_id, $product->listing_fee_cents);
        // ...
    }
}

```

Binding the concrete implementation happens inside `BillingServiceProvider`, invisible to the caller.

```php
// src/Modules/Billing/BillingServiceProvider.php
$this->app->bind(
    \Modules\Billing\Contracts\BillingServiceInterface::class,
    \Modules\Billing\Services\StripeService::class,
);

```

---

Cross-Module Communication via Domain Events
--------------------------------------------

When a module needs to *notify* rather than *query*, use Laravel's event dispatcher with typed event classes. Each module listens only to events it cares about.

```php
// src/Modules/Identity/Events/UserRegistered.php
namespace Modules\Identity\Events;

final class UserRegistered
{
    public function __construct(
        public readonly string $userId,
        public readonly string $email,
    ) {}
}

```

```php
// src/Modules/Billing/Listeners/ProvisionFreeTier.php
namespace Modules\Billing\Listeners;

use Modules\Identity\Events\UserRegistered;

final class ProvisionFreeTier
{
    public function handle(UserRegistered $event): void
    {
        // create a free subscription for $event->userId
    }
}

```

The `Identity` module fires the event and has zero knowledge of `Billing`. The listener is registered in `BillingServiceProvider`:

```php
$this->listen(UserRegistered::class, ProvisionFreeTier::class);

```

---

Enforcing Boundaries with Deptrac
---------------------------------

Conventions break under deadline pressure. Automate enforcement with [Deptrac](https://qossmic.github.io/deptrac/):

```yaml
# deptrac.yaml
layers:
  - name: Billing
    collectors:
      - type: className
        regex: ^Modules\\Billing\\
  - name: Catalog
    collectors:
      - type: className
        regex: ^Modules\\Catalog\\
ruleset:
  Catalog:
    - Billing   # Catalog may only use Billing's Contracts, enforced by regex
  Billing: []

```

Run `deptrac analyse` in CI. Any direct import of a concrete class across module boundaries fails the build.

---

Shared Kernel: What Belongs There
---------------------------------

Not everything is module-specific. A `Shared/` kernel holds:

- Base value objects (`Money`, `Email`, `Uuid`)
- Common exceptions (`DomainException`, `ValidationException`)
- Infrastructure interfaces (`ClockInterface`, `UuidGeneratorInterface`)

Modules may depend on `Shared/`. `Shared/` must never depend on any module.

---

Key Takeaways
-------------

- **Modules own their service providers** — binding, listening, and scheduling stay inside the module.
- **Contracts are the only public API** — no concrete class crosses a module boundary.
- **Events decouple producers from consumers** — the firing module never imports the listener.
- **Deptrac in CI makes boundaries real** — conventions without tooling are just comments.
- **Shared kernel stays thin** — value objects and interfaces only, no business logic.
- **Transactions still work** — because it is one process and one database connection, `DB::transaction()` spans modules freely.

- [laravel](https://www.msaied.com/public/articles?search=laravel)
- [architecture](https://www.msaied.com/public/articles?search=architecture)
- [ddd](https://www.msaied.com/public/articles?search=ddd)
- [modular-monolith](https://www.msaied.com/public/articles?search=modular-monolith)

 Frequently asked questions 
---------------------------

  How is this different from just organising files into folders?The difference is enforcement. Folder organisation is cosmetic. Bounded contexts backed by interface-only contracts and Deptrac CI rules make illegal dependencies a build failure, not a code-review suggestion.

   Can modules share an Eloquent model, for example a User?Avoid it. Instead, each module defines a read-only projection or a lightweight DTO for the data it needs. The Identity module owns the User model; other modules query through a contract method or listen to events.

   Does this approach work with Filament admin panels?Yes. Each module can register its own Filament resources inside its ServiceProvider using Filament::serving(). The panel itself lives in a dedicated Module\\Admin context that imports only contracts from other modules.

   ![Mohamed Said](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp)About the author
----------------

[Mohamed Said](https://www.msaied.com/public#person)Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

[About](https://www.msaied.com/public#about) [GitHub ↗](https://github.com/EG-Mohamed) [LinkedIn ↗](https://www.linkedin.com/in/msaiedm/) [WhatsApp ↗](https://wa.me/201094619204) [Email Address ↗](mailto:hello@msaied.com) [My CV ↗](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)  

   [Previous articleEvent Sourcing and CQRS in Laravel: Aggregates, Projectors, and Eventual Consistency](https://www.msaied.com/public/articles/event-sourcing-and-cqrs-in-laravel-aggregates-projectors-and-eventual-consistency) [Next articleDDD in Laravel: Actions, DTOs, and Value Objects Without the Bloat](https://www.msaied.com/public/articles/ddd-in-laravel-actions-dtos-and-value-objects-without-the-bloat)  

   On this page
-------------

1. [Why a Modular Monolith?](#why-a-modular-monolith)
2. [Directory Layout](#directory-layout)
3. [Internal Contracts: The Only Crossing Point](#internal-contracts-the-only-crossing-point)
4. [Cross-Module Communication via Domain Events](#cross-module-communication-via-domain-events)
5. [Enforcing Boundaries with Deptrac](#enforcing-boundaries-with-deptrac)
6. [Shared Kernel: What Belongs There](#shared-kernel-what-belongs-there)
7. [Key Takeaways](#key-takeaways)

 ###  Have a technical challenge?

 Tell me what you’re building. I reply within two working days.

[Start a conversation](https://www.msaied.com/public#contact) 

   Related articles
-----------------

 [ ![](https://cdn.msaied.com/743/8998fac3a41451ab3fe1588194e17a43.png) Filament · 3 min read### Securing Filament Plugins with Plumb: Automated Security Scoring for PHP Packages

5 Oct 2026 ](https://www.msaied.com/public/articles/securing-filament-plugins-with-plumb-automated-security-scoring-for-php-packages) [ ![](https://cdn.msaied.com/742/2d02018669cdeedccb5de2efb898f0ee.png) Filament · 3 min read### Filament v3.3.56 Released: File Hash Names and Livewire Upload Fix

5 Oct 2026 ](https://www.msaied.com/public/articles/filament-v3356-released-file-hash-names-and-livewire-upload-fix) [ ![](https://cdn.msaied.com/741/5b55c123ad08e4d34e1f4b99ad6a428b.png)  · 3 min read### Filament v4 Schema-Based Forms, Infolists, and the Unified Schema API

5 Oct 2026 ](https://www.msaied.com/public/articles/filament-v4-schema-based-forms-infolists-and-the-unified-schema-api-5) 

  Have a technical challenge?
----------------------------

Tell me what you’re building. I reply within two working days.

 [Discuss your project ↗](https://www.msaied.com/public#contact) 

  © 2026 Mohamed Said · Built with Laravel, meant to last.Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

 - [Home](https://www.msaied.com/public)
- [Articles](https://www.msaied.com/public/articles)
- [Certificates](https://www.msaied.com/public/certificates)
- [GitHub](https://github.com/EG-Mohamed)
- [LinkedIn](https://www.linkedin.com/in/msaiedm/)
- [WhatsApp](https://wa.me/201094619204)
- [Email Address](mailto:hello@msaied.com)
- [My CV](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)
- [Sitemap](https://www.msaied.com/public/sitemap.xml)
