Preventing Cross-Layer Communication (Inside a Microservice)
Within each microservice, we enforce strict boundaries between architectural layers to maintain code quality and prevent tight coupling.1. The Dependency Inversion Principle (via Ports)
Within each service, the Hexagonal Architecture is used. High-level business logic defines interfaces (“Ports”) it requires, and low-level infrastructure (like database code) implements them. Architectural Principle: High-level modules should not depend on low-level modules. Both should depend on abstractions. Example from Tuturuuu:- Domain logic is pure and testable without infrastructure
- Can swap implementations (Supabase → Drizzle → Prisma) without changing business logic
- Inversion of control - domain doesn’t depend on infrastructure
- Technology agnostic core business logic
2. Strict Data Transfer Objects (DTOs)
The outer API layer of a service communicates with its inner Application layer using plain DTOs. This creates a strong boundary that prevents internal, behavior-rich Domain Models from being exposed to external layers, ensuring the core logic remains fully encapsulated. Architectural Principle: External layers communicate via simple data structures, not rich domain objects. Example from Tuturuuu:- Encapsulation of domain behavior
- Clear boundaries between layers
- API stability - internal changes don’t break API contract
- Serialization control - DTOs are JSON-friendly
- Versioning - can support multiple DTO versions
3. Explicit Layered Responsibility
The architecture enforces a clear separation of concerns. The Presentation layer handles HTTP, the Application layer orchestrates workflows, and the Domain layer contains pure business logic. This clarity prevents logic from being misplaced and ensures layers only interact through their well-defined public interfaces. Architectural Principle: Each layer has a single, well-defined responsibility and communicates only through defined interfaces. Layer Structure in Tuturuuu:| From Layer | Can Call | Cannot Call | Reason |
|---|---|---|---|
| Presentation | Application | Domain directly | Bypass orchestration |
| Application | Domain, Infrastructure | Presentation | Circular dependency |
| Domain | Nothing | Application, Infrastructure, Presentation | Pure logic |
| Infrastructure | Domain (via interfaces) | Application, Presentation | Implementation details |
- Clear responsibility per layer
- Easy to reason about where code belongs
- Testability - each layer tested independently
- Maintainability - changes confined to appropriate layer
Preventing Unwanted Cross-Service Communication (Between Microservices)
1. Published Event Contracts as the Sole Interface
The only way services interact is through the events they publish. The internal implementation, database schema, and private functions of each service are completely hidden. Services are black boxes that communicate only through public, versioned event schemas, providing the strongest possible form of encapsulation. Architectural Principle: Services communicate only via well-defined event contracts. Example from Tuturuuu:- Complete encapsulation of service internals
- Clear API contract via event schemas
- Versioning support for evolution
- Independent deployment of services
- No direct dependencies between services
2. Broker-Level Access Control (ACLs)
The architecture does not rely on trust. The message broker itself is configured to enforce communication boundaries. Using ACLs, we can define that only theAccountManagementService can produce UserRegistered events, and only specific downstream services are allowed to consume them.
Architectural Principle: Enforce service boundaries at the infrastructure level, not just by convention.
Example with Trigger.dev:
- Enforced boundaries at infrastructure level
- Security - services can’t spoof events from other services
- Audit trail - know exactly which services produce/consume events
- Documentation - ACL config documents allowed communication
3. Elimination of Synchronous Coupling
By committing to an event-driven design, the architecture inherently avoids direct, synchronous calls between services for core business flows. This prevents the tight coupling that arises when one service needs to know the network location, API signature, and synchronous availability of another, reinforcing service autonomy. Architectural Principle: Services don’t make synchronous calls to each other. Example - Async event flow:- No cascading failures
- Independent availability of services
- Faster user responses
- Easier to add new services
Encapsulation in Practice
Workspace Isolation via RLS
Package Boundaries
Summary
| Encapsulation Type | Pattern | Enforced By | Benefit |
|---|---|---|---|
| Cross-Layer (Internal) | Dependency Inversion | Hexagonal Architecture | Testable, flexible |
| Cross-Layer (Internal) | DTOs | Layer boundaries | Stable APIs |
| Cross-Layer (Internal) | Layered Responsibility | Directory structure | Clear organization |
| Cross-Service (External) | Event Contracts | Event schemas | Service autonomy |
| Cross-Service (External) | Broker ACLs | Infrastructure config | Security, governance |
| Cross-Service (External) | Async-only | Event-driven architecture | No cascading failures |
Related Documentation
- Hexagonal Architecture - Detailed ports and adapters pattern
- Event-Driven Architecture - Event-based communication
- Microservices Patterns - Service boundaries
- Authorization - RLS policies and permissions