Centeva.DomainModeling
10.1.0
Prefix Reserved
dotnet add package Centeva.DomainModeling --version 10.1.0
NuGet\Install-Package Centeva.DomainModeling -Version 10.1.0
<PackageReference Include="Centeva.DomainModeling" Version="10.1.0" />
paket add Centeva.DomainModeling --version 10.1.0
#r "nuget: Centeva.DomainModeling, 10.1.0"
// Install Centeva.DomainModeling as a Cake Addin #addin nuget:?package=Centeva.DomainModeling&version=10.1.0 // Install Centeva.DomainModeling as a Cake Tool #tool nuget:?package=Centeva.DomainModeling&version=10.1.0
Centeva.DomainModeling
This package contains types (classes and interfaces) for building a rich domain layer for your application using some Domain Driven Design tactical patterns.
Built With
Technical Patterns
You can use these coding patterns as part of a Domain Driven Design approach to building your application. You don't have to be using DDD to benefit from these patterns, but they are intended to be used together.
To find out more about this approach, here are some resources:
- Domain Driven Design by Eric Evans (book)
- Implementing Domain Driven Design by Vaughn Vernon (book)
- DDD Part 2: Tactical Domain-Driven Design (blog post)
- Centeva Web API Template (template project)
Entities
An Entity is a plain object for which its identity is important. This is
implemented with a unique Id
that is assigned when the entity is created and
is not changed for the lifetime of the entity.
Two instances of an entity type that have the same Id
should be considered to
be equivalent.
An entity is mutable and its properties can be changed. However, it is preferrable to avoid having public setters for all of those properties. Instead you should use methods to update the entity's properties. This allows you to enforce invariants (validation rules) and to publish Domain Events when the entity is changed. Use additional measures such to protect an entity's invariants such as constructors, guard clauses, and read-only collections.
In most cases your project will involve persisting entities to some kind of data
storage, such as a database. However, the details of such persistence should
not be contained within the definitions of those entities. (Avoid things like
Entity Framework annotation attributes like [Table]
.)
The BaseEntity
class can be inherited for your project's entities.
- The
Id
property (your entity's unique identifier) has a public setter but try to avoid using it in application code, especially if your database is auto-generating values. However, it can be helpful when seeding data both in tests and in your application.
Value Objects
A Value Object represents something in your domain which determines its identity by its properties. Two value object instances are considered equal if their relevant properties are equal. Because of this, a value object is ideally immutable. For example, two Addresses are considered equal if they have the same street address, city, state, and zip code.
Value object classes can and should contain business logic, especially for ensuring valid properties.
Entities can (and should) contain value objects, but value objects should never contain entities.
Your value object classes should inherit from the ValueObject
class to gain
equality functionality.
See https://enterprisecraftsmanship.com/posts/value-objects-explained/ for more information about this concept.
Aggregates
An Aggregate is a collection of domain objects (Entities and Value Objects) that is treated as a single unit for manipulation and enforcement of invariants. An aggregate should adhere to the following rules:
- The aggregate is created, retrieved, and updated as a whole.
- The aggregate is always in a constistent and valid state.
- One of the entities in an aggregate is the main entity or "root" and holds references to the other ones.
- An aggregate should only reference the root of other aggregates.
You can use the IAggregateRoot
interface to mark the roots of your aggregates.
This is just a marker interface (no properties or methods) and it's up to you to
enforce the Aggregate pattern. (See below for information about enforcing in
your repositories.)
Domain Events
Domain Events describe things that happen in your domain model. They are typically used to publish information about changes to your entities. These events will be interest to other parts of your model, and can be handled to produce side effects, such as sending emails or updating other entities.
Each entity inheriting from BaseEntity
contains a DomainEvents
list which
you can use for storing and later publishing Domain Events. You will use the
IDomainEventDispatcher
in your application to publish and handle these, likely
inside of your Entity Framework DbContext
or a domain service.
Repositories
Repository is a pattern used to control and constrain access to data. It defines standard CRUD operations on a set of entities of the same type. If you are implementing Aggregates, your repositories should only operate on the root of each Aggregate, as child entities should never be directly accessed.
Read-only operations are defined in IReadRepository
while
IRepository
adds update operations to those. This not only better adheres
to the Interface Segregation Principle, but allows implementers to add features
such as caching that would only apply to read operations.
You can inherit from these interfaces in your own project if you need to extend the default functionality.
The package Centeva.DomainModeling.EFCore
provides an abstract implementation
of IRepository
named BaseRepository
. See below for details.
Note that the provided Repository implementation will call EF Core's
SaveChangesAsync
method on each operation. If you need some other behavior
then you may need to modify for your project.
Specifications
Specification is a pattern used to pull query logic out of other places in an
application and into self-contained, shareable, testable classes. This
eliminates the need to add custom query methods to your Repository, and avoids
other anti-patterns such as leaked IQueryable
objects.
See the documentation for the Ardalis.Specification library for more information and examples.
Domain Services
Domain Services are used to encapsulate domain logic that doesn't belong in an entity or value object. They are typically used to coordinate operations between multiple entities or aggregates. They are also useful for encapsulating domain logic that is not specific to a single entity or aggregate.
Domain Services can publish Domain Events. For example, a CustomerService
might publish a CustomerDeletedEvent
when a customer is deleted, since the
Customer
entity itself cannot publish an event when it is deleted.
There is no base implementation of a Domain Service in this library. You can create regular C# classes and interfaces for these when they have dependencies on other parts of your application, or use a static class and method for simpler cases.
Factories
Factories are used to encapsulate logic for creating new aggregates. They are useful when:
- Complex business logic is involved in creating an aggregate.
- You need to create an aggregate differently depending on the inputs.
- There is a large amount of input data.
- You need to create multiple aggregates at once.
There is no base implementation of a Factory in this library. You can create one via a static method on an aggregate class for simple cases, or with a separate factory class for more complex cases.
Getting Started
Add a reference to Centeva.DomainModeling
in your project.
You should only need to reference this package from the lowest layer of your solution. If you are using multiple projects to separate Core/Domain, Application, and Web API layers (i.e., "Clean" or "Ports and Adapters" architecture) then reference from the Core project.
Using Entity Framework Core
Reference the Centeva.DomainModeling.EFCore
package to get a base
implementation of the Repository pattern for this ORM. Reference this package
from your Infrastructure project if your solution separates concerns by project.
Create a derived class in your Infrastructure project that inherits from
BaseRepository
and implements the interfaces:
public class EfRepository<T> : BaseRepository<T> where T : class, IAggregateRoot
{
public EfRepository(ApplicationDbContext dbContext)
: base(dbContext) { }
}
Register your derived Repository class, the domain event dispatcher, and EF Core with your application's dependency injection container:
services.AddScoped(typeof(IRepository<>), typeof(EfRepository<>));
services.AddScoped(typeof(IReadRepository<>), typeof(EfRepository<>));
services.AddScoped<IDomainEventDispatcher, MediatRDomainEventDispatcher>();
services.AddDbContext...
serviecs.AddMediatR...
See the documentation for the Entity Framework Core and MediatR packages for more information on how to use them.
Use your repository by injecting it into your application's services. For example:
public class CustomerService
{
private readonly IRepository<Customer> _customerRepository;
public CustomerService(IRepository<Customer> customerRepository)
{
_customerRepository = customerRepository;
}
//...
}
Running Tests
From Windows, use the dotnet test
command, or your Visual Studio Test
Explorer. Integration tests will use an in-memory SQLite database.
Deployment
Use dotnet pack
to generate a NuGet package. This library is versioned by
GitVersion. Create a Git tag for an official release
(e.g., "v1.0.0").
Contributing
Please use a Pull Request to suggest changes to this library. You should not add any functionality or dependency that is not appropriate for use at the lowest level (the "domain" level) of an application.
Resources
Take a look at https://bitbucket.org/centeva/centeva.templates for more ideas on how to use this library in your application.
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. |
-
net6.0
- Ardalis.Specification (>= 8.0.0)
- MediatR (>= 12.4.0)
-
net8.0
- Ardalis.Specification (>= 8.0.0)
- MediatR (>= 12.4.0)
NuGet packages (2)
Showing the top 2 NuGet packages that depend on Centeva.DomainModeling:
Package | Downloads |
---|---|
Centeva.DomainModeling.EFCore
Entity Framework Core implementation of the Repository pattern from Centeva.DomainModeling |
|
Centeva.DomainModeling.Testing
Unit testing helpers for projects that use Centeva.DomainModeling |
GitHub repositories
This package is not used by any popular GitHub repositories.
Version | Downloads | Last updated |
---|---|---|
10.1.0 | 171 | 9/5/2024 |
10.1.0-tags-v10-1-0-pre-1.1 | 50 | 9/5/2024 |
10.0.0 | 133 | 9/5/2024 |
9.0.0-pre.1 | 122 | 12/29/2023 |
8.1.0 | 427 | 12/18/2023 |
8.0.0 | 279 | 10/30/2023 |
7.0.0 | 208 | 10/30/2023 |