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 with Module Service Providers

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

 Learn how to carve a Laravel application into cohesive bounded contexts using per-module service providers, explicit public APIs, and enforced cross-module contracts — without reaching for microservices.

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

ShareCopy linkCopied

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

  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)

 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.

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

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

  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.

   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.

   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.

   ![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 articleFilament v4 Table Bulk Actions: Custom Confirmation Modals and Scoped Authorization](https://www.msaied.com/public/articles/filament-v4-table-bulk-actions-custom-confirmation-modals-and-scoped-authorization) [Next articleMacros, Mixins, and Custom Collection Methods in Laravel](https://www.msaied.com/public/articles/macros-mixins-and-custom-collection-methods-in-laravel-2)  

   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)

 ###  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)
