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

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

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

  [ home ](https://msaied.com)    [ articles ](https://msaied.com/articles)    Modular Monolith in Laravel: Enforcing Bounded Contexts with Module Service Providers        On this page       1. [  Why a Modular Monolith? ](#why-a-modular-monolith)
2. [  Directory Layout ](#directory-layout)
3. [  Per-Module Service Providers ](#per-module-service-providers)
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 vs. Shared Everything ](#shared-kernel-vs-shared-everything)
7. [  Key Takeaways ](#key-takeaways)

  ![Modular Monolith in Laravel: Enforcing Bounded Contexts with Module Service Providers](https://cdn.msaied.com/662/7f9c800590e5d7c07197293837cf0114.png)

  #laravel   #architecture   #modular-monolith   #ddd   #service-providers  

 Modular Monolith in Laravel: Enforcing Bounded Contexts with Module Service Providers 
=======================================================================================

     12 Sep 2026      3 min read    ![Mohamed Said](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp)  Mohamed Said  

       Table of contents

1. [  01   Why a Modular Monolith?  ](#why-a-modular-monolith)
2. [  02   Directory Layout  ](#directory-layout)
3. [  03   Per-Module Service Providers  ](#per-module-service-providers)
4. [  04   Cross-Module Communication via Domain Events  ](#cross-module-communication-via-domain-events)
5. [  05   Enforcing Boundaries with Deptrac  ](#enforcing-boundaries-with-deptrac)
6. [  06   Shared Kernel vs. Shared Everything  ](#shared-kernel-vs-shared-everything)
7. [  07   Key Takeaways  ](#key-takeaways)

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

Microservices solve deployment independence but introduce distributed-systems complexity most teams don't need yet. A modular monolith gives you the bounded-context discipline of microservices while keeping a single deployable unit, shared database transactions, and zero network overhead between modules.

The key discipline: **modules must not reach into each other's internals**. They communicate through explicit contracts — interfaces, DTOs, and domain events.

---

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

```
app/
  Modules/
    Billing/
      BillingServiceProvider.php
      Actions/
      Contracts/
        BillingGateway.php
      Domain/
      Http/
      Models/
    Catalog/
      CatalogServiceProvider.php
      Contracts/
        ProductRepository.php
      ...
    Shared/
      Events/
      ValueObjects/

```

Each module owns its own `ServiceProvider`, routes, migrations (or migration stubs), and a `Contracts/` directory that defines its **public API**. Nothing outside the module imports from `Domain/` or `Models/` directly.

---

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

Register each module provider in `bootstrap/providers.php` (Laravel 11+):

```php
// bootstrap/providers.php
return [
    App\Modules\Billing\BillingServiceProvider::class,
    App\Modules\Catalog\CatalogServiceProvider::class,
];

```

A module provider wires its own internals and publishes only its contracts:

```php
namespace App\Modules\Billing;

use Illuminate\Support\ServiceProvider;
use App\Modules\Billing\Contracts\BillingGateway;
use App\Modules\Billing\Infrastructure\StripeGateway;

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

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

```

The `Catalog` module never imports `StripeGateway`. It only ever type-hints `BillingGateway`.

---

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

Direct method calls between modules create hidden coupling. Use Laravel's event dispatcher with typed event classes that live in `Shared/Events/`:

```php
// Shared/Events/OrderPlaced.php
final readonly class OrderPlaced
{
    public function __construct(
        public string $orderId,
        public string $customerId,
        public int $totalCents,
    ) {}
}

```

The `Orders` module fires the event; `Billing` listens:

```php
// Orders module — fires
event(new OrderPlaced($order->id, $order->customer_id, $order->total_cents));

// Billing module — listens, registered in BillingServiceProvider::boot()
Event::listen(OrderPlaced::class, ChargeCreditCard::class);

```

Neither module imports the other's classes. The shared event is the contract.

---

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

Conventions erode without tooling. [Deptrac](https://github.com/qossmic/deptrac) statically analyses PHP imports and fails CI when a module reaches into another's internals.

```yaml
# deptrac.yaml
layers:
  - name: Billing
    collectors:
      - type: directory
        value: app/Modules/Billing
  - name: Catalog
    collectors:
      - type: directory
        value: app/Modules/Catalog

ruleset:
  Billing:
    - Shared
  Catalog:
    - Shared

```

Add `vendor/bin/deptrac analyse` to your CI pipeline. Any import from `Billing` into `Catalog\Domain` becomes a build failure.

---

Shared Kernel vs. Shared Everything
-----------------------------------

The `Shared/` layer should be **thin**: value objects (`Money`, `Email`), base events, and utility interfaces. If you find yourself putting business logic there, it's a sign a new module is trying to emerge.

A `Money` value object is a legitimate shared kernel member:

```php
namespace App\Modules\Shared\ValueObjects;

final readonly class Money
{
    public function __construct(
        public int $amount,
        public string $currency,
    ) {}

    public function add(Money $other): self
    {
        if ($this->currency !== $other->currency) {
            throw new \DomainException('Currency mismatch');
        }
        return new self($this->amount + $other->amount, $this->currency);
    }
}

```

---

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

- Each module gets its own `ServiceProvider`; only its `Contracts/` directory is public.
- Cross-module calls go through typed interfaces or domain events — never direct class imports.
- Deptrac (or similar) enforces boundaries in CI before humans forget the rules.
- The `Shared/` kernel holds value objects and base types, not business logic.
- You can extract a module to a microservice later by replacing its service provider with an HTTP/gRPC adapter — the rest of the app never notices.

 Found this useful?

          [  ](https://twitter.com/intent/tweet?url=https%3A%2F%2Fmsaied.com%2Farticles%2Fmodular-monolith-in-laravel-enforcing-bounded-contexts-with-module-service-providers&text=Modular+Monolith+in+Laravel%3A+Enforcing+Bounded+Contexts+with+Module+Service+Providers) [  ](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fmsaied.com%2Farticles%2Fmodular-monolith-in-laravel-enforcing-bounded-contexts-with-module-service-providers) 

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

  3 questions  

     Q01  Should each module have its own database schema or tables?        Not necessarily. Sharing a database is fine; what matters is that only the owning module's Eloquent models query its tables. Other modules access data through the module's public contract (repository interface or read DTO), never by querying the table directly. 

      Q02  How do I handle database transactions that span two modules?        Wrap the operation in a `DB::transaction()` at the application layer (e.g., an action or command handler) that calls both module contracts. Because it's a monolith with a shared connection, ACID guarantees still apply — this is one of the key advantages over microservices. 

      Q03  Is Deptrac the only way to enforce boundaries?        No. PHPArkitect is a PHP-native alternative with a fluent API. You can also write a custom Pest architecture test using `expect()-&gt;classes()-&gt;toOnlyUse()` scoped to each module namespace, which integrates naturally if you're already running Pest in CI. 

  Continue reading

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

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

 [ ![Filament v4 Table Bulk Actions: Custom Confirmation Modals and Scoped Authorization](https://cdn.msaied.com/661/5f319b485f1bc0c76e2c82746f730c8c.png) filament laravel authorization 

### Filament v4 Table Bulk Actions: Custom Confirmation Modals and Scoped Authorization

Go beyond the default delete bulk action. Learn how to build custom Filament v4 bulk actions with typed confir...

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

 12 Sep 2026     4 min read  

  Read    

 ](https://msaied.com/articles/filament-v4-table-bulk-actions-custom-confirmation-modals-and-scoped-authorization) [ ![Laravel Horizon: Queue Metrics, Supervisor Tuning, and Safe Deployments](https://cdn.msaied.com/660/4e7fb1097d66f5b5c6bb68e5aad9b211.png) laravel horizon queues 

### Laravel Horizon: Queue Metrics, Supervisor Tuning, and Safe Deployments

Beyond the dashboard: how to use Horizon's metrics API, tune supervisor processes for mixed workloads, and dep...

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

 12 Sep 2026     3 min read  

  Read    

 ](https://msaied.com/articles/laravel-horizon-queue-metrics-supervisor-tuning-and-safe-deployments) [ ![Custom Eloquent Casts: Value Objects, Enums, and Encrypted Composites](https://cdn.msaied.com/659/44f701e1dc43e64d0b7ecc984d0b34bc.png) laravel eloquent ddd 

### Custom Eloquent Casts: Value Objects, Enums, and Encrypted Composites

Go beyond primitive storage with Eloquent's CastsAttributes contract. Build reusable value-object casts, handl...

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

 12 Sep 2026     3 min read  

  Read    

 ](https://msaied.com/articles/custom-eloquent-casts-value-objects-enums-and-encrypted-composites) 

   [  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png)   Mohamed Said Laravel Backend Engineer  ](https://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://msaied.com)
- [Projects](https://msaied.com/projects)
- [Articles](https://msaied.com/articles)
- [Certificates](https://msaied.com/certificates)
- [Contact](https://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)
