The RIGHT Way to Write Unit Tests for Domain-Driven Design applications
0 views
Jun 19, 2025
I’ll walk the audience through the correct approach to writing unit tests for your DDD, CQRS, and Clean Architecture applications—from scratch! What we'll Learn: The core principles of unit testing in DDD Best practices for testing business logic How to structure your test project for scalability Using Fluent Assertions for better readability Testing domain events like a pro Avoiding common mistakes in unit testing Conference Website: https://dotnetconference.com 📺 CSharp TV - Dev Streaming Destination http://csharp.tv 🌎 C# Corner - Community of Software and Data Developers https://www.c-sharpcorner.com #CSharpTV #CSharpCorner #LiveShow #dotNet #DotNetConf2025
View Video Transcript
0:00
hey everyone today we are going to talk
0:03
about domain driven design in practice
0:06
uh I have prepared some images for you
0:09
to just um for just brief explanation of
0:12
domain driven design but our main focus
0:16
is of course the practical one we have
0:19
visual studio project where I will
0:21
explain how you can implement domain
0:23
driven design in practice so first of
0:26
all uh before switching to our practice
0:29
let's try to understand what is
0:31
domaindriven
0:33
design domain design is a software
0:36
development approach it is a software
0:38
development methodology domaining design
0:42
brings with it strategical and tactical
0:45
parents that you can use in your daily
0:48
life when you apply domaining design of
0:51
course most of these uh parents are not
0:54
invented by domaining design guys but
0:57
they are collected together to make
0:59
domain design real so first of all you
1:03
need to understand that design in
1:06
software development helps us to avoid
1:09
translation between tech and business
1:11
people why because in modern days as you
1:14
know we are mostly uh focusing on
1:18
translating requirements to the
1:20
developers language and it is a big
1:22
problem when you have nontechnical guys
1:25
and they are explaining they are
1:27
providing some task for you and you are
1:30
converting this task into code and in
1:33
this conversion process you are losing a
1:35
lot of information and that's why we
1:38
have domain driven design we are
1:40
applying domain design mostly For
1:43
complex applications it is not about the
1:46
complexity of the technical stuff it is
1:49
about complexity of your domain if you
1:52
have complex domain complex business
1:55
logic you can definitely apply
1:58
domain-driven design but if you have
2:01
technical complexities it is of course
2:05
uh not preferred to use domain-driven
2:07
design also for your crowd applications
2:10
please please please don't use domain
2:13
design okay so as I mentioned domain
2:16
design has two main patterns for us one
2:21
is strategical the other one is
2:24
tactical strategical parents are the
2:28
most important ones we have ubiquitous
2:31
language bounded context and context
2:34
mapping and when you apply strategical
2:37
Parents you are actually working on
2:41
problem space the problem space here is
2:44
when you explore problem you are not
2:47
going to have a solution for it you are
2:50
just exploring the problem you are
2:52
trying to understand what is going on
2:54
what you are trying to achieve what you
2:57
are going to uh solve this going to be
3:00
your problem space on the other hand
3:03
technical patterns are applicable when
3:05
you solve this problem so it is our
3:09
solution space so we have problem space
3:12
solution space of course both really
3:15
matters but uh strategical parents are
3:18
the most important ones so in domain
3:22
design we have as I mentioned
3:24
strategical patterns and tactical ones
3:27
we have bounded context we have context
3:29
map we have ubiquitous language they are
3:32
um completely a different uh session
3:35
based topics we have tactical patterns
3:38
like entities value objects aggregates
3:40
domain events factories repositories in
3:43
this tutorial in this session I'm going
3:45
to show you uh how to apply this
3:48
tactical patterns most because I have
3:50
already done the strategical part and I
3:53
will show you also how you can achieve
3:55
using event storming
3:57
okay what else uh when you explore your
4:01
domain you have three main domains you
4:05
have core domain which you need to focus
4:08
on you have generic domain and
4:11
supportive domain um you can uh ask
4:14
someone else another company to solve
4:16
the supporting domain problems uh but uh
4:20
for your company you should focus on
4:24
your core business domain this is really
4:27
important and of course for this session
4:29
I'm going to show you everything using
4:32
clean architecture so we have clean
4:34
architecture we have a lot of cool uh
4:37
design patterns we have joining design
4:41
all of these stuffs together in one
4:43
session okay great now let's first
4:47
switch to our event storming board
4:49
before that you can explore all these
4:53
source codes just navigating to
4:56
github.com trolls layman here we have
4:58
the real DDD CQRS and clean architecture
5:01
and we have more than 100 stars for this
5:04
project and I hope after exploring you
5:06
will also put star for this project and
5:09
here I have provided all the required
5:11
information related to this application
5:14
here you can find all these the basket
5:18
mirror link You can find all these
5:21
source codes and plus if you have least
5:24
knowledge or if you are completely new
5:26
in domain-driven design below I have
5:29
provided some tutorials they are mine
5:32
and you can explore more about domain
5:35
driven design using these tutorials and
5:37
after that you can switch to the source
5:39
code to learn what's going on here okay
5:42
so great now let's switch to our visual
5:44
studio in our visual studio after
5:47
downloading you will have this
5:49
application structure uh I have
5:51
implemented clean architecture with
5:53
domain driven design but please remember
5:56
that domain during design is not
5:58
strictly connected to clean architecture
6:02
you can host your bounded context this
6:04
is actually one bounded context uh what
6:07
is a bounded context when you have uh
6:09
domain models you have conceptual
6:12
boundaries where your domain models are
6:14
valid and this is called bounded context
6:17
basket in my case is going to be my
6:20
bounded context my borders of domain
6:22
models okay and for this bounded context
6:25
I can use different architectures to
6:28
host it right now I have applied clean
6:31
architecture but I may use layered
6:34
architecture onion architecture I don't
6:36
know hexagonal service oriented rest
6:40
based etc so that's totally up to you
6:44
not just up to you but also up to the
6:46
project requirement to apply the
6:48
architecture main hostable architecture
6:51
but for now we have clean architecture
6:52
this is the most popular one let's uh
6:54
work on that so everything in domain
6:58
design starts with domain this is
7:01
applicable for clean architecture also
7:04
uh our main project here is going to be
7:06
web consulting flame basket context
7:10
domain when you open this project you
7:12
will find some folders with some files
7:16
they are going to be my domain models
7:19
what actually domain models are um I
7:22
have seen most times people are using
7:24
entity framework models as domain models
7:28
uh for example you have classes and
7:31
inside this classes you have only
7:35
properties get and set properties uh and
7:38
people trying to move these classes to
7:42
the domain folder and call it domain
7:46
models of course they are not domain
7:48
models first of all I'm sorry for that
7:52
but they are anomic models uh what is
7:55
anomic model dynamic model is when you
7:57
have some sort of model with that you
8:01
think is domain model but it actually
8:04
consists of properties it doesn't have
8:08
any methods so it doesn't have any
8:10
responsibility related to this object
8:13
for example in my case I have basket let
8:17
me zoom a bit so I have basket so you
8:22
see for my basket I have properties like
8:25
in your entity framework classes but I
8:30
also have a lot of methods you see I
8:34
have add item create update item count
8:39
delete item count delete all calculate
8:42
shipping amount calculate basket items
8:44
amount etc so I have tons of methods
8:47
here this is actually domain model so
8:52
for my domain model I I haven't
8:56
encapsulated the behavior of my class to
8:59
the service layer in most cases what we
9:02
are doing is we are creating our models
9:06
and over it we are providing services
9:09
for example for basket you have basket
9:12
service basket service uh encapsulates
9:15
the logic related to basket in it and
9:18
you are using basket for just table
9:22
representation okay so you have table
9:24
with four columns you have basket with
9:27
four properties this actually poker
9:30
model rather than your domain model your
9:33
actual domain model should store the
9:34
business domain driven design say that
9:38
in order uh to use DDD properly you need
9:43
to first focus on your domain this is
9:46
your core stuff implement domain and
9:49
then switch to the external stuff like
9:51
applying Apache Kafka Radius etc they
9:55
are secondary most you should focus your
9:58
core domain because uh domain is
10:00
everything so context is everything
10:02
domain is everything to achieve this
10:05
process you have you may apply different
10:08
approach i have applied um even storming
10:12
yeah by Alberto Brandolini uh I also
10:16
have a podcast with Alberto Brandolini
10:19
in my Charles Lemonetic YouTube channel
10:21
you may check this we have talked a lot
10:23
about uh even storming so here in event
10:26
storming we are analyzing all this stuff
10:29
together okay we are defining our uh
10:33
entities our aggregates our constraints
10:37
our events etc all this stuff are
10:40
defined here in this session uh we have
10:44
technical guys we have non-technical
10:47
guys we have domain experts all in one
10:50
let's say and after all this discussion
10:52
we are defining our main points our um
10:56
strategical parent stuff like entities
10:58
aggregates value objects mostly we are
11:01
not focusing value object in even
11:02
storming but anyway so after defining
11:05
all this stuff and after defining all
11:08
the main functionalities we are
11:10
switching to our code in our code uh we
11:13
will have a lot of entities value
11:16
objects aggregates domain events domain
11:20
services modules
11:22
uh etc they are tactical parents but
11:25
please remember that domain design is
11:28
not about technology domandri design is
11:32
not about coding
11:34
design foremost is about listening
11:37
bringing business value
11:40
uh learning exploring understanding the
11:44
stuff rather than directly coding okay
11:47
first of all you need to understand the
11:49
domain you or every software developer
11:52
should be a really good domain expert
11:54
why because uh you are not um in your
11:58
case when you apply domain design you
12:01
guys are not using translation between
12:03
nontechnical to technical that's why we
12:06
have zero translation and this zero
12:08
translation helps us uh to help business
12:12
de business guys to learn more about the
12:15
business and also they are helping us to
12:18
write a better code this is the main key
12:21
feature of domain driven design okay so
12:24
in my case I have baskets I have coupons
12:27
so you see for my baskets and coupons
12:30
I'm applying aggregate root what is an
12:32
aggregate route aggregate root is a
12:35
tactical pattern applied in domain
12:36
driven design which helps us to
12:39
encapsulate entities and value objects
12:42
in it your one aggregate root one
12:45
aggregate can can consist of one entity
12:48
or multiple entities one entity with
12:51
multiple value objects multiple entities
12:53
with multiple value objects so it is up
12:56
to you it is up to the requirement uh to
12:59
end up with the
13:01
design okay so uh first of all let's
13:05
switch to our basket item my basket item
13:08
is an entity i will have a lot of
13:11
entities in my bounded context and I
13:14
don't want to write this entity every
13:17
time that's why we have special project
13:21
called flame common domain so you see
13:24
flame common domain you can encapsulate
13:27
it to a separate nugget package that's
13:29
also applicable and mostly you will do
13:31
like this in your projects you will
13:33
encapsulate it to a separate nugget
13:35
package and we'll provide other squads
13:38
uh to work on that we have entity this
13:41
abstract class you cannot instantiate it
13:44
but you can inherit from this entity uh
13:46
we are using a template method parent in
13:49
this case for every entity we have ID we
13:53
have created at we have last modified
13:55
UTC okay the case here is you can easily
13:58
add the as much as properties as needed
14:03
in this case that's totally okay for
14:05
example in your case you may end up with
14:07
just ID or ID created at last modified
14:10
at etc etc uh every entity identified by
14:16
their ID because your entity in your
14:19
bounded context the bounded context for
14:21
your entity is going to be your
14:23
aggregate route your entity's life cycle
14:27
is defined inside your aggregate route
14:30
and your entity you are able to
14:32
instantiate this entity to modify this
14:36
entity to delete this entity so you see
14:39
we have a life cycle a special life
14:41
cycle which we don't have in our value
14:44
objects this is really important you
14:47
cannot identify your value objects using
14:49
by ID you will have ID only in your
14:52
aggregates and in your entities okay so
14:56
in my case I have a general entity which
14:59
encapsulates some logic like overriding
15:02
getting cash code and having the related
15:06
properties that's good
15:08
when you inherit from basket item you
15:11
are adding as much as properties as
15:14
needed in this case but not just
15:16
properties but also the methods related
15:20
to basket so you see I have a create
15:22
functionality I have update count why
15:25
update count because uh after carefully
15:28
uh designing all this stuff in our event
15:31
storming we are understanding that the
15:34
update count is related to basket item
15:37
rather than the basket itself we are
15:40
activating the activating basket item
15:44
not basket itself that's why we are
15:48
encapsulating related logic in the
15:52
related entities this is really
15:55
important but in a classical development
15:57
what we are doing we are just uh having
16:01
a poco objects an animic models and we
16:05
are moving all this logic to the service
16:07
layer we are having basket item service
16:10
basket service coupon service and more
16:13
but this is not valid for your domain
16:15
driven design just encapsulate the
16:18
related logic in your case for your
16:21
inside your entities and aggregate
16:24
roots here I'm using the factory pattern
16:27
this factory is also a part of domain
16:30
driven design of course it was invented
16:33
before domain design but uh just
16:35
remember that domain design doesn't
16:38
bring a lot of new to the table it is
16:41
about using the proper ones in the given
16:46
context so we have factory parent we
16:50
have another parent ddd just brings them
16:52
to the table and here you are just use
16:55
it why because I don't want to
16:58
instantiate basket item every time using
17:00
new and I have easy one to drop it to
17:05
the factory and use via factory okay
17:08
that's good now here we have entity
17:11
design pattern we have factory design
17:15
pattern that's good and for the
17:17
construction process of course we are
17:20
doing some sort of validation here in my
17:23
case I have implemented all this
17:26
validation as a separate library so you
17:29
see we have consulting flame common
17:31
domain extensions but if you feel
17:34
comfortable and if it is okay for you to
17:37
use the third part validation tools um
17:41
you are completely uh uh allowed to use
17:44
it that's up to you that's up to your
17:47
management stuff okay so we have
17:49
properties we have validation stuff we
17:52
have construction and we have the
17:55
related functionalities inside our
17:58
domain model that's great it is
18:01
applicable for all sort of entities you
18:03
see I have customer for my customer i
18:07
already implemented our entity i'm
18:09
inheriting from my entity i have the
18:12
related functionality for my quantity on
18:15
the other hand this quantity is also an
18:18
object uh I want to not to repeat the
18:22
related logic in all sort of entities
18:26
that's why I have encapsulated it to a
18:29
separate value object value object is
18:32
also a part of domain driven design
18:35
value object just let me open it so you
18:38
see this source abstract class you can
18:41
handle value object creation using
18:43
records of course net allows us to
18:47
create uh value objects easily using
18:51
record keyword but it is up to you for
18:55
the oldest net versions you can use this
18:57
abstract class value object what is
19:00
value object value object is uh uh let's
19:03
say a special form of entity i can't
19:08
call it actually entity this is uh a
19:11
class which can be identified by all it
19:16
is properties so if you have four
19:18
properties in your value object you um
19:22
these all four properties should be
19:26
equal to all another property to make
19:30
this object same okay and for the value
19:34
object you can't use ID because you
19:37
cannot identify your value objects using
19:41
ID you are able to identify it using all
19:44
it is properties that's why we have
19:46
special template method approach called
19:49
get equality components and for your
19:53
area value object you are just uh
19:56
overriding this functionality for
19:58
example for my quantity I'm overriding
20:01
this get equality components specifying
20:04
that I have value limit price per unit
20:08
okay in your domain you are
20:11
encapsulating all the main logic related
20:14
to your project this is really important
20:17
if you handle domain you are actually
20:20
handling let's say 80% of your project
20:24
okay that's good so uh for the common
20:28
user stuff it will be really good
20:30
practice to encapsulate it to a separate
20:33
library like I did here with flame
20:36
common domain i have aggregate root do
20:39
um class with interface i have date
20:43
range i have entity i have ID why ID you
20:47
can use the simple int ulong etc for
20:52
your identification but mostly in domain
20:54
during design we are using guided and
20:57
for we have some sort of functionalities
21:00
which we are using repeatedly that's why
21:04
I have encapsulated all this logic to a
21:07
single class called ID okay for my
21:11
entities for my aggregate roots I have
21:14
ID let's switch to our basket this is
21:18
going to be my aggregate root uh the
21:22
aggregate root is a biggest entity the
21:26
aggregate root is a special form of
21:29
entity when you go to definition of your
21:32
aggregate root you actually see that my
21:35
in aggregate root inherited from entity
21:39
so you have entity the same entity but
21:43
in this entity you may encapsulate you
21:47
may
21:48
orchestrate multiple entities okay so
21:53
please focus in domain during design
21:55
mostly focus on your domain layer first
21:59
after handling all this stuff we are
22:02
ending up with like separation of
22:05
concerns you have separated the business
22:08
logic into you are encapsulated your
22:11
business logic into your entities value
22:14
objects aggregate roots that's great but
22:18
how can I make sure that they will work
22:21
together because part of my logic is
22:23
inside basket another part is inside my
22:27
coupon so how I can make sure that they
22:30
can handle one user story that's why in
22:34
our application
22:36
In our clean architecture we have let's
22:39
say application layer let's call it
22:42
application layer okay here I have a
22:45
separate folder called baskets i have
22:47
used uh cqs but it is not a case here
22:52
domain driven design doesn't depend from
22:54
the architecture doesn't depend on
22:56
architecture doesn't depend on doesn't
22:58
depend on the modern technologies that
23:01
you use nowadays it is a separate
23:04
concept okay it is a software
23:06
development approach it is a methodology
23:08
so in my case I have commands and I am
23:12
encapsulating the logic related to use
23:16
case inside this commands and queries
23:20
great so you see I have quantity i'm
23:23
creating this quantity i'm creating the
23:26
seller you see so let's go to the
23:29
definition of the seller seller is an
23:31
entity great i need to work with
23:34
quantity also let's go to definition
23:37
quantity is a value object you see and
23:40
that's why we are bringing them together
23:43
in another higher level called
23:46
application in this application we can
23:49
use different entities and make sure
23:50
that they are working together okay and
23:53
we are using repository parent to store
23:56
our information this really important
23:58
you already know about repository parent
24:00
i'm not going to dive into details so
24:02
you how you are storing how you are
24:04
retrieving data in your application
24:06
layer using the repository stuff okay
24:09
that's good and all the concepts related
24:13
your user story you should drop it to
24:16
your application layer how about
24:19
infrastructure how about Apache Kafka
24:22
messaging stuff rabbitim Q all this cool
24:25
stuff just encapsulate them inside your
24:28
infrastructure you see I have
24:30
infrastructure I have Kafka integration
24:32
even publisher all these stuff like
24:35
message sending like the external
24:38
libraries that need to be used by this
24:40
application should be encapsulated to
24:42
your infrastructure because they are a
24:46
part of infrastructure okay persistence
24:50
let's say we have repositories basket
24:52
repository basket repository
24:54
implementation should be inside your
24:57
infrastructure but the contract let's go
25:00
to our contract go to definition so you
25:02
see the contract is inside application
25:05
okay the contract here but the
25:08
implementation is a bit above in your
25:10
infrastructure layer that's the easiest
25:13
one I guess uh and of course how you are
25:17
retrieving the data of course using the
25:20
controller stuff that's up to you that's
25:23
why we encapsulated all this stuff in a
25:26
separate uh library because you can use
25:29
it for your WPF application is net core
25:32
MVC web API I don't know for your uh
25:35
let's say mobile based applications in
25:38
all sort of applications okay for my API
25:41
I have simple controller so you see I
25:44
have base controller why I have base
25:46
controller because I'm encapsulating all
25:48
this logic to a resultbased parent so I
25:52
have the easy factories to uh interact
25:57
with the responses like 400 400 4001 and
26:02
etc but this logic completely is not
26:05
related to domain driven design it is
26:08
about the exact technology and my basket
26:11
controller I'm designing it like I'm
26:13
just using with web API so I have sender
26:17
I using the mediator here the sender we
26:21
are sending the query we are
26:23
encapsulating all this logic into either
26:26
query or to command okay and that's how
26:31
we are interacting with our lower layer
26:34
so we have API API sends request to the
26:38
application in your application you have
26:40
query handlers you have command handlers
26:43
and these command handlers actually
26:45
calls the domain layer elements like
26:48
your entities value objects aggregates
26:53
etc great in our case you see we are
26:56
just separate the exact logic because uh
27:00
when you work with lower layer your
27:03
applic your higher layer should not know
27:06
information about your lower layer
27:09
that's why if you need to interact with
27:12
exact um uh status codes don't move this
27:17
logic to your application layer or don't
27:21
move the application layer logic to your
27:24
controller they should be completely
27:27
separated that's why we are using
27:29
commands and queries and this stuff like
27:33
separating to command queries helps us
27:35
to easily extend our application for
27:38
example we are actively using domain
27:41
design for ERP based for the
27:43
applications
27:44
uh which have a lot of business logic
27:47
and in this case if you are adding the
27:49
business uh elements fast uh the CQS
27:53
will help you to handle it because you
27:54
are not going to every time modify the
27:57
service layer every time change
27:59
something and it will definitely affect
28:01
your application performance plus your
28:03
unit tests etc and of course we have
28:06
unit tests here i have tried to write my
28:10
I have tried to do my best when I
28:13
implemented this project and I hope
28:16
after exploring all this stuff you will
28:19
learn more about unit testing because I
28:22
have
28:22
implemented let's say the best version
28:25
of writing unit tests for domain design
28:28
application and it includes a lot of
28:30
cool parents and you can read more about
28:33
it about the technologies about the
28:36
design patterns so you see we have
28:39
applied CQRS result decorator mediator
28:43
pattern publisher subscriber strategy
28:45
template factory chain of responsibility
28:48
unit of work and more and more here we
28:51
have approximately 15 16 design parents
28:54
and the best practices related to domain
28:58
during design of course so uh that's
29:02
simply it that's simply how you can
29:06
apply domain during design in practice