Coming from the intricacies of the package architecture and function clean structures, it’s time to go deep into the modules. To make it clear, I’m referring to what is called objects in OOP: classes (concrete and abstracts) and interfaces (a set of signatures with a name).
The package architecture modeled previously define not only the nature of the package itself—its purpose for the whole system—but also its inner modules composition. As I had discussed before, the package architecture decidedly determines what’s in it: the module types it should contain and the communications protocols it imposes, between the sibling modules and with their peers from other packages.
Package Architecture and Module Types Overview
Let’s review this model. Remember that from a cleaned architecture product, we need at a minimum the four types of packages. The package containers aren’t simple logical divisions of the modules, but also physical boundaries of the software pieces of the system.
Use Case layer
The application core layer, where all the use cases scenarios —workflows— of the system are represented.
- Interactors – A use case or use case scenarios representation. Actions, requests and responses of the use case scenario are represented by functions, input arguments and return values.
- Data-transfer objects (DTO) interfaces and implementations – They are data structures to transmit data between layers. Most probably are input and return values of the interactor functions.
- Technical service interfaces – Abstract modules to specify service operations and constraints.
- Factories – They are required to instantiate the concrete interactor, DTO and service modules.
- Presenter interfaces – A call-back module for asynchronous calls mainly to the UI layer.
This is the domain policy and business validation layer. It operates over the business-wide rules implemented as functions.
- Domain rulers (Business objects) – the business rules implementations
- Factories – To create the concrete domain rulers.
User Interface layer
The layer from where the end-user «talk» with the system.
- MVC (models, views, controllers) – The models are the data structures to transfer input and output values from the interactors. The views are visual representation of the data in a user-readable format. The controllers manage the user interface messages to the interactors responsible to respond the message.
- Presenter interfaces and implementations – Before the models are presented, they need to be transformed friendly to the users. The presenter changes the models for the views.
- ViewModels –It’s the product of the presenter combining models or domain data with views or presentation data.
Technical service layer
The support service layers for the system (persistence, reporting, exception handling…)
- Servers façades – They are the entry point to the service layer, exposing the main operations to client applications.
- Adapters – Needed to adjust the interfaces from the use case layer service with a blackbox service component.
- DTOs – Data structures required to command or query a service.
That seems a lot of modules. Let’s go through the details.
The Use Case Layer Package (a.k.a Application Layer)
The use case layer contains the application logic. That means, the use case layer will realize all the use cases the solution supports. Each interactor represents a full use case or a use case scenario, and its functions are the operations that the use case scenario steps denote. It’s what we call the «System» or the «Application». The use case layer is a component-strategy blackbox package.
The following types of modules are the essence of this layer:
- Interactor interfaces and implementations
- DTOs interfaces and implementations
- Technical services interfaces
- Presenter interfaces (for asynchronous calls mainly)
A comprehensive discussion follows:
The interactor interfaces and implementations
The module responsible of the complete use-case realization—when there are few use cases—or a use-case scenario realization—for full-dressed use cases—is the Interactor module. The interactor is an example of the Strategy design pattern. It has little state data (variables for the services it will use and domain objects only, for example) and all the operations the use case scenarios steps describe for the system. The interactors family is really a pair of an interface or abstract class with at least one correspondent implementation. The interactors behave as façades for the application and the only entry point to its business-valuable operations.
An Interactor type contains a set of commands and one or more input arguments as those in the following examples:
- GetCustomerMaxDiscount (ICustomerDTO c): void
- LoadProducts (Collection<IProductListDTO> products): void
- StoreSale (ISaleDTO s): void
Why do we define the interactor functions as commands? Why don’t we use query operations? While is the user is the principal data provider for the application, the interactors change the data that the application user provide. For this convention, the interactor operations, instead of query and return values to the use case layer, loads the collection of items to the list the user interface layer provides. But as you may know the effect should be the same in those cases.
The principal argument in favor of declaring the interactor’s functions as commands is that the query functions must implement at some level, the concrete object to return. This implies that the use case layer should implement the DTO or that the other layers that shares data with this layer should implement it to pass through all the layers up to the UI layer. As we clearly see this violates the clean imposition of the DTO interfaces as a sharing tool with the UI. The UI should implement the DTO interface. That also is the argument to define the interactor’s operations as commands receiving the concrete instances of the UI.
The clients of the interactors are the controller or event handler modules from the UI layer. The UI layer module share data with the use case layer by passing data structures as DTOs through the interactor operations. Those data structures are declared in the use case layer according to the system data requirements. In many cases, a simple hashtable is enough to transfer data from and to the UI layer. In a standard operation, the interactor changes the state of the DTO received and by so doing, the state of its clients.
How does the UI controller module get a concrete instance of an interactor? Most of the time, the client uses a Factory to instantiate the concrete version of the interactor class. When we are testing several alternatives or strategies this is the zone where the system determines if version A or B will be delivered. This medullar creational pattern decouples the strategy types from their implementations.
DTOs interfaces and implementations
As it was indicated above, the interactors use data structures as DTOs to communicate with the UI layer via input parameters or return values. In the use case layer, there are as many of those DTOs interfaces as parameters needed for the interactors. Most of the time one DTO per interactor method is enough. That doesn’t mean that the DTOs in this layer are isolated and unrelated data structures, because many of them are founded by multiple narrow data structure interfaces as parents or use another data structure as an attribute itself.
In the above example the address and the phoneNumber attributes may be defined in several ways. The simplest way is declaring those attributes as primitive strings—as in the model at the left. A second way is to declare those types as independent interfaces and define the IProfile type as a subtype of those. And the last possibility, as showed in the above example at the right, is using a nested DTO where the attribute is the DTO itself, conforming a tree of pure data structures. This last format is enormously useful to transfer non-tabular data.
A repetitive recommendation on using the DTO data structures is to avoid mixing the role of the DTO modules with the domain ruler objects. They share some of the state, and probably the same names and vocabulary, because they hover the same data attributes and origin as the domain object. But while the domain rulers are behavioral objects judging the conformance with the business rules; the DTOs, on the contrary, only possess raw data. Most of the time, the interaction between those two module types is limited to the use of DTOs primitive attributes as parameters for domain rulers’ constructors or operations.
Do the DTOs have an implementation in this layer? It’s rarely a necessity to implement a DTO for use case and UI modules communication. On the contrary is the UI layer which should implement the provided interface. But, as client to the technical services modules declared in its layer, the use case layer might require its own concrete version of the DTO to use it as an argument to the services operations, when there isn’t a concrete version from the UI layer. This is a common practice as we will see.
Most of the time the necessity to transfer collections of DTOs doesn’t require a dedicated module. In any case is simple to use a hashtable (Dictionary or Map classes, e.g.) of the DTO as a parameter of the interactor that requires it or a one-dimension array.
Technical service interfaces
The use case layer typically requires varied and separated services to fulfill requirements such as persistence, logging operations, reporting, auditing, exception handling, authentication and authorization and so on. The technical service interfaces allow the use case layer to define its own service model. By that, I mean that the use case layer will have a consistent contract opened to a varied service versions or strategies. As the use case layer consumes multiple service types, the obvious recommendation (high-cohesion, low-coupling) is to separate by package each service needed. This architecture models the concrete servers as plugins for the application.
When there are already well-proved black-boxed components, the standard solution is to interpose an Adapter between the layers to translate the use-case layer technical service interface to the closed component interface.
The typical technical service module is a class with a limited set of operations the use case layer need. Establishing a conventional interface for those services serve to reuse the available plugins and to develop multiple strategies of services. For example, the CRUD operations as in the above example.
The technical service interfaces and their implementations is a very extended subject that might require a deeper exploration, separated by service types, etc.
The UI controllers or event-handlers must call the concrete interactors. The concrete technical service should be instantiated based on the current implementations. Also, it’s possible that those services were implemented as blackboxes with their own instantiation methods. This creational problem is solved with Factories.
The use case layer use at least three types of factories. The most obvious is the factory of the interactors themselves. This factory is a class with static factory methods that dynamically load the use case interactors to the UI layer requester. The factory itself is frequently declared as a static class, if there is no additional complexity in the construction of the factory or other non-static methods.
The interactors themselves need the concrete versions of the technical services servers. This kind of factory should instantiate the proper concrete version of the server module when the package of services is a plugin. When the service package is a blackbox, we use a concrete factory to build and load the factory instances declared inside the service component. The technical service factory instance is then responsible of creating the family of servers from its own package. This is an example of the Abstract Factory design pattern.
Although in the above diagram, the first factory classes are concrete classes that can be instantiated, the inner structure of the class motivates the declaration of such classes as a static class. This implementation is the simplest solution to avoid the circular problem of who instantiate the factory.
The Presenter is a module that prepares the raw business data for the view modules the end-user will see and interact with. It consolidates the business values with the visible attributes of the view and produce a ViewModel module. That operation runs synchronically as follows:
- The controller or event-handler module call the interactor passing a request data structure (DTO)
- The interactor modifies the data structure as requested by the controller
- The interactor returns the control the controller
- The controller passes the resultant data structure to the presenter module, requesting a view model
- The presenter creates a view model which contain business and visual data attributes
- The controller passes the view model to the view where the results are displayed to the user
In the purest version of the clean architecture model, there is a separation between the input ports, implemented by the use-case interactor classes, and the output ports, implemented by presenters in the user interface layers. In this fashion, instead of using a single pair of interaction and DTO to enclose the request and response operations and models; the pair of interactor and request model (the request to the Use Case layer from the UI layer) is separated from the pair of presentation and response model (the response from the Use Case layer to the UI).
This separation of events is most common in asynchronous calls to an interactor, where the response model is constructed and delivered to the UI later (using a queue of pending work, e.g.). In a microservice model, for example, the response might be available after minutes, hours or even days. The use case layer interactor only receives the message and put it in a pipeline pending to be processed. In time, or after some preparatory work, the response is constructed and a callback (calling the presenter) to the UI client is emitted. In those schemes, we need to define the interface of the presenters in the use case layer.
But there is a big difference between a presenter described as asynchronous operations module (called by interactors) or as view model factory. In the first place, the presenter is an event-handler module to manage the interactor’s calls, and then process the view model construction. In the other case, the presenter is only a factory module with the intelligence to build view models acceptable to users.
The declaration of the presenter interfaces in the use case layer brings us another problem. How much of the UI requirements of the views the use case interface layer should know about, as the presenter mix both business and visual attribute values? Sometimes, the format and layout of the data is a business specification. There are also domain language constraints that the system must know (standard color schemes, e.g.). For those cases, the presenter interface (input and return values) should hide any visual feature outside the business-valuable specifications.
Using this mechanism again we decouple the use case layer as a provider from the external environment, being a client or a support service.
In future posts, I will continue with this exploration covering all the other layers.
 I prefer to use this generic name, because there are other types of containers to consider (function libraries, enumerations, etc.) that are not properly objects as in object-oriented jargon.
[…] The XYZ of the Clean Modules (1) […]