More than concentric layers
This article is part of the Software Architecture Chronicle , a series of articles on software architecture. In them I write about what I learned about software architecture, what I think about it and how I use knowledge. The content of this article may make more sense if you read the previous articles in the series.
In the previous article in the series, I published a concept map that shows the relationship between code types.
But it always seemed to me that not everything is very well reflected there, I just did not know how to do it better. Speech about the common core.
In addition, a few more thoughts arose, which I will expound in this short article.
In the infographic of the last article in this series, in the very center of the diagram, we see a common core. It appears that it is inside the domain layer and above the conical sections, which are limited contexts. Despite its location, I did not mean that the common core depends on the rest of the code or that the common core is another layer inside the domain level.
What is a common core ?!
The common core, as defined by Eric Evans, the father of DDD, is code that the development team decides to share between several limited contexts:
[...] a subset of the domain model that the two teams agreed to use together. Of course, along with this subset of the model, the common core includes a subset of the code or database architecture associated with this part of the model. This clearly shared material has a special status and should not be changed without consulting with another team.
- “Common Core” , DDD wiki by Ward Cunningham
Thus, it can be any type of code: domain level code, application level code, libraries ... whatever.
However, in the context of our concept map, I represent it as a subset, as a specific type of code. In my concept map, the common core contains the code for the domain and application levels, which is shared in limited contexts so that communication between limited contexts is possible.
For example, this means that events are triggered in one or more restricted contexts and listened in other restricted contexts. Together with these events, we need to share all the data types used by these events, for example: entity identifiers, value objects, enumerations, etc. Complex objects, such as entities, should not be used directly by events, since they can be difficult serialize / deserialize to / from the queue, so the common code should not spread much.
Of course, if we have a multilingual system of microservices, then the common core must be descriptive, in JSON, XML, YAML, etc., so that all microservices can understand it.
As a result, this common core is completely separated from the rest of the code base, from the components. This is great, because then the components, although connected with the common core, are separated from each other. Generic code is clearly identified and easily retrieved into a separate library.
It is also very convenient if we decide to extract one of the limited contexts into microservice, which is separated from the monolith. We know exactly what is common and we can simply extract the common core to the library, which will be installed both in the monolith and in microservice.
So, to summarize, in my concept map, the core of an application depends on a common core, which contains code from domain levels and an application that is shared by limited contexts.
When language is not enough ...
So, we have application code with all concentric layers, and the application core depends on the common core, which is under all this code.
We can also say that all this code depends on the programming language (s) used, but it is such an obvious fact that we tend to completely ignore it.
However, the question arises: “What to do if language constructs are not enough ?!” Well, obviously, we ourselves create these language constructs and thus compensate for the shortcomings of the language. But I have important follow-up questions: “How and where to substantiate the existence of this code? How can you clearly indicate when and how to use it? ”.
What I saw and did by myself is a package called Utils or Commons, where this code is located. But in the end it ends with the fact that we drop there all the code that we don’t know where to put it! All sorts of code for different purposes and usability (wrapped in an adapter used directly ...) are eventually dumped there. The package has no conceptual meaning, no coherence, no coherence, no clarity, many ambiguities.
I want to drop Utils and Commons!
All packages must have conceptual cohesion! It should be clear when and how to use the package! No ambiguity!
For example, if an application interacts with the command line interface in some special way, then instead of placing it in the Acme / Util / SpecialCli namespace, you can put it in Acme / App / Infrastructure / Cli / SpecialCli. This says that this package is associated with the CLI, it is part of the Acme application infrastructure. The App infrastructure membership also says that in the application kernel there is a port to which this package corresponds.
Alternatively, if we see this package as something that the language itself lacks, you can put it in the appropriate namespace, for example, 'Acme / PhpExtension / SpecialCli'. This shows that this package should be considered as part of the language itself, and therefore its code should be used directly in the code base as any language construct. Of course, if another company depends on this package, it may be reasonable for them not to depend directly on it, but it is safer to create a port / adapter so that they can change it for something else. But if we own the package, we can consider it as part of the language, since the risk of having to replace it with another alternative is not so great. It is always a matter of compromise.
Another example of what can be considered as part of a language is unique UUIDs in PHP. It is quite possible to imagine them outside the language, because every time there is a new version and this is a nightmare with code support, but this is a completely general concept, a broad and consistent enough concept to be part of the language.
So why not create a UUID implementation and not use it, as if part of PHP itself, how do we use a DateTime object ?! While we are monitoring the implementation, I do not see any flaws.
What about Doctrine Query Language (DQL)? (Doctrine is a Hibernate port in PHP) Can we treat DQL as if it were SQL, Elasticsearch QL or Mongo QL?
So, at the macro level, I see four basic types of code and I think it is important to explicitly manifest them in the organization of the code base, in order not to end up with a lot of dirt.
For me, the indisputable truth is that architecture always exists, the only question is, do we control it or not ?!
So let's clearly organize the code in accordance with the architecture , fully or partially on the concept map - mine or another. Most importantly, it is logical to organize the code so that the project explicitly communicates its architecture through the structure and organization of the code.