Solution & Project Structure of a Loosely Coupled Monolith

Solution & Project Structure of a Loosely Coupled Monolith

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.

YouTube

Check out my YouTube channel where I post all kinds of content that accompanies my posts.

System Diagram

Here’s a diagram of what the Solution and C# project structure should reflect.

Solution & Project Structure of a Loosely Coupled Monolith

Bounded Context

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.

Solution & Project Structure of a Loosely Coupled Monolith

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.

References

Each implementation project (Sales & Shipping) will only reference other Contract projects. They will never reference another implementation project.

Solution & Project Structure of a Loosely Coupled Monolith

The example above has the Sales implementation referencing the Shipping.Contracts.

Top-Level

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

ASP.NET Core

This top-level project is an ASP.NET Core app. Which is really just basically a console app using Kestrel as the HTTP server.

Dependencies

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.

ConfigureServices

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.

ASP.NET Core

Worker

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.

Generic Host

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.


Follow @CodeOpinion on Twitter


Enjoy this post? Subscribe!

Subscribe to our weekly Newsletter and stay tuned.

Loosely Coupled Monolith

Loosely Coupled Monolith

With microservices all the rage over the past decade or so, there has been little attention paid to how to develop a Loosely Coupled Monolith.

Both microservices and monoliths have their strengths and weaknesses. The intent of this blog and video series is not to debate the use of one or the other but rather to illustrate how you can develop a loosely coupled monolith. What the benefits come from a loosely coupled monolith and some of the drawbacks.

Loosely Coupled Monolith

This blog post is apart of a series of posts I’ve created around Loosely Coupled Monoliths.

YouTube

Check out my YouTube channel where I post all kinds of content that accompanies my posts.

Boundaries

Regardless of Monolith or Microservice based architectures, boundaries are critical to both. In Domain Driven Design, the concept of a Bounded Context is defined. I’d argue it’s the most important concept from Domain Driven Design. It’s driven by language and mutual understanding between business and software. I’ve written & talked about boundaries in my Context is King: Finding Service Boundaries series.

For a short re-cap, for me, boundaries are about ownership. A Bounded Context is about business capabilities and the data behind those capabilities.

It’s NOT about entities. It’s NOT about having an entity service that performs CRUD over those entities.

It’s about capabilities that will be owned by the bounded context. With that, you cannot have behavior without data.

In a solutions view of a bounded context, I like to think of it as having 3 projects.

Contacts project contains things like interfaces, delegates, and DTOs (Data Transfer Objects).

Implementation project contains all of the code for the actual implementation of your bounded context.

Tests project is for well… tests.

Project References

With any large monolith, you’re going to have many bounded contexts. The key is that they are silo’ed from other bounded contexts implemenation.

Any implementation project will not reference the implementation of another bounded context. The only reference it can have is that of other contract projects.

An implementation project will only couple (reference) to contacts projects because that’s where our DTOs that represent messages & events live. DTOs are nothing more than data buckets. The contract projects contain no actual logic.

Databases

Since each bounded context is the owner of its own data, there is no need for a shared database.

Each bounded context MUST have its own database which it is the owner.

No other bounded context will have access to this database or any type of data access layer such as Entity Framework DbContext. Each context has access to its own database and that’s it.

Top Level Entry Points

There must be some top-level entry points to our application. These are the actual executables that are running in service, in a virtual machine, or in a container.

In the example of this monolith, I’m assuming it’s a web application that is either an HTTP API or is serving HTML as a server-side web app.

ASP.NET Core

There is a single project that is the ASP.NET Core Host. Basically this hosts the WebHostBuilder and the ASP.NET Core Startup class to configure the app. It will reference all of the bounded-context to provide them with a means to expose their HTTP routes (MVC Controllers or Endpoints).

ASP.NET Core is composing all the bounded contexts together.

Message Broker

Loosely Coupled Monolith

Bounded Contexts generally will need to communicate. This is done via events. This is no different than Service Oriented (SOA) or Microservices architecture that is event-driven.

This is where a message broker enters and also why the contracts project contains our DTOs that represent events.

Whenever a bounded-context has a state change that is derived from behavior, it can publish an event to the message broker.

Message Processor

Loosely Coupled Monolith

Another top level entry point is the Message Processor. This is the process that receives events/messages from the Message Broker and dispatches them to the appropriate bounded context that wants to receive them.

An event/message may be received by none or many different bounded contexts.

A bounded context that receives an event might then perform some state change and publish another event back to the message broker.

Loosely Coupled Monolith

This is really just an event/message driven architecture inside a monolith. It has clear boundaries between bounded context and separation of data.

In many ways, you could view a lot of the aspects of this architecture as similar SOA however the difference being that they are all in the same codebase and all hosted in the same top-level entry points (executables).

Benefits & Drawbacks

As with everything there are benefits and drawbacks to this approach here are a few of the bigger ones to note.

Benefits

The benefits of this approach are that the same of any monolithic architecture. The code is altogether. All the source code is all within the same solution/repository.

If you want to refactor an event you can find all the different bounded contexts that use that event.

Simplified deployment (and local debugging). There are only two actual executables that need to be deployed (this can also be a drawback).

Lastly, if you ever want to carve off a bounded context and host it on its own, you simply do it. There’s no coupling preventing it. You simply copy the top-level entry points and only have it access the bounded context you’re extracting.

Drawbacks

Single deployment. This isn’t a drawback on this specific approach but on monoliths in general. When you’re deploying, one bounded context can take down the entire application. Since ultimately all the code gets run together in the top-level entry points, there’s more risk during deployment.

You must be diligent in following the rules of not referencing implementation and not accessing another database of a different bounded context. This takes an understanding of all developers working on the system.

If your system becomes large enough, build times and deployment times can take a while.

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.

Follow @CodeOpinion on Twitter

Enjoy this post? Subscribe!

Subscribe to our weekly Newsletter and stay tuned.

Migrating to .NET Core: Mission Complete

Migrating to .NET Core: Mission Complete

It’s been over 5 years since I started a greenfield project that initially was developed using Katana/Owin with ASP.NET Web API. Over the past 3 years, since .NET Core 2.0 was released and .NET Standard 2.0 was defined, I’ve been slowly chipping away at migrating to .NET Core. It’s been a long road but we’re now fully migrated and running in production with ASP.NET Core on .NET Core 3.1.

Migrating from .NET Framework to .NET Core

This blog post is in a series about migrating from .NET Framework to .NET Core.

Solution/Project Size

To give some sense of scale, our application consist of two primary entry points.

The first is an HTTP API that started out as a shelf hosted (not using IIS) Owin/Kata using ASP.NET WebAPI. Over time that morphed into using Nancy. During the migration, it ultimately moved to ASP.NET Core using the OWIN Middleware to still use Nancy.

The second entry point is a worker process that processes messages from a message broker. This is primarily for handling events and fire-and-forget commands. This has always been a console app.

To give a sense of scale, I ran NDepend over our solution to get lines of code metric, just to give some idea of the size.

Migrating to .NET Core

Journey: Migrating to .NET Core

I’ve given a full experience report on my YouTube channel. This covers pretty much everything I’ve done along with the process of getting our app running under .NET Core in production.

Questions or Feedback

If you’re thinking of doing a migration and have any questions, let me know in the YouTube comments, in the comments on this blog or on Twitter. I’d be glad to help if possible.

If you’ve done a migration and would like to share some tips, get in touch with me!

Follow @CodeOpinion on Twitter

Enjoy this post? Subscribe!

Subscribe to our weekly Newsletter and stay tuned.