Best Design Pattern for Azure Cosmos DB Containers — Factory Pattern
Discussing how to build a powerful factory to work with Cosmos DB containers and encapsulate the complex logic with Cosmos DB .NET SDK in Clean Architecture using ASP.NET Core.
What is Factory Pattern? It is a design pattern in OOP that allows us to encapsulate object creation logic and hide its complexity from the calling program, such as API, Function Apps, and even users. This probably sounds very vague. My personal take-away note is that factory pattern allows me to ask the factory:
- to make me a car, then I will get a car
- to make me a train, then I will get a train
- to make me a universe, then I will get a universe
I do not have to worry about how to make a car/train/universe throughout the whole process, which successfully decouples the “how-to” from me, the calling program. As is in the image above, I only need to know the factory produces Cosmos DB containers, but do not have to know what’s inside the factory.
Complexity with Cosmos DB .NET SDK. When working with Cosmos DB .NET SDK, you will need the following items:
- NuGet packages like Microsoft.Azure.Cosmos
- Cosmos DB Endpoint, Primary Key
- Cosmos DB Account, Databases, Containers
Below is a copy of the sample code from Microsoft documentation Quickstart: Build a .NET console app to manage Azure Cosmos DB SQL API resources. What is wrong?
- Notice my application program now MUST understand how Cosmos DB SDK is configured in order to interact with Cosmos DB. Using the example above, this is requiring me to know how to manufacture a car in order to go to a shop to buy a car. TOO MUCH expectation on me. If my brain farts, the whole workflow crashes.
- Notice it takes many private variables to get connected to Cosmos DB in order to get started.
- Notice this only works for one single container, Family Container. If additional containers are required, more code is required.
Can we improve this? YES! Technically, our program should NOT have to know how Cosmos DB is configured. If all we need is for the program to tell us to,
- give it a car container, then the program gets a car container
- give it a train container, then the program gets a train container
The program takes the container and continues with the business logic of traveling around the world with the car/train. We, through factory pattern, takes car of how the car container is configured and setup in the background.
The goal of this article is to implement factory pattern and create a powerful factory to serve Cosmos DB containers, but hide the complex process and logic away from the calling programs, such as API, Function Apps, etc.. This also allows the factory to be shared among different clients. If any update is required for the factory (e.g. version upgrade), we have a single place to update the factory code!
Step 1 — let’s abstract out the requirements for our factory with ICosmosDbContainerFactory.cs. Notice our factory interface has one single responsibility defined, GetContainer, which is expected to return a container wrapper given the container name. This is similar to our original metaphor at the beginning of the article, “the factory should return a car when a car is asked for”.
Step 2 — let’s look at the ICosmosDbContainer returned by GetContainer above. ICosmosDbContainer is just a wrapper interface for Azure Cosmos DB Container. It has a property _container of type Cosmos DB Container. _container is the bread and butter that allows the calling program (API/Function Apps etc.) to interact with Cosmos DB.
Step 3 — let’s implement the factory interface. This is where the complex process of configuring Cosmos DB happens. A few personal notes:
- the private property _cosmosClient is of type CosmosClient, which is provided by Cosmos DB .NET SDK.
- ContainerInfo is just a custom type class we created to track container name and container partition key property.
- A new instance of CosmosDbContainer class is returned. We will review this class in the next step.
- If a requested container does not exist, we just throw an exception and let the calling program (API/Function Apps etc.) handle exceptions.
- Throwing an exception here instead of handling it is quite important here, as it allows our factory to serve one single purpose and not get bogged down with business logic on how exceptions should be handled. In fact, different calling programs may choose to handle exceptions in a totally different manner. E.g., an API client may choose to just log the exception while a Console App client may choose to automatically create a non-existing container. If this is of interest to you, I recently published an article on how exceptions can be handled in a centralized approach in Clean Architecture.
Step 4 — let’s implement the ICosmosDbContainer wrapper above with CosmosDbContainer. This is very straightforward as we only need to define a constructor for the wrapper.
Step 5 — let’s put our factory to work! We will use a repository class, CosmosDbRepository.cs, to request a container from our factory, and use the container to perform basic CRUD operations.
Please note that using repository pattern is NOT required though. You should be able to call the factory however you would like! The factory itself does not really have any restrictions on how and where its requests for containers come from.
You probably noticed that the CosmosDbRepository<T> is an abstract class. This allows us to have many entity repositories to inherit from it and not have to re-implement the CRUD operations again. E.g., ToDoItemRepository can point to “Todo” container, ToDoItemCategoryRepository can point to “Category” container, UserRepository can point to “User” container. All these three entity-specific repositories have to do is to specific their container names. For example, ToDoItemRepository class below inherits the abstract CosmosDbRepository class above and only needs to override the container name property. ToDoItemRepository also implements IToDoItemRepository interface to support dependency injection, which is outside of the scope of this article. If you would like, I published another article on Partitioned Repository Pattern with Cosmos DB, which covers repositories in greater details.
Step 6 — See it used in ASP.NET Core REST API!
In order to use our factory in the API project, let’s first create an extension method to register a singleton instance of the CosmosDbContainerFactory with the dependency injection container.
- We want to build an extension method so that the calling programs like API and Function Apps can all call the same extension method, AddCosmosDb().
- We need to register a singleton instance of the Cosmos Client, because according to Microsoft documentation, “CosmosClient is thread-safe. Its recommended to maintain a single instance of CosmosClient per lifetime of the application which enables efficient connection management and performance”.
Once we have the extension method, we can register the services in the Startup.ConfigureServices method for ASP.NET Core API with the following code.
CosmosDbSettings is just a strongly-typed configuration class in order to read configuration values from appsettings.
For example, the following JSON section defines the Cosmos DB endpoint URL, primary key for connection, database name, and containers meta data like name and partition key property path.
Below is a create handler within an API project that creates a Todo item in Cosmos DB (see line 31). Notice since we are using repository, the complex logic of how to work with Cosmos DB containers is FULLY decoupled from the API. In fact, the API project does not even have to know that Cosmos DB is used at all!
Our API controller action is even simpler, it handles nothing but routing! No business logic, no complex process of registering Cosmos DB!
Congratulations! We have successfully implemented factory pattern to work with Cosmos DB containers. In our example, we registered a singleton factory instance, which is responsible to take the calling program’s requests for containers and return exactly what the client needs. What does the calling program needs to know? Nothing about how the containers are made! When we need to update the factory, we have one centralized place to update and do not need to update any calling programs like API or Function Apps. Sweet!
The sample code used in this article is from a GitHub starter project, which follows Clean Architecture to organize the projects. This repo features Partitioned Repository Pattern using Azure Cosmos DB to build scalable backend service, REST API, Azure Functions, React SPA, etc.. Feel free to use the whole starter project or part of it to kick start your next exciting adventure!
For more resources relevant to the project:
- GitHub repo: Clean Architecture with partitioned repository pattern using Azure Cosmos DB
- Article: Clean Architecture — ASP.NET Core API using Partitioned Repository Pattern and Azure Cosmos DB
- Article: Audit Log Using Partitioned Repository Pattern With Cosmos DB
- Article: Clean Architecture — Azure Functions Using Partitioned Repository with Cosmos DB
- Article: Best Exception Handling with Consistent Responses in ASP.NET Core API