Orleans.Multiservice
Prevent microservices pain with logical service separation in a modular monolith for Microsoft Orleans 9
Install / Use
/learn @VincentH-Net/Orleans.MultiserviceREADME
<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 10
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:
- of too many microservices before they are needed (if ever)
- of refactoring when the time has come to move part of the application to a separate microservice
Included in (see template usage to get started)
(Note: this repo was transferred from Applicita to VincentH-Net on March 17, 2025 to reflect who actively maintains it)
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.
- See MonolithFirst by Martin Fowler
- See this article for more details on the development overhead of microservices
- See this Orleans meetup video for a real world project where this approach was introduced
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:
-
An example application that demonstrates the pattern in 2 stages of the application's life cycle:
- The application starts out as a single microservice, built by a single team, that contains two logical services - one of which depends on the other<br /> Source: Single Team/Microservice eShop
- Then a second team is added that will become owner of one of the logical services. A second microservice is added and one logical service is moved into that (in a real world application this could also be moving 5 out of 10 logical services to a second microservice)<br /> Source: Two Team/Microservice eShop
-
A
dotnet new mcs-orleans-multiservicetemplate to set up a new multiservice, and to add a logical service in an existing multiservice -
A Roslyn code analyzer and/or unit tests to ensure that the pattern is preserved during development. It is used in the template to ensures that the pattern rules are followed while coding, building and in CI
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
-
On the command line, ensure that the mcs-orleans-multiservice template is installed:
dotnet new install Modern.CSharp.TemplatesNote that the
dotnet new mcs-orleans-multiservicetemplate requires PowerShell to be installed -
Enter this command to read the documentation for the template parameters:
dotnet new mcs-orleans-multiservice -h -
To create a new multiservice with one logical service in it, enter e.g.:
dotnet new mcs-orleans-multiservice --RootNamespace InnoWvateDotNet.eShop --Multiservice TeamA --Logicalservice Catalog --allow-scripts Yes -
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.
Current example baseline
The examples in this repo currently target:
- .NET 10
- Microsoft Orleans 10
The two-team example also demonstrates OpenAPI client generation across multiservices:
TeamBhosts theCatalogServiceAPI onhttp://localhost:5113TeamAkeeps a checked-inCatalogService.jsonsnapshot for normal buildsTeamAcan refresh that snapshot from the runningTeamBAPI by deletingCatalogService.jsonbefore build, or by passing-p:RefreshOpenApiReferences=true
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:

And this is the running API before and after the move:

The example implements two services from an eShop:
- The
CatalogServicecontains the currentProducts in a singleCatalog - The
BasketServicecontains oneBasketfor eachbuyerId. ABasketconsists ofBasketItems that contain product information (title, price). When aBasketis updated, theBasketServicecalls theCatalogServiceto update the product information in theBasketItems.
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:

How to run the example
Single team solution:
- Debug eShopTeamA.sln
Two team solution:
- Build and run eShopTeamBof2.sln first; it hosts
CatalogServiceonhttp://localhost:5113 - Build and run eShopTeamAof2.sln; it hosts the basket API on
http://localhost:5112 - TeamA normal builds use the checked-in
CatalogService.jsonsnapshot and do not require TeamB to be running - To refresh TeamA's generated client from the live TeamB Swagger document, either:
dotnet build src/Example/eShopByTwoTeams/TeamA/eShopTeamAof2.sln -p:RefreshOpenApiReferences=trueor deletesrc/Example/eShopByTwoTeams/TeamA/BasketService/CatalogService.jsonbefore building TeamA - The TeamA refresh build uses MSBuild's built-in cross-platform
DownloadFiletask, so the same workflow works on Windows, macOS, and Linux
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:
- Add some products to the catalog
- Create a basket for a
buyerIdwith some basket items, but use different product titles and prices than you added to the catalog - 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:
-
The only project references allowed are:<br />
Apis -> Contracts<br />Apis -> *Service<br />*Service -> Contracts -
These types are only allowed in specific namespaces:<br /> All API endpoints must be in or under
Apis.<service-name>Api<br /> Allpublicgrain contracts must be in or underContracts.<service-name>Contract<br /> -
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 /> -
The
publickeyword in*Serviceprojects 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.
