The Right Axis Is Not Style: A Response to Fritzsche and Amariles

ivp
engineering
architecture
coupling
Fritzsche argues to retire Clean Architecture in favor of Functional Core / Imperative Shell. Amariles replies that the two are not mutually exclusive. Both arguments hold under their scope, and the scope is the domain’s change-driver structure — which permits three cases, not two.
Published

October 18, 2026

Two recent posts disagree about Clean Architecture, and the disagreement is instructive: it is an argument about the wrong axis.

Rico Fritzsche’s “Why Are We Still Talking About Clean Architecture?” argues to retire the layered family in favor of a Functional Core / Imperative Shell split. David Román Amariles replies that the two are not mutually exclusive: separating business rules from side effects and controlling the direction of dependencies address different concerns, and a system can have both.

Both arguments hold within their scope, and the scope is what neither argument states. The tool that supplies it is the change-driver structure of the domain. Once it is on the table, the question stops being which style wins and becomes which case the domain is in.


1 The formal apparatus, briefly

The Independent Variation Principle (IVP) governs one thing: where elements belong. Given the element set EE and the change-driver assignment Γ(e)\Gamma(e) — the set of change drivers whose state changes create a modification requirement for element ee — the principle is the conjunction of four conditions [1]:

  1. Admissibility — every element has at least one change driver: ∀e∈E,|Γ(e)|≥1\forall e \in E,\ |\Gamma(e)| \geq 1.
  2. Element Form — every element is either pure (|Γ(e)|=1|\Gamma(e)| = 1) or irreducibly composite: no proper subset of its driver set can achieve its purpose, and no decomposition into sub-elements with strictly smaller driver sets jointly preserves that purpose.
  3. Separation — elements assigned different sets of change drivers reside in different modules: Γ(e1)≠Γ(e2)⇒M(e1)≠M(e2)\Gamma(e_1) \neq \Gamma(e_2) \Rightarrow M(e_1) \neq M(e_2).
  4. Unification — elements assigned the same set of change drivers reside in the same module: Γ(e1)=Γ(e2)⇒M(e1)=M(e2)\Gamma(e_1) = \Gamma(e_2) \Rightarrow M(e_1) = M(e_2).

The modules of a compliant system are the Γ\Gamma-equivalence classes of EE. Separation and Unification are the partition content: failing Separation is contamination (elements driven by different conditions co-reside), and failing Unification is scatter (elements that change together are split across modules).

A change driver is an exogenous forcing condition whose change creates a requirement for governed elements to change — a condition anchored in a regulation, a contract, an interface specification, or another domain artifact whose authority is external to the system. Drivers are discovered, not chosen.

Note what the four conditions leave out: purity of functions, ports, adapters, or dependency direction. Those are shapes the partition takes in particular domains. They are not the principle.


2 Where Amariles’ argument holds: the two concerns are independent

His clearest point is structural, not rhetorical. Moving business rules into a pure function and directing dependencies through an abstraction are two decisions, and nothing forces a choice between them:

public static class EvaluateBooking
{
    public static BookingResult Execute(
        BookingRequest request,
        BookingContext context)
    {
        // Business rules
    }
}

The service retrieves the data, builds the context, and calls the function. The rules are pure; the orchestration stays in the service; the service can depend on IBookingRepository rather than on PostgreSQL. All three hold at once.

The four conditions account for this. EvaluateBooking and IBookingRepository have different driver sets, so Separation puts them in different modules. The rules inside EvaluateBooking share one driver set, so Unification keeps them together. Nothing in the partition requires a pure function or a port; those are the shapes the partition takes here. The example describes what the partition looks like in this domain. It is not a competing architectural claim.

A second observation in the same section is easy to miss. It quotes Fritzsche’s conclusion —

What we need are clearly defined business responsibilities and control over consistency within their contextual boundaries. Dependency inversion, ports, and adapters do not provide us with these properties.

— and observes that these are different concerns that can complement each other. So they are. Dependency direction is a decision about which way a reference points. The location of business responsibility is a decision about what belongs together. Nothing about the first settles the second, or the reverse.


3 Where Fritzsche’s argument holds: the port does not remove the dependency

His point is narrower than a rejection of abstraction. It is that inverting a dependency changes its direction in the source and does nothing else:

Dependency inversion makes it possible to reverse source code dependencies… However, the functional dependencies remain.

That is the placement result. A change driver is external and fixed by the domain. No design move adds, removes, or transmutes one. The service still needs the reservation data before it can apply its rules — whether the call passes through a database driver, an adapter, or an interface.

Amariles’ own examples show the same placement at work. The PS5 port in his game example does not eliminate the need to persist progress. It moves that need behind a boundary, and the gameplay code stops naming the mechanism. That is a real and practical benefit, and it is a benefit of placement: the correct implementation of the persistence obligation now sits somewhere the gameplay code need not know about. The obligation itself has not moved.

His .NET example shows the same thing. EvaluateBooking.Execute is pure; the repository call is not. The function is pure because the external call was moved out of it, not because the architecture made it so.


4 The axis nobody is using

Here is where the argument stalls, and it is the same stall on both sides.

Fritzsche’s case against dependency inversion is an observation: “how often have I seen someone come up with the idea of replacing the database technology in an existing application? Exactly zero times!”

Amariles answers with a counterexample: the game that must run where its SQLite binary is unavailable.

Both moves are about how often substitution happens. The observation that database substitution is rare is domain-bound, and the game is a fair counterexample to it. But substituting one frequency claim for another does not settle the question, because frequency is not the criterion. Whether an element’s correctness conditions require knowledge of the infrastructure it depends on is a structural fact about the domain’s governing artifacts, and it holds whether or not the substitution ever occurs [2].

The structural fact is a condition. Write H for Knowledge Independence: no domain change-driver has correctness conditions that require knowledge of an infrastructure system. Write H0 for Behavioural Self-Containment: the module computes every required change from its own inputs, with no dependence on external state at all. H0 implies H, and not conversely — a module can read external domain state, such as a customer’s recorded credit history, while remaining entirely ignorant of infrastructure. Two conditions that can fail independently give three cases:

Case Condition What the core can be
(a) H0 holds A pure functional core — no repository, port, or adapter
(b) H holds, H0 fails A core behind a port expressed in domain vocabulary
(c) H fails No infra-ignorant core; the knowledge must live somewhere

Both posts reason inside case (a), where a pure functional core is available. Fritzsche’s pure function is case (a) exactly. Amariles’ port is the correct answer in case (b), and his objection that the two coexist is the observation that one system can contain modules in different cases. Neither post reaches case (c), where the question is not which style to use but whether the knowledge can be excluded at all.

4.1 What case (c) looks like

H fails in two ways, and neither is a modelling mistake.

Direct. A governing artifact states a correctness condition in terms of infrastructure behaviour. “A patient record must be retrievable within 500 ms under peak load” is a change driver anchored in a medical-device regulation. Its correctness condition is a statement about storage and indexing performance. No implementation ignorant of storage semantics satisfies it.

Indirect. A domain rule’s correctness depends on the outcome of an infrastructure process. A credit rule says: “approve an order only if the customer’s outstanding balance, including committed but not yet settled orders, stays within the credit limit.” “Committed but not yet settled” refers to persistence state under concurrent write. Two storage architectures with different isolation levels produce different answer sets for the same underlying events, and therefore different approval outcomes. The domain rule’s correctness is functionally determined by the storage layer’s guarantee.

Fritzsche’s own observation points at the indirect case. He writes: “Query capabilities, transaction behaviour, and consistency guarantees are largely determined by the database used.” When a domain rule’s correctness condition depends on those guarantees, the database is not a detail. It is part of the domain’s causal structure.

When H fails, no reformulation of the domain removes the required knowledge while preserving correctness. The infra-ignorant core is not hard to reach. It is impossible. Enforcing it produces either an incorrect implementation or an abstraction that quietly models a different, simpler domain.


5 The stability ordering: what sets the direction

“Clean Architecture is fundamentally concerned with the direction of dependencies” is the style’s own account, and it is worth reading closely, because the direction is not arbitrary — it is fixed by a stability ordering.

Clean Architecture’s Dependency Rule points dependencies toward more stable elements. Martin measures stability with two structural metrics: instability I=Ce/(Ca+Ce)I = C_e/(C_a + C_e), the ratio of efferent to total coupling, and abstractness AA, the fraction of abstract types. The Main Sequence A+I=1A + I = 1 is the claimed optimal locus [3].

Neither metric references a change driver. II counts packages in the import graph; AA counts abstract type declarations. They measure structural self-containment — an element is “stable” because nothing imports force it to change — not change-driver identity. The stability ordering is therefore a proxy for the change-driver structure, not a measurement of it.

The proxy is useful where it tracks the domain’s actual ordering, and the theory has a name for the ordering it tracks. A driver stability ordering is a partial order on change drivers, where γ1≻γ2\gamma_1 \succ \gamma_2 holds when γ1\gamma_1 activates less often and γ2\gamma_2’s requirements are chosen to satisfy decisions γ1\gamma_1 governs. Read on this ordering, dependency inversion says that elements governed by the volatile driver depend on elements governed by the stable one, rather than the reverse.

That is the strongest defensible reading of Amariles’ position, and on this ordering it is a real result rather than a proxy — provided the ordering holds. It holds for tax law and a payment gateway. It fails where a domain rule’s correctness depends on what the storage layer guarantees; there, the stable element is the infrastructure behaviour, because the domain rule’s correctness now depends on its outcome. The dependency should then point the other way, and the style’s blanket inward rule asserts a direction the domain does not have.

Two constraints guard this reading, and both bear on the exchange. First, the ordering is a property of the domain’s causal structure, not a rank intrinsic to a driver: stability is one of the two relations in the pair, and it never licenses calling any driver “primary,” “dominant,” or “main.” Second, it informs the direction decision only — it does not change which elements belong in which module, which is the partition’s job. Direction and partition are separate questions, and the two get conflated when the exchange is read as a disagreement about styles rather than about which question is being asked.


6 What the theory confirms in each

Read against the three cases, the exchange resolves into scope conditions rather than verdicts.

The two approaches operate on different questions. Purity is a property of an element’s driver set (case (a)); a port is the shape a module takes when it needs external domain state (case (b)); dependency direction is a separate decision, governed by the stability ordering where that ordering holds. Nothing in the theory requires choosing one, which is the substance of Amariles’ argument.

The port relocates a need; it does not delete one. “The functional dependencies remain” holds in every case. It is decisive only in case (c). In cases (a) and (b) the family’s prescriptions match what the domain calls for, and a pure functional core is a restatement of case (a). The scope of the conclusion is narrower than the argument’s reach.

Both arguments rest on frequency. Fritzsche observes that database substitutions are rare; Amariles answers that other domains differ. Both observations may be accurate. Neither is the criterion. The criterion is whether the domain’s governing artifacts place infrastructure knowledge in a module’s correctness conditions — a fact about the domain, not about substitution rates.

Case (c) appears in neither. Which is where the discussion has real consequences. A conclusion that the style is wrong understates the domain’s latitude; a conclusion that the styles coexist leaves the reader without the case where no style applies.


7 What to do instead

The question “Clean Architecture or Functional Core / Imperative Shell?” sets the axis on style. The right axis is the domain’s change-driver structure, which the style does not select.

It is, for each module, two questions answered from the domain’s governing artifacts:

  1. Does any correctness condition require knowledge of an infrastructure system? (This decides H.)
  2. Does the module depend on any external state at all? (This decides H0.)

Then:

  • Case (a), H0. A pure functional core. No repository, port, or adapter. Adding one is unnecessary indirection. This is what Functional Core / Imperative Shell describes.
  • Case (b), H holds. A port or repository expressed in domain vocabulary. The module needs external state, but the state is domain-governed, so the contract is expressible in domain terms. This is the case the hexagonal prescription was designed for.
  • Case (c), H fails. Accommodate the infrastructure knowledge. Either keep it in the domain module, or move it to an explicitly domain-scoped adapter whose change-driver assignment is stated and whose behaviour is independently testable against the domain change-driver. What is not sound is enforcing infra-ignorance and accepting an incorrect or unverifiable model as the cost of structural conformance.

The style does not choose the case. The domain does.


8 Wrapping up

The theory confirms both observations under their scope. Dependency inversion moves a dependency without removing the driver behind it. Direction and separation are independent concerns that a system can satisfy at once.

The qualification worth adding is one of scope. The infra-ignorant core exists if and only if H holds, and H is discovered from the domain’s governing artifacts, not assumed. When H fails, every style in the family — Clean, Hexagonal, Onion, and Functional Core / Imperative Shell alike — arrives at the same fork: embody the required knowledge, or ship a model that cannot satisfy its obligations.

The two posts are not in conflict. They are answering different halves of a question that has three answers, and neither half needs the other retired.


The formal development. The definitions, theorems, and proofs behind this page are published in:

  • The Independent Variation Principle: Deriving Optimal Module Boundaries from the Element–Change-Driver Graph — Zenodo, 2026 — doi:10.5281/zenodo.20794332
  • On the Nature of Coupling: All Coupling as Shared Resource Access — Zenodo, 2026 — doi:10.5281/zenodo.21925122

The conditional analysis of the infra-ignorant core is developed in The Domain–Infrastructure Boundary as a Structural Property of the Driver Set.

All published papers

References

[1]
Y. Loth, “The independent variation principle.” Zenodo, Jun. 2026. doi: 10.5281/zenodo.20794332.
[2]
Y. Loth, “On the nature of coupling: All coupling as shared resource access.” Zenodo, Aug. 2026. doi: 10.5281/zenodo.21925122.
[3]
R. C. Martin, Clean architecture: A craftsman’s guide to software structure and design. Prentice Hall, 2017.