Hey, this is Denis. This is a three-part series on the topics of modelling time in code, the engineering concepts surrounding temporality and co-dependence between teams. The series covers:
Micro-services solve Time, not Size
Facts vs projections, an event-based approach to modeling time
How your team’s communication cadence impacts architecture
Temporal Coupling
There are many definitions for temporal coupling, which in itself creates needless confusion. Just look at the DateTime library of your favorite language and you’ll see the complexities quickly get out of hand in all domains. Here’s a few good picks that overlap with today’s context of microservices:
Wikipedia:
It is when two actions are bundled together into one module just because they happen to occur at the same time.
Pluralsight:
Temporal coupling is a kind of coupling where code is dependent on time in some way. It is particularly insidious because it is hard to detect unless you know what you are looking for.
Codescene:
Temporal Coupling means that two (or more) modules change together over time. Exploring Temporal Coupling in our codebases often gives us deep and unexpected insights into how well our designs stand the test of time.
What these definitions have in common is a time context: areas of behavior and data change that occur:
at the same time
close together in time
move forward using a sequence of rules (ie. ordered events)
Socio-technical
The most complex to track forms of coupling are the ones that span the entire codebase. These often cross over team or even department boundaries. When neglecting socio-technical influences within the codebase, temporal coupling forces teams to coordinate and slow down processes. Often, this is caused by a lack of domain boundaries and encapsulation, the services ageing to the degree where their architecture no longer serves the current behavioral use cases of a software product.
Operational
Enthusiastic engineers who venture from monoliths to microservices face this one the most. In a space of a single deployable artefact and guarantees that this provides, the monolith may appear as clumsy, but practical. Microservices that communicate over the network rather than in-memory (stack or heap-based) introduce a new form of temporal coupling: lack of guarantees of the network layer.
Logical
The smallest-scale temporal coupling takes the form of Connascence of Execution. In object-oriented languages this takes the shape of efferent coupling, afferent coupling and temporal coupling. This blog post by Chunting Wu covers the distinction well. Here is a simple example where an object exposes a badly modelled temporal problem:
When it comes to objects, mutable state is the root cause of all accidental complexity that stems from managing time-related concerns. Once left unchecked, it bubbles up and may appear as operational or socio-technical issues later in the project’s lifetime.
Time independence
With that vocabulary, let us discuss the obvious solutions that come up when dealing with these concerns.
Asynchronous, unidirectional data flow. Opposed to cyclical request-response structures, unidirectional data flow allows for accident-free logic and data processing. These are your event sourcing, cqrs, stream-based data processing on the logical level. However, this surprisingly does not include the async/await structures in most languages as they do not deal with the core issue.
Message queues and buses. For operational coupling problems one needs to introduce an intermediary to manage the network communication flow. Solutions range from in-memory queues or SQL outboxes to enterprise level solutions like SQS and Kafka. These intermediary open up granularity in the nature of the flow of data and commands: direction, encoding, retry-mechanisms, durability, fan-out, fan-in, ordering, idempotence, etc.
DevOps shift-left and ensemble programming. The socio-technical problems are a product of teams not being able to self-manage and having silos of communication. Hand-offs and chatty data interchange can be remodelled by pair/ensemble programming across team boundaries: product team A with B, backend with frontend, QA with developers. Creating or refactor your abstractions so they vertically match and group folders or repositories of changes that change together close-in-time.
Should you pursue microservices?
Microservices are an output of a team wanting more autonomy. When fixing an existing problem like cruft attached to a monolith, microservices force you regulate accidental complexity caused by temporal concerns. This is due to their detached, separate time context nature.
In contrast, on greenfield projects they solve domain modelling concerns of time-independence in a more architectural or data-flow perspective. Event modeling and domain-driven design lend themselves very well to exploring these areas.
The question then is—do you have any of these challenges? Are you aware of them? If not, it’s possible that an inexperienced team will create a distributed monolith and have to face these issues the hard way—by virtue of service outage or work synchronization until the lesson lands. On social media like youtube and linkedin, this rhetoric is captured as a warning to using high-calibre tools like Kubernetes and Cloud-native containerization.
You are now prepared to decide—Let’s summarise
Temporal coupling can show its effect on three levels: logical, operational and socio-technical.
The coupling smells can be addressed by modelling flow of data to be unidirectional, introducing message queues and vertically re-grouping teams to become cohesive around their frequency of change, rather than infrastructure.
Microservices are a good companion for your toolkit of solutions when these refactoring, modeling and organizational concerns already occur naturally.
Be inquisitive though—microservices bring on the very challenges they address and dilute. If your current project environment lacks these complexities, microservices will introduce them prematurely and accidentally.
This debt will have to be paid at some point in your org’s future. By someone.