الدرس 1 من 2
Rings, and the Rule That Points Inward
Clean and onion architecture are the same structure under two names: business rules at the centre, everything replaceable at the edge, and source dependencies that only ever point inward.
Jeffrey Palermo described onion architecture in 2008; Robert C. Martin published clean architecture in 2012. Alistair Cockburn had described hexagonal architecture — the subject of the next module — in 2005. All three are variations on one idea, and it is worth saying plainly at the start: if you understand the dependency rule, you understand all of them, and the differences between the names are much smaller than the literature suggests.
The rings, from centre outward
Each ring may depend on rings inside it and never on rings outside it. The direction of control at runtime is the opposite.
Four concentric rings. At the centre, entities holding enterprise-wide business rules. Around them, use cases holding application-specific rules. Around those, interface adapters: controllers, presenters, repository implementations. At the outside, frameworks and drivers: the web framework, the database, external services.
Entities (centre)
Business rules
valid regardless of application
Use cases
Application rules
orchestrates entities
Ports
interfaces the use case needs
Interface adapters
Controllers
translate requests
Repositories
implement ports
Presenters
shape output
Frameworks and drivers
Web framework
Database
External providers
| Ring | Contains | Test for membership |
|---|---|---|
| Entities | Rules that would be true even if this application did not exist | Would a paper-based version of the business still obey this rule? |
| Use cases | The steps of one application operation, and the ports it needs | Is this a thing a user or a job asks the system to do? |
| Interface adapters | Translation between the outside world's shapes and the inside's | Does it exist only because of a specific delivery or storage technology? |
| Frameworks and drivers | Everything you did not write and cannot control | Would replacing it be a purchasing decision rather than a design one? |
How data crosses a boundary
The rule has an immediate practical consequence that trips up most first implementations. A use case cannot return a database row, because that type belongs to an outer ring. It also should not return an entity to a controller if that hands the outside world a mutable piece of the domain. So data crosses ring boundaries as simple structures owned by the inner ring — plain values with no behaviour and no framework annotations.
// use case ring — owns the port and the data shapes crossing its boundary.
export interface EnrolmentRepository {
activeEnrolmentCount(learnerId: LearnerId): Promise<number>;
save(enrolment: Enrolment): Promise<void>;
}
export interface EnrolInput { readonly learnerId: string; readonly courseId: string; }
export interface EnrolOutput { readonly enrolmentId: string; readonly startsOn: string; }
export class EnrolLearner {
constructor(
private readonly enrolments: EnrolmentRepository,
private readonly clock: Clock,
) {}
async execute(input: EnrolInput): Promise<EnrolOutput> {
const learnerId = LearnerId.parse(input.learnerId);
const active = await this.enrolments.activeEnrolmentCount(learnerId);
// The entity holds the rule; the use case holds the sequence.
const enrolment = Enrolment.open(learnerId, CourseId.parse(input.courseId), active, this.clock.now());
await this.enrolments.save(enrolment);
return { enrolmentId: enrolment.id.toString(), startsOn: enrolment.startsOn.toISOString() };
}
}
// adapter ring — knows about HTTP and about the use case. The use case knows
// nothing about HTTP, which is why the same use case serves a CLI and a job.Onion and clean: the differences worth knowing
Onion architecture
Palermo's formulation: a domain model core, domain services around it, application services around those, and infrastructure at the edge.
- Emphasises a rich domain model at the centre
- Maps naturally onto domain-driven design vocabulary
- Fewer prescribed ring names to argue about
- Less explicit about how data crosses boundaries
- The distinction between domain services and application services confuses teams
Choose when: You are already working with domain-driven design and want a structural expression of it.
Clean architecture
Martin's formulation: entities, use cases, interface adapters, frameworks — with explicit rules about crossing boundaries and about which side owns an interface.
- Explicit about boundary-crossing data structures
- The use case is a first-class, named unit of application behaviour
- Gives clear guidance on interface ownership
- The full presenter-and-output-port machinery is heavy for most systems
- Frequently applied literally, producing ceremony without benefit
Choose when: You want the discipline named and enforceable, and you are willing to drop the parts — presenters in particular — that your delivery mechanism does not need.
A use case needs to know whether the current user is an administrator. Where does that information come from without breaking the dependency rule?RevealHide
It is passed in, as data the use case defines. The use case declares what it needs — a principal value with the permissions relevant to this operation — and an outer-ring adapter resolves the session, builds that value and hands it over. What it must not do is call a framework session helper from inside the use case, because that inverts the dependency and makes the use case unusable from a background job or a test. Notice that this is the same move as a repository port: the inner ring names the need, the outer ring satisfies it. Once you see that, most boundary questions answer themselves.
Key takeaways
- Clean, onion and hexagonal are one idea: source dependencies point inward, control flows outward at runtime.
- The centre must compile and be testable with every outer ring deleted.
- Data crosses boundaries as simple structures owned by the inner ring, never as ORM rows or entities handed outward.
- Framework annotations on entities are the most common invisible violation of the rule.
- The differences between the named variants are far smaller than the differences between doing this well and doing it as ceremony.