Arrange the codebase in concentric rings with dependencies always pointing inward, so the domain at the center never depends on outer detail.
In a typical layered setup, business logic often directly imports data-access classes — meaning the domain's compile-time dependency graph points outward, toward infrastructure. If the entities that matter most to the business are the ones tied most tightly to a specific database or framework, they're also the hardest things to change safely.
Entities (core business rules) sit at the center, wrapped by use cases, wrapped by interface adapters, wrapped by frameworks and drivers at the outer edge. The strict rule: source code dependencies can only point inward. An outer ring can know about an inner one; an inner ring never imports anything from an outer one.
`Order` (the entity) knows nothing about how it gets saved. The use case coordinates it with a port; only the outer adapter knows about the real database.
// Entity — pure business rule, imports nothing outer.
class Order {
#items;
constructor(items) {
if (items.length === 0) throw new Error("order must have items");
this.#items = items;
}
get total() { return this.#items.length * 10; }
}
// Use case — coordinates entities via a port, still no infrastructure import.
function placeOrderUseCase(orderRepoPort, items) {
const order = new Order(items);
orderRepoPort.save(order);
return order;
}
// Outer ring: interface adapter + framework/driver detail.
const postgresOrderRepo = { save: (order) => console.log(`db: saved order, total ${order.total}`) };
placeOrderUseCase(postgresOrderRepo, ["book", "pen"]);
// Order (entity) and placeOrderUseCase have no idea Postgres exists.