Modular Monolith & Hexagonal Architecture
Vertical slicing, Hexagonal Architecture, and Pragmatic Isolation in Laravel.
The project is structured as a Modular Monolith. This means code is divided into independent vertical slices (Domains) that can interact only through defined contracts.
Module Structure
Each domain (app/Domains/Auth, app/Domains/Reservation) is self-contained and includes:
Entities,Services,DTOs(Domain Layer)UseCases(Application Layer)Controllers,Repositories(Infrastructure Layer)Providers(Dependency Injection)
Hexagonal Architecture (Ports & Adapters)
For Cross-Module Communication, I use Hexagonal Architecture.
Rule:
Module A (Consumer) never depends on specific classes of Module B (Provider). It depends only on the Interface (Port) that it defines itself. This ensures:
- Low Coupling: Changes in the
Usermodule implementation do not break theAuthmodule. - Inversion of Control: Dependencies point towards abstractions, not details.
Interaction Scheme (Auth -> User)
textDomain: Auth (Consumer) Domain: User (Provider) ┌─────────────────────────┐ ┌──────────────────────────────────┐ │ │ │ │ │ [AuthUseCase] │ │ [AuthUserProviderAdapter] │ │ │ (Calls) │ │ (Adapter) │ │ ▼ │ │ │ │ │ [UserProviderInterface]│<──────────│────────────┘ (Implements) │ │ (Port) │ │ │ │ │ │ │ ▼ │ │ │ │ [UserRepository] │ └─────────────────────────┘ │ (Internal Logic) │ │ │ └──────────────────────────────────┘
- Port: The
Authdomain defines theUserProviderInterface. "I need to find a user by email". - Adapter: The
Userdomain implements this interface inAuthUserProviderAdapter. - Binding: The ServiceProvider binds the interface to the implementation.
Advantages
- Isolation: You can rewrite the
Usermodule without breakingAuth. - Testability: The
Authmodule is easy to test by mockingUserProviderInterface. - Scalability: If you need to extract
Userinto a microservice, simply rewrite the Adapter to use an HTTP client.
Pragmatic Data Isolation
I follow the Pragmatic Modular Monolith approach:
-
Write Operations (Command): Strictly via API.
- It is forbidden to modify another module's data directly (e.g.,
UPDATE/INSERTon theuserstable from theReservationmodule). - This guarantees that the Data Owner's business rules are always executed.
- It is forbidden to modify another module's data directly (e.g.,
-
Read Operations (Query):
- Using
JOINs with other modules' tables is allowed for building efficient queries (lists, reports) to avoid N+1 issues. - However, remember: strong coupling at the DB level will complicate potential future separation into microservices.
- Using