Software architecture patterns and alternatives expert. Covers monolith vs microservices, serverless, event-driven, CQRS, hexagonal architecture, clean architecture, DDD, and architecture decision frameworks. Activates for architecture patterns, monolith, microservices, serverless, event-driven, CQRS, hexagonal architecture, clean architecture, DDD, architecture decisions, system design, scalability patterns.
Expert in software architecture patterns (monolith, microservices, serverless, event-driven, CQRS, hexagonal, clean, DDD) and their tradeoffs. Activates when discussing architecture decisions, system design, scalability patterns, or comparing approaches for specific use cases.
/plugin marketplace add anton-abyzov/specweave/plugin install sw-cost@specweaveThis skill is limited to using the following tools:
Expert in software architecture patterns, their tradeoffs, and when to use each approach. Helps teams make informed architecture decisions based on context, scale, and organizational constraints.
Definition: Single deployable unit containing all application logic.
Strengths:
Weaknesses:
Best For:
Example:
// Monolithic e-commerce app
class EcommerceApp {
constructor(
private userService: UserService,
private productService: ProductService,
private orderService: OrderService,
private paymentService: PaymentService
) {}
async checkout(userId: string, cartItems: CartItem[]) {
// All in-process, single transaction
const user = await this.userService.getUser(userId);
const products = await this.productService.validateStock(cartItems);
const order = await this.orderService.createOrder(user, products);
const payment = await this.paymentService.processPayment(order);
return { order, payment };
}
}
Definition: System composed of independently deployable services, each owning its domain.
Strengths:
Weaknesses:
Best For:
Example:
// Microservices e-commerce (order service)
class OrderService {
async createOrder(userId: string, items: CartItem[]) {
// 1. Call User Service via HTTP/gRPC
const user = await this.httpClient.get(`http://user-service/users/${userId}`);
// 2. Call Product Service to validate stock
const stockCheck = await this.httpClient.post('http://product-service/validate', { items });
// 3. Create order locally
const order = await this.orderRepo.create({ userId, items, total: stockCheck.total });
// 4. Publish OrderCreated event (eventual consistency)
await this.eventBus.publish('OrderCreated', { orderId: order.id, items });
return order;
}
}
Decision Matrix:
| Factor | Monolith | Microservices |
|---|---|---|
| Team Size | < 15 | 50+ |
| Domain Complexity | Low-Medium | High |
| Deployment Frequency | Weekly | Multiple/day |
| Scalability Needs | Uniform | Heterogeneous |
| Ops Maturity | Basic | Advanced |
Definition: Application logic runs in managed, ephemeral compute environments (FaaS - Function as a Service).
Patterns:
// AWS Lambda example
export const handler = async (event: S3Event) => {
for (const record of event.Records) {
const bucket = record.s3.bucket.name;
const key = record.s3.object.key;
// Process uploaded image
const image = await s3.getObject({ Bucket: bucket, Key: key });
const thumbnail = await generateThumbnail(image);
await s3.putObject({ Bucket: `${bucket}-thumbnails`, Key: key, Body: thumbnail });
}
};
// API Gateway Lambda handler
export const handler = async (event: APIGatewayProxyEvent) => {
const userId = event.pathParameters?.userId;
const user = await dynamoDB.get({ TableName: 'Users', Key: { id: userId } });
return {
statusCode: 200,
body: JSON.stringify(user),
};
};
Strengths:
Weaknesses:
Best For:
Anti-Patterns:
Definition: System components communicate through events rather than direct calls.
Patterns:
// Store events, not current state
type AccountEvent =
| { type: 'AccountOpened'; accountId: string; initialBalance: number }
| { type: 'MoneyDeposited'; accountId: string; amount: number }
| { type: 'MoneyWithdrawn'; accountId: string; amount: number };
class BankAccount {
private balance = 0;
// Replay events to reconstruct state
applyEvent(event: AccountEvent) {
switch (event.type) {
case 'AccountOpened':
this.balance = event.initialBalance;
break;
case 'MoneyDeposited':
this.balance += event.amount;
break;
case 'MoneyWithdrawn':
this.balance -= event.amount;
break;
}
}
// Commands produce events
withdraw(amount: number): AccountEvent[] {
if (this.balance < amount) throw new Error('Insufficient funds');
return [{ type: 'MoneyWithdrawn', accountId: this.id, amount }];
}
}
// Separate read and write models
// WRITE MODEL (Commands)
class OrderCommandHandler {
async createOrder(command: CreateOrderCommand) {
const order = new Order(command.userId, command.items);
await this.orderWriteRepo.save(order);
await this.eventBus.publish('OrderCreated', order.toEvent());
}
}
// READ MODEL (Queries - optimized for reads)
class OrderQueryHandler {
async getUserOrders(userId: string): Promise<OrderSummary[]> {
// Denormalized view, optimized for fast reads
return this.orderReadRepo.findByUserId(userId);
}
}
// Event handler updates read model asynchronously
class OrderEventHandler {
async onOrderCreated(event: OrderCreatedEvent) {
// Update denormalized read model
await this.orderReadRepo.insertSummary({
orderId: event.orderId,
userId: event.userId,
total: event.total,
status: 'pending',
});
}
}
Strengths:
Weaknesses:
Best For:
Definition: Application core is independent of external concerns (frameworks, databases, UI).
// DOMAIN (Core - no external dependencies)
interface OrderRepository {
save(order: Order): Promise<void>;
findById(id: string): Promise<Order | null>;
}
class Order {
constructor(
public readonly id: string,
public readonly items: OrderItem[],
public status: OrderStatus
) {}
// Business logic here, independent of infrastructure
complete() {
if (this.status !== 'pending') throw new Error('Order already completed');
this.status = 'completed';
}
}
// APPLICATION (Use Cases)
class CompleteOrderUseCase {
constructor(private orderRepo: OrderRepository) {}
async execute(orderId: string) {
const order = await this.orderRepo.findById(orderId);
if (!order) throw new Error('Order not found');
order.complete();
await this.orderRepo.save(order);
}
}
// INFRASTRUCTURE (Adapters)
class PostgresOrderRepository implements OrderRepository {
async save(order: Order) {
await this.db.query('UPDATE orders SET status = $1 WHERE id = $2', [order.status, order.id]);
}
async findById(id: string): Promise<Order | null> {
const row = await this.db.query('SELECT * FROM orders WHERE id = $1', [id]);
if (!row) return null;
return new Order(row.id, row.items, row.status);
}
}
// UI (Adapter)
class ExpressOrderController {
constructor(private completeOrder: CompleteOrderUseCase) {}
async handleCompleteOrder(req: Request, res: Response) {
try {
await this.completeOrder.execute(req.params.orderId);
res.status(200).send({ message: 'Order completed' });
} catch (error) {
res.status(400).send({ error: error.message });
}
}
}
Strengths:
Weaknesses:
Best For:
Definition: Layered architecture with dependency inversion - dependencies point inward.
Layers (outermost to innermost):
Dependency Rule: Inner layers NEVER depend on outer layers.
// ENTITIES (innermost layer)
class User {
constructor(public id: string, public email: string, public passwordHash: string) {}
validatePassword(plainPassword: string, hasher: PasswordHasher): boolean {
return hasher.compare(plainPassword, this.passwordHash);
}
}
// USE CASES (application layer)
interface UserRepository {
findByEmail(email: string): Promise<User | null>;
}
class LoginUseCase {
constructor(
private userRepo: UserRepository,
private passwordHasher: PasswordHasher,
private tokenGenerator: TokenGenerator
) {}
async execute(email: string, password: string): Promise<{ token: string }> {
const user = await this.userRepo.findByEmail(email);
if (!user || !user.validatePassword(password, this.passwordHasher)) {
throw new Error('Invalid credentials');
}
const token = this.tokenGenerator.generate(user.id);
return { token };
}
}
// INTERFACE ADAPTERS (controllers)
class AuthController {
constructor(private loginUseCase: LoginUseCase) {}
async login(req: Request, res: Response) {
const { email, password } = req.body;
const result = await this.loginUseCase.execute(email, password);
res.json(result);
}
}
// FRAMEWORKS & DRIVERS (infrastructure)
class BcryptPasswordHasher implements PasswordHasher {
compare(plain: string, hash: string): boolean {
return bcrypt.compareSync(plain, hash);
}
}
class PostgresUserRepository implements UserRepository {
async findByEmail(email: string): Promise<User | null> {
const row = await this.db.query('SELECT * FROM users WHERE email = $1', [email]);
if (!row) return null;
return new User(row.id, row.email, row.password_hash);
}
}
Strengths:
Weaknesses:
Best For:
Strategic Design Patterns:
// SALES CONTEXT
class Customer {
constructor(public id: string, public creditLimit: number) {}
}
// SUPPORT CONTEXT (different model for same entity!)
class Customer {
constructor(public id: string, public supportTier: 'basic' | 'premium') {}
}
// Each context has its own model, even for same real-world concept
Sales Context → Customer Context (Shared Kernel)
- Share: CustomerId, CustomerName
- Separate: CreditLimit (Sales only), SupportTickets (Support only)
Inventory Context → Sales Context (Upstream/Downstream)
- Inventory publishes StockUpdated events
- Sales consumes events to update product availability
Tactical Design Patterns:
// Order is the Aggregate Root
class Order {
private items: OrderItem[] = [];
// Enforce business invariants
addItem(product: Product, quantity: number) {
if (quantity <= 0) throw new Error('Quantity must be positive');
// Business rule: max 10 items per order
if (this.items.length >= 10) throw new Error('Order cannot exceed 10 items');
this.items.push(new OrderItem(product, quantity));
}
// Aggregate ensures consistency
get total(): number {
return this.items.reduce((sum, item) => sum + item.subtotal, 0);
}
}
// OrderItem is NOT an Aggregate Root, only accessible through Order
class OrderItem {
constructor(public product: Product, public quantity: number) {}
get subtotal(): number {
return this.product.price * this.quantity;
}
}
class Money {
constructor(public readonly amount: number, public readonly currency: string) {
if (amount < 0) throw new Error('Amount cannot be negative');
}
add(other: Money): Money {
if (this.currency !== other.currency) {
throw new Error('Cannot add different currencies');
}
return new Money(this.amount + other.amount, this.currency);
}
equals(other: Money): boolean {
return this.amount === other.amount && this.currency === other.currency;
}
}
// Immutable, defined by value (not identity)
const price1 = new Money(100, 'USD');
const price2 = new Money(100, 'USD');
console.log(price1.equals(price2)); // true (same value)
class OrderPlaced {
constructor(
public readonly orderId: string,
public readonly userId: string,
public readonly total: Money,
public readonly occurredAt: Date
) {}
}
class Order {
private events: DomainEvent[] = [];
place() {
if (this.status !== 'draft') throw new Error('Order already placed');
this.status = 'placed';
this.events.push(new OrderPlaced(this.id, this.userId, this.total, new Date()));
}
getEvents(): DomainEvent[] {
return this.events;
}
}
Best For:
Team Context:
Technical Context:
Business Context:
| Architecture | Complexity | Scalability | Ops Overhead | Best For |
|---|---|---|---|---|
| Monolith | Low | Medium | Low | Startups, MVPs |
| Modular Monolith | Medium | Medium | Low | Growing startups |
| Microservices | High | High | High | Large orgs |
| Serverless | Low | Very High | Very Low | Event-driven, variable load |
| Event-Driven | High | Very High | High | High throughput, audit trails |
Monolith → Modular Monolith:
Modular Monolith → Microservices:
Monolith → Serverless:
Microservices with tight coupling - worst of both worlds.
❌ Symptom: Services can't deploy independently (require coordinated releases) ✅ Fix: Introduce message bus, versioned APIs, backward compatibility
Starting with microservices before understanding domain.
❌ Symptom: Constantly moving logic between services ✅ Fix: Start with modular monolith, extract services when boundaries are clear
Entities with only getters/setters, all logic in services.
❌ Symptom: Entities are just data containers ✅ Fix: Move business logic into domain entities
Single aggregate managing entire system state.
❌ Symptom: All commands touch the same aggregate ✅ Fix: Split into smaller aggregates with clear boundaries
Ask me about:
Creating algorithmic art using p5.js with seeded randomness and interactive parameter exploration. Use this when users request creating art using code, generative art, algorithmic art, flow fields, or particle systems. Create original algorithmic art rather than copying existing artists' work to avoid copyright violations.
Applies Anthropic's official brand colors and typography to any sort of artifact that may benefit from having Anthropic's look-and-feel. Use it when brand colors or style guidelines, visual formatting, or company design standards apply.
Create beautiful visual art in .png and .pdf documents using design philosophy. You should use this skill when the user asks to create a poster, piece of art, design, or other static piece. Create original visual designs, never copying existing artists' work to avoid copyright violations.