In today's cloud-native world, choosing the right architecture patterns is crucial for building applications that can scale effectively. Let's explore some essential patterns and their implementations.
Breaking down applications into smaller, independent services:
// User Service
interface UserService {
getUserById(id: string): Promise<User>;
createUser(user: UserInput): Promise<User>;
updateUser(id: string, data: Partial<User>): Promise<User>;
}
// Authentication Service
interface AuthService {
login(credentials: Credentials): Promise<TokenResponse>;
validateToken(token: string): Promise<boolean>;
refreshToken(token: string): Promise<TokenResponse>;
}
Using events for loose coupling between services:
// Event Publisher
class OrderEventPublisher {
async publishOrderCreated(order: Order) {
await eventBus.publish('order.created', {
orderId: order.id,
userId: order.userId,
timestamp: new Date().toISOString()
});
}
}
// Event Subscriber
class NotificationService {
@Subscribe('order.created')
async handleOrderCreated(event: OrderCreatedEvent) {
await this.sendOrderConfirmation(event.orderId);
}
}
Separating read and write operations:
// Command
interface CreateOrderCommand {
userId: string;
products: Array<{ id: string; quantity: number }>;
}
// Query
interface GetOrderQuery {
orderId: string;
includeDetails?: boolean;
}
// Handlers
class OrderCommandHandler {
async execute(cmd: CreateOrderCommand) {
// Write to primary database
}
}
class OrderQueryHandler {
async execute(query: GetOrderQuery) {
// Read from replica/cache
}
}
Preventing cascading failures:
class CircuitBreaker {
private failures = 0;
private lastFailure: Date | null = null;
private readonly threshold = 5;
private readonly resetTimeout = 60000; // 1 minute
async execute<T>(fn: () => Promise<T>): Promise<T> {
if (this.isOpen()) {
throw new Error('Circuit is open');
}
try {
const result = await fn();
this.reset();
return result;
} catch (error) {
this.recordFailure();
throw error;
}
}
private isOpen(): boolean {
if (this.failures >= this.threshold) {
if (this.lastFailure && Date.now() - this.lastFailure.getTime() > this.resetTimeout) {
this.reset();
return false;
}
return true;
}
return false;
}
private recordFailure() {
this.failures++;
this.lastFailure = new Date();
}
private reset() {
this.failures = 0;
this.lastFailure = null;
}
}
Improving read performance:
class CacheAsideService {
constructor(
private readonly cache: Cache,
private readonly db: Database
) {}
async getData(key: string): Promise<Data> {
// Try cache first
const cached = await this.cache.get(key);
if (cached) {
return cached;
}
// Cache miss - get from database
const data = await this.db.get(key);
// Store in cache for next time
await this.cache.set(key, data, { ttl: 3600 });
return data;
}
}
Design for Failure
Monitor Everything
Scale Smart
Security First