By David Dobbs
Case for next generation V2 API
Like many companies, Mosaic built the initial version of its software as a monolith. We wanted to offer APIs to our partners, so we exposed endpoints in the monolith based on existing controller paths. Several big partners integrated with the API and it unlocked new opportunities. Eventually some of our partners began to ask for new features and capabilities, and we found that we were unable to fulfill those requests because the back end code was too difficult to change in any meaningful way. As we witnessed sluggishness in our API partner growth, we knew we needed a new API, and the journey of “V2 API” began.
For authentication, we wanted to move away from our bespoke system and toward a solution that was robust, configurable and allowed for smooth integration with our new and legacy systems. Auth0 had a very good reputation, and several team members had used it in the past with success. We configured two types of “applications” within Auth0: installers and aggregators. Installers were configured as single entities, in which requests were made on behalf of their installer company. The machine-to-machine tokens held information about a specific installer company that had a reference into our datastore. Aggregators were configured as SaaS providers making requests into our system on behalf of many installer companies that are part of their network. Requests made by aggregators required the relevant partner identifier to be passed in the JSON payload, and the combination of aggregator code passed in the token and partner identifier would be validated in our system. Our SaaS partners only need to be aware of their aggregator Auth0 credentials to make requests on behalf of all of their partners
Regarding Gateways, we considered Nginx, Kong and AWS API Gateway. We ultimately chose Nginx based on the following: 1) it’s widely used and proven; 2) we were already using it in other areas of our architecture, and 3) we knew we could configure it to integrate nicely with Auth0. Behind our public gateway, there were new microservices and several legacy systems. The new microservices had two deployed environments (nonprod and prod), but the legacy systems had anywhere from three to five environments. This presented a routing challenge both for external calls and internal calls between systems. We decided to build integration gateway services that would route http traffic (HIG) and asynchronous message traffic (MIG). By doing this, the public gateway was only responsible for validating JWTs and then passing the request to the integration gateway, which maintained a mapping of public URLs to internal systems URLs.
A “usage context” is something like “demo” or “test” or “prod” and exists as a need of the business, not an implementation detail arising from how we choose to deploy our software. For example, whether we choose to deploy demo and test environments separately or as a single deployment, the business need to demo and to test still exists. Those needs put differing requirements on the accounts, data, etc., associated with those usage contexts.
Our goal was to permit microservices, monolith and web to work together in common usage contexts, even when the number of physical deployments used to embody those contexts differ across projects.
The key to achieving this objective is to:
- “tag” resources and events with their usage context
- route messages to the appropriate physical deployment based on that usage context tag
For example, if a microservice is processing a request in a demo usage context, then any events arising from that processing that need to be consumed by monolith or web must be routed to the appropriate physical deployment that handles the demo usage context.
For API V2 workflows, interactions between the different systems take the form of HTTP requests and kafka messaging.
All HTTP requests made by services within a web (graphql layer), monolith, microservices environments will be made to usage context-specific endpoints like demo.api-url/resource. Responsibility for directing a usage context-specific request to the appropriate environment is delegated to an internal HTTP Gateway (nginx instance).
All Kafka messages from web, monolith, microservices environments will be sent to the usual topics in their dedicated local Kafka clusters. Responsibility for directing those messages to the appropriate microservice environment is delegated to an internal message gateway residing in the microservice environment.
All Kafka messages sent by microservices will be sent with a Kafka header indicating the usage context to the usual topics in a Kafka cluster dedicated to that microservice environment. Responsibility for directing those messages to the appropriate web or monolith environment is delegated to the message gateway.
These flows are illustrated in the diagrams below:
As the infrastructure was being stood up, one of our equipment distributors wanted us to expose some endpoints so they could post equipment status updates for particular solar installations. We recognized this as an opportunity to try out our new infrastructure. We configured the application in Auth0 and set up routes in the Nginx gateway. There was no microservice built for this use case so we exposed an internal endpoint on our monolith that was configured in the HIG (See above). Everything worked as expected and our first V2 API endpoint was live in production within a few weeks.
As we embarked on V2 API, we were about a year into an effort to introduce microservices into our ecosystem. We had partnered with a company called Lightbend, and we established a reference asynchronous architecture around Lagom (Scala and Java), Akka, Kafka and Kubernetes. Our vision for the V2 API was to expose new endpoints on new microservices. There were certain calls that had dependencies on external systems, so we decided those POST calls would return a 202 with a UUID. The callers would need to pass the UUID in GET calls to get the eventual status of the original post. As a result, our response times are very low and our threads do not hang. Lagom has the concept of a journal which stores all inserts and updates to a particular resource. Exposing “journal data” in endpoints is accomplished by populating read side tables at run time. Thus, we ventured into the world of CQRS. The team was eager to apply this technology, and once we got through the learning curve, we are reaping the benefits of high performance reads on our data. We have recently focused on updating our Gitr8 templates and now it is painless to spin up a service that has the required Lagom, GitHub, AWS, and Kubernetes infrastructure out of the box.
Read more about our next generation API journey in part 2 of this blog.