Architecture Patterns
Guideline Metadata
Version: 1.0.0 Last Modified: 2025-01-19T00:00:00Z Category: Core Guidelines Priority: Critical
Standardized architectural patterns and implementation guidelines for all projects.
Three-Layer Architecture
The fundamental architecture pattern used across all projects.
Layer Overview
graph TB
subgraph "External World"
HTTP[HTTP Requests]
DB[(Database)]
API[External APIs]
FILES[File System]
end
subgraph "Application Layers"
subgraph "IO Layer"
CONTROLLERS[Controllers/Routes]
VALIDATION[Input Validation]
SERIALIZATION[Response Serialization]
end
subgraph "Management Layer"
MANAGERS[Managers]
ORCHESTRATION[Business Logic]
COORDINATION[Provider Coordination]
end
subgraph "Provider Layer"
DB_PROVIDER[Database Provider]
API_PROVIDER[API Provider]
FILE_PROVIDER[File Provider]
end
subgraph "Side Layer"
MODELS[Models]
MAPPERS[Mappers]
HELPERS[Helpers]
CONSTANTS[Constants/Enums]
end
end
HTTP --> CONTROLLERS
CONTROLLERS --> MANAGERS
MANAGERS --> DB_PROVIDER
MANAGERS --> API_PROVIDER
MANAGERS --> FILE_PROVIDER
DB_PROVIDER --> DB
API_PROVIDER --> API
FILE_PROVIDER --> FILES
MANAGERS -.-> MODELS
MANAGERS -.-> MAPPERS
MANAGERS -.-> HELPERS
classDef ioLayer fill:#e1f5fe
classDef managementLayer fill:#f3e5f5
classDef providerLayer fill:#e8f5e8
classDef sideLayer fill:#fff3e0
class CONTROLLERS,VALIDATION,SERIALIZATION ioLayer
class MANAGERS,ORCHESTRATION,COORDINATION managementLayer
class DB_PROVIDER,API_PROVIDER,FILE_PROVIDER providerLayer
class MODELS,MAPPERS,HELPERS,CONSTANTS sideLayer
Layer Responsibilities
IO Layer
- Purpose: Handle external communication
- Responsibilities:
- Receive and validate input
- Format and send responses
- Handle authentication/authorization
- Rate limiting and request logging
// Example: API Controller
export class UserController {
constructor(private userManager: UserManager) {}
async getUser(req: Request, res: Response) {
const { id } = this.validateGetUserRequest(req);
const result = await this.userManager.getUserById(id);
if (!result.success) {
return res.status(404).json({ error: result.error });
}
res.json(this.formatUserResponse(result.data));
}
private validateGetUserRequest(req: Request): { id: string } {
// Input validation logic
}
private formatUserResponse(user: User): UserResponse {
// Response formatting logic
}
}
Management Layer
- Purpose: Orchestrate business logic
- Responsibilities:
- Coordinate between providers
- Implement business rules
- Handle complex operations
- Manage transactions
// Example: User Manager
export class UserManager {
constructor(
private userProvider: UserProvider,
private emailProvider: EmailProvider,
private auditProvider: AuditProvider
) {}
async createUser(userData: CreateUserRequest): Promise<Result<User>> {
try {
// Validate business rules
const validation = this.validateUserData(userData);
if (!validation.success) {
return { success: false, error: validation.error };
}
// Coordinate multiple providers
const user = await this.userProvider.createUser(userData);
await this.emailProvider.sendWelcomeEmail(user.email);
await this.auditProvider.logUserCreation(user.id);
return { success: true, data: user };
} catch (error) {
return { success: false, error: error.message };
}
}
}
Provider Layer
- Purpose: Perform specific tasks
- Responsibilities:
- Database operations
- External API calls
- File system operations
- Cache management
// Example: Database Provider
export class UserProvider {
constructor(private db: Database) {}
async getUserById(id: string): Promise<User | null> {
const row = await this.db.query(
'SELECT * FROM users WHERE id = $1',
[id]
);
return row ? this.mapRowToUser(row) : null;
}
async createUser(userData: CreateUserData): Promise<User> {
const row = await this.db.query(
'INSERT INTO users (email, name) VALUES ($1, $2) RETURNING *',
[userData.email, userData.name]
);
return this.mapRowToUser(row);
}
private mapRowToUser(row: DatabaseRow): User {
return {
id: row.id,
email: row.email,
name: row.name,
createdAt: row.created_at
};
}
}
Side Layer
- Purpose: Support all layers with shared utilities
- Components:
- Models (data structures)
- Mappers (data transformation)
- Helpers (utility functions)
- Constants and enums
// Models
export interface User {
id: string;
email: string;
name: string;
createdAt: Date;
}
// Mappers
export class UserMapper {
static toDto(user: User): UserDto {
return {
id: user.id,
email: user.email,
displayName: user.name
};
}
}
// Helpers
export class DateHelper {
static formatDate(date: Date): string {
return date.toISOString().split('T')[0];
}
}
// Constants
export const USER_CONSTANTS = {
MAX_NAME_LENGTH: 100,
MIN_PASSWORD_LENGTH: 8
} as const;
Critical Architecture Rules
Rule 1: No Provider-to-Provider Communication
// ❌ WRONG: Providers calling each other
export class UserProvider {
constructor(private orderProvider: OrderProvider) {} // NO!
async deleteUser(id: string) {
await this.orderProvider.deleteUserOrders(id); // NO!
}
}
// ✅ CORRECT: Manager orchestrates
export class UserManager {
constructor(
private userProvider: UserProvider,
private orderProvider: OrderProvider
) {}
async deleteUser(id: string) {
await this.orderProvider.deleteUserOrders(id);
await this.userProvider.deleteUser(id);
}
}
Rule 2: Providers Return Domain Models
// ❌ WRONG: Returning raw database rows
async getUserById(id: string): Promise<DatabaseRow> {
return this.db.query('SELECT * FROM users WHERE id = $1', [id]);
}
// ✅ CORRECT: Returning domain models
async getUserById(id: string): Promise<User | null> {
const row = await this.db.query('SELECT * FROM users WHERE id = $1', [id]);
return row ? this.mapRowToUser(row) : null;
}
Rule 3: Error Handling at Layer Boundaries
// Providers: Return null/throw specific errors
export class UserProvider {
async getUserById(id: string): Promise<User | null> {
try {
const row = await this.db.query(/* ... */);
return row ? this.mapRowToUser(row) : null;
} catch (error) {
throw new DatabaseError('Failed to fetch user', error);
}
}
}
// Managers: Return Result objects
export class UserManager {
async getUserById(id: string): Promise<Result<User>> {
try {
const user = await this.userProvider.getUserById(id);
return user
? { success: true, data: user }
: { success: false, error: 'User not found' };
} catch (error) {
return { success: false, error: error.message };
}
}
}
// IO Layer: Convert to HTTP responses
export class UserController {
async getUser(req: Request, res: Response) {
const result = await this.userManager.getUserById(req.params.id);
if (!result.success) {
return res.status(404).json({ error: result.error });
}
res.json(result.data);
}
}
Common Patterns
Result Pattern
export interface Result<T> {
success: boolean;
data?: T;
error?: string;
}
// Usage
const result = await userManager.createUser(userData);
if (result.success) {
// Handle success
console.log(result.data);
} else {
// Handle error
console.error(result.error);
}
Repository Pattern (for Providers)
export interface UserProvider {
getUserById(id: string): Promise<User | null>;
createUser(userData: CreateUserData): Promise<User>;
updateUser(id: string, changes: Partial<User>): Promise<User>;
deleteUser(id: string): Promise<void>;
}
export class DatabaseUserProvider implements UserProvider {
// Implementation
}
export class MockUserProvider implements UserProvider {
// Test implementation
}
Factory Pattern (for Complex Creation)
export class ServiceFactory {
static createUserService(config: Config): UserManager {
const userProvider = new DatabaseUserProvider(config.database);
const emailProvider = new EmailProvider(config.email);
const auditProvider = new AuditProvider(config.audit);
return new UserManager(userProvider, emailProvider, auditProvider);
}
}
Project Structure Template
Standard Folder Structure
src/
├── io/ # IO Layer
│ ├── controllers/ # HTTP controllers
│ ├── routes/ # Route definitions
│ ├── middleware/ # Request middleware
│ └── validators/ # Input validation
│
├── managers/ # Management Layer
│ ├── user-manager.ts
│ ├── order-manager.ts
│ └── payment-manager.ts
│
├── providers/ # Provider Layer
│ ├── database/
│ │ ├── user-provider.ts
│ │ └── order-provider.ts
│ ├── external/
│ │ ├── email-provider.ts
│ │ └── payment-provider.ts
│ └── file/
│ └── storage-provider.ts
│
├── models/ # Side Layer - Data structures
│ ├── user.ts
│ ├── order.ts
│ └── payment.ts
│
├── mappers/ # Side Layer - Data transformation
│ ├── user-mapper.ts
│ └── order-mapper.ts
│
├── helpers/ # Side Layer - Utilities
│ ├── date-helper.ts
│ ├── validation-helper.ts
│ └── crypto-helper.ts
│
├── constants/ # Side Layer - Configuration
│ ├── app-constants.ts
│ └── error-messages.ts
│
└── types/ # Side Layer - Type definitions
├── api-types.ts
└── database-types.ts
Technology-Specific Adaptations
SvelteKit Project
src/
├── routes/ # SvelteKit routes (IO Layer)
├── lib/
│ ├── managers/
│ ├── providers/
│ ├── models/
│ ├── mappers/
│ ├── helpers/
│ └── constants/
└── app.html
Node.js/Express API
src/
├── controllers/ # Express controllers (IO Layer)
├── routes/ # Express routes (IO Layer)
├── middleware/ # Express middleware (IO Layer)
├── managers/ # Management Layer
├── providers/ # Provider Layer
├── models/ # Domain models
├── mappers/ # Data transformation
├── helpers/ # Utilities
└── constants/ # Configuration
C# Web API
ProjectName/
├── Controllers/ # ASP.NET controllers (IO Layer)
├── Managers/ # Management Layer
├── Providers/ # Provider Layer
├── Models/ # Domain models
├── Mappers/ # Data transformation
├── Helpers/ # Utilities
└── Constants/ # Configuration
Testing Architecture
Test Structure Mirrors Code Structure
tests/
├── unit/
│ ├── managers/
│ ├── providers/
│ ├── mappers/
│ └── helpers/
├── integration/
│ ├── api/
│ └── database/
└── e2e/
└── user-flows/
Testing Each Layer
// Provider tests (unit)
describe('UserProvider', () => {
it('should return user when exists', async () => {
const provider = new UserProvider(mockDatabase);
const user = await provider.getUserById('123');
expect(user).toEqual(expectedUser);
});
});
// Manager tests (unit with mocked providers)
describe('UserManager', () => {
it('should create user and send email', async () => {
const manager = new UserManager(mockUserProvider, mockEmailProvider);
const result = await manager.createUser(userData);
expect(result.success).toBe(true);
expect(mockEmailProvider.sendWelcomeEmail).toHaveBeenCalled();
});
});
// Controller tests (integration)
describe('UserController', () => {
it('should return 200 for valid user', async () => {
const response = await request(app).get('/api/users/123');
expect(response.status).toBe(200);
expect(response.body).toEqual(expectedUserResponse);
});
});
Performance Considerations
Database Layer Optimization
- Use connection pooling
- Implement query batching
- Add proper indexes
- Use transactions for multi-operation flows
Caching Strategy
- Provider-level caching for external APIs
- Manager-level caching for computed results
- HTTP-level caching for static responses
Async/Await Best Practices
// ✅ Good: Parallel execution when possible
async function getUserWithOrders(userId: string) {
const [user, orders] = await Promise.all([
userProvider.getUserById(userId),
orderProvider.getOrdersByUserId(userId)
]);
return { user, orders };
}
// ❌ Bad: Sequential when parallel is possible
async function getUserWithOrders(userId: string) {
const user = await userProvider.getUserById(userId);
const orders = await orderProvider.getOrdersByUserId(userId);
return { user, orders };
}