Many software engineers are familiar with the terms “orchestration,” “integration,” and “calculation,” but they might not have considered them as related to microservices development in clinical trial systems.
This article will explore these concepts—the three core activities in software consumption that change at different rates and why that’s important for building future-proof clinical trial systems.
I’ll also explain how these activities relate and why you should consider them part of your overall design approach or clinical trial applications.
Scalability, maintainability, and extensibility are challenging to achieve. It takes careful planning and know-how. To make matters worse, keeping your aging code base scalable, maintainable, and high-quality takes focused effort as your clinical trial systems go through decay and fossilization.
Defective system design causes headaches like :
Scalability, maintainability, and extensibility all require considered encapsulation.
But what do you encapsulate? It depends on your aim.
Some system designs encapsulate individual domain entities. For example, you’d have a patient service, a clinical trial service, and a doctor service.
The problem with this approach is that your system behavior (use cases) has one of the highest rates of change of anything in your system except the UI (user interface). So, encapsulating domain entities is risky because the blast radius of change is massive. You’ll need to re-encapsulate them with every use case change.
Not very maintainable, right?
Now you can put all your service coordination in your UI code. However, an issue comes up when you want to extend your system.
None of these cases requires a significant stretch of the imagination. But you can’t do any of this if you tie up workflow in your front-end presentation code.
Not very extendable or scalable, right?
Some system designs encapsulate activities. For example, you’d see a patient visit service, a trial randomization service, or a prescription submission service.
It’s not much better because use cases constantly change, and you’ll still have to stitch together activities. When you have to combine activities, changes start cascading through existing dependencies. Even worse, you push state maintenance to the front end, which means your service contracts explode in complexity because you need to inject the state.
So we still have the same maintainability, scalability, and extensibility challenges as with domain decomposition.
The secret is simpler than you think. You just have to switch your focus.
If you’re reading this, you know software architecture is about structuring your overall system.
If you structure your system around functional or data concerns, you miss a critical opportunity to focus on the non-functional aspects of your design, such as scalability and maintainability.
The core problem here is that an active or domain-centric approach to architecture considers only one of the many dimensions of software: the functional dimension. When you focus on one dimension alone, you end up with an architecture that is hard to extend and maintain because it relies on brittle abstractions.
You need to step back and take a broader view.
While it’s clear that design and construction are different, most developers jump into the code because it’s what they know. It’s rare to find skilled software architects that can make this distinction and boost your chances of success.
Your system design is a leading driver of project success, so don’t get lost in the weeds too early.
You first need a top-down design perspective, focusing on your design’s value instead of how you will implement an app.
Design first for your -ilities: maintainability, scalability, extensibility, and more.
Focus on using encapsulation to make functional and domain modeling flexible because that’s where your clinical trial system will change.
“The hardest part of design… is keeping features out.” –Donald A. Norman
It takes more than agile processes to keep software from going wrong. You must also build agile systems that grow, support, and adapt.
As we’ve discussed, this means designing to contain the blast radius of change so that you can move faster, for longer, and with less risk.
While your systems should always be domain-informed and use-case-driven, you should always design your software structure around change.
Please don’t take my word for it; let’s stand on the shoulders of three software design giants to get a high-level perspective across software architecture history.
“We have tried to demonstrate by these examples that it is almost always incorrect to begin the decomposition of a system into modules on the basis of a flowchart. We propose instead that one begins with a list of difficult design decisions or design decisions which are likely to change. Each module is then designed to hide such a decision from the others. Since, in most cases, design decisions transcend time of execution, modules will not correspond to steps in the processing. To achieve an efficient implementation we must abandon the assumption that a module is one or more subroutines, and instead allow subroutines and programs to be assembled collections of code from various modules.” – D.L. Parnas - On the Criteria To Be Used in Decomposing Systems - 1971
“Dependency Management is an issue that most of us have faced. Whenever we bring up on our screens a nasty batch of tangled legacy code, we are experiencing the results of poor dependency management. Poor dependency managment leads to code that is hard to change, fragile, and non-reusable. Indeed, I talk about several different design smells in the PPP book, all relating to dependency management. On the other hand, when dependencies are well managed, the code remains flexible, robust, and reusable. So dependency management, and therefore these principles, are at the foudation of the -ilities that software developers desire.”
“The next six principles are about packages. In this context a package is a binary deliverable like a .jar file, or a dll as opposed to a namespace like a java package or a C++ namespace.
The first three package principles are about package cohesion,they tell us what to put inside packages:
- REP The Release Reuse Equivalency Principle: The granule of reuse is the granule of release.
- CCP The Common Closure Principle : Classes that change together are packaged together.
- CRP The Common Reuse Principle: Classes that are used together are packaged together.
The last three principles are about the couplings between packages, and talk about metrics that evaluate the package structure of a system.
- ADP The Acyclic Dependencies Principle: The dependency graph of packages must have no cycles.
- SDP The Stable Dependencies Principle: Depend in the direction of stability.
- SAP The Stable Abstractions Principle: _Abstractness increases with stability.” –Robert Martin on his 2002 book Agile Software Development, Principles, Patterns, and Practices
“Decompose based on volilatility.
Volatility-based decomposition identifies areas of potential change and encapsulates these into services or system building blocks. You then implement the required behavior as the interaction between the encapsulated areas of volatility.
The motivation for volatility-based decomposition is simplicity itself: any change is encapsulated, containing the effect on the system.” – Juval Lowy - Righting Software - 2019
Let’s also take our design thinking to the next level by focusing on making change easier.
To get there, let’s think through the expected ways systems change: orchestration, integration, and calculation.
Orchestration is the coordination of multiple microservices to accomplish a task. It’s an essential part of designing any modular system, like microservices, because it lets you assemble applications instead of having everything in the same place.
Orchestration can take many forms: Sometimes, it may be as simple as calling an API to get the data you need, like patient groups. In others, it may involve a complex series of steps to open a new clinical trial in your database.
Think of it this way: You don’t have to know the inner workings of an internal combustion engine to use a car to drive to the doctor’s office. When you build a modular application, orchestration lets you accomplish tasks using other services without knowing the details in the same way.
A typical pattern is to have an orchestrator service to coordinate your other microservices.
For example, once you learn how to drive a stick shift, you can drive any stick shift. In the same way, if all your service contracts (encapsulation boundaries) are well-designed, they can change without affecting your orchestration code.
With explicit orchestration, it’s easy to update each service separately while keeping the changes’ side effects in check so that everything works well together.
Integration is a core microservice activity.
You could look at it from a functional perspective and think of integration services as a repository pattern. But that means we’ve slipped back to domain-centric or activity-based thinking when we need a higher-order design perspective.
And we covered why you need to avoid these distractions and keep the design goals of your architecture in mind.
Integration differs from orchestration. Instead of creating a system that encapsulates workflow across multiple components, integration focuses on encapsulating changes from external systems.
The point of encapsulating integrations is to protect the service you’re using from changes that happen outside of it. For example, say you need to refactor your database because too many trial measurements are killing database performance. You want to ensure the workflow continues running when you normalize and denormalize your data storage to improve performance.
Use integration encapsulation to protect your other service implementations from external changes.
When we think of microservices, we usually envision the orchestration and integration activities. We do this because they are the most obvious.
But the idea is to encapsulate change. And multiple use cases often hit the same code. For example, no matter your clinical trial use case, you must always follow HIPAA regulations.
And if HIPAA changes, you want one and only one place to change, or your code base will quickly decay, becoming indeterminate, buggy, and useless with just a few changes.
This is where calculation services come in: when you want to encapsulate the volatility of a reusable building block of business code.
Only create calculation encapsulations if you need to encapsulate changes. Even if this means most of your business logic will live in your orchestrations.
The vast majority of clinical trial systems are about orchestration. So you’ll see very few calculation services in comparison. However, the existing ones are usually critical, as with our HIPAA compliance calculations.
In contrast, there is less fanfare regarding calculation activities: developers often focus on building back-end processes that run calculations parallel with other services or external resources (such as databases).
Stay focused on the aim of your architecture. And don’t get tripped up by implementation-detail traps.
It takes planning to establish a robust framework for building and managing modular systems, like microservices, that will last.
We discussed three core activities you can use as a design taxonomy for your building blocks. This will help you design a system that will meet your objectives. Thinking through future change is helpful when planning your service boundaries, contracts, and SDKs.
This idea is not new. It comes directly from Juval Lowy’s brilliant service taxonomy of Managers, Engines, and Access components used in successful software designs for decades.
Volatility-based decomposition is a powerful way to think about services and their role in your system. It also provides insight into how you can decompose your system into microservices.
Is your goal to build a system that can scale, adapt to change, and provide value to your users while resisting decay?
One aim of microservices is to make services easier to replace or update. When you have to change all of your services when you change one, you don’t get the value (but you get all the headaches).
Instead, your architecture needs to build in shock absorbers to limit the impact of changes. You need to pack things that change together. You need a deliberate design focused on controlling the effects of change through encapsulation.
Preparing for change is a great way to think about microservice design. It helps you design how parts work together and helps you coordinate activities.
This point of view also tells you when to use orchestration instead of calculation or integration to get the most out of your application and help your clinical trial systems and the patients they serve live long and healthy lives.
Best wishes and warm regards — Matt
By the way…
Are you interested in ensuring scalability, maintainability, and quality in your .NET clinical trial software?
Want to plan for the ways your clinical trial system will grow?
Our open-source .NET microservice foundation focuses on facilitating future change through orchestrations, calculations, and integrations in clinical trial software.
Applinate helps you avoid common mistakes during development, make testing more straightforward, and make good design decisions early on in the lifecycle of your product. Applinate can also provide a path forward to correct yesterday’s design decisions causing today’s headaches.
In addition, Truth Shield provides software design services to help you get the most out of our tool set and software consulting services to help you deploy a long-term solution on budget, on time, and on quality.
Want to get started on improving your clinical trial’s software architecture today? Click here now!