This piece is about making choices for software design. Particularly about larger systems which could potentially be separated into multiple deployables in the form of service endpoints. I won’t be talking particularly about service endpoint design, but I would like to discuss the ideation phase for creating multiple service applications.
When we face complex problems, we usually try to understand the individual pieces of complexity. By decomposing the problem, we turn it into more understandable and manageable pieces.
As is described in many product/project management cycles, for real life problems, this is usually driven by instinct. We don't use a formula to understand what is required to travel to a country that requires a visa. We learn that we need a visa to travel, we slowly grasp the documentation needs, what forms are to be filled and how to fill these. When we handle one of the steps, we don't keep all the details of the process in our minds, we just do the task at hand. This is related to the size of the tasks to accomplish.The underlying real criteria is about timing or schedule, our energy to execute, our cognitive capacity and its relation to familiarity of tasks and maybe even the physical locations where those tasks can be executed (consulate vs. photoshop etc).
This is not different in the software development world. While for years waterfall-like formulated recipes have been applied to the software development process, in the end, mostly heuristic and experience based estimation techniques (planning poker, t-shirt sizing) and agile processes have prevailed.. As in real life, we try to focus on not detailing the whole process, but to try and understand the overall journey by looking at our latest performances.
The same applies to the software that we’ve modeled after problems. We started to break them into different applications with goals to easily manage individual applications, develop and deploy faster with lesser dependencies, and lastly bring more freedom of technological choices. We realize that we cannot formulate a complete process that fits all. We look at individual pieces and recognize our collective experiences with design patterns or techniques and try to apply the best of the choices.
An interesting software design technique to understand and solve complexity is Domain Driven Design (DDD). Domain Driven Design advocates modeling based on the reality of business as relevant to our use cases. As it is now getting older and hype level decreasing, many of us forget that the DDD approach really helps in understanding the problem at hand and design software towards the common understanding of the solution. When building applications, DDD talks about problems as domains and subdomains. It describes independent steps/areas of problems as bounded contexts, emphasizes a common language to talk about these problems, and adds many technical concepts, like entities, value objects and aggregate root rules to support the implementation. Sometimes these technical rules are perceived as hard barriers implementing the DDD, but at the end, people tend to forget that the important part is to organize code artifacts in alignment with business problems and using the same common, ubiquitous language as (1).
Bounded Contexts Designed as Service Applications
The architectural style I would like to talk about is very similar to microservices. It is about separating the monolithic applications into multiple stand alone service applications or developing them separately from the beginning with the help of bounded contexts, a DDD concept.
There are many resources which highlight pros of having more granular services explained part of microservices narratives. Increasingly, there are more articles, blogs and other content available about the pitfalls and the kind of safety nets that you should have before or during the transition to granular services. I will try not to repeat the benefits of microservices or other supporting elements that you need to have, to migrate into such an architecture. Instead of emphasizing on the "small sized" nature of the resultant services, I would like to emphasize on how we can separate these better by applying domain driven design concepts.
Let's use a real-world example to materialize our ideas - a debit/credit card acquiring domain. This domain could be (as is the case many times, unfortunately) realized as a set of monolithic applications. The only reason that we have multiple applications is due to the hard technical limitations (such as a desire to execute batch processes) in different applications.
Most successful architectures that I have seen, recognize that integrating through databases is a bad practice, as it makes the boundary between technical application and business responsibility blurry, allows business logic to leak into the database and prevents horizontal scaling by adding more application servers. So the evolution to a better architecture happens in the form of service integration of monolithic applications.
Now the boundaries between applications are clearer. But as you can see, there are still hidden DB interations, this time happening inside the individual applications. I call them hidden as they are generally hard to notice at first. As time passes, tangling of the code will make originally- separated business processes related artificially and introduce more friction in business development, as this co-location requires joint deployment of separate features which potentially can slow down the pace.