Application architectures have evolved very quickly during the latest few years. The classic monolithic architecture has been broken down into a collection of microservices to support a more dynamic development and deployment infrastructure. However, albeit its popularity, there are certain downsides to using a microservice architecture. Recently, a more granular breakdown of a distributed application components is becoming popular: nanoservices. Nanoservices aren’t a replacement for microservices but are adept at addressing some of their shortcomings, and they can provide better isolation and granularity.
This article will introduce the nanoservice architecture and show how to create them with ASP.NET and Azure Functions.
Table of Contents
From Monolithic to Nanoservice Architecture
A monolithic architecture comprises a collection of components that are built, deployed, scaled, and maintained as a single unit. Albeit the simplicity, it is extremely difficult to change, scale or maintain such applications. A monolithic application typically uses a homogenous technology platform. Building a monolith with heterogeneous technologies is extremely difficult.
Over a while, a monolith can become highly complex to handle: operational agility, scalability, and maintainability can become a challenge. Microservice architecture evolved to address the shortcomings of monolithic architectures over the past few years. Microservices adoption has been on the rise primarily because of the quest for improved scalability, flexibility, and performance.
What is microservice architecture?
Microservice architecture is a service-oriented architecture (SOA) variant that builds an application comprising lightweight, loosely coupled, modular services. These services can execute on a wide variety of platforms and are independently developed, tested, deployed, and managed. Microservice architecture is easier to maintain, scale, and test because they are smaller and more focused than their predecessors. You can leverage microservice architecture to replace long-running, complex monolithic applications with significant resource and management overheads. The term microservice refers to the limited scope of functionality provided by the service rather than the length of the code used to create it.
The advent of nanoservice architecture
There is no precise definition of how big or small a microservice should be. Although microservice architecture can address a monolith’s shortcomings, each microservice might grow large over a while. Microservice architecture is not suitable for applications of all types. Without proper planning, microservices can grow as large and cumbersome as the monolith they are meant to replace.
A nanoservice is a small, self-contained, deployable, testable, and reusable component that breaks down a microservice into smaller pieces. A nanoservice, on the other hand, does not necessarily reflect an entire business function. Since they are smaller than microservices, different teams can work on multiple services at a given point in time.
A nanoservice should perform one task only and expose it through an API endpoint. If you need your nanoservices to do more work for you, link them with other nanoservices. Nanoservices are not a replacement for microservices – they complement the shortcomings of microservices.
Benefits of nanoservice architecture
The advantages of using nanoservices are as follows:
Nanoservices vs. microservices
The words microservice and nanoservice tend to be synonymous, but there are subtle differences between them. While the former has evolved to address monoliths’ shortcomings, the latter is an evolved form of microservice architecture and handles its complexities but is not a replacement.
Nanoservice architecture is a good choice when your microservices become too large or if you need more flexibility and isolation than they can provide. Nanoservices have a limited scope than microservices, and they are typically at the functional level. You could construct a microservice out of nanoservices.
Microservices outperform monoliths in several ways, and nanoservices are even better. You may use nanoservices to build the business logic once and encapsulate it within a serverless function allowing it to be reused. To design a robust, scalable architecture, you should use a combination of both microservice and nanoservice architecture to leverage the best of both worlds while at the same time eliminating the downsides of each.
Serverless computing and Azure Functions
The cloud’s pledge of unlimited size, improved resource maintenance, and lower costs necessitated new ways of thinking about executing apps. Serverless computing is one such technology that has proliferated over the past few years. It allows you to quickly build and deploy software and services while removing the need to maintain the underlying infrastructure.
The nanoservices built in this article will leverage serverless technology by using Azure Functions. An Azure Function is essentially a piece of code that runs in the cloud, typically in a serverless environment designed to accelerate and simplify application development. It is a serverless compute service that allows one to run code on-demand without managing resources or hosting it on a server.
In using Azure Functions, you would need to pay for serverless computing based on the amount of time your Azure Function has run. Azure Functions are a great way to building nanoservices – serverless with simple APIs. You can trigger an Azure Function using any event in Azure, using third-party services as well as any on-premise systems.
The ItemCheckOut Application
Let’s put nanoservice concepts into practice by implementing a concrete example. Consider an item checkout process of an e-commerce application. Typically, the list of the steps you would have to follow is:
Each of these steps corresponds to a nanoservice which can be implemented by a stand-alone serverless function. You can use Azure, AWS or Google Cloud, etc., to write your serverless function. In this article, we’ll take advantage of Azure Functions to build and deploy our serverless functions. Together, these functions comprise the ItemCheckout microservice, as shown in the following picture.
To execute the code examples shown in this article, here are the minimum requirements you should have installed in your system:
The application structure
The application you are going to build will comprise four projects as part of a single Visual Studio solution:
The following picture shows how the solution structure of the completed application will look like:
We’ll be building each of these projects in the sections that follow.
The following are the dependencies of each of the four projects in this application:
The following dependency diagram illustrates these dependencies:
Implementing the ItemCheckOut Application
We’ll follow the steps outlined below to build the ItemCheckOut application and deploy the nanoservices to Azure:
So, let’s start.
Create the entity classes
To keep things simple, we’ll use just three entity classes, namely, CartItem
, Customer
, and Product
. Create a class library project in Visual Studio and name it Nanoservices.Entities
. Then, create a class file in this project named CartItem.cs
with the following code:
The CartItem
class represents each item in the cart.
Add a new class file to the Nanoservices.Entities
project named Customer.cs
. This class will have the following code:
Finally, add the Product.cs
class file with this code:
Create the repository infrastructure
Create another class library project in the same solution named Nanoservices.Infrastructure
. This project will contain the repository and the business logic classes, and their interfaces. The Nanoservices.Infrastructure
project has a dependency on the Nanoservices.Entities
project you created in the previous step. So, add this dependency in Visual Studio.
Now, create three solution folders in this project named Definitions
, Repositories
, and Services
. The following picture provides you with a preview of how we are going to organize the files in this project:
For the sake of simplicity, we’ll not be using any database in this example. Applying the Repository Design Pattern, the sample data will reside in the CheckOutRepository
class only.
Before creating that class, let’s first define an interface named ICheckOutRepository
. In the Definitions
folder, create a class file named ICheckOutRepository.cs
with the following code in there:
In the Repositories
folder, create a CheckOutRepository.cs
class file with the following code:
The CheckOutRepository
class implements the ICheckOutRepository
interface, and its constructor adds some sample data to play with.
Create the business logic component
The business logic component of our microservice is responsible for providing its core functionalities. This being a minimal implementation of a microservices-based application together with its nanoservices, you wouldn’t actually have any business logic as such in this component. In our case, it will act as a passthrough between the API controller we will implement in a later section and the repository.
Let’s first create the service interface. In the Definitions
solution folder of the Nanoservices.Infrastructure
project, create a class file named ICheckOutService.cs
with the following code in there:
The ICheckOutService
interface contains the signature of all the operations that correspond to a nanoservice. Next, create a class named CheckOutService
in a file named CheckOutService.cs
inside the Services
solution folder of the Nanoservices.Infrastructure
project with the following code in there:
Create an Azure Functions project in Visual Studio
Assuming you already have an Azure account, we’ll examine how to build and deploy Azure Functions. You can create an Azure Function either from the Azure Portal or Visual Studio. In this article, we’ll create and deploy Azure Functions using Visual Studio 2019.
Follow the steps outlined below to create an Azure Function in Visual Studio 2019.
A new Http-triggered Azure Function project will be created. By default, a file named Function1.cs
will be created with the code of a default function for your reference. Now, delete this file as we don’t need it in our application. We’ll create our own Azure Functions shortly.
Register the dependencies
ASP.NET Core provides built-in support for the dependency injection (DI) design pattern. DI enables you to achieve Inversion of Control (IoC) between classes and their dependencies and helps in creating loosely-coupled components and writing maintainable code. ASP.NET Core provides a built-in service container named IServiceProvider
. You should register your dependencies with the service container in the ConfigureServices
method of the Startup
class in your application.
To learn more on dependency injection in .NET, check out this article.
Azure Functions support dependency injection as well. Let’s explore how to enable it in this project.
As the first step, install the Microsoft.Azure.Functions.Extensions
package in this project by using the NuGet package manager. Then, add the NanoServices.Infrastructure
project as a dependency in this project.
Now, add a class file named Startup.cs
to your Azure Function project with the following code in there:
The FunctionsStartup
assembly attribute indicates the type name to be used during startup.
The Startup
class extends the FunctionsStartup
base class. Within its Configure
method, you register the needed dependencies through the AddScoped
method.
Create the Azure Functions
In this section, you’ll create the Azure Functions that implement the nanoservices. These functions will be invoked based on an HTTP request, hence the name HTTP-triggered Azure Functions. You can learn more on HTTP-triggered Azure Functions from here.
Now, create a file named CartManager.cs
in the root of the NanoservicesFunctionApp
project with the following code in there:
We’ll now create all our Azure Functions one by one as methods of the CartManager
class.
The IsItemAvailable Azure Function
This function checks if an item is available for purchase and returns true if it is available, false otherwise. The following code snippet illustrates the IsItemAvailable
method to add to the CartManager
class:
The GetAllCartItems Azure Function
This function returns a list of all cart items. The following code snippet illustrates the GetAllCartItems
implementation:
The GetAllCustomers Azure Function
The GetAllCustomers
function retrieves all customer records. The following code snippet illustrates this function:
The GetAllProducts Azure Function
The GetAllProducts
function returns a list of all available products:
The AddItemToCart Azure Function
This function is used to add items to the cart. The following is the source code of the AddItemToCart
Azure Function:
The UpdateStock Azure Function
The UpdateStock
function is used to update the stock of items. The following is the source code of this Azure Function:
The CartManager class
The complete source code of the CartManager.cs
file is given below for your reference:
Deploy the Azure Functions
To deploy the Azure Functions we created earlier, follow the steps outlined below:
Once your Azure Function has been published, you can consume it in your application much the same way you consume any other service using HttpClient
.
Testing the Azure Functions
So far, so good. Since the nanoservices are deployed in the Azure environment, you can test them from within the Azure Portal itself. To test the Azure Functions, follow the steps outlined below:
Here’s how the output would look like:
Call the Nanoservices from an ASP.NET Core Web API Application
To complete the application, we need to create the ItemCheckOutAPI
microservice, which will consume the nanoservices published on Azure. For this purpose, follow the steps mentioned below in your Visual Studio instance opened on the solution we are working on:
Build the ItemCheckOut microservice
We’ll now build our minimalistic microservice. It will implement action methods that correspond to each operation of the item checkout process we discussed earlier. Each of these action methods would wrap the calls to the serverless functions we created earlier.
Before starting to code, add a reference to the Nanoservices.Entities
project.
Now, in the Controllers
folder, create an API controller named ItemCheckOutController
and replace the default code with the following:
Call the Azure Functions from the API Methods
Each of the action methods of the ItemCheckOutController
class corresponds to a nanoservice we’ve implemented using Azure Functions earlier. Note that the methods of the ItemCheckOutController
class having the “Internal” suffix are private. These are the methods that will actually communicate with the Azure Functions.
As an example, the following code snippet shows how the ASP.NET Core Web API calls the Azure Function. The IsItemAvailableInternal
method accepts the product code as a parameter and returns true
or false
depending on whether or not the product is available for purchase.
The other private methods follow the pattern shown above.
Use Swagger UI to Test the ItemCheckOut Application
Now that the nanoservices are already published in Azure, you can run the ItemCheckOutAPI
microservice application and then invoke the API endpoints using Swagger UI with your browser. Swagger is available in your ASP.NET application thanks to the integrated OpenAPI support.
The following picture shows the output of the GetAllCartItems
API endpoint called using Swagger UI:
Nanoservices: The Future
Microservices evolved to address the flaws of traditional monolithic architecture. Nanoservices are essentially extra-small microservices with a narrower scope and are more focused and driven by serverless computing. Although nanoservices are adept at addressing the shortcomings of microservice architecture, they have their own set of issues and challenges.
Nanoservice technology is not quite there yet, but with serverless computing and serverless solutions getting cheaper day by day, it is all set to be the technology of choice for building high-performant and scalable applications for quite some time. It will find its place in this envisioned ubiquitous world where new technologies and architectures suffice the constantly changing business world’s needs.
The era of extra-small microservices powered by serverless computing is inevitable. For example, BBC’s website is a real-world application of nanoservices used to render dynamic web pages, generate the headline, retrieve weather information, and update cricket match scores, among other things.
The complete source code of the ItemCheckOut application built throughout this article is available here.
This content was originally published here.