Modular Monolith in Laravel: Bounded Contexts | 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 Microservice Tax

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

 Learn how to carve a Laravel application into cohesive bounded contexts using modules, internal contracts, and cross-boundary rules — all without splitting into microservices.

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

ShareCopy linkCopied

 ![Modular Monolith in Laravel: Enforcing Bounded Contexts Without a Microservice Tax](https://cdn.msaied.com/555/c194fc79e9397fef3bcd3a896eb558fd.png) 

  On this page +1. [Why a Modular Monolith?](#why-a-modular-monolith)
2. [Directory Structure](#directory-structure)
3. [Cross-Boundary Communication via Internal Contracts](#cross-boundary-communication-via-internal-contracts)
4. [Enforcing Boundaries with Deptrac](#enforcing-boundaries-with-deptrac)
5. [Testing Module Isolation with Pest](#testing-module-isolation-with-pest)
6. [Key Takeaways](#key-takeaways)

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

Microservices promise isolation but deliver operational overhead. A well-structured modular monolith gives you the same bounded-context discipline inside a single deployable unit. The key is treating module boundaries as real contracts enforced by tooling, not just folder conventions.

Directory Structure
-------------------

Organise each module under `src/Modules/{Context}/` with a predictable internal layout:

```php
src/
  Modules/
    Billing/
      Actions/
      Data/          # DTOs
      Domain/        # Entities, value objects
      Http/
      Infrastructure/ # Eloquent models, repositories
      Providers/
        BillingServiceProvider.php
      routes.php
    Catalog/
      ...

```

Each module registers itself. `BillingServiceProvider` is the only entry point the framework touches:

```php
// src/Modules/Billing/Providers/BillingServiceProvider.php
namespace App\Modules\Billing\Providers;

use Illuminate\Support\ServiceProvider;

class BillingServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->app->bind(
            \App\Modules\Billing\Domain\Contracts\PaymentGateway::class,
            \App\Modules\Billing\Infrastructure\StripeGateway::class,
        );
    }

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

```

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

Cross-Boundary Communication via Internal Contracts
---------------------------------------------------

Modules must never import each other's Eloquent models directly. Define a thin contract in the consuming module:

```php
// src/Modules/Catalog/Domain/Contracts/ProductPricingPort.php
namespace App\Modules\Catalog\Domain\Contracts;

interface ProductPricingPort
{
    public function priceForProduct(string $productId): Money;
}

```

The Billing module provides the adapter:

```php
// src/Modules/Billing/Infrastructure/CatalogPricingAdapter.php
namespace App\Modules\Billing\Infrastructure;

use App\Modules\Catalog\Domain\Contracts\ProductPricingPort;
use App\Modules\Catalog\Domain\ValueObjects\Money;

class CatalogPricingAdapter implements ProductPricingPort
{
    public function priceForProduct(string $productId): Money
    {
        // Billing queries its own read model, not Catalog's Eloquent model
        $row = \DB::table('billing_product_prices')
            ->where('product_id', $productId)
            ->sole();

        return new Money($row->amount_cents, $row->currency);
    }
}

```

Binding lives in `BillingServiceProvider::register()`. Catalog never knows which module satisfies the port.

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

Folder conventions break under deadline pressure. [Deptrac](https://qossmic.github.io/deptrac/) statically analyses `use` statements and fails CI when a boundary is crossed:

```yaml
# deptrac.yaml
deptrac:
  paths:
    - src/Modules
  layers:
    - name: Billing
      collectors:
        - type: directory
          value: src/Modules/Billing/.*
    - name: Catalog
      collectors:
        - type: directory
          value: src/Modules/Catalog/.*
  ruleset:
    Billing:
      - Catalog   # Billing may depend on Catalog contracts only
    Catalog: ~    # Catalog depends on nothing

```

Run `deptrac analyse` in your GitHub Actions pipeline. Any direct `use App\Modules\Catalog\Infrastructure\` inside Billing fails the build.

Testing Module Isolation with Pest
----------------------------------

Test each module in isolation by binding fakes in the test service provider:

```php
// tests/Modules/Billing/ChargeCustomerActionTest.php
use App\Modules\Billing\Actions\ChargeCustomerAction;
use App\Modules\Billing\Domain\Contracts\PaymentGateway;
use App\Modules\Billing\Tests\Fakes\FakePaymentGateway;

beforeEach(function () {
    $this->fake = new FakePaymentGateway();
    app()->instance(PaymentGateway::class, $this->fake);
});

it('charges the correct amount', function () {
    $action = app(ChargeCustomerAction::class);
    $action->execute(customerId: 'cus_123', amountCents: 4999);

    expect($this->fake->charges())->toHaveCount(1)
        ->and($this->fake->charges()[0]->amountCents)->toBe(4999);
});

```

No database, no HTTP — the module is a self-contained unit.

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

- **One service provider per module** is the only seam the framework touches.
- **Ports and adapters** prevent Eloquent models from leaking across boundaries.
- **Deptrac in CI** turns architectural rules into failing builds, not suggestions.
- **Pest fakes** let you test domain logic without a running database.
- The modular monolith is a stepping stone: each module can become a microservice later with minimal refactoring because the contracts already exist.

- [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 a modular monolith different from just organising code into folders?Folders are a naming convention. A modular monolith enforces boundaries through service providers as the sole entry point, interface-based cross-module communication, and static analysis tools like Deptrac that fail CI when a boundary is violated.

   Can I share Eloquent models between modules?You should not. Sharing models couples modules at the database schema level. Instead, each module owns its own read models or queries, and exposes data through typed contracts (interfaces returning DTOs or value objects).

   Does this approach work with Laravel 11's flat bootstrap structure?Yes. In Laravel 11 you register module service providers in bootstrap/providers.php. Each module's ServiceProvider handles its own route loading, migration paths, and container bindings independently.

   ![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 articleOctane Worker Lifecycle, State Leakage, and Memory Management in Production](https://www.msaied.com/public/articles/octane-worker-lifecycle-state-leakage-and-memory-management-in-production-2) [Next articleContextual Macros and Mixins: Extending Laravel Collections Without Bloat](https://www.msaied.com/public/articles/contextual-macros-and-mixins-extending-laravel-collections-without-bloat)  

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

1. [Why a Modular Monolith?](#why-a-modular-monolith)
2. [Directory Structure](#directory-structure)
3. [Cross-Boundary Communication via Internal Contracts](#cross-boundary-communication-via-internal-contracts)
4. [Enforcing Boundaries with Deptrac](#enforcing-boundaries-with-deptrac)
5. [Testing Module Isolation with Pest](#testing-module-isolation-with-pest)
6. [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/740/cce86edc21eddcbdd2f2454fadaf9c70.png)  · 3 min read### The Pipeline Pattern in Laravel: Custom Pipelines Beyond Middleware

5 Oct 2026 ](https://www.msaied.com/public/articles/the-pipeline-pattern-in-laravel-custom-pipelines-beyond-middleware-1) [ ![](https://cdn.msaied.com/739/2d6897fdcdcf090613f96f72a64b8a78.png)  · 4 min read### MySQL Full-Text Search in Laravel: Indexes, Relevance Scoring, and Boolean Mode

4 Oct 2026 ](https://www.msaied.com/public/articles/mysql-full-text-search-in-laravel-indexes-relevance-scoring-and-boolean-mode) [ ![](https://cdn.msaied.com/738/073696a3fefe18bec825beec5ac658f5.png)  · 4 min read### Laravel Queue Rate-Limited Middleware: Throttling Jobs Without Losing Work

4 Oct 2026 ](https://www.msaied.com/public/articles/laravel-queue-rate-limited-middleware-throttling-jobs-without-losing-work) 

  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)
