Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Oasis.MicroService: A Small Package that Simplifies Microservice Deployment

5.00/5 (2 votes)
30 Sep 2023MIT7 min read 17.8K   64  
A simple way to contain microservices in class libraries for deployment
This article introduces a package that helps developers to contain web controllers in class libraries for easy-deployment purpose. Tutorial of using the package and demo code are provided.

Introduction

The concept micro-service has been raised for a long time. While the idea of grouping relevant services together and deploying them separately from other irrelevant services really increases the flexibility and reduces complexity and coupling of code, it may raise maintainability issues as increasing number of micro-service usually leads to more complicated deployments. Apparently, deploying everything with one package into one website is much easier than dividing the package into several small parts and deploying them separately to different websites.

To mitigate the deployment problem, this article introduces a small package for ASP.NET Core developers to contain micro-services in class libraries, and simply deploy the micro-service binaries by copying them under a path and doing a little configuration at the web API host, so many such micro-services can be deployed under the same website as plug-ins.

Background

Traditional web APIs are usually built as a monolith, with a lot of controllers providing various features within one website. It would be easy to simply pile up all features whenever necessary to the same solution, but it is also common sense that this approach requires the whole code base to be re-published for any tiny update, increases the possibility of irrelevant code coupling, and without divide-and-conquer, the whole code base together may scare the code readers by its size.

The concept of micro-service that groups relevant APIs and deploys them separately solves the problems. Simply deploying each group of API into a separate website may cause another problem, that a lot of websites and a lot of ports need to be managed. It's not a serious problem, but can be a little annoying for management. Then an idea of solving this problem would be deploying all such micro-services under the same website. This is definitely possible because we can distribute web controllers in class libraries and make them work normally in a web API project.

The package Oasis.MicroService is designed to encapsulate the details of distributing web controllers in separate class libraries with micro-service concept.

Using the Code

To demonstrate how to use the package, we need to build at least two simple microservices, and one website to host the microservices.

To Build a Micro-Service

  1. Initialize a class library project as the micro-service main assembly, define all controllers in this assembly, and implement all relevant classes and interfaces.

  2. Add reference of Oasis.MicroService to the class library project, implement a class that inherits from Oasis.MicroService.MicroServiceContextBuilder class. Note that:

    1. This abstract class as one abstract method to be implemented, which is Initialize method, in it all dependency injection for the microservice should be done to the IServiceCollection parameter of the method, like the code piece below the section. One thing to highlight is that each micro-service has its own independent dependency injection container if configured this way, so micro-services are separated from each other.

    2. Initialize method has an second parameter of type IConfigurationRoot, it is the content of configuration read with logic with protected virtual method GetConfiguration. The default implementation assumes the configuration file to be named appsettings.json and under the same directory with the micro-service assembly. Environment specific configuration works the same way as normal ASP.NET Core web API, which means appsettings.Development.json will be taken when environment variable ASPNETCORE_ENVIRONMENT = Development. Such configuration files are optional as normal ASP.NET Core web API behaves, if they are mandatory for the micro-service, some defensive code can be implemented in Initialize method. The method GetConfiguration is virtual for the possibility of customizing configuration file reading feature.

C#
protected override void Initialize(IServiceCollection serviceCollection,
    IConfigurationRoot configuration)
{
  serviceCollection.AddSingleton<ISimpleDemoServiceDemoService>(
    new SimpleDemoServiceDemoService());
  var simpleDemoServiceConfiguration =configuration.Get<SimpleDemoServiceConfiguration>();
  if (simpleDemoServiceConfiguration == null)
  {
    throw new FileLoadException(
      $"Configuration for {typeof(SimpleDemoServiceConfiguration)} missing",
      Path.GetFileName(this.GetType().Assembly.Location));
  }

  serviceCollection.AddSingleton<ISimpleDemoServiceConfiguration>(
    simpleDemoServiceConfiguration);
}

To Build a Website

  1. Create a web API project, add reference of Oasis.MicroService to it. Copy published micro-service binaries to expected paths (The BuildForDemo.ps1 script in the sample code does all publishing and copying work, please refer to it).

  2. Create a MicroServices section in the configuration file, listing all micro-service main assemblies to be hosted with the web API project.

    1. Included section lists all micro-service assemblies to be included, Oasis.MicroService will consider all assemblies in Directory and all its sub-directories whose file name matches SearchPattern value to be micro-service assemblies. Root folder of the configuration is the directory of the assembly from which AddMicroServices is called (usually should be the web API assembly). Note that wildcard characters "*" and "?" are supported in SearchPattern (but not in Directory) to provide the possibility for not having to manually add/delete a configuration entry whenever they add/delete a micro-service from the web API host. Environment value will override the value of environment variable ASPNETCORE_ENVIRONMENT if specified.

    2. To exclude certain assemblies that may be wrongfully matched with wildcard characters in Included section, Excluded section is introduced to list such assemblies so Oasis.MicroService will skip them (In the sample code Oasis.DemoServiceWithSqlite.dll will be matched by pattern *DemoService.dll, but it is a dependency assembly which doesn't contain micro-service required definitions. So, if we don't exclude it specifically or set flag IgnoreAssemblyLoadingErrors to be true, relevant exceptions will be thrown upon the web API starting).

    3. Oasis.MicroService will throw exception if any micro-service assembly it tries to load got problems (e.g., assembly doesn't exist or is of wrong format, assembly doesn't properly implement relevant classes and so on), with value of the flag IgnoreAssemblyLoadingErrors set to "true" the exceptions will be swallowed (Excluded section won't be necessary in this case), so such invalid assemblies are simply ignored. If there are a lot of assemblies wrongfully matched by wildcard characters, this flag is a convenient way to avoid listing them one by one in Excluded section. But it's always recommended to leave the flag unconfigured (so the value is defaulted to false).

    4. For detailed information for MicroServices section, please refer to the document of the package.

  3. Read the section from the configuration file, and call AddMicroServices method to plug-in the micro-services (See program.cs file in Oasis.DemoWebApi in the sample code for details).

JavaScript
{
  "MicroServices":{
    "Included": [
      { "Directory": "MicroServices", "SearchPattern": "*DemoService.dll" },
      { "Directory": "MicroServices", "SearchPattern": 
        "Oasis.DemoServiceWithSqlite.dll", "Environment": "Test" }
    ],
    "Excluded": [
      { "Directory": "MicroServices", "SearchPattern": 
        "Oasis.CommonLibraryForDemoService.dll" }
    ],
    "IgnoreAssemblyLoadingErrors": false
  }
}
C#
var builder = WebApplication.CreateBuilder(args);

var microServiceConfiguration = new MicroServiceConfiguration();
builder.Configuration.GetSection("MicroServices").Bind(microServiceConfiguration);
builder.AddMicroServices(microServiceConfiguration);

If everything is set up correctly, the controllers defined in the micro-service assemblies should be accessible from the website. That's all it takes to build up micro-services and deploy them by simply copying over the binaries.

To Try the Sample Code Attached

  1. Execute BuildForDemo.ps1 script, it builds the web API project, publishes the three demo micro-services and copy over relevant binaries to expected locations.

  2. Debug Oasis.DemoWebApi project, the controllers defined in the three micro-services should be available at paths /EmptyDemoService/Test, /SimpleDemoService/Test and /DemoServiceWithSqlite/Test.

  3. Oasis.EmptyDemoService is an empty micro-service that has no configuration files, and to be included by *DemoService.dll search pattern; Oasis.SimpleDemoService is a simple micro-service that has a configuration file and to be included by *DemoService.dll search pattern as well; Oasis.DemoServiceWithSqlite is a microservice that has environment specific configuration files, accesses a sqlite database, and to be included by the entry without any wildcard character; Oasis.CommonLibraryDemoService is a dependency library that the three micro-service depends on, its existence helps to demonstrate how Excluded section or IgnoreAssemblyLoadingErrors flag should be used. Oasis.DemoWebApi is the web API host.

Image 1Image 2Image 3

Please note that the sample code and the package is built with .NET 8.0 preview version, which may not be supported by Visual Studio 2022 yet, so please install .NET 8.0 SDK and open the project and run it with Visual Studio Code.

Points of Interest

Since all micro-services are actually being executed in the same process, dependency version may become an issue if multiple hosted micro-services depend on the same assembly of different versions. It's highly recommended that all dependency libraries should be strong named.

For any suggestions or inquires, please leave a comment under this article or visit https://github.com/keeper013/Oasis.MicroService.

History

  • Version 0.1.0: Expecting user to implement a ServiceContext class, which isn't so convenient.
  • Version 0.2.0: Eliminated ServiceContext to simplify user interface.
  • Version 0.3.0: Strong named Oasis.MicroService assembly, environment specific configuration feature implemented
  • Version 0.4.0: Simplified environment specific configuration user interface, so MicroServiceContextBuilder only has the default constructor now.
  • Version 0.5.0: Enhanced environment specific configuration so micro-services consider value of environment variable ASPNETCORE_ENVIRONMENT if not configured in MicroServices section. Defaulted configuration file name to be appsettings.json instead of taking the assembly name.
  • Version 0.6.0: Enhanced MicroServices configuration section to allow matching assembly names with wildcard characters. Existing words refined a little.

License

This article, along with any associated source code and files, is licensed under The MIT License