After going deeply into the inner structure of the function, the natural inclination would be to think and discuss the details of the containers—the modules—of this essential building block. The function is the minimum behavioral element that a module exposes to strangers, and the structured implementation of its internal communication. But not all the modules have behavior; and not all behaviors are the same. Think about the differences between a data structure, a factory or a domain entity class.
Paradoxically, is the package architecture, the cohesive container of modules, which defines the nature of the modules it hosts. The basic structure of clean packages is based on the following module relations:
This elementary structure is based on the well-known and savvy GoF’s principle: «programming to an interface, not an implementation». With this simple set of modules and relations we might define three architectural models:
• Plugin Architecture
Defines at least two packages: one for the client and other for the plugin. The plugin is a server of the services the client has defined.
• Component-Strategy Blackbox
Same as the plugin, it defines at least two packages: the client and the component blackbox. But, in this case, the component defines the strategy, its interface and, probably, a set of implementations. The client only sees the component as a closed subsystem.
• Component-Strategy Framework
It defines at least three packages: one for the client, one for the service interface (IService) and one for the implementation of the services (the server). It provides an open and flexible mechanism to decouple the three modules: the client of the service from the model of the service and then, the model from its implementation.
Although the package is a logical division, we will treat them as well as a physical division of software pieces. Each package architectural model in the following descriptions are also a separate compiled file. That decision follows the Reuse/Release Equivalence Principle that states: «the granule of reuse is the granule of release».
Let’s check out each one in detail:
In the plugin architecture model, the client application defines the interface of a service that the plugin package implements in a server module.
A plugin architecture is happily used in extensible programming IDE applications, web browsers and word processing applications. The value of such architecture is the possibility to plug unlimited functionality and processing methods to an existing application, without changing it.
But a plugin isn’t limited by such value. It’s used as an important structural factor to define the relations within a Use-Case Interactors package and its services: UI, persistence, exception handling, among many others:
In the above example, the UI and the Persistence packages are plugins for the UseCaseInteractorAPI. Notice that the plugin interfaces (IXView, IPersistenceService) have coupled relations with their client InteractorClass. The client is a higher ruler than the plugin. It defines the plugin. Truly, the plugin is its slave.
The principle behind a plugin architecture is to protect the high-level policy packages from the implementation packages. The use-case interactor package as in the example, should model its servants. It proposes and imposes the required interface for the service it needs.
But there’re other architectural options. In other instances, the product modeled is a complete and independent heavy-weight subsystem. It exposes a rich set of behavior, attractive to clients. This type of components are blackboxes that offers their valuable operations for others to consume.
There are many concrete examples of blackbox devices: a car, an airplane, a computer, a TV. Those systems expose friendly interfaces to interact with users: buttons, gears, sticks, screens, keyboards. The inner mechanisms that implement the connection between the user message DoThis() and the operation with the same name, wherein the magic occurs, are hidden. The system is a blackbox for its clients.
In the manufacturing industry, the componentization is a standard design policy to provide such devices. The infinite variety of devices built with such architecture is minimized with the conventional agreements, from the formal ISO standards to the more relaxed social protocols and cultural practices. They normalize the variations with familiar interfaces to consumers (humans or machines).
The dependencies here are in the opposite direction of the plugin: the service interface within the blackbox defines the expected behavior for the clients. The client is then the slave. If the component changes its interface, the client is broke or fails. That’s why the value of a blackbox is opposite to the plugin value.
A blackbox component usually comprises of a rich set of functionality and multiple strategy implementations to its client. It can be developed independently for an infinitive number of clients with different expectations. Also, it isn’t uncommon that the longevity of the component is accompanied with mature, secure, almost bug-free rich behavior.
Precisely, and continuing with the previous example, the UseCaseInteractor package which models an application of a business process might be represented as a blackbox microservice:
In the above example, the micro service behaves as a blackbox to the client applications. It declares its interface and deliver one strategy implementation of it. It might develop other alternatives for IServiceX (MicroserviceXProd, MicroserviceXDev…) using any valuable criteria (running environment, algorithm variations, stable/new features, etc.). As the MicroServiceApi contains a factory for instantiate all the concrete implementations is easy to move from one version to other—depending on the client character— without any harm to the outside world.
For the closed nature of the blackbox, it’s sometimes a necessity to combine the plugin architecture and a blackbox putting an Adapter in the middle. In those scenarios, the client application has defined a plugin with the service interface it prefers, but as we have already a very good black-boxed component that fulfill all the necessities, the situation cries for an Adapter combining the two models:
Many times, for the multiple strategy implementations a component might offer or require, there is the opportunity to decoupling the interfaces from the concrete modules of the servers. The interface and abstracts modules are then separated in their own package and the implementations are allocated in one or multiple packages with alternative implementations. That’s the architecture of many known frameworks and development platforms.
From the modules perspective the framework architecture is equal to the blackbox. It imposes the interface to its clients but, at the same time, it offers the option for new implementations. The client, instead of couple on to a bloated component package with lots of implementations it doesn’t want to use, it consumes only the interface and creates its own alternative implementation or uses another provider’s implemented version.
The presence of abstractions (interfaces, abstract classes) force us to use some creational pattern to load the concrete instances of the servers. There are rules to follow as enumerated below:
- Factory location near the abstracts. The factories or building mechanism to instantiate the concrete instance should reside in the same package of the interface or abstracts modules.
- Abstract factories called by concrete factories. It’s a common practice to use a concrete Factory to instantiate an abstract type factory, which has the factory methods for a family of objects declared in the implementations package.
- Data-Driven instantiation of factories.The location of the concrete implementation of the abstract factories, should be consumed from an external data source and the loading process dynamically bind the factory component at runtime.
See the illustration:
The chain of messages would be: a ConcreteFactory creates dynamically an instance of the AbstractFactory (using Reflection or a similar mechanism to locate the concrete instances package); the AbstractFactory instance at the request of a ClientModule uses a factory method to create concrete implementations (ConcreteServer) of the IService module. In any case the ClientApp modules will receive the concrete instances via abstract variables.
Finally, let’s show a complete model:
The examples above are a brief description of a Clean Architecture model and its implications. There are more details attached to the module models that the different packages types force under this architecture. Each package has its own set of module types, running from factories, DTOs, service interfaces or business rules objects.
This should be the next step for our investigation.
 Package – A set of inter-related modules that defines the scope and behavior of the container. Also known as subsystem
 Martin, Robert C. (2018). Clean Architecture. Prentice Hall, page 104.