Appearance
Design Patterns
Design patterns are proven solutions to recurring problems in software development. At Synapse Studios, we use specific patterns that align with our clean architecture principles and help us build maintainable, testable applications.
Why Patterns Matter
Patterns provide:
- Common vocabulary - Teams can communicate complex ideas efficiently
- Proven solutions - Avoid reinventing the wheel for solved problems
- Consistent structure - Makes codebases more predictable and maintainable
- Clear responsibilities - Each pattern has a specific purpose and boundary
Our Core Patterns
Use Cases
The heart of our business logic organization. Use cases orchestrate the flow of data to and from entities, and direct those entities to use their business rules to achieve the goals of the use case.
When to Use:
- Implementing business workflows and processes
- Coordinating multiple domain operations
- Keeping business logic independent of frameworks
Key Benefits:
- Single Responsibility Principle adherence
- Framework independence
- Excellent testability
- Clear business intent
Application Service
The Domain-Driven Design equivalent of Use Cases. Application Services orchestrate domain operations with strict separation—they contain no business logic, only coordination.
When to Use:
- Working in DDD contexts
- Need strict "no business logic" discipline in orchestration layer
- Building systems with bounded contexts
Key Benefits:
- Clear separation between orchestration and domain logic
- Aligns with DDD terminology and patterns
- Forces domain logic into appropriate places
Note: At Synapse, we typically prefer Use Cases over Application Services for broader accessibility.
Domain Service
Encapsulates domain operations that don't naturally belong to entities or value objects. When business logic spans multiple aggregates or is fundamentally an activity rather than a thing, Domain Services provide a home for that behavior.
When to Use:
- Logic spans multiple entities or aggregates
- Operation is intrinsically an activity (verb, not noun)
- Forcing logic into an entity would distort its purpose
Key Benefits:
- Prevents domain logic from leaking into application layer
- Keeps entity responsibilities focused
- Maintains domain model integrity
Repository Pattern
Mediates between the domain and data mapping layers, providing a more object-oriented view of the persistence layer. Repositories encapsulate the logic needed to access data sources.
When to Use:
- Abstracting database operations
- Switching between different data sources
- Testing business logic without a database
- Implementing complex queries
Key Benefits:
- Separation of concerns
- Improved testability through mocking
- Consistent data access interface
- Database technology independence
Page Object Pattern
Encapsulates page structure and behaviors in acceptance tests, providing a clear API for test scenarios while hiding implementation details of the UI.
When to Use:
- Writing end-to-end tests
- Creating maintainable test suites
- Reducing test brittleness
- Sharing test utilities across scenarios
Key Benefits:
- DRY principle in tests
- Reduced maintenance when UI changes
- Readable test scenarios
- Reusable page interactions
Pattern Relationships
These patterns work together to create a cohesive architecture:
- Use Cases (or Application Services in DDD) call Repositories to fetch and persist data
- Use Cases may delegate complex business logic to Domain Services
- Domain Services contain business logic that doesn't fit in entities
- Page Objects interact with the UI that triggers Use Cases
- Repositories return domain entities that Use Cases orchestrate
Getting Started
- Start with Use Cases when implementing new features
- Add Repository abstractions for data access
- Implement Page Objects for acceptance tests
Further Reading
See how these patterns are implemented in practice:
- NestJS Implementation
- React Implementation
- Testing Guidelines