Clean Reactive Architecture is an implementation of the Clean Architecture concept for reactive applications, built around the observer pattern.
The Clean Architecture concept is outlined with the following circle diagram:
The implementation of the concept for reactive applications is outlined with the following UML diagram:
The double lines on the diagram represent boundaries, which data crosses as primitive data types or data structures, for example DTOs or plain objects.
mermaid
graph TD
subgraph B1["Boundary"]
G["Gateway"]
ER["External Resource"]
end
subgraph B2["Boundary"]
UI["User Interface"]
end
subgraph B3["Boundary"]
E["Entities"]
end
P["Presenter"]
C["Controller"]
PI["Presenter < I >"]
CI["Controller < I >"]
GI["Gateway < I >"]
UC["Use Case Interactor"]
%% implementation relation
P -. implements .-> PI
C -. implements .-> CI
G -. implements .-> GI
%% dependency relation
UI -- depends --> PI
UI -- depends --> CI
C -- depends --> UC
P -- depends --> E
UC -- depends --> E
UC -- depends --> GI
G -- depends --> ER
classDef boundary fill:none,stroke:#666,stroke-width:2px,stroke-dasharray: 5 5;
class B1,B2,B3 boundary;
- Entities: A unit that maintains one or more enterprise business entities, application business entities, or a mix of both.
- Use Case Interactor: A unit that orchestrates the flow of data to the entities, and directs those entities to use their business rules to achieve the goals of the use case.
- Gateway: A unit that encapsulates access to an external system or resource.
- Controller: A unit that handles input data from the user interface and converts it into a format most convenient for the use cases and entities.
- Presenter: A unit that converts data from entities into a format most convenient for the user interface.
- User Interface: A unit that displays information to the user based on the data prepared by the presenter, and captures user input and transfers it to the controller.
- External system or resource: A unit that represents external systems or resources the application interacts with, e.g., services, databases, storage, or other applications and libraries with an API.
- Enterprise Business Entity: An entity that encapsulates enterprise business rules and data.
- Enterprise Business Rules and Data: Rules and data that would exist even if the application didn't exist. These enterprise-wide rules are independent of any specific application.
- Application Business Entity: An entity that encapsulates application-specific business rules and data.
- Application Business Rules and Data: Rules and data specific to the application's functionality. These are application-wide rules that don't exist outside the context of a particular application.
- State: The data of entities at a given point in time, typically represented as an object structure.
- Valid State: One of a finite number of states considered valid according to the enterprise and application business rules.
NOTE: The Clean Architecture concept remains the primary source of truth. This document does not redefine its principles, instead, it provides concrete guidance for areas where the original concept is abstract or implementation-agnostic. Unless explicitly stated otherwise, engineers and developers should rely on the principles of Clean Architecture when making architectural decisions.
See also:
Robert C. Martin. The Clean Architecture
Robert C. Martin. (2017). Clean Architecture: A Craftsman's Guide to Software Structure and Design
Where did the diagram come from?
Clean Architecture is a formalized architectural concept for application software. The most overlooked factor here is that the implementation of Clean Architecture is a UML diagram. The concept itself is too abstract, while the codebase is too concrete. And a UML diagram bridges that.
mermaid
flowchart TD
A["Clean Architecture Concept"] --> B["UML Diagram (units, boundaries, flow)"]
B --> C["Application Codebase (concrete implementation)"]
For a typical backend application written in Java, a well-known Clean Architecture implementation has existed for years.
mermaid
graph TD
subgraph B1["Boundary"]
C["Controller"]
P["Presenter"]
VM["View Model < DS >"]
end
ID["Input Data < DS >"]
IB["Input Boundary < I >"]
OD["Output Data < DS >"]
OB["Output Boundary < I >"]
subgraph B2["Boundary"]
V["View"]
end
UC["Use Case Interactor"]
DAI["Data Access Interface < I >"]
subgraph B3["Boundary"]
DA["Data Access"]
DB["Database"]
end
subgraph B4["Boundary"]
E["Entities"]
end
%% implementation relations
P -. implements .-> OB
UC -. implements .-> IB
DA -. implements .-> DAI
%% dependency relations
C -- depends --> IB
C -- depends --> ID
P -- depends --> VM
P -- depends --> OD
V -- depends --> VM
UC -- depends --> ID
UC -- depends --> OB
UC -- depends --> OD
UC -- depends --> DAI
UC -- depends --> E
DAI -- depends --> E
DA -- depends --> DB
classDef boundary fill:none,stroke:#666,stroke-width:2px,stroke-dasharray: 5 5;
class B1,B2,B3,B4 boundary;
Clean Architecture. A craftsman’s guide to software structure and design. Robert C. Martin. Copyright © 2018 Pearson Education, Inc.
However, this implementation does not directly apply to reactive applications. Attempts to use it introduce code solely for adapting it. Nevertheless, the Clean Architecture concept is universal and an implementation tailored for a reactive application, for example with external API integration, is constructed as follows:
mermaid
graph TD
subgraph CA["The Clean Architecture Circle Diagram"]
subgraph L5["Frameworks & Drivers"]
CUI["UI"]
CWEB["Web"]
CDB["Database"]
CEXT["External Interfaces"]
CDEV["Devices"]
end
subgraph L4["Interface Adapters"]
CC["Controllers"]
CG["Gateways"]
CP["Presenters"]
end
subgraph L3["Application Business Rules"]
CUC["Use Cases"]
end
subgraph L2["Enterprise Business Rules"]
CE["Entities"]
end
%% Layer dependencies (inward direction)
L5 -- depends --> L4
L4 -- depends --> L3
L3 -- depends --> L2
end
subgraph CRA["The Clean Reactive Architecture UML Diagram"]
G["Gateway"]
ER["External Resource"]
UI["User Interface"]
E["Entities"]
P["Presenter"]
C["Controller"]
PI["Presenter < I >"]
CI["Controller < I >"]
GI["Gateway < I >"]
UC["Use Case Interactor"]
%% implementation relation
P -. implements .-> PI
C -. implements .-> CI
G -. implements .-> GI
%% dependency relation
UI -- depends --> PI
UI -- depends --> CI
C -- depends --> UC
P -- depends --> E
UC -- depends --> E
UC -- depends --> GI
G -- depends --> ER
end
CA -. implementation .-> CRA
NOTE: The diagram shows units (conceptual responsibilities), not files or folder structure. Units are how the system should be thought about at runtime - which unit owns what, dependencies, where responsibilities lie. How units are organized into files is a separate organizational decision, independent of the architecture.
Where does the application business entity come from?
Clean Architecture distinguishes between enterprise business rules and data and application business rules. Enterprise rules and data are independent of any particular application and live inside entities. Application rules are specific to a particular application's behavior and live inside use cases and apply as orchestration logic during the use case call.
However, reactive client applications introduce a category of application-specific concern that requires an extension of this model: persistent, observable state with its own validity rules and lifetime, independent of any single use case invocation.
Consider the runtime state of an asynchronous refresh: idle, loading,
error with a message. This state has its own valid configuration
(transitions are not random - it cannot move directly from error to
loading, it requires an explicit retry, an idle state cannot have an error
message). The state has its own observable lifetime - different parts of the
user interface may react to it independently. It makes sense only within
applications that perform asynchronous synchronization. And it persists across
many operations longer than any use case call.
This is application-specific, but it is not orchestration logic. It is state (data) with rules, which can be recognized as an application business entity. Clean Reactive Architecture puts it where it belongs - the entities layer, and gives presenters that read it and use cases that transition it.
Reactive client applications deal with this type of state constantly, though it rarely receives architectural recognition. Examples:
- Operation state —
idle/loading/error, per operation. - Initialization state —
uninitialized/initializing/ready. - Session state —
authenticated/refreshing/expired. - Route state — current route, parameters, back stack.
- Form state — field values, dirty flags, validation status, submission lifecycle.
- Connectivity state —
online/offline/metered. - Selection state — multi-select sets, expansion flags, focus.
Each of these has its own validity rules and observable transitions. Each is application-specific.
Does it support unidirectional flow?
Clean Reactive Architecture supports unidirectional flow out of the box, and holds it more strictly than in architectures with stateful hubs. The flow never crosses and reverses.
Unidirectional flow of control and data is the following:
mermaid
graph TD
G["Gateway"]
GI["Gateway < I >"]
ER["External Resource"]
UI["User Interface"]
E["Entities"]
P["Presenter"]
C["Controller"]
PI["Presenter < I >"]
CI["Controller < I >"]
UC["Use Case Interactor"]
%% implementation relation
P -. implements .-> PI
C -. implements .-> CI
G -. implements .-> GI
%% dependency relation
UI -- depends --> PI
UI -- depends --> CI
C -- depends --> UC
P -- depends --> E
UC -- depends --> E
UC -- depends --> GI
G -- depends --> ER
%% flow
UI -. flow .-> CI
CI -. flow .-> C
C -. flow .-> UC
UC -. flow .-> E
E -. flow .-> P
P -. flow .-> PI
PI -. flow .-> UI
The controller receives user input, the presenter reacts and projects
entities for the user interface. They both are never connected to each
other - they share only the entities, where one path writes and the other
reads. There is no single unit (hub) which sits on both paths.
Where do the SOLID principles fit?
For Clean Reactive Architecture SOLID was a generative lens, not a governing law. The UML diagram was derived by asking: "If we apply Clean Architecture to reactive apps, what units do we actually need, and how do they interact?" SOLID helped think through that derivation. But once the UML diagram existed, it became the primary specification. SOLID didn't need to be enforced anymore because the diagram's structure already encoded it. Some principles still guide development, but not as rules the codebase is audited against.
-
The single-responsibility principle (SRP) - structural, not aspirational.
Each unit of the architecture has its own and only one well-defined responsibility, and the diagram enforces it through reachability rather than through discipline. The
presenteronly readsentitiesand projects them for theuser interface, thecontrolleronly converts user input into ause casecall. The two are never connected, and no unit sits on both paths, so a unit cannot quietly acquire a second responsibility - there is no path along which it could.This is what the separation buys concretely: a decision on the write path can never be made from data prepared for display.
-
The open–closed principle (OCP) - a completion milestone.
In B. Meyer's original formulation a unit is closed once its public interface is declared. In the outside-in flow the
user interfacelayout is built first, andpresenter<I>andcontroller<I>are extracted from what that layout actually consumes. At this point theuser interfaceunit is closed: everything downstream depends on the declared interfaces, not on the layout implementation. The unit can be handed off, and the remaining units can be built in parallel against the declared contract.At the same time it remains open for extension (composition): the same layout can be composed into other screens, or rendered against other implementations of its interfaces - mocks, fixtures, another driver - through the LSP.
Practically, OCP is used here as a milestone in the development flow, a point at which a unit is done and work can move on. It is not a mandate to design extension points for hypothetical future requirements.
-
Liskov substitution principle (LSP) - substitution as a working tool.
Explicitly declared
presenter<I>,controller<I>andgateway<I>interfaces make implementations behind them interchangeable for their consumers. For example, a local in-memorygatewayand a remote one both satisfy the samegateway<I>, and neither theuse case, nor thepresenter, nor theuser interfacechanges when one replaces the other.This is what makes it possible to develop and demo the application without a backend, to run integration tests that never touch the network, to vary behavior per environment, and to render the
user interfaceagainst a mockpresenter. -
Interface segregation principle (ISP) - a decomposition signal.
The
user interfaceunit is implemented top-down: one large layout that covers the feature's requirements is decomposed into smaller parts. The extracted interfaces support that decomposition. A thickpresenter<I>is a signal that theuser interfaceunit carries too much and should be split, and the segments of that interface suggest where the split may happen - each smaller part then gets its own, thinner interface. The same reading applies to a thickcontroller<I>orgateway<I>.ISP is applied here at refactoring time, on an interface extracted from a concrete consumer. It is not a reason to design small interfaces upfront.
-
Dependency inversion principle (DIP) - the structural one.
This is the principle the diagram actually rests on, rather than merely agrees with. Units from inner layers do not depend on concrete implementations of units from outer layers, they depend on abstractions. The
use caseunit (inner layer) depends on thegatewayunit (outer layer) through thegateway<I>interface, and theuser interfaceunit depends onpresenter<I>andcontroller<I>rather than on their implementations. Remove this and the layers collapse into a graph of concrete imports - the boundaries on the diagram exist because of it.
See also:
B. Meyer. (1997). Object-Oriented Software Construction
Robert C. Martin. The Principles of OOD
How does it scale?
As the codebase grows, it will obviously need more units to share common logic. Following the Clean Architecture concept, the UML diagram can be extended with additional units, which will fit an additional circle (layer) in the circular diagram of the concept. Such extension is consistent.
The extended diagram is the following:
mermaid
graph TD
subgraph B1["Boundary"]
G["Gateway"]
ER["External Resource"]
end
subgraph B2["Boundary"]
UI["User Interface"]
end
subgraph B3["Boundary"]
E["Entities"]
end
P["Presenter"]
C["Controller"]
PI["Presenter < I >"]
CI["Controller < I >"]
GI["Gateway < I >"]
UC["Use Case Interactor"]
SE["Selector"]
TR["Transaction"]
EF["Effect"]
%% implementation relation
P -. implements .-> PI
C -. implements .-> CI
G -. implements .-> GI
%% dependency relation
UI -- depends --> PI
UI -- depends --> CI
C -- depends --> UC
P -- depends --> SE
UC -- depends --> SE
UC -- depends --> EF
UC -- depends --> TR
UC -- depends --> E
SE -- depends --> E
EF -- depends --> SE
EF -- depends --> E
EF -- depends --> GI
TR -- depends --> SE
TR -- depends --> E
G -- depends --> ER
classDef boundary fill:none,stroke:#666,stroke-width:2px,stroke-dasharray: 5 5;
class B1,B2,B3 boundary;
The diagram represents units which are empirically sufficient for a quite large codebase.
- Selector: Unit that derives values or aggregates data structures from the entities without modifying them.
- Transaction: Unit that transitions entities between two valid states, ensuring business rules are maintained.
- Effect: Unit that manages data flows to, from, and across gateways (sequential, parallel, etc.) and derives data structures from them.
How does it share business logic?
Enterprise business rules and data are explicit in Clean Reactive Architecture, so they can be extracted into a separate core (library) to be shared across multiple reactive and non-reactive clients.
Such a core (library) will know nothing about any client. It will have its own API and, for example, its own mechanism for storing data. The most practial thing here is that the core (library) can be built following the same Clean Architecture concept but outlined with the request-response UML diagram - so one concept covers two different types of applications.
High level architecture:
mermaid
flowchart LR
RC1["Reactive client"]
RC2["Reactive client"]
C["Core (library)"]
RC1 -- depends --> C
RC2 -- depends --> C
Architecture of the reactive client:
mermaid
graph TD
subgraph B1["Boundary"]
G["Gateway"]
ER["External Resource"]
end
subgraph B2["Boundary"]
UI["User Interface"]
end
subgraph B3["Boundary"]
E["Entities"]
end
P["Presenter"]
C["Controller"]
PI["Presenter < I >"]
CI["Controller < I >"]
GI["Gateway < I >"]
UC["Use Case Interactor"]
%% implementation relation
P -. implements .-> PI
C -. implements .-> CI
G -. implements .-> GI
%% dependency relation
UI -- depends --> PI
UI -- depends --> CI
C -- depends --> UC
P -- depends --> E
UC -- depends --> E
UC -- depends --> GI
G -- depends --> ER
classDef boundary fill:none,stroke:#666,stroke-width:2px,stroke-dasharray: 5 5;
class B1,B2,B3 boundary;
Architecture of the core (library):
mermaid
graph TD
subgraph B1["Boundary"]
C["Controller"]
P["Presenter"]
VM["View Model < DS >"]
end
ID["Input Data < DS >"]
IB["Input Boundary < I >"]
OD["Output Data < DS >"]
OB["Output Boundary < I >"]
subgraph B2["Boundary"]
V["View"]
end
UC["Use Case Interactor"]
DAI["Data Access Interface < I >"]
subgraph B3["Boundary"]
DA["Data Access"]
DB["Database"]
end
subgraph B4["Boundary"]
E["Entities"]
end
%% implementation relations
P -. implements .-> OB
UC -. implements .-> IB
DA -. implements .-> DAI
%% dependency relations
C -- depends --> IB
C -- depends --> ID
P -- depends --> VM
P -- depends --> OD
V -- depends --> VM
UC -- depends --> ID
UC -- depends --> OB
UC -- depends --> OD
UC -- depends --> DAI
UC -- depends --> E
DAI -- depends --> E
DA -- depends --> DB
classDef boundary fill:none,stroke:#666,stroke-width:2px,stroke-dasharray: 5 5;
class B1,B2,B3,B4 boundary;
In fact, the core (library) implementation can follow any other architectural concept, for example, hexagonal, or none at all. It can even be developed in parallel with a reactive client - the client's gateway will connect the two parts later.
Does it have a ViewModel?
Clean Reactive Architecture has a ViewModel, though it is not outlined in the
diagram for reasons of simplicity. Here "ViewModel" means what it originally
meant - the data structure the presenter returns and the user interface
consumes.
mermaid
graph LR
subgraph B1["Boundary"]
UI["User Interface"]
end
VM["ViewModel < DS >"]
PI["Presenter < I >"]
P["Presenter"]
%% Relationships
UI -- depends --> VM
UI -- depends --> PI
P -- depends --> VM
P -. implements .-> PI
classDef boundary fill:none,stroke:#666,stroke-width:2px,stroke-dasharray: 5 5;
class B1 boundary;
The ViewModel is just a value: comparable, snapshotable, safe to pass around, trivial to construct for a preview or a test. It has no behavior, no lifecycle, no identity.
It is important to note that every property of a presenter returns its own ViewModel. Let's look at an example:
interface BooksByAuthor {
authorName: string;
bookTitle: string;
}
interface UserBooksPresenter {
userName: string;
books: BooksByAuthor[];
}Here userName and books are properties of the UserBooksPresenter
interface, each property return own ViewModel - userName a primitive value,
books a structured one.
Does it have a repository?
Clean Reactive Architecture has a repository, but the diagram does not have
a separate unit for it. The repository is a composite of the gateway and
entities units that appears in code - a single implementation that satisfies
gateway<I> and holds the entities it serves.
mermaid
graph TD
subgraph R1["Repository"]
E["Entities"]
G["Gateway"]
end
ER["External Resource"]
UI["User Interface"]
P["Presenter"]
C["Controller"]
PI["Presenter < I >"]
CI["Controller < I >"]
GI["Gateway < I >"]
UC["Use Case Interactor"]
%% implementation relation
P -. implements .-> PI
C -. implements .-> CI
G -. implements .-> GI
%% dependency relation
UI -- depends --> PI
UI -- depends --> CI
C -- depends --> UC
P -- depends --> E
UC -- depends --> E
UC -- depends --> GI
G -- depends --> ER
classDef repository fill:none,stroke:#666,stroke-width:2px,stroke-dasharray: 5 5;
class R1 repository;
Some implementations let the repository absorb the gateway<I> interface -
which is acceptable, except the case where the repository defines the contract
rather than consume it. This is not desired, and the development
methodology prevents it.
mermaid
graph TD
subgraph R1["Repository"]
E["Entities"]
G["Gateway"]
GI["Gateway < I >"]
end
ER["External Resource"]
UI["User Interface"]
P["Presenter"]
C["Controller"]
PI["Presenter < I >"]
CI["Controller < I >"]
UC["Use Case Interactor"]
%% implementation relation
P -. implements .-> PI
C -. implements .-> CI
G -. implements .-> GI
%% dependency relation
UI -- depends --> PI
UI -- depends --> CI
C -- depends --> UC
P -- depends --> E
UC -- depends --> E
UC -- depends --> GI
G -- depends --> ER
classDef repository fill:none,stroke:#666,stroke-width:2px,stroke-dasharray: 5 5;
class R1 repository;
Where does an alternative driver fit?
The user interface is not a privileged unit, it is a detail. Clean Reactive
Architecture treats the user interface as one driver among several.
Driver - a unit that exercises the core: it provides input through a controller, observes the core's state through a presenter, or both.
Normally each driver has its own controller and presenter implementations,
which are specific to a particular driver. What is shared is the core:
use case, entities and gateway<I> that every driver's controllers and
presenters meet.
mermaid
graph TD
subgraph D1["Driver"]
UI["User Interface"]
PI["Presenter < I >"]
CI["Controller < I >"]
end
G["Gateway"]
ER["External Resource"]
E["Entities"]
P["Presenter"]
C["Controller"]
GI["Gateway < I >"]
UC["Use Case Interactor"]
%% implementation relation
P -. implements .-> PI
C -. implements .-> CI
G -. implements .-> GI
%% dependency relation
UI -- depends --> PI
UI -- depends --> CI
C -- depends --> UC
P -- depends --> E
UC -- depends --> E
UC -- depends --> GI
G -- depends --> ER
classDef driver fill:none,stroke:#666,stroke-width:2px,stroke-dasharray: 5 5;
class D1 driver;
Common drivers:
- a test harness drives the core through controllers and observes state to assert against it;
- a notification or deep link drives the core when an external event arrives;
Let's consider a WebSocket. Incoming messages drive the core: a listener receives them and feeds them in through a controller, exactly as a user gesture would. Outgoing commands take the other path: they leave the core through a gateway, since the socket is also an external resource. The same connection is therefore both a driver (inbound) and an external resource (outbound), sitting on both sides of the core - the core does not know (and does not care) that the input arriving through its controller and the output leaving through its gateway belong to the same socket.
mermaid
graph TD
subgraph D1["Driver"]
WSL["WS Listener"]
CI["Controller < I >"]
end
UI["User Interface"]
PI["Presenter < I >"]
G["Gateway"]
ER["External Resource"]
E["Entities"]
P["Presenter"]
C["Controller"]
GI["Gateway < I >"]
UC["Use Case Interactor"]
WST["WS Transmitter"]
%% implementation relation
P -. implements .-> PI
C -. implements .-> CI
G -. implements .-> GI
%% dependency relation
UI -- depends --> PI
UI -- depends --> CI
C -- depends --> UC
P -- depends --> E
UC -- depends --> E
UC -- depends --> GI
G -- depends --> ER
G -- depends --> WST
WSL -- depends --> CI
classDef driver fill:none,stroke:#666,stroke-width:2px,stroke-dasharray: 5 5;
class D1 driver;
Where does a Backend for Frontend fit?
A gateway adapts a general-purpose external API into the shape a client actually needs - mapping responses, combining several calls, reshaping data to match the entities the application works with. The more a general-purpose API differs from what the client needs, the more adaptation accumulates in the gateways.
mermaid
graph TD
subgraph B1["Boundary"]
GI["Gateway < I >"]
end
G["Gateway (adapt. logic)"]
ER["External Resource"]
subgraph B2["Boundary"]
GAPI["General Purpose Server-side API"]
end
G -. implements .-> GI
G -- depends --> ER
ER -- depends --> GAPI
%% high-level relations
RCC["Reactive client"]
subgraph B3["Boundary"]
CGAPI["General Purpose Serve-side API"]
end
RCC -- depends --> CGAPI
classDef boundary fill:none,stroke:#666,stroke-width:2px,stroke-dasharray: 5 5;
class B1,B2,B3 boundary;
That accumulated adaptation is the signal for a Backend for Frontend (BFF). A BFF is a server-side layer built for one specific client, it performs the mapping and aggregation the client would otherwise do itself. In these terms, the BFF is the difference between the gateway and the general-purpose API - the adaptation moved to the server, where it can be done once and closer to the data.
mermaid
graph TD
subgraph B1["Boundary"]
GI["Gateway < I >"]
end
G["Gateway"]
ER["External Resource"]
subgraph B2["Boundary"]
BFF["BFF (adapt. logic)"]
GAPI["General Purpose Server-side API"]
end
G -. implements .-> GI
G -- depends --> ER
ER -- depends --> BFF
BFF -- depends --> GAPI
%% high-level relations
subgraph RCC["Reactive client"]
CGI["Gateway < I >"]
end
subgraph B3["Boundary"]
CBFF["Reactive Client BFF"]
CGAPI["General Purpose Serve-side API"]
end
CBFF -. implements .-> CGI
CBFF -- depends --> CGAPI
classDef boundary fill:none,stroke:#666,stroke-width:2px,stroke-dasharray: 5 5;
class B1,B2,B3 boundary;
The architecture makes this visible. Because adaptation is localized in gateways rather than distributed across the codebase, heavy mappers and aggregations in the gateways are a concrete indication that the work belongs server-side. When a BFF takes over that adaptation, the client's gateways become thin - the BFF returns data already shaped for the client. As the client's needs evolve, the gateways begin accumulating adaptations again, signalling the next round of BFF update.
See also:
S. Newman. Pattern: Backends For Frontends
How does it map to the testing pyramid?
Clear Reactive Architecture maps cleanly onto the testing pyramid.
Each level corresponds to a level of unit composition:
- Unit tests test individual architectural units in isolation - a presenter, a use case, a gateway implementation.
- Integration tests test architectural units working together - a controller through its use case to a gateway, or a gateway integration with an external resource.
- End-to-end tests test the full path through the architectural units, from
the
user interfaceunit to theexternal resourceunit.
Because the units and their boundaries are explicit, each test level has a clear target: the pyramid's layers are the architecture's layers of composition.














