Home

Awesome

<img src="img/CSharp-Toolkit-Icon.png" alt="Backend Toolkit" width="64px" />Orleans.Multiservice

Prevent microservices pain with logical service separation in a modular monolith for Microsoft Orleans 8

Orleans.Multiservice is an automated code structuring pattern for logical service separation within a Microsoft Orleans (micro)service.

Benefits: allows development teams to avoid significant development overhead / friction common in microservices applications:

Included in Nuget (with prereleases) (see template usage to get started)

Background

It is not uncommon to find single development teams that maintain multiple microservices, ignoring Conway's Law to their detriment. A benefit of microservices is that they can be deployed independently. However, a single team typically has a single deployment rhythm (e.g. in sync with a sprint) and therefore usually deploys their services together.

A lot of development effort can be avoided by structuring the application as a single modular monolith that is designed to be split up with little effort - if and when that actually becomes necessary.

Microsoft Orleans allows to build distributed applications using grains, which have grain contracts - C# interfaces. These grains can be considered a kind of 'nano services'. By grouping Grains and their contracts into logical services within an Orleans (micro)service, teams can avoid and postpone the development friction that microservice separation brings until the moment that physical separation is actually needed - e.g. when adding more teams or when dependencies diverge. Even then, the total microservices count can be much lower; many teams will need no more than a single microservice.

Orleans.Multiservice leverages Microsoft Orleans and Conway's law to make life better for developers building distributed (microservices) applications. NJoy!

What it is

Orleans.Multiservice consists of:

The code analyzer / unit tests will be added in a future release. Note that the multiservice pattern can be used without the analyzer by following the code structure of the template and the pattern rules

Template usage

  1. On the command line, ensure that the mcs-orleans-multiservice template is installed:

    dotnet new install Modern.CSharp.Templates
    

    Note that the dotnet new mcs-orleans-multiservice template requires PowerShell to be installed

  2. Enter this command to read the documentation for the template parameters:

    dotnet new mcs-orleans-multiservice -h
    
  3. To create a new multiservice with one logical service in it, enter e.g.:

    dotnet new mcs-orleans-multiservice --RootNamespace Applicita.eShop --Multiservice TeamA --Logicalservice Catalog --allow-scripts Yes
    
  4. To add a logical service to an existing multiservice solution, type e.g. this command in PowerShell while in the solution folder:

    .\AddLogicalService.ps1 Basket
    

These two short commands create the solution structure as seen in the single team example below. The solution is ready to run.

Proof by Example: eShop

The example in this repo illustrates how logical services within a microservice have very low development friction, and how little code needs to be changed when moving logical services to a separate microservice.

This is the code structure before and after the move: Example Vs Solutions Single Team And Two Teams

And this is the running API before and after the move: Example Api Single Team And Two Teams

The example implements two services from an eShop:

The only code change needed to move the CatalogService to the Team B microservice is in the CatalogServiceClientGrain; it is modified to use the generated CatalogServiceClient instead of the ICatalogGrain Orleans grain contract:

Example Service Client Within And Across Microservices

How to run the example

Single team solution:

Two team solution:

How to test the example

When testing the API in the generated swagger UI, you can use any integer for buyerId. A common scenario is:

  1. Add some products to the catalog
  2. Create a basket for a buyerId with some basket items, but use different product titles and prices than you added to the catalog
  3. Observe how in the returned basket the product prices and titles have been updated from the catalog

Pattern rules

These rules ensure that the pattern remains intact:

  1. The only project references allowed are:<br /> Apis -> Contracts<br /> Apis -> *Service<br /> *Service -> Contracts

  2. These types are only allowed in specific namespaces:<br /> All API endpoints must be in or under Apis.<service-name>Api<br /> All public grain contracts must be in or under Contracts.<service-name>Contract<br />

  3. References between types in these namespaces are not allowed:<br /> Apis.<service-name>Api -> Apis.<other-service-name>Api<br /> Apis.<service-name>Api -> Contracts.<other-service-name>Contract<br /> Contracts.<service-name>Contract -> Contracts.<other-service-name>Contract<br />

  4. The public keyword in *Service projects is only used on interface member implementations, grain constructors and serializable members in a type.<br /> This ensures that the only external code access is Orleans instantiating grains. It makes it safe to reference the service implementation projects in the silo host project (Apis) to let Orleans locate the grain implementations; the types in the service implementation projects will not be available in the silo host project.