How to Write Microservices in C#
Writing Microservices in C#
The term “microservices” was coined circa 2011, so not very long ago, however, the pattern and the term have become ubiquitous when discussing software architecture.
These nimble, independent units of functionality have become the standard of scalability, enabling businesses to adapt and thrive in a dynamic digital landscape.
Today, we’re talking about microservices with a focus on harnessing the power of C#. C#, a language known for its versatility and robust capabilities, is perfect for the task at hand.
So let’s learn how to build microservices using C#!
Decoding Microservices: Best Practices Handbook for Developers
Learn how to overcome microservices challenges by following some best practices
Pros and cons of building microservices with C#
While there is no single programming language that is perfect for building microservices, there are always “pros” and “cons” we can assess while picking the right stack for our project.
Let’s take a look at some of the benefits of choosing C# as our main driving language:
- Language Familiarity: C# is a widely adopted and well-understood programming language. Many developers are already proficient in C#, making it easier to build and maintain microservices without the need for extensive training.
- .NET Ecosystem: C# is part of the .NET ecosystem, which provides a rich set of libraries, frameworks, and tools. This ecosystem streamlines development, offering solutions for various aspects of microservices, including web APIs, database access, and messaging. In essence, you’re never coding “alone”, while other languages might have a community of builders making up for the lack of language support, the .NET Ecosystem is around to provide ample options.
- Cross-Platform Compatibility: With the advent of .NET Core, C# has become cross-platform. You can build microservices that run on Windows, Linux, or macOS, giving you flexibility in choosing your deployment environment.
- Tooling: C# benefits from robust development tools, including integrated development environments (IDEs) like Visual Studio and Visual Studio Code. These tools provide features such as code analysis, debugging, and profiling, which can enhance developer productivity.
- Performance: C# is known for its strong performance, and with the introduction of .NET Core and later versions, it has become even more efficient. This makes C# a suitable choice for building high-performance microservices.
- Scalability: Microservices often require horizontal scaling to handle varying workloads. C# and .NET provide support for building scalable applications using technologies like Azure Service Fabric, Kubernetes, and Docker.
- Security: C# offers robust security features, making it easier to implement authentication, authorization, and other security provisions in your microservices. Additionally, the .NET ecosystem provides security libraries and tools to help secure your applications.
As mentioned before, there is no “perfect” solution, and while the above points might sound ideal, there is also a downside to this choice of language:
- Platform Dependency: Historically, C# was primarily associated with Windows development. Although .NET Core has made significant strides in cross-platform compatibility, legacy applications, and libraries may still tie you to a Windows-centric environment. This can limit your flexibility in choosing deployment platforms.
- Resource Consumption: C# applications running on the Common Language Runtime (CLR) can be more resource-intensive compared to some other languages. The runtime overhead introduced by the CLR can affect the efficiency of microservices, especially in resource-constrained environments.
- Learning Curve: While C# is known for its developer-friendly syntax, developers who are new to the language may face a learning curve, especially when it comes to advanced features, frameworks, and best practices in microservices development.
- Vendor Lock-In: If you choose to use Microsoft’s Azure cloud platform or other Microsoft technologies in conjunction with C#, you might experience a level of vendor lock-in. Migrating away from such services could be challenging and costly.
- Performance Overhead: While C# offers good performance, it may introduce a performance overhead compared to lower-level languages like Go. This overhead can be a concern in platforms or specific services where microsecond-level response times are critical.
- Licensing Costs: Depending on your development and deployment choices, there may be licensing costs associated with certain Microsoft products or tools, which can impact the overall cost of your microservices project. That said, this extra cost usually comes associated with extra levels of support as well, so this one might not be such a negative point after all. It’s really up to you to decide.
Monolithic vs microservices-based business process automation platforms
Building Scalable Business Automation with Microservices
C# microservices example
In this hands-on example, we’ll create a simplified product catalog microservice using C# and ASP.NET Core. This microservice will expose RESTful endpoints for managing product information.
Step 1: Project setup
Before we move on, make sure to download and install the .NET 7 SDK from this URL.
With that out of the way, continue by setting up a new ASP.NET Core project using your preferred build tool, such as Visual Studio or Visual Studio Code. You can use the built-in templates to generate a project with the necessary dependencies.
Step 2: Define the data model
Create a C# class to represent the product entity. Use Entity Framework Core to map it to a database table:
namespace MyMicroservice;
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public double Price { get; set; }
}
Step 3: Implement RESTful endpoints
Create a controller class to define RESTful endpoints for managing products. The controller will capture HTTP requests for specific URLs (endpoints) and redirect the program’s flow to the appropriate service class (where the actual business logic resides).
You can think of these controllers as the bread and butter of your microservice. You’ll be creating one for every resource your service handles, and ideally, each controller will only talk to one service. That way you keep responsibilities separated and your codebase a lot cleaner.
using Microsoft.AspNetCore.Mvc;
using MyMicroservice.Services;
namespace MyMicroservice.Controllers;
[Route("api/products")]
[ApiController]
public class ProductsController : ControllerBase
{
private readonly IProductService _productService;
public ProductsController(IProductService productService)
{
_productService = productService;
}
[HttpGet]
public IActionResult GetAllProducts()
{
var products = _productService.GetAllProducts();
return Ok(products);
}
[HttpGet("{id}")]
public IActionResult GetProductById(int id)
{
var product = _productService.GetProductById(id);
if (product == null)
{
return NotFound();
}
return Ok(product);
}
[HttpPost]
public IActionResult AddProduct([FromBody] Product product)
{
var addedProduct = _productService.AddProduct(product);
return CreatedAtAction(nameof(GetProductById), new { id = addedProduct.Id }, addedProduct);
}
[HttpPut("{id}")]
public IActionResult UpdateProduct(int id, [FromBody] Product product)
{
var updatedProduct = _productService.UpdateProduct(id, product);
if (updatedProduct == null)
{
return NotFound();
}
return Ok(updatedProduct);
}
[HttpDelete("{id}")]
public IActionResult DeleteProduct(int id)
{
_productService.DeleteProduct(id);
return NoContent();
}
}
Step 4: Implement the service logic
Create a service class to handle business logic and interact with the database. Here is an example defining both the interface and the class for it (we’ll use these interfaces in a second):
using MyMicroservice.DBContext;
namespace MyMicroservice.Services;
public interface IProductService
{
IEnumerable GetAllProducts();
Product GetProductById(int id);
Product AddProduct(Product product);
Product UpdateProduct(int id, Product product);
void DeleteProduct(int id);
}
public class ProductService : IProductService
{
private readonly IApplicationDbContext _context;
public ProductService(IApplicationDbContext context)
{
_context = context;
}
public IEnumerable GetAllProducts()
{
return _context.Products;
}
public Product GetProductById(int id)
{
return _context.Products.FirstOrDefault(p => p.Id == id);
}
public Product AddProduct(Product product)
{
_context.AddProduct(product);
return product;
}
public Product UpdateProduct(int id, Product product)
{
var existingProduct = _context.GetProductById(id);
if (existingProduct != null)
{
_context.UpdateProduct(id, product);
return existingProduct;
}
return null;
}
public void DeleteProduct(int id)
{
_context.DeleteProduct(id);
}
}
Step 5: Implement the repository class
The repository class serves as the data access layer and interacts with the database. In this example, I’m using a simple in-memory example which is not ideal, but should be enough to get the app to compile and run:
namespace MyMicroservice.DBContext;
using MyMicroservice;
using System.Collections.Generic;
public interface IApplicationDbContext
{
IEnumerable Products { get; }
Product GetProductById(int id);
Product AddProduct(Product product);
Product UpdateProduct(int id, Product product);
void DeleteProduct(int id);
// Add other methods or properties as needed
}
public class ApplicationDbContext: IApplicationDbContext
{
private readonly Dictionary _productStore = new Dictionary();
private int _nextProductId = 1;
public IEnumerable Products => _productStore.Values;
public Product GetProductById(int id)
{
if (_productStore.TryGetValue(id, out var product))
{
return product;
}
return null; // Product with the given ID not found
}
public Product AddProduct(Product product)
{
product.Id = _nextProductId++;
_productStore.Add(product.Id, product);
return product;
}
public Product UpdateProduct(int id, Product product)
{
if (_productStore.ContainsKey(id))
{
product.Id = id;
_productStore[id] = product;
return product;
}
return null; // Product with the given ID not found
}
public void DeleteProduct(int id)
{
if (_productStore.ContainsKey(id))
{
_productStore.Remove(id);
}
}
}
Step 6: Set up the dependency injection
Now that our code is ready, it’s still not going to work. This is because we haven’t set up the dependency injection for .NET to understand how to inject the Product Service class into the Controller and the Application DB Context class into the service.
Just add these four lines to the Program.cs file:
using MyMicroservice.DBContext;
using MyMicroservice.Services;
//....
builder.Services.AddScoped();
builder.Services.AddScoped();
That’s it! You’ve just created a basic product catalog microservice in C# using ASP.NET Core.
You can expand on this example by adding more features like authentication, pagination, and validation to suit your specific requirements.
Orchestrating your microservices
Camunda is a powerful workflow automation and decision-making platform that can be seamlessly integrated into your microservices architecture. It allows you to orchestrate complex workflows, manage business processes, and make data-driven decisions within your C#-based microservices ecosystem.
For example, if you are creating an e-commerce platform and setting up the microservices for it, you can use Camunda to automate order processing, inventory management, and customer support ticket handling.
Curious to see how Camunda can help you with microservice orchestration? Check out this article about the top 6 benefits of setting up an event-driven process orchestration, and then dive right into Camunda with a free account.