Introduction
The CorLife platform delivers clinical coaching via a mobile application to improve the wellbeing of the employees of our clients using clinically proven methods.
The mobile application integrates with wearable devices and smart scales from Garmin to collect biometric data from participants. Participants answer several questions about their medical history and behaviour as part of their onboarding journey.
The data collected from Garmin smart devices and a participant’s history is then used to build their wellbeing profile. The CorLife platform uses this profile to tailor coaching in the form of goals and activities for each participant. This coaching is delivered primarily through the CorLife mobile application supported by clinicians in the form of doctors, nutritionists and dietitians where required.
Guiding principles
We set out some guiding principles to help the team make good choices about how and what we choose to build the CorLife platform. The platform was being developed from a completely fresh perspective with an empty code repository, no build pipelines, or environments.
We are a start-up
Like any typical start-up, at the beginning of a build the team is small, and some technical skills are unavailable. Despite this, we still needed to ship high quality code as quickly as possible and support the general operation of the platform.
For the CorLife team, there was a realisation early on that there would be a heavier workload building backend services and creating build chains. What could be done to ensure we were as efficient as possible when writing and deploying code? How could work be better balanced across the development team in terms of frontend vs. backend work?
Architecture principles
We wanted to lay down some guiding architectural principles. We made some early decisions to follow Domain Driven Design and a microservice architecture. The principles are outlined in here.
The entire concept of how the CorLife platform responds to inputs from a myriad of smart devices and participants led us to opt for an Event Driven Architecture (CQRS pattern) We based our implementation on the concepts outlined in the following Microsoft article: Domain events: design and implementation
We wanted developers to be writing code and generating value
Another strategic decision we made at the beginning of our build was to ensure that the team were not wasting time on tasks that could possibly be avoided, such as supporting and maintaining infrastructure, unless this was absolutely necessary.
Automate everything
Wherever possible we wanted to automate everything from builds to testing. An example of this was our selection of Terraform to allow us to codify the deployment and configuration of all our underlying infrastructure and services.
A “green field” build
Based on our guiding principles we decided on the following initial technology options to build the CorLife platform.
Azure Functions
We used Azure Functions as this was the serverless offering on Azure. It seemed a good option from a cost perspective while we were learning and building some of the early services, as the consumption plan provides a free grant every month. More information on Azure Functions pricing can be found here: Azure functions pricing
As a start-up, we wanted our developers to be writing production code and not maintaining infrastructure. Microsoft manages the infrastructure for Azure Functions, and we get scalability out of the box.
To support our Event Driven architecture principle, we already knew that we would be using other Azure services such as Event Hubs and Service Bus. We found Azure Functions already had the relevant bindings available. This made using these services with Azure Functions easier for the developers.
React and React Native
We identified the need to build two web-based portals for CorLife staff and a mobile application (Android / iOS) for CorLife participants.
We chose React and React Native for the following reasons:
- A single codebase for both iOS and Android.
- The ability to share code between mobile and web applications.
- As a start-up, we did not want to hire native app developers.
- Frontend developers who know React could help with React Native work.
- Large and highly active community for support.
We also made a choice to adopt TypeScript to reduce the type safety issues with JavaScript. This choice has the benefit of reducing bugs (as reported by AirBnB) and was deemed to be attractive for hiring new developers. It is also marked as “Adopt” in the Thoughtworks Tech Radar report which you can find here.
Azure API Management as a Backend For Frontend (BFF)
Our intention was to use Azure API Management services as a Backend For Frontend (BFF). More information about BFFs can be found in the following link: Pattern: Backends For Frontends
The idea being that a BFF would provide a nice separation between Frontend and Backend development with pre-agreed contracts. The decision to use Azure API Management was made even though we knew we would require a separate instance of Azure API Management acting as a BFF for each of our frontends, Clinician, Administrator and Mobile. We did consider GraphQL at this early stage but chose not to proceed with it.
Figure 1 is a diagram of this initial architectural approach.
Trouble ahead…
As we started to develop the initial building blocks of the CorLife platform we began to experience some challenges when developing and consuming APIs.
These included:
- Frontend developers found themselves waiting for backend developers to complete “their work”.
- Changes to APIs led to breaking changes sometimes due to poor communication regarding contract changes.
- Web and mobile clients did not know what data they might receive and were in some cases receiving redundant data. This meant extra work when doing the frontend development.
- Using Azure API Management to serve APIs required developers building the services to configure and deploy changes.
- The team had very little knowledge and experience with the XML policies needed to configure the Azure API Management layer.
- There was no straightforward way of writing tests for these XML policies.
Why we chose GraphQL?
We were fortunate to have some GraphQL experience in the team. We started to explore the feasibility of adding this to our platform and selected Apollo GraphQL.
The objective was to solve many of the issues stated above and to also:
- Empower frontend developers.
- Allow a better division of work within the team by providing data via an appropriate API and then allowing consumption using GraphQL.
Lessons learned adopting and using GraphQL
As a start-up we had some concerns that any problems deploying and supporting Apollo GraphQL would result in the team being occupied troubleshooting rather than writing value generating code.
Our chosen approach:
- We did some investigation work (a spike) to explore how we might deploy GraphQL into our environments.
- We started small and focused on delivering a single outcome (of value) using GraphQL.
We used the following resources to help with this investigation:
Ironically, we spent more time talking about GraphQL than actually getting it deployed, running, and utilized in the CorLife platform.
Deployment learning
We deferred the decision to adopt Apollo GraphQL several times, worried about the risks. This delay meant we were accruing technical debt, such as building against APIs where the implementation would need to be changed once we adopted Apollo GraphQL.
When we finally deployed Apollo GraphQL, it was straightforward except for one issue that Microsoft Support helped us with. Information about the issue can be found here
Coding challenges
A major adoption obstacle we experienced was a lack of an agreed strategy regarding what we would use GraphQL for versus the API. After a few months, we agreed on some rules as this was causing confusion in the team.
We used the following simple rules:
- GraphQL should be used to “stitch” data together from single or multiple APIs.
- GraphQL should not be used as a data layer, for example for heavy aggregation of data.
We used this article for guidance: GraphQL is not OData
Once we started to use GraphQL we were able to remove Redux boilerplate and state management code and replace this with Apollo caching.
Testing
Writing unit tests was straightforward using the Apollo MockedProvider on the client side and the test client on the server side.
More information can be found in the following links:
- Apollo GraphQL – Testing React components Using MockedProvider and associated APIs
- Integration testing – Utilities for testing Apollo Server
Azure Functions and “Cold Starts”
We suggest you upgrade the plan for your Azure Functions to Premium as we experienced some issues with “cold starts”
Cold starts caused:
- Failures and delays in our automated testing.
- Unpredictable increases in response times when an Azure Function was warming up resulting in a poor user experience.
More information on cold starts can be found in the following article: Understanding serverless cold start
Authorization and Authentication
Our approach for authentication and authorization was simple with respect to GraphQL: we passed tokens to Azure API Management. It was the responsibility of the policies in Azure API Management to validate the tokens and verify any claims.
Key takeaways
Our adoption of GraphQL has been straightforward and has achieved many of the objectives we sought. Figure 2 below shows our final state architecture.
Statistics
In the last four months, we have used GraphQL to serve the following number of queries with many more lined up as we continue to build new capabilities in the CorLife frontend platforms.
Platform | Queries served by GraphQL |
---|---|
Mobile application | 10 |
Admin web portal | 3 |
Clinician web portal | 4 |
Recommendations
Here is a list of recommendations from the CorLife team that will help make your adoption of GraphQL a smooth one.
- Consider doing a spike to gauge how difficult it might be it deploy Apollo GraphQL in your solution landscape.
- Agree what you are going to use GraphQL for and what the API will provide.
- Start by migrating something small to GraphQL.
- Choose to implement a feature which provides your user with real value to experience the benefits of GraphQL.
- If you choose to use Azure Functions to host GraphQL, upgrade your plan to Premium to avoid issues with cold starts.
- Use any provided tooling to help you develop, monitor, and optimize your usage of GraphQL. We used Apollo Graph Manager to monitor request rate, latency, and errors.
We hope that you found this article useful and that it helps you bring the benefits of Azure Functions and GraphQL to your React and React Native applications.
0 comments