Laravel Modular Monolith: Bounded Contexts Guide | Mohamed Said        [  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png)   Mohamed Said Laravel Backend Engineer  ](https://www.msaied.com) [ Home ](https://www.msaied.com) [ Projects ](https://www.msaied.com/projects) [ Articles  ](https://www.msaied.com/articles) [ Certificates ](https://www.msaied.com/certificates) [ Contact ](https://www.msaied.com#contact-section) 

       [  ](https://github.com/EG-Mohamed)       

 [ Home ](https://www.msaied.com) [ Projects ](https://www.msaied.com/projects) [ Articles ](https://www.msaied.com/articles) [ Certificates ](https://www.msaied.com/certificates) [ Contact ](https://www.msaied.com#contact-section) 

  [ home ](https://www.msaied.com)    [ articles ](https://www.msaied.com/articles)    Modular Monolith in Laravel: Enforcing Bounded Contexts Without a Framework        On this page       1. [  Why a Modular Monolith? ](#why-a-modular-monolith)
2. [  Directory Convention First ](#directory-convention-first)
3. [  Module Service Providers ](#module-service-providers)
4. [  Enforcing Boundaries with Deptrac ](#enforcing-boundaries-with-deptrac)
5. [  Cross-Module Communication ](#cross-module-communication)
6. [  Shared Kernel ](#shared-kernel)
7. [  Testing in Isolation ](#testing-in-isolation)
8. [  Key Takeaways ](#key-takeaways)

  ![Modular Monolith in Laravel: Enforcing Bounded Contexts Without a Framework](https://cdn.msaied.com/190/6fc8d99e2eb72257ed8ce5a9e5176ae2.png)

  #laravel   #architecture   #ddd   #modular-monolith  

 Modular Monolith in Laravel: Enforcing Bounded Contexts Without a Framework 
=============================================================================

     15 Jun 2026      3 min read    ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said  

       Table of contents

1. [  01   Why a Modular Monolith?  ](#why-a-modular-monolith)
2. [  02   Directory Convention First  ](#directory-convention-first)
3. [  03   Module Service Providers  ](#module-service-providers)
4. [  04   Enforcing Boundaries with Deptrac  ](#enforcing-boundaries-with-deptrac)
5. [  05   Cross-Module Communication  ](#cross-module-communication)
6. [  06   Shared Kernel  ](#shared-kernel)
7. [  07   Testing in Isolation  ](#testing-in-isolation)
8. [  08   Key Takeaways  ](#key-takeaways)

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

Microservices solve distribution problems you probably don't have yet. A well-structured monolith that respects domain boundaries gives you most of the organisational benefits — independent deployability aside — without the operational overhead. The goal is to make cross-module coupling *visible and painful*, so it stays rare.

Directory Convention First
--------------------------

Start with a `modules/` directory at the project root. Each module is a self-contained PHP namespace:

```
modules/
  Billing/
    src/
      BillingServiceProvider.php
      Domain/
        Invoice.php
        InvoiceRepository.php
      Application/
        CreateInvoiceAction.php
      Infrastructure/
        EloquentInvoiceRepository.php
    composer.json
  Catalog/
    src/
      CatalogServiceProvider.php
      ...
    composer.json

```

Each module has its own `composer.json` declaring its namespace. The root `composer.json` pulls them in as path repositories:

```json
{
  "repositories": [
    {"type": "path", "url": "modules/Billing"},
    {"type": "path", "url": "modules/Catalog"}
  ],
  "require": {
    "acme/billing": "@dev",
    "acme/catalog": "@dev"
  }
}

```

Composer symlinks each module into `vendor/`, so autoloading works identically to a real package. The boundary is now enforced by Composer's dependency graph, not just a gentleman's agreement.

Module Service Providers
------------------------

Each module registers its own bindings, routes, and migrations:

```php
// modules/Billing/src/BillingServiceProvider.php
namespace Acme\Billing;

use Illuminate\Support\ServiceProvider;
use Acme\Billing\Domain\InvoiceRepository;
use Acme\Billing\Infrastructure\EloquentInvoiceRepository;

class BillingServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->app->bind(InvoiceRepository::class, EloquentInvoiceRepository::class);
    }

    public function boot(): void
    {
        $this->loadMigrationsFrom(__DIR__.'/../database/migrations');
        $this->loadRoutesFrom(__DIR__.'/../routes/api.php');
    }
}

```

Register it in `bootstrap/providers.php` (Laravel 11+) or `config/app.php`:

```php
return [
    Acme\Billing\BillingServiceProvider::class,
    Acme\Catalog\CatalogServiceProvider::class,
];

```

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

Conventions break under deadline pressure. [Deptrac](https://github.com/qossmic/deptrac) statically analyses `use` statements and fails CI when a module imports from a sibling it shouldn't:

```yaml
# deptrac.yaml
layers:
  - name: Billing
    collectors:
      - type: namespace
        value: Acme\\Billing
  - name: Catalog
    collectors:
      - type: namespace
        value: Acme\\Catalog
ruleset:
  Billing:
    - Catalog   # Billing MAY depend on Catalog's public API
  Catalog: []   # Catalog must not depend on anything else

```

Run `deptrac analyse` in your pipeline. A `use Acme\Billing\...` inside `Catalog` now breaks the build.

Cross-Module Communication
--------------------------

Modules talk through three mechanisms only:

1. **Public contracts** — interfaces and DTOs in a `Contracts/` namespace that other modules may import.
2. **Laravel events** — fire a domain event; other modules listen without coupling to the source.
3. **The service container** — resolve a contract, never a concrete class from another module.

```php
// Catalog fires an event; Billing listens
event(new ProductPurchased($productId, $userId));

// In BillingServiceProvider::boot()
Event::listen(ProductPurchased::class, GenerateInvoiceListener::class);

```

### Shared Kernel

Put truly cross-cutting concerns — `Money`, `UserId`, base exceptions — in a `SharedKernel` module that every other module may depend on. Keep it tiny.

Testing in Isolation
--------------------

Because each module is a Composer package, you can run Pest inside the module directory with its own `phpunit.xml`:

```bash
cd modules/Billing && vendor/bin/pest

```

Mock the `InvoiceRepository` interface; never touch the database in unit tests. Integration tests live at the root and boot the full application.

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

- Use Composer path repositories to make module boundaries real, not aspirational.
- Each module owns its service provider, migrations, and routes.
- Deptrac in CI prevents accidental cross-module coupling before it becomes technical debt.
- Cross-module communication flows through events, contracts, and the container — never direct class imports.
- A `SharedKernel` module holds value objects and base types; keep it deliberately small.

 Found this useful?

          [  ](https://twitter.com/intent/tweet?url=https%3A%2F%2Fwww.msaied.com%2Farticles%2Fmodular-monolith-in-laravel-enforcing-bounded-contexts-without-a-framework&text=Modular+Monolith+in+Laravel%3A+Enforcing+Bounded+Contexts+Without+a+Framework) [  ](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fwww.msaied.com%2Farticles%2Fmodular-monolith-in-laravel-enforcing-bounded-contexts-without-a-framework) 

 Frequently Asked Questions 
----------------------------

  3 questions  

     Q01  Do I need a dedicated package like nwidart/laravel-modules?        No. Composer path repositories plus a per-module ServiceProvider give you the same autoloading and isolation without an additional dependency. Third-party module packages add conventions you may not agree with; rolling your own keeps full control. 

      Q02  How do I handle shared database tables across modules?        Prefer each module owning its own tables. When two modules genuinely share data, expose it through a repository contract in the SharedKernel or the owning module's public API. Direct cross-module Eloquent model imports are a coupling smell. 

      Q03  Can this structure work with Filament admin panels?        Yes. Each module can register its own Filament resources inside its ServiceProvider using Filament::serving() or a dedicated panel provider. This keeps admin UI co-located with the domain it manages. 

  Continue reading

 More Articles 
---------------

 [ View all    ](https://www.msaied.com/articles) 

 [ ![Laravel Telescope Alternatives: Building a Lightweight Request Inspector with Middleware](https://cdn.msaied.com/216/9b6d240010b80483f072902dafcd216c.png) laravel middleware debugging 

### Laravel Telescope Alternatives: Building a Lightweight Request Inspector with Middleware

Telescope is powerful but heavy for production. Learn how to build a focused, low-overhead request inspector u...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 16 Jun 2026     1 min read  

  Read    

 ](https://www.msaied.com/articles/laravel-telescope-alternatives-building-a-lightweight-request-inspector-with-middleware) [ ![RAG Pipelines in Laravel: Chunking, Embedding, and Retrieval with pgvector](https://cdn.msaied.com/215/e037e13535aa77822f879ee829ec3f68.png) laravel ai pgvector 

### RAG Pipelines in Laravel: Chunking, Embedding, and Retrieval with pgvector

Build a production-ready Retrieval-Augmented Generation pipeline in Laravel using pgvector, OpenAI embeddings,...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 16 Jun 2026     3 min read  

  Read    

 ](https://www.msaied.com/articles/rag-pipelines-in-laravel-chunking-embedding-and-retrieval-with-pgvector) [ ![Laravel Pest: Architecture Tests, Mutation Testing, and Type Coverage in CI](https://cdn.msaied.com/214/0d4822fa8ee1765d0689e387dd849d92.png) laravel pest testing 

### Laravel Pest: Architecture Tests, Mutation Testing, and Type Coverage in CI

Go beyond feature tests. Learn how to enforce architectural rules, catch logic gaps with mutation testing, and...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 16 Jun 2026     4 min read  

  Read    

 ](https://www.msaied.com/articles/laravel-pest-architecture-tests-mutation-testing-and-type-coverage-in-ci) 

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

Explore

- [Home](https://www.msaied.com)
- [Projects](https://www.msaied.com/projects)
- [Articles](https://www.msaied.com/articles)
- [Certificates](https://www.msaied.com/certificates)
- [Contact](https://www.msaied.com#contact-section)

Connect

- [   hello@msaied.com ](mailto:hello@msaied.com)
- [   +20 109 461 9204 ](tel:+201094619204)

© 2026 Mohamed Said. All rights reserved.

 [  ](https://github.com/EG-Mohamed) [  ](https://www.linkedin.com/in/msaiedm/) [  ](https://wa.me/201094619204) [  ](mailto:hello@msaied.com) [  ](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)
