I’ve been doing some deep thinking on how to structure code and found myself coming up with a (for me) new pattern. The pattern is based on a highly modular approach with a large focus on indirection, reusability and enforcement of a singular “right way” of coding.
I have started trying the pattern out and found that the design goals are achieved. However, projects tend to become a bit complex and hard to refactor by hand due to a lot of tools using source-root relative paths. I’m therefore interested in some feedback – especially if you recognize this pattern from somewhere else!
The pattern consists of three main layers. Starting from the bottom we have
Specification
This is your typical “core” module. However, its main purpose is to declare the public interfaces that makes the application work. It should also declare the common data representation “model” that is used by the rest of the application. As a non-C programmer, this layer sounds a lot like C header files to me.
Logic should preferably not be part of the specification layer, though it’s not uncommon for utils
or helpers
to end up here.
Implementation
Whereas the specification layer specifies the application, the implementation layer implements this specification.
The implementation layer consists of many smaller modules, each of which are highly specialized in a single area such as interacting with a certain remote API, persisting data, calculating directions, etc.. The modules should be designed like mini-libraries that implement specific parts of the interfaces defined in the specification layer. Care should also be taken to not expose implementation details public
ly, e.g. by defining concrete implementations as C# internal
or java “package-private”.
When an implementation layer module requires features it itself does not implement (such as data storage), it should rely on dependency injection of an interface type (e.g. UserRepository
) from the specification layer. This also means that while modules are free to use their own internal models, all communication between modules must happen via the common models defined in the specification layer.
Finally, implementation layer modules should expose their implementations, not by making them public
(unless that’s desirable) but rather by binding them to their appropriate specification layer interface via a system for dependency injection. This could for example be done by having each module expose a single ModuleInitializer
which takes care of initialization and binding for the individual module.
Orchestration
At the top, the orchestration layer is used to orchestrate all of the implementation layer modules. This is where we find the program’s main entrypoint.
The main purpose of the orchestration layer is to select and initialize the implementation layer modules required to run the application. This also means that a single code base can have multiple orchestration modules, e.g. if it is supposed to have a library output and an application output.
As with the specification layer, the orchestration layer should contain very little logic.
Tests
Unit tests should be placed alongside the specific module they are testing. Dependencies that are not implemented by the module can easily be mocked since the module relies only on specification layer abstractions.
Integration tests can for example be placed alongside the primary orchestration module they intend to exercise.
Naming
This pattern emphasizes the use of abstractions over concrete implementations. Therefore, it also recommends interface Car
and class CarImpl
over interface ICar
and class Car
.
EDIT
I have been asked to clarify the problem I have with this architecture and what I’m requesting feedback on:
- My main issue is that the architecture results in a large number of sub-projects. In part, this is desired, but I’ve also found that it makes organization and refactoring harder, especially when not using a specialized IDE such as Visual Studio (which I typically don’t). I’d be interested in your thoughts on this issue as well as potential solutions.
- I’m interested to know if this architectural scheme is already established and if so, under what name so that I can conduct further research.
5
Here’s is my critique of your architecture. Bear in mind that you only give us this short questions worth of information.
It is similar to other layered architectures such as clean architecture. The key differences seem to be:
Specification/Domain/Model Layer
You have interfaces, data models and no logic. Much like an ADM approach which I think is great.
You put helpers and utils here which is very bad indeed and really goes against the rest of your pattern. Surely these should be implementations.
Implementation/Domain Logic/Service layer
Keeping modules code small and focused is good and follows SOLID principles.
Keeping all the objects private and forcing the use of Builders to create them, seems unnecessary and overly complex. What layer do the builders live in? Will it even work if the interfaces have public methods?
Orchestration/Application Layer
You don’t have any other layers so I guess your actual application goes in this layer, which makes it odd that you think you can have multiple orchestration modules? It’s unclear how this would work or what problem it solves.
Tests
Seems like normal tests.
Naming
You really should follow language normative naming, so interface ICar in C# and Car in Java
Notes:
My main issue is that the architecture results in a large number of sub-projects
This is generally considered a good thing.
…especially when not using a specialized IDE such as Visual Studio
(which I typically don’t).
This just calls into question the entirety of you question and poses a dozen questions. Everyone uses IDEs. Are you crazy?
I’m interested to know if this architectural scheme is already
established and if so, under what name so that I can conduct further
research.
Aspects of what you describe are common, I don’t see anything that stands out in your description to differentiate it from other layered approaches. Maybe the multiple Orchestration objects? Implementation hiding? Are these critical to the approach or if I do everything else but have public constructors am i still following the architecture?
does the concept seem sound or are there cracks I’m missing?
There’s not enough here. You need some concept to anchor the approach. It needs to be designed to solve a specific problem.
8