Laravel Modular Monolith: Bounded Contexts in Practice | 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 Without a Microservices Tax        On this page       1. [  Why a Modular Monolith? ](#why-a-modular-monolith)
2. [  Module Structure ](#module-structure)
3. [  Internal Contracts: The Boundary Enforcement Mechanism ](#internal-contracts-the-boundary-enforcement-mechanism)
4. [  Enforcing the Rules with Deptrac ](#enforcing-the-rules-with-deptrac)
5. [  Cross-Module Events Instead of Direct Calls ](#cross-module-events-instead-of-direct-calls)
6. [  Shared Kernel ](#shared-kernel)
7. [  Key Takeaways ](#key-takeaways)

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

  #laravel   #architecture   #ddd   #modular-monolith  

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

     6 Jul 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   Module Structure  ](#module-structure)
3. [  03   Internal Contracts: The Boundary Enforcement Mechanism  ](#internal-contracts-the-boundary-enforcement-mechanism)
4. [  04   Enforcing the Rules with Deptrac  ](#enforcing-the-rules-with-deptrac)
5. [  05   Cross-Module Events Instead of Direct Calls  ](#cross-module-events-instead-of-direct-calls)
6. [  06   Shared Kernel  ](#shared-kernel)
7. [  07   Key Takeaways  ](#key-takeaways)

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

Microservices solve distribution problems you probably don't have yet. A modular monolith gives you the same conceptual separation — bounded contexts, explicit contracts, isolated domain logic — while keeping a single deployable, a single database transaction scope, and zero network overhead between modules.

The goal is not folder organisation for its own sake. It is **preventing accidental coupling** so that the Billing team can refactor invoicing without breaking the Shipping team's code.

---

Module Structure
----------------

Each bounded context lives under `app/Modules/{Context}/`. The internal layout mirrors a mini-application:

```php
app/Modules/
  Billing/
    Actions/
    Data/          # DTOs, value objects
    Domain/        # Eloquent models, domain services
    Http/          # Controllers, requests, resources
    Infrastructure/ # Repositories, external adapters
    Providers/
      BillingServiceProvider.php
    routes.php
  Shipping/
    ...

```

Each module registers itself via its own service provider, which is loaded from `bootstrap/providers.php` (Laravel 11+):

```php
// bootstrap/providers.php
return [
    App\Modules\Billing\Providers\BillingServiceProvider::class,
    App\Modules\Shipping\Providers\ShippingServiceProvider::class,
];

```

```php
// app/Modules/Billing/Providers/BillingServiceProvider.php
class BillingServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        $this->loadRoutesFrom(__DIR__ . '/../routes.php');
        $this->loadMigrationsFrom(__DIR__ . '/../Infrastructure/Migrations');
    }

    public function register(): void
    {
        $this->app->bind(
            \App\Modules\Billing\Domain\Contracts\InvoiceRepository::class,
            \App\Modules\Billing\Infrastructure\EloquentInvoiceRepository::class,
        );
    }
}

```

---

Internal Contracts: The Boundary Enforcement Mechanism
------------------------------------------------------

Modules must never import each other's internal classes directly. Instead, each module exposes a **public API interface** in a `Contracts` namespace:

```php
// app/Modules/Billing/Contracts/BillingModuleInterface.php
namespace App\Modules\Billing\Contracts;

use App\Modules\Billing\Data\InvoiceData;

interface BillingModuleInterface
{
    public function issueInvoice(int $orderId, int $customerId): InvoiceData;
    public function getOutstandingBalance(int $customerId): Money;
}

```

Shipping resolves this interface through the container — it never touches `Billing\Domain\` directly:

```php
// app/Modules/Shipping/Actions/DispatchOrder.php
class DispatchOrder
{
    public function __construct(
        private readonly BillingModuleInterface $billing,
    ) {}

    public function handle(Order $order): void
    {
        $balance = $this->billing->getOutstandingBalance($order->customer_id);

        if ($balance->isNegative()) {
            throw new OutstandingBalanceException();
        }

        // dispatch logic ...
    }
}

```

---

Enforcing the Rules with Deptrac
--------------------------------

Folder conventions are worthless without tooling. [Deptrac](https://github.com/qossmic/deptrac) analyses static imports and fails CI when a module reaches across a boundary:

```yaml
# deptrac.yaml
deptrac:
  paths:
    - app/Modules
  layers:
    - name: Billing
      collectors:
        - type: directory
          value: app/Modules/Billing/
    - name: Shipping
      collectors:
        - type: directory
          value: app/Modules/Shipping/
  ruleset:
    Shipping:
      - Billing  # Shipping may only use Billing's public Contracts
    Billing: ~

```

Run `./vendor/bin/deptrac analyse` in CI. Any direct import of `Billing\Domain\` from `Shipping\` becomes a build failure.

---

Cross-Module Events Instead of Direct Calls
-------------------------------------------

For truly decoupled side-effects, prefer domain events over direct method calls:

```php
// Billing fires:
event(new InvoicePaid($invoiceId, $customerId, $amount));

// Shipping listens in its own EventServiceProvider:
Event::listen(InvoicePaid::class, ReleaseHeldShipments::class);

```

The event class lives in a shared `app/Events/` namespace (or a dedicated `app/Shared/` module) so neither context owns it.

---

Shared Kernel
-------------

Some primitives — `Money`, `Address`, `UserId` — are used everywhere. Place them in `app/Shared/` and treat that namespace as a read-only dependency for all modules. No module writes back to Shared.

---

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

- **One service provider per module** keeps registration isolated and explicit.
- **Public contract interfaces** are the only surface other modules may depend on.
- **Deptrac in CI** turns architectural rules into hard failures, not guidelines.
- **Domain events** decouple side-effects without requiring a message broker.
- **Shared kernel** is small and stable; resist the urge to dump utilities there.

 Found this useful?

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

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

  3 questions  

     Q01  Do I need separate databases per module?        No. A modular monolith shares one database. The boundary is in code, not infrastructure. You can extract a module to a microservice later if needed, but separate databases are not a prerequisite. 

      Q02  How do I handle shared Eloquent models that multiple modules need?        Place truly shared models (e.g. User) in a Shared or Core module. Each consuming module accesses them through that shared namespace, never by reaching into another module's Domain folder. 

      Q03  Is Deptrac the only option for enforcing boundaries?        PHPArkitect is a PHP-native alternative with a fluent API. Both integrate well with GitHub Actions or any CI pipeline and serve the same purpose: turning architectural conventions into automated checks. 

  Continue reading

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

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

 [ ![HTTP Query Method Support in Laravel 13.19](https://cdn.msaied.com/394/4c917aad559beddb5eeb9a7a1e107f4c.png) Laravel Laravel 13 HTTP Client 

### HTTP Query Method Support in Laravel 13.19

Laravel 13.19 adds Http::query() for the HTTP QUERY verb, query/queryJson testing helpers, a reduceInto() coll...

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

 8 Jul 2026     3 min read  

  Read    

 ](https://msaied.com/articles/http-query-method-support-in-laravel-1319) [ ![Blackfire & Xdebug Profiling in Laravel: Finding Real Bottlenecks in Production-Like Environments](https://cdn.msaied.com/393/a8dfc4ec52c4febc3ef41a491eb452ea.png) laravel performance profiling 

### Blackfire &amp; Xdebug Profiling in Laravel: Finding Real Bottlenecks in Production-Like Environments

Stop guessing where your Laravel app is slow. This guide walks through practical Blackfire and Xdebug profilin...

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

 8 Jul 2026     4 min read  

  Read    

 ](https://msaied.com/articles/blackfire-xdebug-profiling-in-laravel-finding-real-bottlenecks-in-production-like-environments) [ ![Laravel Cloud Security Defaults Behind Every Deploy](https://cdn.msaied.com/395/28df8f542fbe81ecee3ba4ad897f36d6.png) Laravel Cloud Security PHP 

### Laravel Cloud Security Defaults Behind Every Deploy

Laravel Cloud ships WAF rules, DDoS mitigation, automatic PHP patching, SOC 2 Type II compliance, and deploy-t...

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

 8 Jul 2026     4 min read  

  Read    

 ](https://msaied.com/articles/laravel-cloud-security-defaults-behind-every-deploy) 

   [  ![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)
