Delivering Better Software, Faster
by Eko Prasetya and Hunter Fernandes
This philosophy drives everything we do, including an approach to engineering that differentiates us from the disparate point solutions on the market. Those solutions, designed to address one specific problem, often become dormant over time due to their static architectures, infrequent updates and limited functionality.
Thanks to our own ongoing iterations and opportunities for user customization, our platform’s potential is limitless. And it’s all thanks to the thoughtful and rigorous release process that we have honed over five years.
As any software engineer will tell you, iteration brings risk. Every new release, major or minor, has the potential to bring everything crashing down if it hasn’t been thoroughly vetted.
So, how do you regularly deliver high-quality features without sacrificing the current user experience? Below, we invite you behind the scenes to take a peek at our code-to-production pipeline that maximizes impact and speed while minimizing risk.
1. We collaborate on new code to add/change functionality.
Successful development doesn’t start with the first few lines of written code—it starts with establishing the right environment for writing that code.
We’ve opted for an opinionated baseline developer experience at Carium, which means all of our projects are uniformly organized and use the same tools. This structured, well-defined ecosystem simplifies our process so engineers can focus on solving the problem instead of solving the problems around the problem.
This opinionated experience also allows developers to work on virtually any project at any stage of development, enabling them to easily shift to new priorities and write new code with speed and confidence. Even new engineers can make an immediate impact.
2. The code passes a comprehensive series of unit tests.
All new code must first pass a comprehensive set of unit tests—small, specific tests of expected functionality. These tests confirm that:
New code is working as expected
Old code is not regressing
Performance has not been diminished
Any change in the codebase triggers corresponding unit test changes. No matter how many changes we have to make, our minimum required test coverage ensures that testing remains rigorous and code quality continues to improve.
3. Peers conduct code reviews.
Next, the code is peer-reviewed by engineers experienced with the codebase. Their job is to ask critical questions of the code: Will it achieve the desired goal? Is it maintainable? How does it fit into the existing system? Will it affect other systems?
These code reviews don’t just help the code get better—they help our engineers get better too. The process familiarizes reviewers with best practices and design/implementation issues, a fundamental career growth and knowledge-sharing opportunity for our team.
Once all reviewers have approved, changes are merged into the main code branch (“main”) and versioning is automatically applied and tagged to the codebase.
4. The code is deployed in a staging environment.
Our CI pipeline then builds an immutable container that acts as the service deliverable to be deployed to our staging cluster, a nearly perfect replica of our production environment. In this staging environment, the code interfaces with data stores and with other microservices that talk to each other using the same REST API that we make publicly available.
The staging deployment is our first opportunity to confirm quality against other services and databases and ensure our ability to perform successful releases.
5. The code is put through rigorous system tests.
Next, we run a battery of system tests to simulate typical user load patterns in a controlled environment, exercising the user interface, API interface and custom solution code.
System tests are driven by both our test and development engineers. This joint responsibility encourages the team to collaborate on products, environments and methodologies that are easy to test, develop and maintain. The result is a robust test selection mechanism that any member of our team can simplify or modify it as needed
These tests are the most rigorous yet because everything is tested together. Success tells us we can move quickly to production release with the confidence we won’t break anything.
6. The code is approved for production deployment.
Production release is when we tolerate the least amount of risk—safety, consistency, stability and confidence are key. To that end, we have a large “Release Playbook” that outlines how to execute a production release with the least risk.
Once we have consensus from engineering and product management that the product is in a releasable state, we take a database snapshot and perform other disaster recovery preparedness tasks. Then, we perform online database migrations and progressively roll out new versions of our microservices and updated user interfaces. Once complete, we destroy or clean up old versions of microservices and begin performing production system tests.
Finally, we celebrate releases with a Slack message in our engineering channel. Shipping code to customers and fixing bugs is something worth celebrating!
7. Feature flags allow us to expand releases slowly.
The entire release process is performed while we remain online—no downtime or outages. If everything goes as planned, you’ll never know we updated Carium while you were using it.
That’s because we need to keep our platform running continuously and at scale. Carium plays a vital daily role in patients’ healthcare journeys, and our customers rely on us to drive the care experience they provide.
We don’t think it’s acceptable to disrupt these critical workflows for features that aren’t done. To further minimize that risk, we keep features gated behind feature flags during development. If we’re working on something new, you won’t see it in the product until it’s done. These flags allow us to protect the default experience and prevent disruption while we validate feature functionalities in the production environment.
Each customer has feature flags that control their workflow and overall experience, allowing us to turn features on or off for customers at the organizational or user level. They can opt into these experimental features, and they can even provide crucial feedback that leads to better software for all users.
Our customers help Carium evolve.
Our process is designed to minimize the inherent risk in delivering innovative new features as quickly as possible. Faster, more frequent releases put these features in the hands of our customers sooner, accelerating the feedback loop that plays such a vital role in Carium’s evolution. As a result, we are able to develop features with our customers, not just for them.
Speaking of developing with our customers, we have announced two exciting customization enhancements, Care Pathways and the upcoming Carium for Developers, that will put Carium's evolutionary capabilities in their hands.
That’s been our mission at Carium from day one. We’ll address your immediate needs, then we'll help you realize Carium's full potential for simplifying your tech stack and improving the care experience for providers and patients alike.
Those are just two ways a comprehensive virtual care platform can drive tangible ROI for your organization. Here are the other business drivers to consider when making strategic technology decisions.