design-patterns
Design Patterns Skill
Provides comprehensive architectural patterns for building scalable systems. This skill focuses on High-Level Architecture, Layer Boundaries, and Package Organization.
For implementation details, refer to the specific layer skills:
- Controllers: HTTP Boundary & Input Validation
- Services: Business Logic & Orchestration
- Repositories: Data Access & Persistence
Architectural Layers
Organize code into distinct layers with clear responsibilities:
┌─────────────────────────────────┐
│ HTTP API Layer │ Controllers, Route handlers
│ (Request/Response handling) │ Input validation, HTTP status codes
└──────────────┬──────────────────┘
│ Calls Services
│
┌──────────────▼──────────────────┐
│ Business Logic Layer │ Services, Orchestration
│ (Core domain operations) │ Transactions, Validation, Error handling
└──────────────┬──────────────────┘
│ Calls Repositories
│
┌──────────────▼──────────────────┐
│ Data Access Layer │ Repositories, Queries
│ (Database operations) │ ORM mapping, Query building
└──────────────┬──────────────────┘
│ Calls Database
│
┌──────────────▼──────────────────┐
│ Database Layer │ Tables, Relationships
│ (Data persistence) │ Constraints, Migrations
└─────────────────────────────────┘
Key Boundaries:
- Controller Boundary: Only Controllers speak HTTP (Req/Res, Status Codes). Services should NEVER know about HTTP.
- Service Boundary: Services implement all business logic. Controllers should NEVER contain business logic.
- Repository Boundary: Repositories hide the Database/ORM. Services should NEVER write raw queries or know about SQL.
- Types Boundary: Use Shared API Types (
@eridu/api-types) at the external edges (Controller inputs/outputs). Use Domain/DB types internally.
Dependency Injection (High Level)
Pattern: Inversion of Control.
- Inject dependencies, do not instantiate them manually.
- Low Coupling: Rely on interfaces/contracts instead of concrete implementations where possible.
- Testability: Ensure dependencies can be easily mocked in unit tests.
Service Architecture Strategy
Distinguish between two types of services to manage complexity and avoid circular dependencies.
| Type | Responsibility | Dependencies | Example |
|---|---|---|---|
| Model Service | CRUD for a Single Entity. | Repository, UtilityService | UserService, ShowService |
| Orchestration Service | Coordinate Multiple Entities. | Multiple Model Services or Repositories | ShowOrchestrationService |
Decision Tree:
- Does it touch only one table/entity? -> Model Service.
- Does it touch multiple tables/entities in a transaction? -> Orchestration Service.
Monorepo Package Organization
Organize workspace packages by concern:
packages/api-types: Single Source of Truth for API contracts. Shared between FE and BE.packages/auth-sdk: Authentication utilities (JWT, JWKS) shared across apps.packages/ui: Shared UI components (React) and styles.packages/eslint-config: Shared linting rules.
Best Practices:
- ✅ Always export compiled code from
dist/in packages. - ✅ Use
workspace:*for internal dependencies. - ❌ Never import from an
appinto apackage(Cyclic dependency). - ❌ Ensure apps rely on packages, not other apps.
Module Exports
Rule: Export Services Only. No exceptions.
Modules export their Service as the only public API. Repositories are private implementation details — never export them.
✅ exports: [UserService] ❌ exports: [UserService, UserRepository] ❌ exports: [UserRepository]
Why: Exporting a repository leaks the data layer. Every consumer would couple to your database access patterns. When you need a new repository operation from an orchestration service, add a method to the model service instead.
Join/Association Table modules: Follow the same rule.
- Add a service even if it's thin (it generates UIDs and exposes the repo methods)
- Export only the service
- Examples: ShowMcModule (DB-level:
ShowMCPrisma model, API surface uses "creator"), ShowPlatformModule, TaskTargetModule - Naming note: Some model-layer modules retain legacy DB names (e.g.
ShowMcModule/ShowMcService) while the API surface and admin routes use creator-first naming (/admin/show-creators). This is intentional — the Prisma model isShowMC, and renaming it would require a migration.
Reference/Lookup table modules: Same rule.
- Examples: ShowStandardModule, ShowStatusModule, ShowTypeModule
Orchestration modules: Same rule.
- Export only the orchestration service
- Never export orchestration-internal repositories or processors
When to Create a Separate Module for Join Tables
Create a separate module when the association:
- Has its own business lifecycle (create/restore/cascade-delete methods)
- Carries extra payload fields beyond the two FK columns
- Is referenced independently by multiple domains
Fold into the parent module when:
- It is purely a FK link with no extra data or logic
- Only created/deleted within a single transaction owned by the parent
Examples:
- ShowMcModule (separate) → has restore/cascade methods, note field (DB model:
ShowMC; API surface: "show-creator") - ShowPlatformModule (separate) → has liveStreamLink, viewerCount
- TaskTargetModule (separate) → simple join, but kept separate for consistency
Performance Optimization Strategy
Address performance at the correct layer:
1. Database Layer (The Foundation)
- Create indexes on foreign keys and frequently queried fields.
- Use correct column types.
2. Repository Layer (The Query)
- Eager Loading: Use
includeto solve N+1 problems. - Bulk Operations: Use
createMany/updateManyinstead of loops. - Soft Deletes: Always filter
deletedAt: null.
3. Service Layer (The Logic)
- Parallel Execution: Use
Promise.all()for independent operations. - Transactions: Keep transactions short and focused on DB writes.
4. HTTP Layer (The Edge)
- Caching: Cache responses where appropriate.
- Pagination: Always paginate list endpoints.
Related Skills
For architecture-specific patterns (N+1 queries, Soft Deletes, etc.), refer to:
More from allenlin90/eridu-services
service-pattern-nestjs
Comprehensive NestJS service implementation patterns. This skill should be used when implementing Model Services, Orchestration Services, or business logic with NestJS decorators.
8erify-authorization
Patterns for implementing authorization in erify_api with current StudioMembership + AdminGuard behavior, plus planned RBAC references
6data-validation
Provides comprehensive guidance for input validation, data serialization, and ID management in backend APIs. This skill should be used when designing validation schemas, transforming request/response data, mapping database IDs to external identifiers, and ensuring type safety across API boundaries.
6repository-pattern-nestjs
Comprehensive Prisma repository implementation patterns for NestJS. This skill should be used when implementing repositories that extend BaseRepository or use Prisma delegates.
6task-template-builder
Provides guidelines for the Task Template Builder architecture, including Schema alignment, Draft storage, Drag-and-Drop, and Validation logic.
6engineering-best-practices-enforcer
Enforces repo-aligned engineering best practices for review and refactoring. This skill should be used when auditing or refactoring code across eridu-services with priority on local architecture/context, then official framework docs, then principles such as SOLID.
2