Architectural Philosophy
Our architectural approach is guided by several core principles:- Loose Coupling, High Cohesion: Services and components are independently deployable with well-defined boundaries
- Evolutionary Design: The system can adapt to changing requirements without extensive rewrites
- Resilience by Default: Failures are isolated and don’t cascade across the system
- Developer Experience First: Architecture choices prioritize maintainability and developer productivity
- Pragmatic Trade-offs: We make conscious decisions about complexity vs. simplicity based on actual needs
Architectural Layers
Core Architectural Patterns
1. Event-Driven Architecture (EDA)
Asynchronous, event-based communication for core business workflows using Trigger.dev. When to use:- Background processing (email sending, data transformations)
- Long-running operations that shouldn’t block user requests
- Cross-service coordination that benefits from decoupling
2. Hexagonal Architecture (Ports & Adapters)
Clean separation between business logic and infrastructure concerns within each service. When to use:- Complex business logic that needs protection from technology changes
- Services requiring high testability
- When multiple adapters might be needed (REST + tRPC + GraphQL)
3. Microservices in a Monorepo
Logical service boundaries with shared tooling and dependencies via Turborepo. When to use:- Independent teams working on different features
- Services with different scaling needs
- Features that benefit from technological flexibility
4. React Modular Monolith
Organized frontend with clear module boundaries and headless UI patterns. When to use:- Complex UIs with shared component libraries
- Multiple client types (web, mobile) sharing logic
- Teams that value fast iteration over distributed deployments
Decision Matrix
| Requirement | Recommended Pattern | Alternative | Trade-off |
|---|---|---|---|
| Background job processing | Event-Driven (Trigger.dev) | Synchronous API | Complexity vs. Resilience |
| User-facing API | tRPC + Next.js API Routes | GraphQL | Type safety vs. Flexibility |
| Business logic isolation | Hexagonal Architecture | Traditional Layers | Testability vs. Simplicity |
| Service boundaries | Monorepo Microservices | Polyrepo | DX vs. Independence |
| Frontend state | React Query + Jotai | Redux | Simplicity vs. Predictability |
| Data access | Supabase RLS | Application-level auth | Security vs. Performance |
Key Design Documents
Foundational Decisions
- Architectural Decisions - Why we chose microservices, EDA, hexagonal architecture, and React patterns
Pattern Deep Dives
- Event-Driven Architecture - Advantages, drawbacks, and implementation
- Extensibility, Resilience & Scalability - 15 reasons these properties are prioritized
- Encapsulation Patterns - Preventing unwanted coupling
Implementation Guides
- Hexagonal Architecture - Ports, adapters, and layering
- Microservices Patterns - Service boundaries and communication
Integration with Existing Docs
This system design documentation complements our existing architecture guides:- Routing: Workspace-scoped routing implementation
- Data Fetching: RSC, Server Actions, React Query patterns
- Authentication: Supabase Auth integration
- Authorization: RLS and permission system
- tRPC: Type-safe API layer
- API Routes: REST endpoint patterns
Quality Attributes
Our architecture explicitly optimizes for:- Extensibility: New features can be added with minimal changes to existing code
- Resilience: Failures are isolated and the system degrades gracefully
- Scalability: Services can scale independently based on demand
- Maintainability: Code is organized, testable, and understandable
- Developer Experience: Fast feedback loops and productive workflows
Getting Started
For developers new to the codebase:- Start with Architectural Decisions to understand the “why”
- Review Microservices Patterns to understand service boundaries
- Explore Hexagonal Architecture for code organization within services
- Read Event-Driven Architecture for async communication patterns
- Determine service boundary (which app in
apps/?) - Apply hexagonal architecture within the service
- Use events (Trigger.dev) for background work
- Follow data fetching patterns for frontend integration
- Ensure proper encapsulation and testing
Architecture Evolution
This architecture is not static. As the platform grows, we continuously evaluate:- When to extract shared logic into new packages
- When to split apps into smaller services
- When to introduce new communication patterns
- When to adopt new technologies