A lot happened this and last year, so why not to share some thoughts and experience from implementing DDD on a monolithic application.

Warning: it's just a high level overview and probably will not make much sense without experiencing similar issues. 

About project

I've been mainly working on an ERP system called TCIS for a Czech manufacturing company engaged in processing metal sheets and profiles. The system consists of several modules like Customers, Suppliers, Base Items, Stock, Deliveries, Reporting and others. 

Technically, backend it's written in C#, .NET Framework 4.7, MS SQL database and Entity Framework as ORM, dependency injection everywhere.

Web frontend is written in DotVVM, Javascript and is using SignalR, mobile frontend is in Xamarin and communicates with backend using web api and couple of external systems too.

To simplify typical common tasks used in forms (get list, display detail, update and save) we made a plugin to VS to design data using generators (dynamic data).

So the customer can even design and update forms directly by himself. All extendable with events of course. 

The solution contains common patterns, like facades, services, repositories, separated data, business and presentation layer, all via interfaces and dependency injection, not bad at all. 

The issues

Now after a few years as the system grows up and business requirements are changing over time suddenly the system became very hard to maintain. One service now calls another service in a different context. Adding more if-else clauses only makes the problem worse. Changing one part of the application results in braking another part. Have you already been here as well?

So we decided to split functionality into modules (domains) in a microservice style, but with all the benefits from monolithic application for small teams (one solution, easier deployments). But to succeed, we must be very careful: Don't touch code outside of your domain directly! But then, how to access data from another domain? 

DDD and CQRS

Reading data is simple. Use queries to retrieve data from the database (either from views or tables) and transform into some DTO objects. Updates are a bit more complicated, but not too much. Since we don't want to use CRUD anymore, but strict DDD, there must be another DOMAIN layer to keep that logic. 

Upon each change the domain object generates a domain event, which is handled by:

1/ event handler in repository - to persist changes to DB

2/ domain event handler - to perform another task. It can be whatever, some work on the same domain, enqueue a new integration event or something else. 

Thanks to Jimmy Bogard and his Mediatr, which makes implementation very straightforward with Commands and Notifications. Many modules are now already separated and code is much easier to maintain. 

Testing

Since we now have simple domain objects without additional dependencies, unit testing becomes a lot easier! No excuses anymore. 

Event Sourcing

Thinking further. When all events have their own handles, why not to use event sourcing! The idea is it save the event first, then replay it. However, it's a bit overhead on this project and doesn't bring any customer value (apart of us geeks to try this juicy approach). We didn't implement it. 

Message Queue

With an optimistic assumptions about importance of queues and amount of data being processed, I decided to build my own on SQL. After some time it turned out to be underestimated. I mean, it works fine, but not worth the effort and not bullet proof. I'd suggest to use some existing queue, even MSMQ, anything, custom queues will never be so perfect. 

Lesson learnt

DDD is must simpler when starting from scratch. Implementing it into existing projects is very difficult.

Relational database is not the best option for DDD due to the fact, that it's the aggregate root, which is loaded and it may spread across many tables (yes, we can always create views).

On the other hand, NoSQL databases are designed exactly for this purpose and may be a better choice. 

With CQRS you can always build read model in relational SQL.

One of OOP principle says, that every class should enter valid states only. The bigger class it is, the harder to do. Typically we use class constructors so put class into that valid state. But it's uncomfortable to have dozen of parameters as it becomes  disorganized and difficult to use. Let's say, your class is persisted to database, so typically has 2 constructors. First for a new instance and second one for the existing instance read from the database (and in that state). Hierarchical class structure adds even more complexity. How to simplify?

Consider following example. We're building a message queue and so it contains processing records. It can do anything, imagine it's a job which is triggered at some point and results are written back to the database. The record has a message serialized in json, which tells the handler what to do, and then there is some message processor (handler of the message) responsible to deal with that. Retries counter is there to know how many times the processing failed in total or daily, and after xx fails it disables the job and notifies someone. The states are at least New, Running, Finished, Failed, etc..

On the SQL side: 

CREATE TABLE [dbo].[Processing](
[Id] [uniqueidentifier] NOT NULL,
[ProcessingId] [int] IDENTITY(1,1) NOT NULL,
[Message] [ntext] NOT NULL,
[ProcessingTypeId] [int] NOT NULL,
[ProcessingSubTypeId] [int] NULL,
[IsProcessing] [bit] NOT NULL,
[Retries] [int] NOT NULL,
[RetryCounter] [int] NOT NULL,
[IsEnabled] [bit] NOT NULL,
[StartDate] [datetime] NULL,
[FinishDate] [datetime] NULL,
[ProcessedDate] [datetime] NULL,
[ProcessingResult] [ntext] NULL
)

... and domain object:

class ProcessingMessage
{
    public ProcessingMessage(string processingType, string processingSubType, string message)
    { 
        Id = Guid.NewGuid();
        ProcessingType = processingType;
        ProcessingSubType = processingSubType;
        Message = message;
    }

    public ProcessingMessage(Guid id, DateTime? dateStarted, DateTime? dateFinished, DateTime? dateProcessed, string processingType, string processingSubType, string message, int retryCounter, int maxRetries, int retries, bool isEnabled, ProcessingResult result, bool isProcessing)
    {
        Id = id;
        DateStarted = dateStarted;
        DateFinished = dateFinished;
        DateProcessed = dateProcessed;
        ProcessingType = processingType;
        ProcessingSubType = processingSubType;
        Message = message;
        RetryCounter = retryCounter;
        MaxRetries = maxRetries;
        Retries = retries;
        IsEnabled = isEnabled;
        Result = result;
        IsProcessing = isProcessing;
    }
    // ...
}

 The class has 2 constructors. Whilst the first constructor is not an issue and easy to use, the other is quite heavy. One solution is to break the class into several states, like ProcessingMessageRunning, ProcessingMessageFailed, etc. and have only relevant properties on each. State pattern. However, here it's not worth the effort. Much simpler is Builder pattern. The idea is to leave the responsibility to correctly instantiate the class to someone else, who knows how. I always tend to have my Builder nested class so I can set private fields directly. Why not public methods or properties? Methods may have additional logic to keep your class in a valid state. Property setters are not public visible - almost never - again, don't let anyone to change state of your class from outside directly.

Fluent interface then adds better readability to the code.

public class ProcessingMessage
{
    private ProcessingMessage(Guid id, string processingType, string message, bool isProcessing, bool isEnabled)
    {
        Id = id;
        ProcessingType = processingType;
        Message = message;
        IsProcessing = isProcessing;
        IsEnabled = isEnabled;
    }

    public class Builder
    {
        private readonly ProcessingMessage message;
        private Builder(ProcessingMessage message)
        {
            this.message = message;
        }

        public static Builder New(string processingType, string msg)
        {
            var pm = new ProcessingMessage(Guid.NewGuid(), processingType, msg, false, true)
            {
                IsEnabled = true
            };
            return new Builder(pm);
        }

        public static Builder Existing(Guid id, string processingType, string message, bool isProcessing, bool isEnabled)
        {
            var pm = new ProcessingMessage(id, processingType, message, isProcessing, isEnabled);
            return new Builder(pm);
        }

        public Builder WithSubType(string processingSubType)
        {
            message.ProcessingSubType = processingSubType;
            return this;
        }

        public Builder StartedOn(DateTime? dateStarted)
        {
            message.DateStarted = dateStarted;
            return this;
        }

        public Builder TimesRetried(int totalRetries, int dailyRetries)
        {
            message.Retries = totalRetries;
            message.RetryCounter = dailyRetries;
            return this;
        }

        public Builder FinishedOn(DateTime? dateFinished)
        {
            message.DateFinished = dateFinished;
            return this;
        }

        public Builder WithResult(ProcessingResult processingResult)
        {
            message.Result = processingResult;
            return this;
        }

        public Builder WithResult(string jsonResult)
        {
            if (string.IsNullOrWhiteSpace(jsonResult))
                return this;

            message.Result = ProcessingResult.FromJson(jsonResult);
            return this;
        }

        public ProcessingMessage Build()
        {
            return message;
        }
    }

Then it's ProcessingRepository's responsibility to retrieve data from database and create my domain object. 

ProcessingMessage.Builder
    .Existing(entity.Id, processingType, entity.Message, entity.IsProcessing, entity.IsEnabled)
    .StartedOn(entity.StartDate)
    .TimesRetried(entity.Retries, entity.RetryCounter)
    .FinishedOn(entity.FinishDate)
    .WithResult(entity.ProcessingResult)
    .Build();

Again, important rule to follow is that your class should enter valid states only. Looking at the previous example we could say, when I call FinishedOn it should also have ProcessingResult as a parameter, because when the call is finished we must have some result.