ClearDomain 0.9.2

There is a newer version of this package available.
See the version list below for details.
dotnet add package ClearDomain --version 0.9.2                
NuGet\Install-Package ClearDomain -Version 0.9.2                
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="ClearDomain" Version="0.9.2" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add ClearDomain --version 0.9.2                
#r "nuget: ClearDomain, 0.9.2"                
#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.
// Install ClearDomain as a Cake Addin
#addin nuget:?package=ClearDomain&version=0.9.2

// Install ClearDomain as a Cake Tool
#tool nuget:?package=ClearDomain&version=0.9.2                

ClearDomain

A collection of base classes and interfaces for DDD (Domain Driven Design) projects.

TempIcon

build-status downloads downloads activity

Overview

ClearDomain gives you:

  • 🌱 Compact and straightforward API (3 classes and 3 interfaces)
  • 💪 Flexible to your needs
  • 🏠 Reliable, consistent behavior
  • 💽 Works with ADO.NET, Dapper, EF, and MongoDB

Table of Contents

Samples

If you would like code samples for ClearDomain, they may be found here.

Support

ClearDomain supports any version of .NET that allows for C# 9.0 and above. This currently includes NET5, NET6, NET7, and NET8.

NetStandard 2.1 is not supported. This is due to a design decision to use the "init" keyword for properties.

Dependencies

ClearDomain has no dependencies on any external Microsoft or third-party packages.

Installation

The easiest way to get started is to: Install with NuGet.

Install where you need with:

Install-Package ClearDomain

Contents

ClearDomain gives you:

  • Entities in either int, long, string, or Guid format with an interface constraint
  • Value Objects with no generics or boiler-plate required
  • Aggregate Roots with an interface constraint
  • An empty interface used to constrain a Domain Event

Quick Start

Value Objects

ValueObjects should all derive from the "ValueObject" base class.

public class Money : ValueObject
{
    // implementation
}

The main characteristic of value objects is that their equality is based on their underlying properties.

var first = Money.FromDollars(20);

var second = Money.FromDollars(20);

var areEqual = first == second;

// true

The main use for value objects is to model concepts that logically have no use for uniqueness. This may entail names, addresses, time, or in this example, money.

Value Objects should be pure, modifying any value objects should return a completely new object.

It's a good idea to stick to static initializers for value objects. The DateTime object is a good example of an API to follow.

Entities

Use an Entity when an object must have a unique identifier associated with it or needs to be persisted in physical storage.

using ClearDomain.GuidPrimary;

// Guid based entity.
public class Person : Entity
{
}

ClearDomain supports entities with either int, long, string, or Guid based identifiers. The type you use is determined by the namespace you import.

Entities use equality based on their identifiers.

var first = new Person(1);

var second = new Person(1);

var areEqual = first.Equals(second);

// true

Entities have a default constructor that may be initialized during creation.

public class Airplane : Entity
{
}

var airplane = new Airplane
{
    Id = Guid.NewGuid(),
};

All entities use the "init" keyword for setters. The Id value may be set during object initialization, but not afterwards. This is to preserve encapsulation.

If you choose to use either Guid or string-based entities, the default constructor will initialize your object with an identifier value for you.

var person = new Person();

var isEmpty = person.Id == Guid.Empty;

// false

If you don't know which identifier to go with, it is highly recommended to start with either GUIDs or strings.

Aggregate Roots

An AggregateRoot is a designator for a logical boundary of entities and value objects.

AggregateRoots may contain domain events that other parts of your application may react to.

Typically these domain events are published when the aggregate root is saved, updated, or removed from persistence.

The following is a small example of a theoretical aggregate root that contains a list of items.

public class ShoppingCart : AggregateRoot
{
    public IEnumerable<Item> Items { get; }

    public void AddItem(Item item)
    {
        Items.Add(item);

        AppendDomainEvent(new CartUpdated(item));
    }
}

The "AppendDomainEvent" method is virtual. You may override it to your liking.

Aggregate Roots are a more specialized kind of entity that serve as an entry point to a model in your application.

Domain Events

Domain Events in ClearDomain need to be inherited from the IDomainEvent interface. This is an empty constraint used to enforce that all domain events are classes.

public class CardUpdated : IDomainEvent
{
    // properties in here
}

Use and publish a Domain Event when an Aggregate or model has something interesting to notify the rest of your application about.

The main benefit of using events is that you can decouple your application from hard dependencies by publishing events and let consumers choose what they want, and what they want to do with it.

Detailed Usage

Entity Constraints

All entities derive from a single interface IEntity that contains a getter-only property for the identifier.

This may be used when trying to query your persistence and you only require the single property.

public async Task<int> DeleteEntity<T>(IEntity entity)
{
    var objValue = await Context.Set<T>.FindAsync(entity.Id);

    await Context.Set<T>.Remove(objValue);

    return await Context.SaveChangesAsync();
}

AggregateRoot Constraints

All aggregate roots derive from a single interface IAggregateRoot that contains an IEnumerable of current domain events.

Similar to the IEntity interface, you may use this to constrain a parameter when you just need the domain events.

private async Task PublishEvents(IAggregateRoot aggregateRoot)
{
    foreach (var domainEvent in aggregateRoot.DomainEvents)
    {
        await _bus.Publish(domainEvent);
    }
}

The method above is a small example of how you can publish domain events inside an object Repository.

Aggregate Roots have all other downstream constraints such as IEntity

Entity Encapsulation

If you do not wish for your entities to expose an empty constructor, you may define a constructor with a parameter that must be called.

using ClearDomain.StringPrimary;

public class Airplane : Entity
{
    public Airplane(string id)
        : base(id)
    {
    }
}

// not possible, compile error
var incorrect = new Airplane();

var correct = new Airplane(Guid.NewGuid().ToString());

Domain Events with MediatR

If you are using MediatR to publish events. It may be easier to create an interface that aggregates the interfaces necessary.

public interface IEventNotification : INotification, IDomainEvent
{
}

Your new interface will now be used.

public class CartUpdated : IEventNotification
{
    // properties in here
}

Using a Different Identifier Type

If you wish to use an identifier type not provided you may extend the base classes.

  1. Create an interface "IEntity" that extends from IEntity of type T where T is your new type
  2. Create an abstract class "Entity" that extends from both Entity of T and your IEntity interface, and implement constructors as needed
  3. Create an interface "IAggregateRoot" that extends from IAggregateRoot of type T where T is your new type and your closed "IEntity" interface
  4. Create an abstract class "AggregateRoot" that extends from both AggregateRoot of T and your IAggregateRoot interface, and implement constructors as needed

FAQ

Do I need ClearDomain if I'm not using Domain Driven Design?

Domain Driven Design (DDD) is a buffet, you can pick and choose what you want to use. You can still use aspects of ClearDomain even if your application is not a full DDD implementation.

What's the difference between an Entity and ValueObject?

An Entity has a unique identifier and equality is based on said identifier.

A ValueObject has no identifier and equality is determined by individual property values.

Dates, Names, Addresses, Time, and Money are all examples of ValueObjects.

What's the difference between an Entity and AggregateRoot?

An AggregateRoot that defines an Aggregate is typically persisted via a Repository and is a logical barrier that contains other entities and value objects.

Every AggregateRoot is an Entity, but not every Entity is an AggregateRoot.

An entity is only accessed via the AggregateRoot that contains them. Entities may contain ValueObjects or references to other Entities.

Object Hierarchy Visualized

ValueObjects and Entities at the bottom rung.

Aggregates are composed of a single AggregateRoot that contains other Entities and ValueObjects at the next level.

Finally, Aggregates are persisted via Repositories (not part of this package) and may publish DomainEvents exclusive to the model they represent.

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

    • No dependencies.
  • net6.0

    • No dependencies.
  • net7.0

    • No dependencies.
  • net8.0

    • No dependencies.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on ClearDomain:

Package Downloads
ClearDomain.Identity

A set of domain base classes and interfaces for .NET.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
0.9.4 239 5/19/2024
0.9.3 238 4/14/2024
0.9.2 182 2/27/2024
0.9.1 171 2/18/2024
0.9.0 128 2/17/2024