| name | drn-sharedkernel |
| description | DRN.Framework.SharedKernel - Foundational domain primitives, exception hierarchy, repository contracts and cancellation semantics, pagination, JSON conventions, app constants, and shared extensions. Keywords: entity, aggregate-root, domain-event, repository, repository-cancellation, cancellation, pagination, exception, json, domain-modeling, source-known-id, entity-type, appconstants, path-extensions |
| last-updated | "2026-07-15T00:00:00.000Z" |
| difficulty | intermediate |
| tokens | ~2.5K |
DRN.Framework.SharedKernel
Lightweight domain primitives, exceptions, and shared code. No external DRN dependencies — safe for Contract and Domain layers.
When to Apply
- Defining domain entities and aggregates
- Working with domain events
- Using or extending DRN exceptions
- Understanding repository contracts
- Accessing JSON serialization conventions
- Using shared casing or safe path extensions in lower-layer packages
DiSCOS Alignment
| DiSCOS Principle | SharedKernel Expression |
|---|
| Security First | Source-Known IDs hide internal long behind external Guid; MaliciousRequestException aborts connection |
| Abstraction | Domain primitives (SourceKnownEntity, AggregateRoot, DomainEvent) encode patterns; implement specifics |
| TRIZ (Separation in Space) | Dual-ID system resolves DB performance vs. external security without tradeoff |
Entity Base Class
public abstract class SourceKnownEntity(long id = 0)
{
public long Id { get; internal set; }
[ConcurrencyCheck]
public DateTimeOffset ModifiedAt { get; protected internal set; }
public DateTimeOffset CreatedAt { get; }
public SourceKnownEntityId EntityIdSource { get; internal set; }
public Guid EntityId => EntityIdSource.EntityId;
public SourceKnownEntityId GetEntityId(Guid id, byte entityType);
public SourceKnownEntityId GetEntityId<TEntity>(Guid id);
public SourceKnownEntityId ToSecure(SourceKnownEntityId id);
public SourceKnownEntityId ToPlain(SourceKnownEntityId id);
internal void MarkAsCreated();
internal void MarkAsModified();
internal void MarkAsDeleted();
}
[!WARNING]
Each entity requires unique [EntityType(byte)]. DrnContext validates at startup.
AggregateRoot
public abstract class AggregateRoot(long id = 0) : SourceKnownEntity(id);
public abstract class AggregateRoot<TModel>(long id = 0) : AggregateRoot(id), IEntityWithModel<TModel>
{
public TModel Model { get; set; } = null!;
}
Domain Events
public interface IDomainEvent { Guid Id { get; } DateTimeOffset Date { get; } Guid EntityId { get; } }
public abstract class DomainEvent(SourceKnownEntity entity) : IDomainEvent;
public abstract class EntityCreated(SourceKnownEntity entity) : DomainEvent(entity);
public abstract class EntityModified(SourceKnownEntity entity) : DomainEvent(entity);
public abstract class EntityDeleted(SourceKnownEntity entity) : DomainEvent(entity);
Source-Known Identity System
Balances DB performance (long) with external security (Guid) and type safety. ISourceKnownEntityIdOperations defines the core contract (Generate, Parse, ToSecure, ToPlain) in SharedKernel; implemented by SourceKnownEntityIdUtils in Utils and injected into entities by EF interceptors.
public readonly record struct SourceKnownId(
long Id, DateTimeOffset CreatedAt, uint InstanceId, byte AppId, byte AppInstanceId);
public readonly record struct SourceKnownEntityId(
SourceKnownId Source, Guid EntityId, byte EntityType, bool Valid, bool Secure);
Validation Approaches
1. Injectable Utility (service layer — recommended):
var id = sourceKnownEntityIdUtils.Validate<User>(externalGuid);
2. Repository (data entry point):
var id = userRepository.GetEntityId(externalGuid);
3. Domain Entity (intra-domain):
var id = userInstance.GetEntityId<User>(externalGuid);
Secure ↔ Plain Conversion
Available on entity, repository, and injectable utility — idempotent:
var secureId = entity.ToSecure(entityId);
var plainId = entity.ToPlain(entityId);
Repository Contract
public interface ISourceKnownRepository<TEntity> where TEntity : AggregateRoot
{
RepositorySettings<TEntity> Settings { get; set; }
CancellationToken CancellationToken { get; }
void CancelWhen(CancellationToken token);
void CancelChanges();
Task<int> SaveChangesAsync();
SourceKnownEntityId GetEntityId(Guid id, bool validate = true);
SourceKnownEntityId? GetEntityId(Guid? id, bool validate = true);
SourceKnownEntityId GetEntityId<TOtherEntity>(Guid id) where TOtherEntity : SourceKnownEntity;
SourceKnownEntityId? GetEntityId<TOtherEntity>(Guid? id) where TOtherEntity : SourceKnownEntity;
SourceKnownEntityId[] GetEntityIds(IReadOnlyCollection<Guid> ids, bool validate = true);
SourceKnownEntityId?[] GetEntityIds(IReadOnlyCollection<Guid?> ids, bool validate = true);
Task<TEntity> GetAsync(Guid id);
Task<TEntity> GetAsync(SourceKnownEntityId id);
Task<TEntity?> GetOrDefaultAsync(Guid id, bool validate = true);
Task<TEntity?> GetOrDefaultAsync(SourceKnownEntityId id, bool validate = true);
Task<TEntity[]> GetAsync(IReadOnlyCollection<Guid> ids);
Task<TEntity[]> GetAsync(IReadOnlyCollection<SourceKnownEntityId> ids);
Task<TEntity[]> GetAllAsync();
Task<bool> AnyAsync(Expression<Func<TEntity, bool>>? predicate = null);
Task<bool> AllAsync(Expression<Func<TEntity, bool>> predicate);
Task<long> CountAsync(Expression<Func<TEntity, bool>>? predicate = null);
void Add(params IReadOnlyCollection<TEntity> entities);
void Remove(params IReadOnlyCollection<TEntity> entities);
Task<int> CreateAsync(params IReadOnlyCollection<TEntity> entities);
Task<int> DeleteAsync(params IReadOnlyCollection<TEntity> entities);
Task<int> DeleteAsync(params IReadOnlyCollection<Guid> ids);
Task<int> DeleteAsync(params IReadOnlyCollection<SourceKnownEntityId> ids);
Task<PaginationResultModel<TEntity>> PaginateAsync(PaginationRequest request, EntityCreatedFilter? filter = null);
Task<PaginationResultModel<TEntity>> PaginateAsync(PaginationResultInfo? resultInfo = null, long jumpTo = 1,
int pageSize = -1, int maxSize = -1, PageSortDirection direction = PageSortDirection.None,
long totalCount = -1, bool updateTotalCount = false);
IAsyncEnumerable<PaginationResultModel<TEntity>> PaginateAllAsync(PaginationRequest request, EntityCreatedFilter? filter = null);
}
Key Behaviors:
Get(OrDefault)Async with Guid auto-validates ID format and EntityType byte before querying
PaginateAllAsync returns IAsyncEnumerable for efficient large dataset streaming
CancellationToken exposes the repository-group token, CancelWhen(token) links a lifetime token, and CancelChanges cancels that group.
- Group selection is implementation-defined. The default EntityFramework implementation groups repositories by concrete type; see drn-entityframework.
- See drn-utils for root-wide and operation-only cancellation.
Pagination
public class PaginationRequest
{
public long PageNumber { get; init; }
public PageSize PageSize { get; init; }
public PageCursor PageCursor { get; init; }
public static PaginationRequest Default;
}
public class PaginationResultModel<TModel>
{
public IReadOnlyList<TModel> Items { get; }
public PaginationResultInfo Info { get; }
public PaginationResultModel<TMapped> ToModel<TMapped>(Func<TModel, TMapped> mapper);
}
- Stable Navigation:
PageCursor with FirstId/LastId prevents data inconsistencies during concurrent viewing
- Bi-directional:
RequestNextPage(), RequestPreviousPage(), RequestPage(n) on PaginationResultInfo
- URL-Serializable:
PaginationRequest natively works with [FromQuery]
- Filtering:
EntityCreatedFilter.After(date) for date-based filtering
var filter = EntityCreatedFilter.After(DateTimeOffset.UtcNow.AddDays(-7));
var result = await repository.PaginateAsync(request, filter);
return result.ToModel(entity => entity.ToDto());
DTO Rules
[!IMPORTANT]
- DTOs must derive from
Dto base class (primary constructor accepting SourceKnownEntity?)
- Public APIs must never return Entities — always DTOs
- Expose only
Guid IDs, never long Id or SourceKnownEntityId
Exceptions
| Factory | Exception | HTTP Status |
|---|
ExceptionFor.Validation(msg) | ValidationException | 400 |
ExceptionFor.Unauthorized(msg) | UnauthorizedException | 401 |
ExceptionFor.Forbidden(msg) | ForbiddenException | 403 |
ExceptionFor.NotFound(msg) | NotFoundException | 404 |
ExceptionFor.Conflict(msg) | ConflictException | 409 |
ExceptionFor.Expired(msg) | ExpiredException | 410 |
ExceptionFor.UnprocessableEntity(msg) | UnprocessableEntityException | 422 |
ExceptionFor.Configuration(msg) | ConfigurationException | 500 |
ExceptionFor.MaliciousRequest(msg) | MaliciousRequestException | Abort |
All factories accept optional category parameter for sub-classification: ExceptionFor.NotFound("User not found", category: "Users"). Exceptions expose Category and Status properties.
JSON Conventions
JsonConventions.DefaultOptions — globally applied: Web defaults, enum→string, long→string, camelCase, AllowTrailingCommas, MaxDepth=32. Auto-applied by DrnTestContext and DrnProgramBase.
AppConstants
AppConstants.ProcessId; AppConstants.AppInstanceId;
AppConstants.EntryAssemblyName; AppConstants.EntryAssemblyNameNormalized;
AppConstants.EntryAssemblyFullName;
AppConstants.LocalAppDataPath; AppConstants.TempPath;
AppConstants.LocalIpAddress;
TempPath order: DrnAppDataSettings__TempPath -> DrnAppDataSettings__DataPath/Temp -> local app data Temp. LocalAppDataPath order: DrnAppDataSettings__DataPath -> app-specific local app data. IAppData creates/cleans directories and resolves safe child paths.
Shared Extensions
using DRN.Framework.SharedKernel.Extensions;
"OrderHistory".ToSnakeCase();
"sample hosted".ToPascalCase();
"/data/app".GetPathWithinDirectory("exports", "orders.json");
GetPathWithinDirectory() rejects parent-directory and symbolic-link traversal. Use it for file-serving, manifests, uploads, and app-data child paths.
Attributes
| Attribute | Purpose |
|---|
[IgnoreLog] | Exclude properties from scoped logging |
[SecureKey] | Validate string meets secure key requirements |
[EntityType(byte)] | Unique entity type ID for Source-Known IDs |
Related Skills
Global Usings
global using DRN.Framework.SharedKernel.Domain;
global using DRN.Framework.SharedKernel;
global using DRN.Framework.SharedKernel.Extensions;
global using DRN.Framework.SharedKernel.Json;