Module 92: Architecture Patterns
Clean architecture
🏠The everyday picture
A wall socket: any appliance with the right plug works, and the electricity company can change how it generates power without you changing your appliances. The socket is a fixed agreement between the two sides.
📘What it really is
-
Clean architecture (and the closely related hexagonal, or ports and adapters, architecture) puts the business core at the centre and makes everything else depend on it, not the other way round.
-
Layers, from the inside out:
- domain (entities): the core business concepts and rules (a
Bookingcan't be in the past; aSlotcan't be double-booked); plain TypeScript, no frameworks or databases; - use cases: application actions (
BookSession,CancelBooking) that coordinate the domain; - ports: interfaces the use cases need (
BookingRepository,PaymentGateway,Notifier); - adapters: implementations of those ports for real technologies (a PostgreSQL repository, a Razorpay or Stripe payment adapter, an email adapter), and the HTTP routes that call use cases.
- The dependency rule: code dependencies point inward. The domain knows nothing about Express, PostgreSQL or the payment provider.
// port (inside)
export interface PaymentGateway {
createOrder(amountPaise: number, bookingId: string): Promise<{ orderId: string }>;
}
// use case (inside) depends only on ports
export class BookSession {
private bookings: BookingRepository;
private payments: PaymentGateway;
constructor(bookings: BookingRepository, payments: PaymentGateway) {
this.bookings = bookings;
this.payments = payments;
}
async execute(input: BookSessionInput) { /* domain rules, then ports */ }
}
// adapter (outside) implements the port for a real provider
export class RazorpayGateway implements PaymentGateway { /* calls the provider's API */ }
-
Benefits: business rules are easy to unit-test with fake adapters; changing the database, payment provider or framework touches only adapters; the code tells you what the business does.
-
Costs: more files and indirection; overkill for simple CRUD features. Use it where business rules are rich and important (bookings, payments), and simpler layering elsewhere.
🖱️Try it
Implement the BookSession use case for the booking platform with the clean structure: a Booking domain type with its rules,
BookingRepository and PaymentGateway ports, a PostgreSQL adapter, and a fake payment adapter. Write unit tests for the use case
using in-memory fakes, including "slot already booked" and "payment order creation fails".
✅Quick check
What's the dependency rule in clean architecture?
Show the answer
Code dependencies point inward: the business core doesn't depend on frameworks, databases or outside services.
What's a port?
Show the answer
An interface the core needs (such as a repository or payment gateway), implemented by an adapter for a specific technology.
