This is a follow-up on the post Software Design – The Big Picture (Intro).
Processing a request to do something, be it a user interaction, a remote service call, or a local scheduler invocation always require the same “logical” sequence of steps:
- Check whether the request can be granted execution (permission checking)
- Translate the external request into internal data structures and validate input
- Log the request
- Call one or more repositories and/or local services to retrieve data and perform computations, progress some workflow, and update some data.
Simple enough. In reality however things get messy quickly. That is why in this post we describe a simple but effective and many-times proven abstraction that help us breaking up complexities in code by layers of responsibilities:
The separation of code into three layers that are orderd in terms of control flow simplify dependencies and make the implementation simple to understand.
A Facade is a consumer-specific presentation of a function that is implemented via one or more internal services and data subsystems. Facades is the natural place to check on permissions and to do any kind of logging that describes the interaction (as in audit logging). Facades are the “adapter” between internal business logic and data definitions and the external interface (e.g. service or user interface).
It may well be that your code has a definition of a facade. For example, if you use a Model-View-Controller structure, a controller class would be part of the facade.
Essentially any kind of application needs some understanding and access to shared business data. This is for many reasons what should be modeled first when starting a development effort as
Data lasts longer than function, and function lasts longer than interface.
Investing in an effective and robust data model is much better invested money and time than polishing the last details of your user interface. We define all domain data type definition as well as access methods (query and update) to form the Repository layer.
We want our application to actually do something useful besides reading and updating data. What we really want is to solve a business problem and that means to implement some business logic that humans cannot or should not perform by hand.
Services implement business functions over the domain data model beyond data access and modification. Services are modeled in terms of a function domain, not a presentation or interaction and so services are the place to implement logic that is not specific to a facade but instead inherent to the business domain and may be re-used for other facades.
While facade changes are driven by external presentation requirements, while changes are driven by changes in the business workflows, which leads to the inverse formulation of what we had above:
Facades change more often than services and services change more often than the domain data model.
By introducing and sticking to an implementation pattern, which is what we are discussing here, we make the structure of code more readable, more maintainable, and more extensible because concepts repeat.
Note that the layering has very little to do with modularization or even deployment and distribution of microservices or even hexagonal architecture and the likes. We are still talking code structuring – micro-architecture if you will. The motivation and tasks in modularization are different and require deep solution specific abstraction to be done well:
That is, we are still on a rather fine-granular level. That will change when we discuss modularization.