
- Part 1 - EcommerceDDD overview
- Part 2 - Strategic and Tactical Design
- Part 3 - Domain events and Event Sourcing
- Part 4 - Implementing Event Sourcing with MartenDB
- Part 5 - Wrapping up infrastructure (Docker, API gateway, IdentityServer, Kafka)
- Part 6 - Angular SPA and API consumption
- EcommerceDDD++: Streamlining API Client Generation with Kiota and Koalesce
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 cover 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 rough boundary between contexts defined only 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 as new requirements emerge. 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 can 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:
- IdentityServer: authentication and authorization using Duende IdentityServer.
- ApiGateway: API gateway using Ocelot to route and aggregate requests across microservices.
- ServiceClients: typed HTTP clients for inter-service communication, generated with Kiota.
- Services: The microservices composing the backend are built to be as simple as possible, structured as a vertically sliced structure with
API,Application,DomainandInfrastructure. - 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.dcprojis a project containing thedocker-compose.ymlfile, with all the definitions for containers, ports, paths for Dockerfiles and what we need to run everything within docker.
Inside each microservice
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,queriesandevents. - Domain A structured implementation of the domain through defining
aggregates,commands,value objects,domain services,repository contracts, anddomain events. - Infrastructure It is a supporting logical layer for the other logical layers, handling infrastructural matters such as
data persistencewith implementing repositories,database mapping, andexternal 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 widely assumed 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 use 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 push DDD implementation off track, making the whole effort feel overwhelming and fruitless.
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 quickly at the expense of good design, leading to mixed concerns and a data-driven approach with no commitment to 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 held that misconception myself until reading Domain Modeling Made Functional: Tackle Software Complexity with Domain-Driven Design and F# by Scott Wlaschin, a great book demonstrating that the heart of it is not the paradigm or language you use.
Like any other tool, programming languages are means to an end, not the end itself. 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-defined 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
In this series of posts, I 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, I strongly encourage you to consider the pitfalls and the cost-benefit before implementing anything you see in the following articles in a real product using any of these concepts.
In the next post, I will touch on Strategic and Tactical designs, the key concepts of DDD. See you there!
Links worth checking
- Domain-Driven Design
- Ubiquitous language
- Bounded context
- Domain-Driven Design: Tackling Complexity in the Heart of Software
- Domain Modeling Made Functional Tackle Software Complexity with Domain-Driven Design and F#
- Implementing Domain-Driven Design
- Domain-Driven Design Distilled

