Sponsor: Do you build complex software systems? See how NServiceBus makes it easier to design, build, and manage software systems that use message queues to achieve loose coupling. Get started for free.
Here’s how you can create a solution and project structure to develop a loosely coupled monolith using a .NET Solution with C# Projects.
Each boundary is in a solutions folder with 3 projects. Implementation, Contracts, and Tests. All of which are class libraries. The two top-level executable projects, AspNetCore and Worker are console applications that reference the implementation projects.
Loosely Coupled Monolith
This blog post is apart of a series of posts I’ve created around Loosely Coupled Monoliths.
Check out my YouTube channel where I post all kinds of content that accompanies my posts.
Here’s a diagram of what the Solution and C# project structure should reflect.
The middle row of the diagram has 3 boxes that represent a boundary. These boundaries should define the set of capabilities of your system. Each boundary contains 3 projects: Contracts, Implementation, and Tests.
In my example above, there are 2 boundaries: Sales, and Shipping. For each one, I’ve created a solutions folder which the 3 respective projects for each boundary inside. All 6 projects are C# Class Libraries.
Each implementation project (Sales & Shipping) will only reference other Contract projects. They will never reference another implementation project.
The example above has the Sales implementation referencing the Shipping.Contracts.
There are two top-level projects that are our executable projects. AspNetCore and a Message Process (Worker).
Top-level projects will reference the implementation projects to expose their capabilities
This top-level project is an ASP.NET Core app. Which is really just basically a console app using Kestrel as the HTTP server.
The mentioned this project will reference the Sales and Shipping projects.
This is because each implementation project must define its own configuration. Things like Dependency Injection, HTTP routing, etc.
The above example is of an extension method for defining all of the types that needed to be registered with a ServiceCollection. In the case above it’s adding the Entity Framework SalesDbContext.
This can then be used in the AspNetCore’s ConfigureServices.
The Message Processor (Worker) is the other top-level project that is a console application. Its purpose is to connect to a message broker and dispatch the relevant messages to the implementation projects. I’ll cover message processing in other blogs/videos as the purpose of this post is simply the solution and project structure.
As you can expect, it will reference the implementation projects and use the extension methods just as the AspNetCore project does.
The Worker is using the Generic Host which is very similar to the WebHost from ASP.NET Core. It provides all the same goodness that you get with an ASP.NET Core app such as Dependency Injection and Logging.
In this case, I’m creating a HostedService which is a BackgroundService. This is ultimately where we would be communicating with a message broker to dispatch and handle messages within our Monolith.
Again, more on how that works in other posts (which will be linked at the bottom of this post when they are published).
Questions or Feedback
If you have any questions or comments, let me know in the comments of the YouTube video, in the comments on this post or on Twitter.