• Stars
    star
    512
  • Rank 86,323 (Top 2 %)
  • Language
    C#
  • License
    MIT License
  • Created over 4 years ago
  • Updated over 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Some design approaches to enforcing a business rule requiring no duplicates. Domain driven design.

Designing a Domain Model to enforce No Duplicate Names

Some design approaches to enforcing a business rule requiring no duplicates.

The Problem

You have some entity in your domain model. This entity has a name property. You need to ensure that this name is unique within your application. How do you solve this problem? This repository shows 11 different ways to do it in a DDD application where the goal is to keep this business logic/rule in the domain model.

Approaches

Below are different approaches to solving this problem. In each case the necessary classes are grouped in a folder. Imagine that in a real application these would be split into several projects, with the domain model in one, repository implementations in another, and tests (or actual UI clients) in others still.

The Database

This simplest approach is to ignore the rule in your domain model (which is basically cheating in terms of DDD), and just rely on some infrastructure to take care of it for you. Typically this will take the form of a unique constraint on a SQL database which will throw an exception if an attempt is made to insert or update a duplicate value in the entity's table. This works, but doesn't allow for changes in persistence (to a system that doesn't support unique constraints or doesn't do so in a cost or performance acceptable fashion) nor does it keep business rules in the domain model itself. However, it's also easy and performs well so it's worth considering.

This repo doesn't use an actual database so the behavior is faked in the ProductRepository.

Approach 1 - Database

Domain Service

One option is to detect if the name is duplicated in a domain service, and force updates to the name to flow through the service. This keeps the logic in the domain model, but leads to anemic entities. Note in this approach that the entity has no logic in it and any work with the entity needs to happen through the domain service. A problem with this design is that over time it leads to putting all logic in services and having the services directly manipulate the entities, eliminating all encapsulation of logic in the entities. Why does it lead to this? Because clients of the domain model want a consistent API with which to work, and they don't want to have to work with methods on the entity some of the time, and methods through a service some of the time, with no rhyme or reason (from their perspective) why they need to use one or the other. And any method that starts out on the entity may need to move to the service if it ever has a dependency.

Approach 2 - Domain Service

Pass Necessary Data Into Entity Method

One option is to give the entity method all of the data it needs to perform the check. In this case you would need to pass in a list of every name that it's supposed to be different from. And of course don't include the entity's current name, since naturally it's allowed to be what it already is. This probably doesn't scale well when you could have millions or more entities.

Approach 3 - Pass Necessary Data into Entity Method

Pass a Service for Checking Uniqueness Into Entity Method

With this option, you perform dependency injection via method injection, and pass the necessary dependency into the entity. Unfortunately, this means the caller will need to figure out how to get this dependency in order to call the method. The caller also could pass in the wrong service, or perhaps no service at all, in which case the domain rule would be bypassed.

Approach 4 - Pass Service to Method

Pass a Function for Checking Uniqueness Into Entity Method

This is just like the previous option, but instead of passing a type we just pass a function. Unfortunately, the function needs to have all the necessary dependencies and/or data to perform the work, which otherwise would have been encapsulated in the entity method. There's also nothing in the design that requires the calling code to pass in the appropriate function or even any useful function at all (a no-op function could easily be supplied). The lack of encapsulation means the validation of the business rule is not enforced at all by our domain model, but only by the attentiveness and discipline of the client application developer (which even if it's you, is easily missed).

Approach 5 - Pass Function to Method

Pass Filtered Data Into Entity Method

This is a variation of Approach 3 in which the calling code now passes just the existing names that match the proposed name, so that the method can determine if the new name already exists. It seems like it's doing almost all of the useful work required for the business rule, without actually doing the check for uniqueness. This to me requires far too much work and knowledge for the calling code.

Approach 6 - Pass Filtered Data to Method

Use an Aggregate with Anemic Children

The problem doesn't mention whether the entity in question is standalone or part of an aggregate. If we introduce an aggregate, we can make it responsible for the business invariant (unique name) by making it responsible for all name changes or additions. Having an aggregate responsible for its invariants, especially when they related between child entities, often makes sense. However, you want to be careful not to have your aggregate root become a god class and turn all of your child entities into anemic DTOs (as this approach basically does).

Approach 7 - Aggregate with Anemic Children

Use an Aggregate with Double Dispatch

Double dispatch is a pattern in which you pass an instance of a class into a method so that the method can call back to the instance. Often it's "the current instance" or this that is passed. It provides a way for an aggregate to allow children to stay responsible for their own behavior, while still calling back to the aggregate to enforce invariants. I prefer to keep relationships one-way in my domain models, so while there is a navigation property from the aggregate to its children, there isn't one going the other way. Thus, to get a reference to the aggregate, one must be passed into the UpdateName method. And of course, there's nothing enforcing that the expected thing is actually passed here. Calling code could pass null or a new instance of the aggregate, etc.

Approach 8 - Aggregate with Double Dispatch

Use an Aggregate with C# Events

This is the first option that introduces using events. Events make sense when you have logic that needs to respond to an action. For example, "when someone tries to rename a product, it should throw an exception if that name is already in use". This approach uses C# language events, which unfortunately require a lot of code to implement properly.

Approach 9 - Aggregate with C# Events

Use an Aggregate with MediatR Events

This approach uses an aggregate combined with MediatR-managed events. To avoid the need to pass MediatR into the entity or its method, I'm using a static helper class. This is a common approach and one that has been used successfully for over a decade (see Domain Event Salvation from 2009). This approach provides one of the cleanest client experiences in terms of API. Look at the tests and notice that all each test does is fetch the aggregate from the repository and call a method. The test code, which mimics real client code in this case, doesn't need to fiddle with any plumbing code or pass in functions or special services or any of that. The methods are clean, the API is clean, and the business logic is enforced in specific classes with that single responsibility.

Approach 10 - Aggregates with MediatR Events

Use Domain Events (no aggregate)

Sometimes it doesn't make sense to involve or model an aggregate. In this case you can still leverage domain events to provide a way to keep logic encapsulated in the entity while still leveraging infrastructure dependencies. The client experience in this approach is very similar to the previous one, with the exception of a bit more client logic being required when adding new products. Otherwise, the client experience is very clean and consistent and not muddied with implementation details leaking from the domain layer.

Approach 11 - MediatR Domain Events

I also have an article walking through how to set this up in your ASP.NET Core app, Immediate Domain Event Salvation with MediatR.

Summary

In most situations where there are business rules that involve multiple entities and their peers, I've found introducing an aggregate makes sense. Assuming you go the aggregate route, be careful to avoid putting all logic in the root and having anemic children. I've had good success using events to communicate up to the aggregate root from child entities (in addition to this sample see AggregateEvents).

If you don't have an aggregate or there would only ever be one and it would have millions of other entities in it (which might be untenable), then you can still use domain events to enforce constraints on the collection as demonstrated in the last example. Using domain events as shown in the last couple of approaches does violate the Explicit Dependencies Principle, but that principle mainly applies to services, not entities, and in this case I feel that the tradeoff of leveraging domain events to provide a clean interface to my domain and keep logic encapsulated in my entities is worth it.

Reference

This project was inspired by this exchange on twitter between Kamil and me. Kamil's ideas for approaching this problem included:

  1. Pass all the current names to the update method. Done
  2. Pass all the names matching the proposed name to the update method. Done
  3. Pass an IUniquenessChecker into the update method which returns a count of entities with that name. Done
  4. Pass a function that performs the same logic as #3. Done
  5. Check the invariant in an Aggregate. Done - Anemic Children
  6. Create a unique constraint in the database. Done

My own two approaches include:

  1. Use a domain service (which will lead to an anemic entity) Done
  2. Use domain events and a handler to perform the logic

Learn More

Learn Domain-Driven Design Fundamentals

More Repositories

1

CleanArchitecture

Clean Architecture Solution Template: A starting point for Clean Architecture with ASP.NET Core
C#
16,284
star
2

GuardClauses

A simple package with guard clause extensions.
C#
3,063
star
3

ApiEndpoints

A project for supporting API Endpoints in ASP.NET Core web applications.
C#
2,934
star
4

SmartEnum

A base class for quickly and easily creating strongly typed enum replacements in C#.
C#
2,178
star
5

Specification

Base class with tests for adding specifications to a DDD model
C#
1,922
star
6

pluralsight-ddd-fundamentals

Sample code for the Pluralsight DDD Fundamentals course by Julie Lerman and Steve "ardalis" Smith
CSS
841
star
7

Result

A result abstraction that can be mapped to HTTP response codes if needed.
C#
819
star
8

CleanArchitecture.WorkerService

A solution template using Clean Architecture for building a .NET Core Worker Service.
C#
720
star
9

ddd-guestbook

A DDD guestbook example written for ASP.NET Core
C#
691
star
10

kata-catalog

My list of code katas
C#
670
star
11

DesignPatternsInCSharp

Samples associated with Pluralsight design patterns in c# courses.
C#
518
star
12

SolidSample

C#
474
star
13

new-software-project-checklist

The ultimate new software project decision/question checklist
443
star
14

ddd-vet-sample

A sample meant to demonstrate domain driven design using a veterinary hospital management system.
C#
305
star
15

OrganizingAspNetCore

Offers several different ways to organize content in ASP.NET Core MVC and Razor Pages projects.
C#
207
star
16

Ardalis.Extensions

Some random C# extension methods I've found useful. Published as Ardalis.Extensions on Nuget.
C#
147
star
17

WebApiBestPractices

Resources related to my Pluralsight course on this topic.
C#
144
star
18

CachedRepository

A sample demonstrating the CachedRepository pattern
C#
104
star
19

HttpClientTestExtensions

Extensions for testing HTTP endpoints and deserializing the results. Currently works with XUnit.
C#
92
star
20

CertExpirationCheck

A simple xUnit C# test showing how to verify a certificate for a domain has at least 30 days before it expires.
C#
79
star
21

DotNetDataAccessTour

A tour of different data access approaches in .NET 8+.
C#
73
star
22

AspNetCoreStartupServices

A simple demo listing all services available to an app at startup
C#
69
star
23

GettingStartedWithFilters

Filters samples associated with MSDN article
C#
67
star
24

AspNetCoreRouteDebugger

An ASP.NET Core Route Debugger implemented as a Razor Page
C#
67
star
25

Ardalis.SharedKernel

Some useful base classes, mainly used with the CleanArchitecture template. Also, a template to make your own SharedKernel nuget package.
C#
56
star
26

MediatRAspNetCore

Sample showing MediatR with ASP.NET Core
C#
50
star
27

DevIQ-gatsby

JavaScript
48
star
28

DomainEventsConsole

A console app showing domain events in action using .NET 5
C#
41
star
29

BuilderTestSample

Show how to use a builder with unit tests.
C#
40
star
30

EFCore.Extensions

Extension methods to make working with EF Core easier.
C#
38
star
31

CSharpGenerics

Some samples using C# generics for a Pluralsight course.
C#
38
star
32

Ardalis.ApiClient

Some classes to make working with APIs easier.
C#
33
star
33

TestPatterns

Examples of approaches to unit testing different kinds of code in C#.
C#
33
star
34

StatePattern

An example of the State design pattern in C#
C#
30
star
35

MinimalWebApi

A minimal Web API for ASP.NET Core
C#
29
star
36

BlazorAuth

A sample showing how to add ASP.NET Core Identity auth options to the basic Blazor app.
C#
27
star
37

EditorConfig

A sample editorconfig file for use with .NET / C# applications
27
star
38

DevResources

A list of links to resources.
26
star
39

NotFoundMiddlewareSample

Middleware for detecting and correcting website 404 errors.
C#
25
star
40

DomainModeling

Some examples showing domain modeling tips and traps
C#
21
star
41

AggregateEvents

A sample showing how to use domain events within aggregates
C#
19
star
42

Specification.EFCore

Some utilities for EF Core to use Specifications
C#
16
star
43

LazyLoading

C#
16
star
44

TestSecureApiSample

A sample showing how to test a secure API endpoint using xunit, identityserver4, and environment variables
JavaScript
15
star
45

EulerCSharpStarter

A starting point for solving Project Euler problems using C#.
C#
14
star
46

EnumAlternative

A sample demonstrating strongly typed C# alternatives to enums.
C#
13
star
47

RouteAndBodyModelBinding

A model binder for ASP.NET Core that supports pulling in values from the route and body of a request.
C#
13
star
48

DoubleDispatchSamples

Some examples of double dispatch in C# and how to use in domain models.
C#
13
star
49

ardalis-com-gatsby

Back end content for ardalis.com running with Netlify and Gatsby.
JavaScript
12
star
50

GildedRoseStarter

A starting point for the Gilded Rose kata using dotnet core, C#, and xunit.
C#
11
star
51

ValueObjectsDemo

C#
10
star
52

MongoDbDotNetHelloWorld

Demonstrating how to get started with MongoDB as quickly as possible in dotnet
C#
10
star
53

TestingLogging

A sample showing how to test logging is working as expected in ASP.NET Core apps.
C#
10
star
54

GuardClauses.Analyzers

A project for holding Roslyn analyzers that leverage Ardalis.GuardClauses
C#
10
star
55

ValidateModel

A very small library/package that includes a model validation attribute for ASP.NET Core projects.
C#
9
star
56

RegExLib.com

Source code for the regexlib.com regular expression library site.
C#
9
star
57

SoftwareQualityWorkshop2020

Public resources supporting private software quality workshops I'm delivering in 2020.
C#
9
star
58

RepoMultiImplementation

C#
8
star
59

MediatREndpointsSample

A sample using MediatR to map endpoints using minimal apis
C#
8
star
60

DomainEventsDemo

A sample project showing how to wire up Domain Events
C#
8
star
61

MessagingDemo

C#
8
star
62

RefactoringKataAttempts2024

C#
8
star
63

AspNetCoreNewIn21

Samples showing what's new in ASP.NET Core 2.1
JavaScript
7
star
64

CSharpPropertiesExamples

Some examples of when and how C# properties work in different application scenarios.
C#
6
star
65

EulerNodeStarter

A starting point for solving Project Euler problems using node.js
JavaScript
6
star
66

RefactoringSamples

C#
6
star
67

UnitTestPerf

Some projects used to demonstrate the performance of different unit testing frameworks for .NET Core
C#
6
star
68

AccessTokenSample

A small sample showing how to create, manage, use access tokens.
C#
6
star
69

IntegrationEventSample

Sample showing Domain Events and Integration Events with Web API 2
C#
6
star
70

azure-cloud-native-book

5
star
71

ExtensionMethodSample

Some simple extension method samples
C#
5
star
72

Cachify

A .NET library to optimize data and reflection-based operations using caching.
PowerShell
5
star
73

Refactoring-To-SOLID-Pluralsight

Resources for my Pluralsight course on Refactoring to SOLID C# Code
C#
5
star
74

CodinGameFall2022

C#
5
star
75

EFCoreOwnedEntityTests

A unit test project that tests owned entities in EF Core
C#
5
star
76

EFCoreStringInterpolationDemo

Showing new EF Core 2.0 feature for raw SQL queries
C#
5
star
77

Diagrams

Text and images from UML and similar diagrams
5
star
78

RedisDotNetHelloWorld

Getting started with Redis in dotnet
C#
5
star
79

WhenAllTest

A simple app showing perf gains of using WhenAll for parallel task execution.
C#
5
star
80

AdventOfCode2022

Working on this: https://adventofcode.com for 2022
C#
4
star
81

ardalis

4
star
82

StructureMapLoggingSample

C#
4
star
83

ConsoleLoggingSample

A sample using NLog to create custom loggers that include app-specific data in the output.
C#
4
star
84

ValidationRules

A collection of validation rules for VS Web Tests
C#
3
star
85

CraftsmanshipCalendarIdeas

Ideas for next year's software craftsmanship calendar
3
star
86

CodinGameSpring2021

C#
3
star
87

Logging

Some logging utilities for .NET Core
C#
3
star
88

MigrateDotNetWithIIS

JavaScript
3
star
89

CastleWindsorSample

A console app showing the basic setup of Castle Windsor for IOC
C#
3
star
90

GeekDinnerSample

An incomplete sample showing clean code and DI in ASP.NET Core
C#
3
star
91

RegExLib

Regexlib.com source
C#
3
star
92

MontyHallMvc

An ASP.NET MVC application that lets users try out the Monty Hall problem and shows their stats.
C#
3
star
93

yarp-passthrough

The simplest YARP ASP.NET Core app that just passes everything through to another domain.
C#
3
star
94

AggregatePatterns

C#
2
star
95

EulerNode

A node.js implementation of Project Euler problems.
JavaScript
2
star
96

TennisGameKatas

C#
2
star
97

AspNetCoreLabs

2
star
98

EFCoreFeatureTests

Tests demonstrating EF Core features across versions.
C#
2
star
99

TestRepo

Just a repo for testing some things
2
star
100

FizzBuzzVS2015

A sample FizzBuzz kata implementation using VS2015
Smalltalk
2
star