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.
Domain modeling helps to identify and separate tangled implementations, if you are lucky to have a domain model to guide you. If you don't already have a domain model for an existing application (which is generally true in most cases), instead of going through the code to understand the different responsibilities, building a domain model and mapping the capabilities to the application at hand can probably be a better approach. This will both save time and prevent the risk of being lost in the weeds of detail. Also, if there is a gap between the business and the development team (which could be the major reason that the domain model didn’t exist in the first place), talking about the domain model and mapping to the capabilities of existing applications will help narrowing this gap.
The next step in our design evolution is to reflect domain boundary separation to our architecture as well as bounded contexts. A domain having more than one bounded context means that there can be multiple service applications which operate in the same domain. With the proper domain model in place, potential separation points are more visible, which allows us to benefit from potentially more granular applications (benefits such as separate release and versioning, potential to have more capability-driven pure service endpoints etc. Most of these have already been discussed in microservices articles). While much of the microservices discussion centers around technology agnosticism and development discipline (avoiding / breaking the monolith), a very valuable item for the applications that most of us work on is the domain and design aspect. Once transitioned to a microservices architecture (with a help of domain model), DDD and more granular services can work in synergy to support each other.
This will also provide a level of independence to the teams, more refined capabilities of services and more decoupled interactions as explained in many microservices texts.
Also, as can be seen in our sample credit card payment acquiring domain, this is not the most granular separation we could have with our services. Instead, this is the most meaningful separation guided with our domain knowledge. The emphasis is not on the size, but instead on the business capabilities. I believe this is the "SOA done right", as is said in many circles.
Thanks to Ryan Murray, Ra-el Peters and Steven Lowe for their valuable commentary and discussion about this article.
Disclaimer: The statements and opinions expressed in this article are those of the author(s) and do not necessarily reflect the positions of Thoughtworks.