One of the difficult things in software is to build teams and integrate them with each other socially and in code.
There are a lot of techniques to have it done well like Event Storming, Domain Storytelling or User Story Mapping. All of these are really helpful to get to know business and processes. Finally, we would like to optimize and automate processes of course.
But it is not enough. When we have done our job like Event Storming and business analyzing and even already designed our Bounded Contexts we could think that just development left, couldn’t we?
What is the most important thing to have good modules and healthy integrations?
We have to be aware of why and how modules integrate, adapt other others concepts and influence each other. It allows us to understand relations and make sure that our resolutions are respected.
It helps to have a landscape of which systems integrate with another one and how they do that and how it impacts their social relations and code.
Does it sound interesting? So let’s go through types of collaborating of modules and teams and consider how some examples could look like when we use them.
Context Map Patterns
Upstream Patterns
Open Host Service
OHS is a protocol which allows a Downstream System to integrate with us – an Upstream System. There isn’t any list of how we can do that. It may be a REST API, a Facade, an access to a CQRS bus, SOAP, etc.
The best way is to think about this type of integration like about Http Controllers or REST API. It is an entrypoint to our system.
Let’s imagine that a part of your system is a Payment Gateway. How to integrate with them? Maybe by querying the database or using app code like services or entities? It sounds like a disaster… Of course we should define a protocol to get access to the Gateway.
It’s like someone wants something from you so you give a possibility to do that in some way and on your rules.
Events Dispatching
Upstream Systems only inform the Downstream Systems about what happened. You can do it through a messaging system or simply in memory. It depends on which architecture you will chosen whether Modular Monolith, Microservices or something else.
It makes coupling really loose because systems even don’t know about each other and who is producer of some event and it does not affect when some system is removed from our ecosystem.
For example when a customer pays for their order the Payment Gateway dispatches an event about that and then our Downstream System subscribes for that event and runs other processes. If we do it without a messaging communication like events or hooks the Gateway Payments will have an integration (coupling) with us to do it directly by some invocation.
It’s like someone has to know that something has happened in your system so you inform them about that.
Midway Patterns
Shared Kernel
Subset of concepts we want to share with others. It may be a model, database and code also. The best way is to share stable concepts like Money for example or some concept which will look the same everywhere. We should be careful with this. When something is changed we have to consult it. Sometimes the coupling could be implicit and in fact of change we will get known about it when an error occurs.
For example, addresses of customers may be that concept we want to share between us and the Payment Gateway. They have designed it in the same way so we define a common model for addresses.
It is like an Upstream System and a Downstream System keep the same concept in a common place – Shared Kernel. The Notifications sending is a good example too so when it looks the same in your systems maybe it is a good idea to share this concept.
Separate Ways
Is an abstraction or an integration always needed? Sometimes it might bring more problems than benefits. Integrations are expensive. Especially when our domain is not well known at the moment and it isn’t an archetypal one. Sometimes it is easier, and cheaper to choose this way – an integration and adapting to other concepts or ACL may be harder than the Separate Ways.
It does not mean that two systems shouldn’t integrate with each other at all. They don’t always have to share some concepts when at first they look similar. Following The Payment Gateway example. Do we have to have a common model for Money? Sometimes it would be better when they have their own model for even generic concepts. They don’t have to discuss changes and if we plan to move our Payment Gateway as an autonomous application it will be easier without common concepts and integrations.
We have to know an application’s vector of changes to make the right decisions.
For sure, Downstream System shouldn’t do that instead of integration with Core Domain’s Bounded Context.
It is like an Upstream System and a Downstream System make the same concept on their own. For example notification sending. You don’t have to integrate with another system or create a shared kernel to share this concept. You decide to resolve the problem separately. It makes systems more independent and sometimes it is cheaper and faster because you don’t have to discuss this concept and changes.
Published Language
When you give access to your Bounded Context another system they use some data in their own model. It is important to understand those concepts in the same way or when they are not commonly known to provide some definition and documentation.
Could you imagine a payment gateway which does not provide documentation for their statuses of payment? We wouldn’t know what the flow looks like.
It is important to know all the steps we have to make and which data we have to forward in subsequent.
The same with money – which format do the Gateway accept? Int, string or something different?
A real example is the Portable Game Notation standard for recording chess games. Everyone understands the format but they may have their own model for storing and presenting the data with GUI.
It is like you develop an Upstream System and also prepare documentation or you with Downstream System’s developers prepare together common understanding of some concepts. You use the same names for fields, the same format of data etc.
Partnership
It’s a strategic concept. Two teams are aware that when one fails it means that it is their common failure. Teams understand that they have a common goal. It influences the planning process.
When a Payment Gateway is a Third-Party Provider it probably don’t even know we exist so they certainly won’t be discussing or asking us about our needs. Even if we have that possibility the needs probably won’t be our common goals.
So if we are partners we are going to achieve the same goals and we will cooperate.
It is like you develop an Upstream System and you go to Downstream System’s developer and you tell them about your plans and ask if it is ok for them and vice versa when it comes about the Downstream’s developers.
Downstream patterns
Customer/Supplier
When two teams which are in an upstream-downstream relation discuss the shape of the Upstream’s model to accommodate the Downstream’s needs too.
We can imagine developers which integrate with our Payment Gateway come to us and we discuss how the flow and data should look like. For example they want only one endpoint to process a payment when it comes to flow and also they will send to us only an user’s TAX identifier instead of all of data like address etc.
It is like you develop an Upstream System and Downstream’s developers come to you and show you an interface which they want you to implement and you agree to accommodate their needs.
Conformist
The pattern where an Upstream System’s concepts are adapted by a Downstream system. When a Payment Gateway has three steps and a specific structure of data you do it similar or even in the same way in your system.
This can be illustrated by a Downstream team which at first checks how the system they want to integrate with looks like and then designs their adopting the Upstream system’s concepts.
Also a Downstream system can’t influence an Upstream system to meet the Downstream system’s needs.
Our Payment Gateway may need some special data for payment which the Order system doesn’t need so in this case the Order system is obligated to source these data during the checkout process so the Order system has to adapt this concept and adjust the model to the Upstream’s needs.
It is like you develop an Upstream System and Downstream’s developers come to you and ask how they can integrate with yours. You show an interface they have to use and they have to adjust to that interface.
Anti-Corruption Layer
Everyone wants to design and develop their systems without considering how the system we have to integrate with looks like – a structure of data and a workflow of process.
For example if you plan to integrate with a couple payment gateways and you design your code you don’t want a gateway payment’s code or a model to leak into yours.
With ACL you can keep your model and code agnostic. It allows you to test your application in isolation and it doesn’t matter which payment gateway you would like to integrate with.
Integrations are just an implementation detail. The Payment Gateway’s changes also don’t affect your model mostly. Some changes may require some adaptations at the ACL’s side. Sometime after Upstreamer’s changes you may need some new data that you don’t have yet. It may oblige you to change something in your model.
An example in code could be an implementation of the hexagonal architecture’s adapter or a facade. Part of code where the models are translated in both directions.
Try to design your part of the system as if the Upstream system you have to integrate with does not exist yet. Then make an integration by a facade or a port-adapter architecture which translates your model to the Upstream’s model and vice versa.
It is like you develop an Upstream System and Downstream’s developers come to you and ask how they can integrate with yours. You show an interface they have to use and they have to adjust to that interface BUT they decide to develop their system on their own and create the ACL which is a mediator between systems and translate your model to their needs and vice versa.
Advantages:
- autonomous – it protects ours from other system’s concepts;
- testability – it is easier to mock/spy/stub code – we have dependencies to abstractions only
- extensibility – the control how our model looks like behind the ACL
- change resistant – only ACL have to be adapted after changes in most of cases – coupling to abstraction
Disadvantages:
- expensive – time consuming – an additional layer we have to create and also make an abstraction;
- difficult – it is hard to figure out an abstraction when we have just one use case;
- not always universal – it is not possible to create a model which allow to develop system forward all vector of changes – you have to choose the model which allow you to accommodate the most important for you in the future;
What to choose?
There isn’t just one best. We have to be aware of our goals and choose the one which allows us to achieve them. So what can lead us to choose the best option? Architecture Drivers like Quality Attributes, Business or Technical Constraints and more. More about architecture drivers in another article.
Team relationships
Mutually Dependent/Partnership
You understand that your system is just a part of something bigger and that others systems take a part of that too, so you cooperate when designing your models – maybe you will find common concepts you can share or give access to them and it will help your partner.
Free/Separate Ways
You do something on your own but you are aware of that together.
Upstream & Downstream
It implies how systems influence each other. Upstream System can influence and force changes on a Downstream System and how strongly. It depends on which of Downstrem’s patterns was chosen. With ACL may be really small.
What happens when integration is incidental?
Unfortunately, in most of cases it leads to Big Ball Of Mud (Single Bounded Context). It means we don’t know how processes looks like for real and we aren’t aware of integrations and why they actually was made. So cohesion and coupling are accidental, implicit and tight.
Unifying an Elephant is when we maybe have independent systems/modules/part of codes but there is something what bonds everything – a database for example. Maybe It’s even worse – we can’t change some logic or structure of data berceuse we don’t event know that someone uses it.
Summary
We were considering the patterns with the Payment Gateway and Notification Sending examples but it looks similar for others. So be careful and aware how your Modules, Bounded Context, Applications, Vendors, Third Party Provider are integrated and cooperate with each other.
Bring them all together
- Upstream Patterns:
- OHS – Open Host Service
- ED – Event Dispatcher
- Midway Patterns:
- SK – Shared Kernel –
- PL – Published Language
- SW – Separate Ways
- P – Partnerships
- Downstream patterns:
- C – Conformist
- CS – Customer/Supplier
- ACL – Anti-Corruption Layer
As you can see in the diagram BC can have multiple integration between. You can describe what each integration concerns and why this way was chosen. They are a type of Architecture Decisions so you should keep it in the Architecture Decision Log and describe architecture drivers which lead you to those decisions.
Related resources
- https://www.oreilly.com/library/view/what-is-domain-driven/9781492057802/ch04.html
- https://github.com/ddd-crew/context-mapping
- https://www.youtube.com/watch?v=OthhRfqp-44
- https://contextmapper.org/
- https://www.facebook.com/ConnectisPL/videos/272741738386998
- https://pubs.opengroup.org/architecture/o-aa-standard/DDD-strategic-patterns.html#mapping-context-map-patterns
Source of concept
- Domain-Driven Design: Tackling Complexity in the Heart of Software – Eric Evans
- Implementing Domain-Driven Design – Vaughn Vernon