Solving The Cloud Native Microservice Autonomy Problem: Implementing Search With Cosmos DB Graph and Azure Search (Part 2)
Continuing from where we last left off in part 1 of this autonomy problem, we had Jake and Ella trying to figure out how to implement microservices in the cloud. Specifically, how were they going to deliver search functionality in a microservice architecture when the data is isolated from each other? How were they going to keep the microservices autonomous?
As a reminder, we learned that a microservice boundary is primarily a logical one. It doesn’t mean that physical isolation doesn’t matter! What it means is that the code we create should have this logical separation clearly represented.
We left the conversation between Ella and Jake right when Ella was introducing this idea of Azure Search service. Let’s listen in on Ella and Jake again and see if they what they decide.
[Enter Ella and Jake on the scene]
Ella: I've done a preliminary evaluation of Azure's Search service before coming to you, and I think it will allow us to meet the UX requirements on search response performance.
Jake: Does Azure Search allow us to create separate accounts for each microservice? I know we’re new to microservice design, but the one thing I think we don’t want to do is violate that boundary. If we end up combining the data together from each service, we’re going to end up violating that, plus we’re creating a single point of failure.
Ella: I understand what you’re saying, so let’s think about it for a minute. While Azure Search does let you create separate accounts, given that we’ve got ten more microservices that will probably end up with data in the main search of the UI, that’s going to run the cost through the roof.
“It always comes down to how much something costs, doesn’t it?” Jake shrugs.
“Well, hear me out. Azure Search offers a tier that we can do about 15 indexes. That will allow for us to get all existing and planned microservices in there and still allow for some head room if needed. If we have a single account, we can easily support the number of documents right now.” Ella replies.
Jake: OK, but what about coupling? We end up with a single search service, don’t we?
Ella: I think we’re ok. Logically, we’re keeping the microservices’ data separated. The reason why I think that, is because nothing but that particular microservice is permitted to do anything with its data in Azure Search.
Jake: That makes sense. So Azure Search becomes our backend composition point for the microservices. Then we can support a single HTTP endpoint for what the frontend needs.
Ella: Right. We may end up with a few different indexes depending on the model, but the search service have criteria entered, then present the results, and a user can take action.
Jake: You said something interesting right there. Maybe what we’re saying now is search is really its own thing. It’s a microservice, I think?
Ella: Why would that be?
Jake: Well, for one, I realize we’re using different language. Remember what we read from Eric Evans’s Domain Driven Driven design? You used “criteria” and “results”, and not only that, we’re suddenly talking about a model now.
Ella: Cool. I agree. Are you good with taking the next hour and designing a draft solution out?
Jake: Sounds good to me.
[They head to a huddle room where they create a diagram of the solution.]
After referencing documentation on Azure Cosmos DB and Azure Search, Ella and Jake came up with the following physical view for each microservice to be able to reference.
Ella and Jake are satisfied they maintained the integrity of the microservice design, and were able to leverage features of Azure to fulfill the requirements of more than one microservice.
Understanding the Design
Cosmos DB accounts
Ella and Jake realized that having multiple Cosmos DB accounts wasn't necessary. In the world of the cloud, the logical divisions become important and SLA should be considered when determining how isolation needs are met. The 99.99% SLA of Cosmos DB was more than sufficient in their case.
In their write up, they recommended to consolidate to a single account with each microservice getting their own database, after reading more closely how Cosmos DB is designed.
Data movement
First, the data movement path was from Cosmos DB to Search. It was accomplished through two options or a combination of the two.
Option 1. The first option was by setting up a data source and indexer to use Cosmos DB database as a source for an index. The indexer can be scheduled to craw the database and update the documents in the Search index.
Option 2. The second option was using Azure Functions Cosmos DB trigger capability to update an indexed document, reactively. This solved a problem they were facing with efficiently handling a requirement for data to be up to date as possible for one of their microservices.
They would do this by creating a Function that would be triggered by a Cosmos DB event. They used this moment to create an anti-corruption layer to map from the source model to the search model, and then pushed the changes to the document(s) to Search using the Azure Search SDK.
Azure Search - An implementation detail
While Azure Search met their needs, they wanted to prevent direct coupling to Azure Search by their codebase. So the Angular service would make an HTTP call to the API Gateway where a search API was defined using OpenAPI specification. Using API Management, the request would be translated to a backend request to Azure Search.
The "Best Practices" Architecture Improved
Fix #1. The fictional account of the two software developers was used to demonstrate that "best practices" can become more damaging if followed strictly. A better approach is to form or document the design principles that you're going to use. "Best practices" infers "always the right choice", and in many cases, it is not.
Fix #2. With microservices, isolation doesn't always mean separate service instances. In this case it was Cosmos DB.
Fix #3. Search is often another microservice. This means that in addition to the other microservices, it must be represented from the frontend codebase all the way through to the backend, having its own boundary of both logic and data.
Fix #4. A microservice isn't a REST API. It isn't a running Docker image in a container. A microservice may extend past a process boundary. And most often does if you have a UI.
Fix #5. A microservice is primarily logical. Deploying a microservice codebase alongside another, even in the same process is completely acceptable. It all depends on the scalability, reliability, and availability the business requires.
Fix #6. Maintain strict denial of synchronous communication between microservices. In the scenario this article covers, the Search microservice (as it emerged) never calls exposed HTTP endpoints of the other two services in question.
Wrapping it up
Microservices in a cloud world will sometimes make you rethink at what level you create resources for those services to consume. Cosmos DB, for example, can legitimately be used with one account and more than one database, and maintain data isolation plus high availability.