KUKULCAN.SharedKernel 1.0.0

dotnet add package KUKULCAN.SharedKernel --version 1.0.0
                    
NuGet\Install-Package KUKULCAN.SharedKernel -Version 1.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="KUKULCAN.SharedKernel" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="KUKULCAN.SharedKernel" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="KUKULCAN.SharedKernel" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add KUKULCAN.SharedKernel --version 1.0.0
                    
#r "nuget: KUKULCAN.SharedKernel, 1.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package KUKULCAN.SharedKernel@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=KUKULCAN.SharedKernel&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=KUKULCAN.SharedKernel&version=1.0.0
                    
Install as a Cake Tool

KUKULCAN.SharedKernel

A lightweight, domain-driven Shared Kernel for building enterprise-grade .NET applications.

.NET DDD Clean Architecture SOLID Semantic Versioning License


Overview

KUKULCAN.SharedKernel is a lightweight, highly cohesive, and dependency-minimal framework designed to provide the fundamental building blocks required by modern Domain-Driven Design (DDD) applications.

Unlike traditional utility libraries, this project is not intended to become a miscellaneous collection of helpers.

Its primary goal is to establish a stable architectural foundation shared by every bounded context of an enterprise solution.

The framework follows these principles:

  • Domain-Driven Design (DDD)
  • Clean Architecture
  • SOLID Principles
  • Immutability by default
  • Explicit Domain Modelling
  • Framework Independence
  • High Testability
  • Minimal Dependencies
  • Semantic Versioning

Philosophy

KUKULCAN.SharedKernel is built around a very strict design philosophy.

The framework intentionally avoids:

  • Helper classes
  • Manager classes
  • God objects
  • Static business logic
  • Hidden dependencies
  • Infrastructure coupling
  • Framework-specific abstractions

Instead, every component has one single responsibility and belongs to a clearly defined architectural module.

The result is a Shared Kernel that remains:

  • Predictable
  • Maintainable
  • Extensible
  • Easy to understand
  • Easy to evolve

Main Features

  • Strongly Typed Identifiers
  • Rich Value Objects
  • Aggregate Roots
  • Domain Events
  • Specification Pattern
  • Result Pattern
  • Maybe Pattern
  • Validation Model
  • Time Abstractions
  • Globalization Support
  • Semantic Versioning
  • Guard Clauses
  • Collection Utilities

Table of Contents


Installation

Install the NuGet package.

dotnet add package KUKULCAN.SharedKernel

Or using the Package Manager.

Install-Package KUKULCAN.SharedKernel

Requirements

  • .NET 10
  • C# 13
  • Nullable Reference Types enabled

Quick Start

The following example demonstrates the creation of a strongly typed identifier.

public sealed class CustomerId : EntityId<CustomerId>
{
    public CustomerId(Guid value)
        : base(value)
    {
    }
}

CustomerId id = new(Guid.NewGuid());

Creating a Result.

Result result = Result.Success();

Result<Customer> customer =
    Result.Success(new Customer(...));

Returning an error.

return Result.Failure(
    CommonErrors.NotFound(
        nameof(Customer),
        customerId));

Creating a Value Object.

public sealed class Email : ValueObject
{
    public Email(string value)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(value);

        Value = value;
    }

    public string Value { get; }

    protected override IEnumerable<object?> GetEqualityComponents()
    {
        yield return Value;
    }
}

Working with Specifications.

public sealed class ActiveCustomerSpecification
    : Specification<Customer>
{
    public override Expression<Func<Customer, bool>> ToExpression()
        => customer => customer.IsActive;
}

Using Domain Events.

public sealed class CustomerCreatedEvent : DomainEvent
{
    public CustomerCreatedEvent(CustomerId customerId)
    {
        CustomerId = customerId;
    }

    public CustomerId CustomerId { get; }
}

Publishing the event from an Aggregate Root.

AddDomainEvent(
    new CustomerCreatedEvent(Id));

Design Goals

KUKULCAN.SharedKernel has been designed around the following architectural goals.

Goal Description
Cohesion Every module has a single responsibility.
Low Coupling Modules have minimal dependencies.
Testability Every component can be tested independently.
Immutability Value Objects are immutable by design.
Extensibility New modules can be added without modifying existing ones.
Framework Independence Domain model never depends on infrastructure.
Maintainability Every module can evolve independently.

Architectural Principles

The framework follows these rules.

  • Domain first.
  • Infrastructure last.
  • No cyclic dependencies.
  • Explicit models.
  • Immutable Value Objects.
  • Aggregate consistency.
  • Rich domain model.
  • Strong typing over primitive obsession.
  • Public API kept intentionally small.
  • Internal implementation hidden whenever possible.

Architecture

KUKULCAN.SharedKernel has been designed as a true architectural foundation rather than as a collection of utility classes.

Every public type belongs to a well-defined architectural module with a single responsibility.

The framework follows the principles of:

  • Domain-Driven Design (DDD)
  • Clean Architecture
  • SOLID
  • Explicit Domain Modelling
  • Low Coupling
  • High Cohesion

One of the main goals of the project is to guarantee long-term maintainability by keeping module responsibilities clearly separated.


High-Level Architecture

graph TD

    Abstractions

    Identifiers
    Results
    Collections
    Guards

    Domain
    DomainEvents
    Specifications

    Validation

    Time
    Globalization
    Versioning

    Abstractions --> Identifiers

    Abstractions --> Results

    Abstractions --> Collections

    Abstractions --> Guards

    Identifiers --> Domain

    Results --> Domain

    Domain --> DomainEvents

    Domain --> Specifications

    Results --> Validation

    Abstractions --> Time

    Abstractions --> Globalization

    Domain --> Versioning

Architectural Rules

The framework follows a strict dependency model.

Rule 1

Dependencies always point towards lower-level modules.

Higher-level modules never introduce cyclic dependencies.


Rule 2

Domain code never depends on infrastructure.

The Domain layer only knows:

  • Abstractions
  • Identifiers
  • Results

Rule 3

Infrastructure concerns are represented only through contracts.

Concrete implementations belong to the application or infrastructure layers.


Rule 4

Every module has a single responsibility.

Examples:

Module Responsibility
Results Functional result model
Validation Validation model
Domain Domain base classes
DomainEvents Domain Event infrastructure
Specifications Specification Pattern
Globalization Culture and localization abstractions
Versioning Semantic Version model
Time Time abstractions

Module Organization

The project is intentionally divided into small independent modules.

KUKULCAN.SharedKernel
│
├── Abstractions
├── Attributes
├── Collections
├── Domain
├── DomainEvents
├── Exceptions
├── Globalization
├── Guards
├── Identifiers
├── Internals
├── Maybe
├── Results
├── Specifications
├── Time
├── Validation
└── Versioning

Each module exposes only the public types required by consumers.

Implementation details remain internal.


Module Responsibilities

Abstractions

Contains the fundamental contracts used across the framework.

Examples:

  • IEntity
  • IEntityId
  • IAggregateRoot
  • IClock
  • ILocalizationProvider

Domain

Provides the building blocks for Domain-Driven Design.

Includes:

  • Entity
  • AggregateRoot
  • AuditableEntity
  • ValueObject
  • Enumeration

Domain Events

Implements the Domain Event pattern.

Includes:

  • DomainEvent
  • IDomainEvent
  • IDomainEventDispatcher

Aggregate roots own and publish domain events while remaining infrastructure independent.


Results

Implements a functional result model inspired by railway-oriented programming.

Includes:

  • Error
  • Result
  • Result<T>
  • CommonErrors
  • CommonErrorCodes

Exceptions are reserved for programming errors.

Business failures are represented as Result objects.


Validation

Provides a lightweight validation model independent of any external validation framework.

Includes:

  • ValidationResult
  • ValidationFailure
  • ValidationExtensions
  • ValidationThrowExtensions

Specifications

Implements the Specification Pattern.

Specifications are composable and can be translated into LINQ expressions.


Time

Provides testable abstractions over system time.

Includes:

  • IClock
  • SystemClock
  • FakeClock

Globalization

Provides culture-aware abstractions without depending on any localisation framework.

Includes:

  • SupportedCulture
  • ICurrentCultureProvider
  • ILocalizationProvider
  • ITextLocalizer

Versioning

Provides Semantic Versioning support.

Includes:

  • SemanticVersion

The implementation follows Semantic Versioning 2.0.


Public API Philosophy

The framework intentionally exposes a very small public surface.

Only concepts representing part of the domain language become public.

Everything else remains internal.

This minimises breaking changes and simplifies long-term maintenance.


Design Decisions

Several architectural decisions intentionally differentiate this framework from traditional Shared Kernels.

Rich Domain Model

Entities contain behaviour.

Business rules belong inside the domain model.


Strong Typing

Primitive obsession is avoided through strongly typed identifiers and value objects.


Immutability

Value Objects are immutable by design.


Explicit Errors

Business failures never rely on exceptions.

Instead, they use Result and Error.


Infrastructure Independence

The Shared Kernel contains no infrastructure implementations.

Only abstractions are provided.


Internal Components

Some components are intentionally hidden from consumers.

Examples:

  • StructuralComparer
  • ObjectFormatter
  • DictionaryComparer
  • EnumerableComparer

These classes exist solely to support the public API and are not considered part of the framework contract.


Stability Policy

Every module is individually audited before being frozen.

Once a module is frozen, breaking changes are avoided unless required by a critical architectural reason.

This policy guarantees long-term API stability while allowing the framework to evolve incrementally.

Using the Shared Kernel

This chapter demonstrates how the different components of KUKULCAN.SharedKernel work together to build a rich domain model.

Rather than describing every individual class, the following examples illustrate the recommended usage patterns.


Results

The framework follows a functional approach for representing business outcomes.

Business failures are represented using Result rather than exceptions.

Successful Result

Result result = Result.Success();

if (result.IsSuccess)
{
    Console.WriteLine("Operation completed.");
}

Failed Result

Result result =
    Result.Failure(
        CommonErrors.NotFound(
            nameof(Customer),
            customerId));

if (result.IsFailure)
{
    Console.WriteLine(result.Error.Description);
}

Returning Values

Result<Customer> customer =
    Result.Success(
        new Customer(customerId));

Returning Errors

Result<Customer> customer =
    Result.Failure<Customer>(
        CommonErrors.NotFound(
            nameof(Customer),
            customerId));

Chaining Results

Result<Customer> customer =
    repository.Get(customerId);

if (customer.IsFailure)
{
    return customer;
}

customer.Value.Activate();

return Result.Success(customer.Value);

Maybe

Some operations may legitimately return "no value".

Instead of returning null, use Maybe<T>.


Creating a Value

Maybe<Customer> customer =
    Maybe.From(existingCustomer);

Empty Value

Maybe<Customer> customer =
    Maybe.None<Customer>();

Pattern

if (customer.HasValue)
{
    Console.WriteLine(customer.Value.Name);
}

Validation

Validation is completely independent of any external validation framework.


Creating a Validation Result

ValidationResult validation =
    ValidationResult.Success();

Adding Errors

validation.AddFailure(
    ValidationFailure.Create(
        nameof(Customer.Name),
        "Customer name is required."));

Returning Result

if (!validation.IsValid)
{
    return validation.ToResult();
}

Throwing Validation Exceptions

validation.ThrowIfInvalid();

Guard Clauses

Guard clauses simplify argument validation.


Null Validation

Guard.NotNull(customer);

String Validation

Guard.NotNullOrWhiteSpace(name);

Range Validation

Guard.Positive(quantity);

Collection Validation

Guard.NotEmpty(customers);

Strongly Typed Identifiers

Primitive identifiers should never be exposed throughout the domain.

Instead, create strongly typed identifiers.


public sealed class CustomerId
    : EntityId<CustomerId>
{
    public CustomerId(Guid value)
        : base(value)
    {
    }
}

Usage:

CustomerId id =
    new(Guid.NewGuid());

Value Objects

Value Objects represent immutable concepts.


public sealed class Email
    : ValueObject
{
    public Email(string value)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(value);

        Value = value;
    }

    public string Value { get; }

    protected override IEnumerable<object?> GetEqualityComponents()
    {
        yield return Value;
    }
}

Comparison becomes automatic.

Email first =
    new("[email protected]");

Email second =
    new("[email protected]");

bool equals = first == second;

Entities

Entities are identified by identity rather than value.


public sealed class Customer
    : Entity<CustomerId>
{
    public Customer(CustomerId id)
        : base(id)
    {
    }

    public string Name { get; private set; } = string.Empty;

    public void Rename(string name)
    {
        Guard.NotNullOrWhiteSpace(name);

        Name = name;
    }
}

Aggregate Roots

Aggregate Roots encapsulate consistency boundaries.


public sealed class Customer
    : AggregateRoot<CustomerId>
{
    public Customer(CustomerId id)
        : base(id)
    {
    }

    public void Activate()
    {
        AddDomainEvent(
            new CustomerActivatedEvent(Id));
    }
}

Domain Events

Domain Events capture something that happened inside the domain.


public sealed class CustomerActivatedEvent
    : DomainEvent
{
    public CustomerActivatedEvent(
        CustomerId customerId)
    {
        CustomerId = customerId;
    }

    public CustomerId CustomerId { get; }
}

Publishing events.

customer.Activate();

foreach (IDomainEvent domainEvent in customer.DomainEvents)
{
    await dispatcher.DispatchAsync(
        domainEvent,
        cancellationToken);
}

customer.ClearDomainEvents();

Specifications

Specifications encapsulate business rules.


public sealed class ActiveCustomerSpecification
    : Specification<Customer>
{
    public override Expression<Func<Customer, bool>>
        ToExpression()
    {
        return customer => customer.IsActive;
    }
}

Using specifications.

Specification<Customer> specification =
    new ActiveCustomerSpecification();

IEnumerable<Customer> customers =
    repository.Find(specification);

Specifications are composable.

Specification<Customer> specification =
    new ActiveCustomerSpecification()
        .And(new PremiumCustomerSpecification())
        .AndNot(new DeletedCustomerSpecification());

Putting Everything Together

A typical application service may look like this.

public async Task<Result<CustomerDto>> Handle(
    ActivateCustomerCommand request,
    CancellationToken cancellationToken)
{
    Guard.NotNull(request);

    Result<Customer> customer =
        await repository.GetAsync(
            request.CustomerId,
            cancellationToken);

    if (customer.IsFailure)
    {
        return customer.Error;
    }

    customer.Value.Activate();

    await repository.SaveAsync(
        customer.Value,
        cancellationToken);

    return mapper.Map(customer.Value);
}

The application code remains:

  • Explicit
  • Strongly typed
  • Easy to test
  • Infrastructure independent
  • Domain driven

Cross-Cutting Concerns

Modern enterprise applications require more than a rich domain model.

They also require a consistent approach for handling:

  • Time
  • Culture
  • Localization
  • Semantic Versioning

KUKULCAN.SharedKernel provides abstractions for each of these concerns while remaining completely independent of infrastructure frameworks.


Time

One of the most common problems in enterprise applications is the direct usage of DateTime.UtcNow.

This creates hidden dependencies and makes automated testing considerably harder.

Instead, every application should depend on the IClock abstraction.


IClock

public sealed class Customer
{
    private readonly IClock _clock;

    public Customer(IClock clock)
    {
        _clock = clock;
    }

    public DateTimeOffset CreatedOn =>
        _clock.UtcNow;
}

The domain no longer depends on the operating system clock.


SystemClock

Production code should normally use SystemClock.

IClock clock =
    new SystemClock();

DateTimeOffset now =
    clock.UtcNow;

FakeClock

Unit tests should never depend on real time.

Instead, use FakeClock.

FakeClock clock =
    new(
        new DateTimeOffset(
            2026,
            7,
            29,
            10,
            0,
            0,
            TimeSpan.Zero));

Advancing Time

clock.AdvanceHours(4);

clock.AdvanceMinutes(30);

clock.AdvanceDays(2);

Rewinding Time

clock.RewindHours(1);

clock.RewindMinutes(15);

clock.RewindDays(7);

Example

FakeClock clock =
    new(DateTimeOffset.UtcNow);

Customer customer =
    new(clock);

clock.AdvanceDays(30);

Assert.True(
    customer.HasExpired());

Time-dependent behaviour becomes fully deterministic.


Globalization

Applications frequently need to support multiple cultures.

The Shared Kernel models culture as an explicit domain concept.

Rather than depending directly on CultureInfo, applications interact through abstractions.


SupportedCulture

SupportedCulture culture =
    SupportedCulture.Parse("en-US");

or

SupportedCulture culture =
    SupportedCulture.Parse("es-ES");

TryParse

if (SupportedCulture.TryParse(
    "fr-FR",
    out SupportedCulture? culture))
{
    ...
}

Current Culture

Applications should depend on ICurrentCultureProvider.

public sealed class CustomerService
{
    private readonly ICurrentCultureProvider
        _cultureProvider;

    public CustomerService(
        ICurrentCultureProvider cultureProvider)
    {
        _cultureProvider = cultureProvider;
    }

    public SupportedCulture Culture =>
        _cultureProvider.CurrentCulture;
}

Localization

Localization is represented by abstractions.

public interface ITextLocalizer
{
    string Get(
        string key);
}

The Shared Kernel intentionally provides no implementation.

Infrastructure remains responsible for loading translations.


Localized Resources

LocalizedString title =
    new(
        "Customer.Name",
        "Customer");

or

LocalizedText text =
    new(
        "Customer.Name",
        "Customer",
        SupportedCulture.Parse("en-US"));

Versioning

Versioning is represented as a first-class Value Object.

The implementation follows the Semantic Versioning 2.0 specification.


Creating Versions

SemanticVersion version =
    new(
        1,
        0,
        0);

Parsing

SemanticVersion version =
    SemanticVersion.Parse(
        "2.3.1");

TryParse

if (SemanticVersion.TryParse(
    "1.4.0-beta.1",
    out SemanticVersion? version))
{
    ...
}

Comparison

SemanticVersion stable =
    SemanticVersion.Parse(
        "1.0.0");

SemanticVersion beta =
    SemanticVersion.Parse(
        "1.0.0-beta");

bool newer =
    stable > beta;

Equality

SemanticVersion first =
    SemanticVersion.Parse(
        "2.1.0");

SemanticVersion second =
    SemanticVersion.Parse(
        "2.1.0");

bool equals =
    first == second;

Why These Components Belong in the Shared Kernel

Although these modules are not part of the domain model itself, they represent concepts that are shared across every bounded context.

Keeping them inside the Shared Kernel guarantees:

  • Consistent behaviour
  • Consistent APIs
  • Testability
  • Framework independence

Always

✔ Depend on IClock

✔ Use SupportedCulture

✔ Represent versions using SemanticVersion

✔ Inject localisation through abstractions


Avoid

❌ DateTime.UtcNow

❌ CultureInfo.CurrentCulture inside the domain

❌ String comparisons for versions

❌ Infrastructure dependencies inside entities


Example

The following example combines all three modules.

public sealed class ApplicationInfo
{
    public ApplicationInfo(
        IClock clock,
        ICurrentCultureProvider cultures)
    {
        StartedAt =
            clock.UtcNow;

        Culture =
            cultures.CurrentCulture;

        Version =
            SemanticVersion.Parse(
                "1.0.0");
    }

    public DateTimeOffset StartedAt { get; }

    public SupportedCulture Culture { get; }

    public SemanticVersion Version { get; }
}

This object remains:

  • deterministic
  • testable
  • immutable
  • infrastructure independent
  • fully aligned with Clean Architecture

Extending the Framework

KUKULCAN.SharedKernel has been designed to be extended rather than modified.

Applications should rarely need to change the framework itself.

Instead, they should extend it by creating new domain types that inherit from the existing abstractions.

This chapter demonstrates the recommended extension points.


Creating Strongly Typed Identifiers

Every aggregate should expose its own strongly typed identifier.

Avoid using primitive types such as Guid, int or string directly throughout the domain model.

Instead, derive from EntityId<T>.

public sealed class CustomerId
    : EntityId<CustomerId>
{
    public CustomerId(Guid value)
        : base(value)
    {
    }
}

Usage

CustomerId customerId =
    new(Guid.NewGuid());

Benefits

  • Compile-time safety
  • Explicit intent
  • No primitive obsession
  • Better readability

Creating Value Objects

Value Objects should always be immutable.

Example

public sealed class EmailAddress
    : ValueObject
{
    public EmailAddress(string value)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(value);

        Value = value;
    }

    public string Value { get; }

    protected override IEnumerable<object?> GetEqualityComponents()
    {
        yield return Value;
    }
}

Usage

EmailAddress email =
    new("[email protected]");

Creating Enumerations

Business concepts that behave like enumerations but require richer semantics should inherit from Enumeration.

public sealed class CustomerType
    : Enumeration
{
    public static readonly CustomerType Standard =
        new(1, "Standard");

    public static readonly CustomerType Premium =
        new(2, "Premium");

    public static readonly CustomerType Vip =
        new(3, "VIP");

    private CustomerType(
        int id,
        string name)
        : base(id, name)
    {
    }
}

Usage

CustomerType customerType =
    CustomerType.Premium;

Unlike standard enums, enumerations support:

  • Methods
  • Validation
  • Rich behaviour
  • Metadata

Creating Entities

Entities should inherit from Entity<TId>.

public sealed class Customer
    : Entity<CustomerId>
{
    public Customer(CustomerId id)
        : base(id)
    {
    }

    public string Name { get; private set; } = string.Empty;

    public void Rename(string name)
    {
        Guard.NotNullOrWhiteSpace(name);

        Name = name;
    }
}

Creating Aggregate Roots

Aggregate Roots inherit from AggregateRoot<TId>.

public sealed class Customer
    : AggregateRoot<CustomerId>
{
    public Customer(CustomerId id)
        : base(id)
    {
    }

    public void Activate()
    {
        AddDomainEvent(
            new CustomerActivatedEvent(Id));
    }
}

Aggregate Roots own consistency boundaries.


Creating Domain Events

Domain Events should inherit from DomainEvent.

public sealed class CustomerActivatedEvent
    : DomainEvent
{
    public CustomerActivatedEvent(
        CustomerId customerId)
    {
        CustomerId = customerId;
    }

    public CustomerId CustomerId { get; }
}

Raise the event from the Aggregate Root.

AddDomainEvent(
    new CustomerActivatedEvent(Id));

Creating Specifications

Business rules should be encapsulated inside specifications.

public sealed class PremiumCustomerSpecification
    : Specification<Customer>
{
    public override Expression<Func<Customer, bool>>
        ToExpression()
    {
        return customer => customer.Type ==
               CustomerType.Premium;
    }
}

Specifications may be combined.

Specification<Customer> specification =
    new ActiveCustomerSpecification()
        .And(new PremiumCustomerSpecification());

Creating Validation Rules

Validation should remain independent of infrastructure frameworks.

ValidationResult validation =
    ValidationResult.Success();

if (string.IsNullOrWhiteSpace(customer.Name))
{
    validation.AddFailure(
        ValidationFailure.Create(
            nameof(Customer.Name),
            ValidationMessages.Required));
}

Return

return validation.ToResult();

instead of throwing exceptions for business failures.


Creating New Supported Cultures

Applications may register additional cultures by extending the globalisation layer.

SupportedCulture culture =
    SupportedCulture.Parse("es-MX");

The Shared Kernel intentionally separates culture modelling from localisation implementation.


Extending Semantic Versioning

Applications normally should not derive from SemanticVersion.

Instead, compose it.

public sealed class ApplicationVersion
{
    public ApplicationVersion(
        SemanticVersion version)
    {
        Version = version;
    }

    public SemanticVersion Version { get; }
}

Adding New Modules

When introducing a completely new module, follow these architectural rules.

Every module must:

  • Have a single responsibility.
  • Be independent of infrastructure.
  • Avoid cyclic dependencies.
  • Expose the smallest possible public API.
  • Prefer immutable models.
  • Keep implementation details internal.

Dependency Rules

The following dependency graph must always be respected.

Abstractions
        │
        ▼
Identifiers
        │
        ▼
Results
        │
        ▼
Domain
        │
        ├───────────────┐
        ▼               ▼
DomainEvents     Specifications
        │
        ▼
Validation

Time

Globalization

Versioning

Dependencies should always point downwards.


What Should NOT Be Extended

Some framework components are intentionally internal.

Applications should never depend directly on:

  • StructuralComparer
  • DictionaryComparer
  • EnumerableComparer
  • ObjectFormatter

These classes exist exclusively to support the framework implementation.


Design Guidelines

When extending the framework, follow these recommendations.

✔ Prefer composition to inheritance.

✔ Create rich domain models.

✔ Keep Value Objects immutable.

✔ Raise Domain Events from Aggregate Roots only.

✔ Use Result for business failures.

✔ Reserve exceptions for programming errors.

✔ Keep infrastructure outside the domain.

✔ Minimise the public API.


Anti-Patterns

Avoid the following practices.

❌ Primitive obsession

❌ Anemic domain models

❌ Static business logic

❌ God objects

❌ Infrastructure dependencies inside entities

❌ Mutable Value Objects

❌ Returning null instead of Maybe

❌ Throwing exceptions for business validation


Summary

The Shared Kernel is intended to evolve by extension rather than modification.

Applications should build upon its abstractions while preserving the architectural principles that guarantee long-term maintainability and stability.

Best Practices

KUKULCAN.SharedKernel has been designed to provide a stable architectural foundation for enterprise applications following Domain-Driven Design and Clean Architecture.

The following recommendations should be considered mandatory for achieving the highest level of maintainability.


Keep the Domain Pure

The domain model should never depend on infrastructure concerns.

Avoid references to:

  • Entity Framework
  • ASP.NET Core
  • HTTP
  • Serialization frameworks
  • Dependency Injection frameworks
  • Logging frameworks

The Domain should only depend on the abstractions provided by the Shared Kernel.


Prefer Rich Domain Models

Entities should contain behaviour.

Avoid anaemic models such as:

public class Customer
{
    public string Name { get; set; }
}

Prefer:

public sealed class Customer
    : AggregateRoot<CustomerId>
{
    public void Rename(string newName)
    {
        Guard.NotNullOrWhiteSpace(newName);

        Name = newName;
    }
}

Avoid Primitive Obsession

Do not expose primitive identifiers across the domain.

Instead of

Guid CustomerId

prefer

CustomerId CustomerId

Use Value Objects

Whenever a concept is identified by its value instead of its identity, implement it as a Value Object.

Examples:

  • Email
  • Address
  • Money
  • PhoneNumber
  • TaxIdentifier

Use Result for Business Failures

Business rules should return Result.

return Result.Failure(
    CommonErrors.NotFound(
        nameof(Customer),
        customerId));

Programming errors should continue throwing exceptions.


Never Return Null

Prefer

Maybe<Customer>

instead of

Customer?

This makes the API explicit.


Raise Domain Events

Aggregate Roots should communicate state changes through Domain Events.

AddDomainEvent(
    new CustomerCreatedEvent(Id));

Avoid directly invoking external services from entities.


Keep Aggregate Boundaries Small

Aggregate Roots should enforce consistency.

Do not create aggregates containing dozens of entities.


Use Specifications

Business rules that are reusable should become Specifications.

Avoid duplicating LINQ expressions throughout the application.


Depend on IClock

Never use

DateTime.UtcNow

inside the domain.

Instead

IClock

Prefer Immutability

Value Objects should always be immutable.

Aggregate state should change only through explicit behaviour.


Keep the Public API Small

Every public type becomes part of the framework contract.

If a class is not intended for consumers, make it internal.


Minimise Dependencies

The Shared Kernel intentionally depends only on the .NET Base Class Library.

Avoid introducing external dependencies unless they provide significant architectural value.


Preserve Module Independence

Each module should have a single responsibility.

Do not create cross-module shortcuts.


Write Self-Documenting Code

Prefer expressive names to comments.

Good code should explain itself.


Keep Breaking Changes Rare

Once a module is frozen, breaking changes should only occur for critical architectural reasons.

Stable APIs create stable applications.


Roadmap

The following roadmap describes the expected evolution of the framework.

Version 1.0.0-beta1

  • Initial public beta
  • Stable public API
  • Complete Shared Kernel
  • Full XML Documentation
  • GitHub Documentation
  • NuGet Packaging

Version 1.0.0

  • Production-ready release
  • Performance review
  • Roslyn analysers
  • Additional unit tests
  • SourceLink support

Version 1.1

Possible improvements under evaluation.

  • Additional Value Objects
  • Additional Specifications
  • Performance optimisations
  • Additional globalisation features
  • More domain primitives

No breaking changes are planned.


Long-Term Vision

The Shared Kernel is expected to become the architectural foundation for all KUKULCAN products.

Its evolution will prioritise:

  • Stability
  • Simplicity
  • Predictability
  • Long-term maintainability

Contributing

Contributions are welcome.

Before submitting any contribution, please read the following guidelines.


General Principles

Every contribution should preserve the architectural principles of the framework.

New code should be:

  • Simple
  • Cohesive
  • Well documented
  • Fully tested

Pull Requests

Every Pull Request should:

  • Address a single concern.
  • Preserve backward compatibility whenever possible.
  • Include XML documentation.
  • Include unit tests.
  • Respect the existing coding style.

Coding Guidelines

Follow the existing conventions.

  • PascalCase for public members.
  • Nullable Reference Types enabled.
  • File-scoped namespaces.
  • One public type per file.
  • Prefer readonly.
  • Prefer sealed.
  • Prefer immutable models.

Architecture

Contributors should avoid introducing:

  • Helper classes
  • Utility classes
  • God Objects
  • Static business logic
  • Infrastructure dependencies

Every new abstraction should have a clearly defined responsibility.


Breaking Changes

Breaking changes require explicit discussion before implementation.

The framework prioritises API stability.


Version Policy

KUKULCAN.SharedKernel follows Semantic Versioning 2.0.

https://semver.org


MAJOR

Incremented when incompatible API changes are introduced.

Example

2.0.0

MINOR

Incremented when new functionality is added in a backward-compatible manner.

Example

1.4.0

PATCH

Incremented for backward-compatible bug fixes.

Example

1.4.2

Pre-release Versions

Examples

1.0.0-alpha1

1.0.0-beta1

1.0.0-rc1

Compatibility Policy

Public APIs remain stable once a module has been frozen.

Breaking changes are exceptional.


License

MIT License

Copyright (c) 2026 KUKULCAN

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to:

  • use
  • copy
  • modify
  • merge
  • publish
  • distribute
  • sublicense
  • sell copies

subject to the conditions described in the LICENCE file.


GitHub Metadata

The following files are recommended for a professional GitHub repository.

/
├── .editorconfig
├── .gitattributes
├── .gitignore
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── SECURITY.md
├── SUPPORT.md
└── logo.png

The project file should contain metadata similar to the following.

<PropertyGroup>

  <PackageId>KUKULCAN.SharedKernel</PackageId>

  <Title>KUKULCAN Shared Kernel</Title>

  <Authors>KUKULCAN</Authors>

  <Company>KUKULCAN</Company>

  <Product>KUKULCAN.SharedKernel</Product>

  <Description>
    Lightweight Shared Kernel for Domain-Driven Design and Clean Architecture.
  </Description>

  <PackageTags>
    ddd;clean-architecture;sharedkernel;domain-driven-design;
    value-object;result;specification;domain-events
  </PackageTags>

  <PackageLicenseExpression>MIT</PackageLicenseExpression>

  <PackageReadmeFile>README.md</PackageReadmeFile>

  <RepositoryType>git</RepositoryType>

  <RepositoryUrl>https://github.com/KUKULCAN/KUKULCAN.SharedKernel</RepositoryUrl>

  <PackageProjectUrl>https://github.com/KUKULCAN/KUKULCAN.SharedKernel</PackageProjectUrl>

  <PublishRepositoryUrl>true</PublishRepositoryUrl>

  <EmbedUntrackedSources>true</EmbedUntrackedSources>

  <IncludeSymbols>true</IncludeSymbols>

  <SymbolPackageFormat>snupkg</SymbolPackageFormat>

  <GenerateDocumentationFile>true</GenerateDocumentationFile>

  <GeneratePackageOnBuild>true</GeneratePackageOnBuild>

  <ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>

</PropertyGroup>

Repository Structure

KUKULCAN.SharedKernel
│
├── src/
│   └── KUKULCAN.SharedKernel
│
├── tests/
│   └── KUKULCAN.SharedKernel.Tests
│
├── docs/
│
├── README.md
├── CHANGELOG.md
├── LICENSE
└── CONTRIBUTING.md

Support

For questions, bug reports and feature requests, please use the GitHub Issues section.


Final Notes

KUKULCAN.SharedKernel is intended to provide a stable, expressive and long-lived foundation for enterprise software.

The project favours architectural consistency over feature accumulation and prioritises simplicity, explicitness and maintainability above all else.

Product Compatible and additional computed target framework versions.
.NET 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • net10.0

    • No dependencies.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on KUKULCAN.SharedKernel:

Package Downloads
KUKULCAN.SharedKernel.Database

KUKULCAN.SharedKernel.Database is a foundational library designed to provide shared database abstractions and infrastructure components across multiple modules and bounded contexts. This project acts as part of the SharedKernel, enabling consistency, reuse, and standardization of database-related concerns across multiple modules and bounded contexts.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0 1,634 8/14/2026

Initial release of KUKULCAN.SharedKernel. Provides shared domain primitives, abstractions, result handling, standardized errors, validation, time services, value-object utilities, common messages, and cross-cutting components for KUKULCAN applications and services.