In my experience, development approaches like Domain-Driven Design and structural concepts as the Hexagonal Architecture or the Onion Architecture carry a lot of wisdom but don't necessarily provide practical guidance when it comes to starting off with a new project. After several unsatisfactory experiments, I felt a sort of relief when I first read about the Clean Architecture, which nicely aggregates some high-level concepts while simplifying things at the same time.
However, even the Clean Architecture doesn't provide a simple-to-follow recipe for layouting a project, naming classes, files and directories and deciding where to settle a particular functionality. So I trial-and-errored myself to the point where I had a rather concise, opinionated implementation of the Clean Architecture that prove useful in several projects — even and especially in combination with each other. Let me introduce you to the Clear Architecture.
Note: I won't go into too much detail about the various high-level concepts involved in the Clear Architecture but rather focus on their practical application. Follow the links to learn more about them.
In the Clear Architecture, source code is structured into 3 tiers that build on one another, most easily illustrated as concentric circles with the outermost one consisting of 3 complementary sectors.
In a banking application, the domain layer holds the definitions of the bank account, the account holder, the currency etc. as well as their relationships with each other.
The application layer executes different types of bank transactions, provides a currency exchange service and so on.
The client layer implements the database operations, provides a web interface for online banking and a FinTS interface to be used by external applications.
The Ports sector is the public interface of your application. It accept requests from external agencies (e.g. the Web, the command-line, an embedding system etc.), communicates them to the infrastructure sector as well as the ② application layer and responds with the processed results. It represents your system to the outer world by taking the form of a web or native GUI, a CLI, a REST API or any other type of interface suitable for accessing your application. Components in this sector may include (but are not limited to):
Note: If your application provides a web interface or similar API, don't directly use the Ports
directory (or any of its subdirectories) as document root for web server scripts. Instead, create a top-level public
directory and put your bootstrap script files there. Search for a similar solution when providing a CLI (see below for PHP specific recommendations).
The Infrastructure sector is not strictly private, but it holds the implementation details that are not necessarily of public interest. While the Ports interface should be stable in the long run, it's acceptable for infrastructural components to change with the requirements. Ideally, they can be swapped against equivalent mechanisms without affecting the overall system functionality. External agencies should avoid accessing the infrastructure layer directly, but there might be exceptions for efficiency's sake. Typically in this sector:
The Tests sector holds all resources required for testing your application. In general, tests are nothing more than highly specialized clients of your application and must be granted full access to your ③ client and ② application layers. Test resources may also be accessed by external agencies (e.g. by extension) and typically include:
`-- src
`-- <Module>
|-- Application
|-- Domain
|-- Infrastructure
|-- Ports
`-- Tests
src
separates the actual source files from other package resources, e.g. documentation, dependency modules, web interface files etc. <Module>
must be replaced with a vendor-unique UpperCamelCase package name (e.g. MyApp
).Inside the five main directories, your application may add additional structures as needed. However, to keep things consistent, I recommend sticking to these conventions:
Factory
instead of Factories
).Facade
, Contract
, Service
or Factory
for grouping classes with similar functionality.In the Clear Architecture, source code dependencies may only ever point to the same or an inward layer.
Nothing in an inner circle can know anything at all about something in an outer circle. In particular, the name of something declared in an outer circle must not be mentioned by the code in an inner circle. That includes functions, classes, variables or any other named software entity. (Clean Architecture, Robert C. Martin)
In other words, it's perfectly fine to reference same or higher-level components e.g. by
Adhering to the Dependency Rule makes your application testable and flexible in terms of implementation details (persistence strategy, database platform, provided client APIs etc.).
In order to not violate the Dependency Rule, the Dependency Inversion Principle must be used whenever complex data needs to be passed across a boundary to an inward layer. Instead of expecting and directly referencing a low-level component (e.g. as a function parameter), the high-level layer provides and references an interface that must be implemented and inherited from by the caller. This way, the conventional dependency relationship is inverted and the high-level layer is decoupled from the low-level component.
In general, avoid tight coupling between components and prefer abstractions / interfaces over concrete implementations, thus enabling polymorphism and making your application a lot better testable. You will always have to deal with object instantiation and globals at some point, but try to limit their occurrences to a minimum and manage them smartly. Some type of Dependency Injection mechanism might be of great help, but please consider the possible drawbacks as well. DI configuration should be part of the Infrastructure sector or of a general bootstrap process, stored somewhere outside the layer directories altogether.
The following special components (and their files) must be named after their role:
Interface
suffix (e.g. MyCustomInterface
)Trait
suffix (e.g. MyCustomTrait
)Factory
suffix (e.g. MyCustomFactory
). Public factory method names must use the create
prefix (e.g. createFromParams
). Note: I mostly use the Clear Architecture for projects written in PHP but it should be easy to adapt its principles to other environments as well. Please let me know if you succeed (or fail) in doing so.
<Module>
being the base directory corresponding to the namespace prefix (e.g. Jkphl\MyApp
). Follow the next point to get autoloading for free.vendor
directory.Copyright © 2017 Joschi Kuphal joschi@kuphal.net / @jkphl. Licensed under the terms of the MIT license. Originally published at at https://jkphl.is/articles/clear-architecture-php