← All teaching notes

SOFTWARE ARCHITECTURE

Clean Architecture, without the confusion

Separating business rules from delivery details.

Consider a university bus registration system. Students choose a route, submit a request, and wait for approval. The rule that only approved requests become active registrations is a business rule. The Express route receiving the request is a delivery detail.

Start with responsibilities

The domain describes the important business concepts. The application layer coordinates use cases. Infrastructure handles storage and external services. The interface layer translates incoming requests into application calls and formats the response.

LayerBus registration example
DomainRegistration and its valid status transitions
ApplicationApprove a student’s registration
InfrastructureLoad and save registrations in PostgreSQL
InterfacesAn Express controller handling the HTTP request

The dependency rule matters more than folders

Business rules should not depend on Express or a particular database library. The application can depend on a repository contract, while infrastructure provides its implementation.

// Application use case: repository is supplied from outside.
async function approveRegistration(id, repository) {
  const registration = await repository.findById(id);
  if (!registration) throw new Error("Not found");
  registration.approve(); // Domain enforces valid transitions.
  await repository.save(registration);
  return registration;
}

The controller can call this function without putting the approval rule inside an HTTP handler. A database adapter can implement the repository without forcing the domain to know SQL.

Keep the design proportional

A small teaching project does not need dozens of abstractions. Begin with a single use case and make the responsibilities visible. Add structure when it helps explain or change behavior.

If you replaced Express tomorrow, which parts of the approval process should remain the same?
← More teaching notesDiscuss this topic ↗