ddd-net-ef-core
ddd-net-ef-core copied to clipboard
Self study: DDD, .net core, entity framework core
An Example of using DDD with .NET 8
- Domain Driven Design (aka DDD)
- .NET 8.0.202
Overview
Domain Models
In the Product Catalog bounded context, I have a following Aggregate-Roots, Entities and Value Objects
-
Aggregate-Roots:
- Product
- Category
- Catalog
-
Entities:
- CatalogCategory
- CatalogProduct
-
Value Objects:
- CategoryId
- ProductId
- CatalogId
- CatalogCategoryId
- CatalogProductId
Aggregate Root Relationships
-
Catalog
andCategory
- The
CatalogCategory
represents the instance ofCategory
in specificCatalog
- The
-
Category
andCategory
- In specific
Catalog
, the categories can be organized as tree structure. Therefore, theCatalogCategory
can have another as its parent.
- In specific
-
Product
,Category
andCatalog
- The
CatalogProduct
represents the instance ofProduct
in specificCatalogCategory
- The
CQRS
-
DDD.ProductCatalog.Application.Commands
: for Create, Update and delete operators by consuming repositories. -
DDD.ProductCatalog.Application.Queries
: for all of the query operators by using Dapper
Highlighted Points
Strongly-Typed Entities Id
I want to use Strongly-Typed Ids for all models (i.e. CatalogId
, CatalogCategoryId
and so on) because of the benefits that described very well in the series of Using strongly-typed entity IDs to avoid primitive obsession part 1, part-2, part-3. Therefore, I have to add some advance steps to accomplish this need
- For EntityFramework Core
-
Use Value Conversion feature to define the mapping.
builder .Property(x => x.Id) .UsePropertyAccessMode(PropertyAccessMode.Field) .HasConversion(x => x.Id, id => (CatalogId)id);
- For Dapper
-
Custom
SqlMapper.TypeHandler
public class StronglyTypedIdMapper<TIdenity> : SqlMapper.TypeHandler<TIdenity> where TIdenity : IdentityBase { #region Overrides of TypeHandler<TIdenityType> public override void SetValue(IDbDataParameter parameter, TIdenity value) { parameter.Value = value.Id; } public override TIdenity Parse(object value) { return IdentityFactory.Create<TIdenity>(value); } #endregion }
- For .NET Core
-
Custom
TypeConverter
public class StronglyTypedIdConverter<TIdentity> : TypeConverter where TIdentity : IdentityBase { #region Overrides of TypeConverter public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) { return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType); } public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { var stringValue = value as string; if (!string.IsNullOrEmpty(stringValue) && Guid.TryParse(stringValue, out var guid)) { return IdentityFactory.Create<TIdentity>(guid); } return base.ConvertFrom(context, culture, value); } #endregion }
-
Custom
JsonConverter
public class IdentityJsonConverter<TIdentity> : JsonConverter<TIdentity> where TIdentity : IdentityBase { public override bool CanConvert(Type typeToConvert) { return typeToConvert == typeof(TIdentity); } public override TIdentity Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return IdentityFactory.Create<TIdentity>(reader.GetGuid()); } public override void Write(Utf8JsonWriter writer, TIdentity value, JsonSerializerOptions options) { writer.WriteStringValue(value.Id); } }
For CQRS
- Use MediatR for commands/queries dispatcher
- Use FluentValidation for commands/queries validation
For Testing
About Test Projects
-
DDD.ProductCatalog.Core.Tests
- Unit Test for the behaviors of Domain Models
-
DDD.ProductCatalog.Infrastructure.EfCore.Tests
- Integration Test with EntityFramework Core and SqlServer for repositories of Aggregate-Roots
-
DDD.ProductCatalog.Application.Commands.Tests
- Unit Test of Command Handlers.
- Use MockQueryable.FakeItEasy to mock
DbSet
.
-
DDD.ProductCatalog.Application.Queries.Tests
- Integration Test with Dapper and SqlServer for Query Handlers.
-
DDD.ProductCatalog.WebApi.Tests
- Integration Test with Web Api
Interesting Points
For every test project, I use the following packages
- Shoudly: Should testing for .NET - the way Asserting Should be!
- AutoFixture: AutoFixture is an open source library for .NET designed to minimize the 'Arrange' phase of your unit tests in order to maximize maintainability. Its primary goal is to allow developers to focus on what is being tested rather than how to setup the test scenario, by making it easier to create object graphs containing test data.
- MSSQL TestContainer to isolating database from integration test with others environment
-
Memory Configuration Provider to override the settings for testing, i.e -
connection-string
- EF Core Migration Bundles
How to run
Run Test Projects
Install Cake Tool
dotnet tool install --global Cake.Tool --version 3.1.0
Starting SqlServer via Docker
docker compose -f docker-compose.yaml up -d
Run via Tye
- Starting web api via Tye
dotnet-cake.exe "./cake/dev.cake" --target="Tye" --verbosity=normal
- Access the url
http://localhost:5009
How to see the codecoverage report
- Execute tests & generate report
dotnet-cake.exe "./cake/report-tests.cake" --target="Report" --verbosity=normal
- After run successfully, go to
code_coverage
folder, and open theindex.html
by browser to see the report
Give a Star! :star2:
If you liked this project or if it helped you, please give a star :star2: for this repository. Thank you!!!