ASP.NET Core app with Dynamic Modular Monolith Architecture - Part 2

In my previous post, ASP.NET Core App with Dynamic Modular Monolith Architecture - Part 1, I explained why I initiated this project and how I selected the Dynamic Modular Monolith architecture. In this post, let's discuss the architecture in more depth, but before that, let me share the core of this project, which is "The ability to develop, build, and deploy each feature in complete isolation". In this idea, what my preemption is, each feature will have its own DLL, which will be loaded in WebHostApp at runtime, for the first time the app is started, and in my mind, I already started thinking about pulling features in and out, while the app is in running state, crazy!


 I have shared enough from a theoretical perspective to build the base, now let's start coloring the drawing with some code. The solution has three physical components:

  1. WebHostApp, the launching application to load web configs and modules.
  2. Core, which contains the abstract contracts and interfaces, and all logic to load and manage plugins.
  3. Modules, which contain multiple features in their directory called module, examples could be Outflow, User, Auth, etc.

This is how my solution looks with these three components:



This IFeatureModule is the main interface of this solution and the core of this architecture, which is why it's placed inside the core component.

public interface IFeatureModule
{
    string Name { get; }
    void RegisterServices(IServiceCollection services);
    void MapEndpoints(IEndpointRouteBuilder endpoints);
}

 Next, let's see how I have implemented this interface for my Outflow Module and how it look like as a separate project in the same solution.



It's clearly visible, the Outflow module has its separate Controllers, Models, and Views, and the most important bit is the OutflowModule which has implemented theIFeatureModule interface.

public class OutflowModule : IFeatureModule
{
    public string Name => "Outflows";
    public void RegisterServices(IServiceCollection services)
    {
        services.AddControllersWithViews()
            .AddApplicationPart(typeof(OutflowModule).Assembly)
            .AddRazorRuntimeCompilation(); // Enables view discovery in DLLs
    }

    public void MapEndpoints(IEndpointRouteBuilder endpoints)
    {
        endpoints.MapControllerRoute(
            name: "UserModule",
            pattern: "outflow/{action=Index}/{id?}",
            defaults: new { controller = "outflow" });
    }
}

At last, the slimmest component is <code class="codekeyword">WebHostApp</code>, only having Program.cs file and only has the responsibility of loading these modules. The following are the only three lines that take care of loading and configuring all the modules in the background.

// Load plugins (DLLs), on line no 18
var pluginManager = new PluginManager();
pluginManager.LoadPlugins(AppContext.BaseDirectory + "Modules", builder.Services);

// Map plugin routes, on line no 32
pluginManager.MapEndpoints(app);



With that, let me conclude this post, and in the next one, I will go one step further, deep into the working of this project, with an agenda of exploring PluginManager and PluginLoadContext classes. At last, I am a strong supporter of open-source on GitHub and believe that if you come across a problem, you should try solving it by writing your own code.