Goodtocode.Domain
1.1.24
dotnet add package Goodtocode.Domain --version 1.1.24
NuGet\Install-Package Goodtocode.Domain -Version 1.1.24
<PackageReference Include="Goodtocode.Domain" Version="1.1.24" />
<PackageVersion Include="Goodtocode.Domain" Version="1.1.24" />
<PackageReference Include="Goodtocode.Domain" />
paket add Goodtocode.Domain --version 1.1.24
#r "nuget: Goodtocode.Domain, 1.1.24"
#:package Goodtocode.Domain@1.1.24
#addin nuget:?package=Goodtocode.Domain&version=1.1.24
#tool nuget:?package=Goodtocode.Domain&version=1.1.24
Goodtocode.Domain
Domain-Driven Design (DDD) base library for .NET Standard 2.1 and modern .NET projects.
Goodtocode.Domain provides foundational types for building DDD, clean architecture, and event-driven systems. It includes base classes for domain entities, audit fields, domain events, and secured/multi-tenant entities. The library is lightweight, dependency-free, and designed to work with EF Core, Cosmos DB, or custom repositories.
Target Frameworks
- Library:
netstandard2.1 - Tests/examples:
net10.0
Features
- Domain entity base with audit fields (
CreatedOn,ModifiedOn,DeletedOn,Timestamp) - Domain event pattern and dispatcher (
IDomainEvent,IDomainHandler,DomainDispatcher) - Equality and identity management for aggregate roots
- Partition key support for document stores (
PartitionKeydefaults toId.ToString(); for secured entities, defaults toTenantId.ToString()) - Secured entity base for multi-tenancy and ownership (
OwnerId,TenantId,CreatedBy,ModifiedBy,DeletedBy) - Extension methods for authorization and ownership queries
- Invariant state protection for audit and security fields (fields are only set if not already set, ensuring consistency and preventing accidental overwrites)
Install
dotnet add package Goodtocode.Domain
Quick-Start (Repo)
- Clone this repository
git clone https://github.com/goodtocode/aspect-domain.git - Build the solution
cd src dotnet build Goodtocode.Domain.sln - Run tests
cd Goodtocode.Domain.Tests dotnet test
Core Concepts
DomainEntity<TModel>: Base entity with audit fields (CreatedOn,ModifiedOn,DeletedOn,Timestamp), identity (Id), partition key, and domain event tracking.SecuredEntity<TModel>: ExtendsDomainEntity<TModel>withOwnerId,TenantId, and audit fields for user actions (CreatedBy,ModifiedBy,DeletedBy).PartitionKeydefaults toTenantId.ToString()for multi-tenant isolation.- Invariant state protection: Methods like
MarkCreated,MarkDeleted, etc. only set fields if not already set, ensuring entity state is consistent and protected from accidental changes. - Domain events: Implement
IDomainEvent<TModel>and dispatch withDomainDispatcher.
Key Examples
1. Basic Domain Entity with Audit Fields
using Goodtocode.Domain.Entities;
public sealed class MyEntity : DomainEntity<MyEntity>
{
public string Name { get; private set; } = string.Empty;
public int Value { get; private set; }
private MyEntity() { }
public MyEntity(Guid id, string name, int value) : base(id)
{
Name = name;
Value = value;
}
}
2. Secured Entity with Multi-Tenant Ownership
using Goodtocode.Domain.Entities;
public sealed class Document : SecuredEntity<Document>
{
public string Title { get; private set; } = string.Empty;
private Document() { }
public Document(Guid id, Guid ownerId, Guid tenantId, string title) : base(id, ownerId, tenantId)
{
Title = title;
}
}
// Query helpers
var ownedDocuments = queryableDocuments.WhereOwner(ownerId);
var tenantDocuments = queryableDocuments.WhereTenant(tenantId);
var authorized = queryableDocuments.WhereAuthorized(tenantId, ownerId);
person.ClearDomainEvents();
3. Domain Events + Dispatcher
using Goodtocode.Domain.Entities;
using Goodtocode.Domain.Events;
public sealed class Person : SecuredEntity<Person>
{
public string Email { get; private set; } = string.Empty;
public Person(Guid id, Guid ownerId, Guid tenantId, string email)
: base(id, ownerId, tenantId)
{
Email = email;
AddDomainEvent(new PersonCreatedEvent(this));
}
}
public sealed class PersonCreatedEvent : IDomainEvent<Person>
{
public Person Item { get; }
public DateTime OccurredOn { get; }
public PersonCreatedEvent(Person person)
{
Item = person;
OccurredOn = DateTime.UtcNow;
}
}
public sealed class PersonCreatedHandler : IDomainHandler<PersonCreatedEvent>
{
public Task HandleAsync(PersonCreatedEvent domainEvent)
{
Console.WriteLine($"Created: {domainEvent.Item.Email}");
return Task.CompletedTask;
}
}
// Dispatcher usage (with your DI container)
var serviceProvider = new ServiceCollection();
serviceProvider.AddTransient<IDomainHandler<PersonCreatedEvent>, PersonCreatedHandler>();
serviceProvider.BuildServiceProvider();
var dispatcher = new DomainDispatcher(serviceProvider);
await dispatcher.DispatchAsync(person.DomainEvents);
person.ClearDomainEvents();
Integrating with EF Core: Audit & Security Field Automation
To ensure audit and security fields are set correctly and invariant state is protected, you must wire up your DbContext to set these fields during the entity lifecycle.
Example:
public class ExampleDbContext : DbContext
{
private readonly ICurrentUserContext _currentUserContext;
public ExampleDbContext(DbContextOptions options, ICurrentUserContext currentUserContext)
: base(options)
{
_currentUserContext = currentUserContext;
}
public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
SetAuditFields();
SetSecurityFields();
return base.SaveChangesAsync(cancellationToken);
}
private void SetAuditFields()
{
foreach (var entry in ChangeTracker.Entries())
{
if (entry.Entity is IAuditable auditable)
{
if (entry.State == EntityState.Modified)
{
auditable.MarkModified();
}
else if (entry.State == EntityState.Deleted)
{
auditable.MarkDeleted();
entry.State = EntityState.Modified;
}
}
}
}
private void SetSecurityFields()
{
if (_currentUserContext is null) return;
foreach (var entry in ChangeTracker.Entries())
{
if (entry.Entity is ISecurable securable)
{
if (entry.State == EntityState.Added)
{
if (securable.OwnerId == Guid.Empty)
securable.ChangeOwner(_currentUserContext.OwnerId);
if (securable.TenantId == Guid.Empty)
securable.ChangeTenant(_currentUserContext.TenantId);
}
}
}
}
}
Note:
- This pattern should be implemented in your infrastructure layer (not in the domain library).
- See
Goodtocode.Domain.Tests/Examples/ExampleDbContext.csfor a working reference
Complete Examples
See the fully working examples in the test project:
Goodtocode.Domain.Tests/Examples/RowLevelSecurityExample.cs(row-level security, audit fields, and partition key usage)Goodtocode.Domain.Tests/Examples/CommandHandlerWithEventsExample.cs(command handlers, domain events, dispatcher, and service bus integration)Goodtocode.Domain.Tests/Examples/ExampleDbContext.cs(EF Core integration for audit and security fields)
Technologies
Version History
| Version | Date | Release Notes |
|---|---|---|
| 1.0.0 | 2026-Jan-19 | Initial release |
License
This project is licensed with the MIT license.
Contact
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. 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 was computed. 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. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.1 is compatible. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.1
- No dependencies.
-
net10.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.