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.


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


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.


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.


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.


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.


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

Software Architecture & Design

Get all my latest YouTube Vidoes and Blog Posts on Software Architecture & Design

CQRS: Refactoring Queries without Repositories

CQRS: Refactoring Queries without Repositories

This question posted on Twitter (and the subsequent thread) caught my attention. I had to pipe in with my own opinion on having queries without repositories.

To summarize, the question is about using Aggregate Roots with an ORM like Entity Framework. Should you eager load all navigation properties or alternatively I guess, use lazy loading?

My answer is if you’re only using an aggregate root for commands (to change state) and you generally have a higher read to write ratio, then eager load the navigation properties. As a general rule of thumb.


I recently heard about eShipOnWeb from Steve Smith. It’s a reference application using ASP.NET Core 3.1 using a layered monolithic architecture.

I decided to take a look around and try and refactor a spot that was using a repository for a query.

There is a query to return all of the orders for a signed-in user. It’s pretty simple but is a good example of it using the OrderRepository along with a specification to fetch out the relevant orders. From there it creates a ViewModel that is used in a Razor view.

Here’s the razor view for reference

Looking at the Razor view, you can see that it doesn’t use some of the properties on OrderViewModel. The OrderItems or ShippingAddress properties are never used.

The Repository with the specification is retrieving the entire Order Aggregate Root and eager loading the OrderItems. We’re fetching in a lot of data that we simply aren’t using.

Refactoring Queries without Repositories

The refactor is rather simple. Just remove the repository and instead inject the Entity Framework DbContext (CatalogContext). The second part is creating a separate ViewModel (MyOrdersViewModel ) that only contains the data we need for our Razor view. Then use this new ViewModel as a projection in our EF Query.

Side Notes

In many situations, I prefer to create a SQL Database View that I will create a new class to represent and use that as a DbSet in my DbContext. And most often I have a completely different DbContext for writes vs reads. My Reads DbContext is what will have a model that represents the SQL view. This allows you to do that projection as an SQL View rather than doing the projection in your code.

If you were using a SQL Database View and created an OrderSummary class that was on your DbContext, that now might look like this:

Live Coding Session

I did a live coding session on my YouTube and Twitch channels. You can check out the recording here of doing this exact refactor.


If you want to take a look a the full sample, you can also find my refactor changes on my fork of eShipOnWeb on GitHub.


Follow @CodeOpinion on Twitter

Software Architecture & Design

Get all my latest YouTube Vidoes and Blog Posts on Software Architecture & Design

Loosely Coupled Show: Software Architecture and Design

Loosely Coupled Show

I’m excited to announce the Loosely Coupled Show, a new project I’m working on with James Hickey. Check out the YouTube Channel and subscribe!

The premise is simple. To have discussions about software architecture and design. The target audience is intermediate to senior software developers.

Episode #000

We recorded a quick video to kind of describe what the plan is for the show, check out our “first” episode for more about the show and us and our backgrounds.

Loosely Coupled Show

I’ve also posted a short video on my CodeOpinion YouTube channel to elaborate a bit more on the announcement, which you can check out as well.

Software Architecture and Design

I think at no matter what level you are in terms of experience in software development, architecture and design are something you already do. To what degree is what varies.

From how you organize code, define dependencies and even naming. They’re all different aspects of design.

There are many more advanced topics we’d like to talk about such as:

  • Top-level Architectures
  • Event Driven Architecture
  • Domain Driven Design
  • Service Boundaries
  • Microservices
  • Messaging
  • Concurrency
  • Fault Tolerance

And we can’t leave out the cloud. Yes, it’s just someone else’s computer but it’s a real enabler for new ways to build and deploy applications.

If you have any recommendations for specific topics you would like us to cover, or to recommend a guest, let me know on Twitter or in the comments!

Related Links

Follow @CodeOpinion on Twitter

Software Architecture & Design

Get all my latest YouTube Vidoes and Blog Posts on Software Architecture & Design