Use Vulthil.SharedKernel for domain primitives reused across bounded contexts.
- Entities, aggregate roots, and domain event abstractions
- Shared domain exceptions and base contracts
- Keep business invariants inside domain types
- Raise domain events from aggregate roots
- Keep domain model independent from infrastructure concerns
Entities compare by identity, so the identifier type must have value equality — use a record (or
readonly record struct), not a plain class:
public sealed record UserId(Guid Value);
public sealed class User : AggregateRoot<UserId>
{
public string Email { get; private set; }
private User(UserId id, string email) : base(id)
{
Email = email;
}
public static User Create(string email)
{
var user = new User(new UserId(Guid.NewGuid()), email);
user.Raise(new UserCreatedEvent(user.Id));
return user;
}
}public sealed record UserCreatedEvent(UserId UserId) : IDomainEvent;public sealed class UserCreatedEventHandler : IDomainEventHandler<UserCreatedEvent>
{
public Task HandleAsync(UserCreatedEvent notification, CancellationToken cancellationToken)
{
// React to user creation
return Task.CompletedTask;
}
}DomainException is abstract and takes an Error — derive a concrete exception per invariant:
public sealed class UserNotFoundException : DomainException
{
public UserNotFoundException(UserId userId)
: base(Error.NotFound("User.NotFound", $"User {userId.Value} was not found"))
{ }
}