Home Hands-on DDD and Event Sourcing [1/6] - Project's overview
Post
Cancel

Hands-on DDD and Event Sourcing [1/6] - Project's overview



A quick overview of this project


It has been a while since I decided to start this project. It began as a type of developer’s sandbox for combining exciting things and seeing how they get along. As you’ll find in all the projects I present on my blog, this one has no pretense of being a postulate in any subject, but instead describes the steps I followed to learn something of interest. This humble and personal take may spark the interest of beginners in delving deeper into the topics I scratch here.

Suppose you check the project’s Git history; you’ll notice it changed from a simple monolith, barely touching the concept of event sourcing and a very thin limiter for the bounded contexts delimited by a namespacing scheme, to gradually moving to a more robust microservice-based architecture, yet a very straightforward implementation of event sourcing. I called this whole refactoring v2 because of that.

It’s also worth mentioning that this move started to introduce a lot of accidental complexity, so I had to cut or replan things to keep them simple. However, increasing complexity is inevitable due to the need to handle new needs. Moving from one architectural approach to another requires a clear plan and a thorough understanding of the complexity of maintaining and scaling your application. I advocate that architecture evolves as needed and not for mere deliberate choice. A decentralized design requires more mechanisms to handle the load, connect boundaries, and revert transactions when needed.

If you are already well versed in Domain-driven Design or Event Sourcing and are more interested in checking how to implement Event Sourcing using Marten, you likely jump straight to Part 4.


High-Level System Architecture

Project’s Detailed Architecture

The project’s architecture is straightforward. I kept the number of projects as small as possible, so each microservice repeats a vertically sliced structure with everything it needs to keep API, Application, Domain, and Infrastructure well placed. At the same time, the Presentation is held by the SPA alone.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
├── Core
├── Core.Infrastructure
│
├── Crosscutting
│   ├── ServiceClients
│   ├── ApiGateway
│   └── IdentityServer
│
├── Services
│   ├── CustomerManagement
│   ├── InventoryManagement
│   ├── OrderProcessing
│   ├── PaymentProcessing
│   ├── ProductCatalog
|   |   └─ EcommerceDDD.ProductCatalog
│   │      ├── API
│   │      ├── Application
│   │      ├── Domain
│   │      └── Infrastructure
│   ├── QuoteManagement
│   └── ShipmentProcessing
│
└── SPA
  • Core: It defines the building blocks and abstractions used on all underlying projects. Its nature is very abstract, with no implementations.
  • Core.Infrastructure: It holds some abstractions and implementations for infrastructure to be used across all microservices, such as authentication, message streaming using Kafka, Event Sourcing using MartenDB and lots of third-party packages.
  • Crosscutting: It contains project implementations that cross-cut all microservices, such as IdentityServer for authentication and authorization, and an API gateway using Ocelot.
  • Services: The microservices composing the backend are built to be as simple as possible, structured as a vertically sliced structure with  API, Application, Domain and Infrastructure.
  • SPA (Single Page Application): It’s a SPA built with Angular and TypeScript and provides a functional and user-friendly UI.
  • docker-compose The docker-compose.dcproj is a project containing the docker-compose.yml file, with all the definitions for containers, ports, paths for Dockerfiles and what we need to run everything within docker.

Inside the microservices

Let’s take the ProductCatalog microservice as an example; this is how the folder structure looks like:

1
2
3
4
5
├── EcommerceDDD.ProductCatalog
│   ├── API
│   ├── Application
│   ├── Domain
│   └── Infrastructure
  • API RESTful API is used to enable communication between client and server.
  • Application It orchestrates the interactions between the external world and the domain to perform application tasks through use cases by handling commands, queries and events.
  • Domain A structured implementation of the domain through defining aggregates, commands, value objects, domain services, repository contracts, and domain events.
  • Infrastructure It is a supporting logical layer for the other logical layers, handling infrastructural matters such as data persistence with implementing repositories, database mapping, and external integrations.


Why and when use Domain-driven Design?


Our field is full of buzzwords, usually shrunk to mnemonics, short in length but carrying a world of complexity. There are countless times I’ve seen non-technical people repeat these terms without understanding what they mean, yet expect developers to have years of experience with them. With domain-driven design, it wouldn’t be different (like any other Whatever-Driven design); it’s broadly spread that it’s about technology, architecture, or a framework, while it’s, in fact, an approach that requires a shift in the way you view things. At the same time, the implementation is just a way to get there.

A fair answer to why using it depends on the nature of the software you’re building. Not all projects will benefit from DDD, period. You don’t want to use it if the domain is not complex enough (remember, Tackling Complexity in the Heart of Software), if you have tight budgets and time constraints, or if the outcomes are technology-centric, like frameworks or tools. Even if you feel the project is a suitable case for DDD, the lack of domain knowledge (like not having at least a domain expert) and an experienced team implementing DDD principles will force DDD down the path, and it will feel overwhelming for nothing.

Finding a team fully ready to embrace it is sometimes a matter of luck; for some, there’s always the first time, which will require effort and discipline to keep it on track. It also needs upper management support because time can be a pressing factor that can easily derail an inexperienced team that can’t handle the pressure to deliver fast from a good design, forcing the project to mix concerns and lack of attention to details with speeding up everything in a more data-driven design, with no commitment to the overall cohesion.

Is object-oriented programming the only way?

Although DDD is agnostic to technology, there’s a misconception that it can be implemented only using an object-oriented programming language (like C# or Java) to express the domain in code, and that’s not true. I believed in it before reading Domain Modeling Made Functional: Tackle Software Complexity with Domain-Driven Design and F# by Scott Wlaschin, a good book evidencing that the heart of it is not the paradigm or language you use.

Programming languages are only tools for building paths, not the way. Keeping an open-minded attitude towards technology is healthy. Everyone has biases, and developers love their favorite stuff, sometimes forgetting that it may not be suitable for different needs.

What does implementing domain-driven design look like?

Looking back to 2003, when Eric Evans published the famous blue book, Domain-Driven Design: Tackling Complexity in the Heart of Software, we see that more than 20 years have passed, and it remains relevant today, if not more than ever, and especially with AI tools that we know only work effectively within well-limited contexts.

However, the book is abstract and foundational, with insufficient practicality for concrete implementations, so I recommend that you read complementary literature focused on implementation with more concrete examples for corporate projects, such as Implementing Domain-Driven Design and Domain-Driven Design Distilled.


Final thoughts


This series will showcase my thought process for building a usable application that addresses many challenges I would expect to encounter in a real-world, non-monolithic architecture.

Architectural approaches may significantly impact overall functioning, deployment, and maintenance. Changing from a simple monolith to a fragmented microservice-based architecture, on top of event sourcing, required a deeper analysis and accountability for the complexity introduced.

Since it’s a study project, and my primary goal was to exercise all the concepts and technologies used here, you may seriously consider the pitfalls and the cost-benefit before implementing anything you see in the following articles in a real project.

In the next article, I will scratch a bit on Strategic and Tactical designs, the key concepts of DDD. See you there!


Check the project on GitHub





This post is licensed under CC BY 4.0 by the author.