Code Monkey home page Code Monkey logo

domainobjects's Introduction

DomainObjects

The base-classes for DDDomain objects (Entities, Value-Types, and Aggregates)

The tactical bit of Domain-driven design describes value-types, entities, and aggregates. A couple of rules apply:

  • Compare Value-types by their values
  • Entities have an ID
  • Aggregates have an ID
  • Compare Entities and aggregates by their ids
  • Aggregates reference other entities by id

To be able to do so, requires some code. Hence this repository.

This package is available on NuGet

Install-Package DomainDrivenDesign.DomainObjects

How to create a value-type

This package contains a base-class for a value type. It supports:

  • Comparing value types by the values it contains using .Equals(x), == and !=
  • Converting it to a string using the ToString() method
  • Sorting lists of values

Create a value type by implementing the Value<T> class, for example:

public class Euro : Value<double>
{
    public static Euro Create(double value)
    {
        return new Euro(value);
    }

    private Euro(double value) : base(value)
    {
    }

    // Add the methods and operators you need here
}

How to use it:

var a = Euro.Create(3.5f);
var b = Euro.Create(3.5f);

if(a == b) // This works out of the box
{
    Console.WriteLine("Eureka!");
}

Important: This class is based on the principle that a value object's values never change! Do not change the value of a value-object. Instead, create a new instance of the value-object with the new value or consider implementing the entity<T> class. If the values of a value-object are changed, the ==, != and .Equals(x) will not work correctly any-more. This behaviour is by design!!

Read about the philosophy of value objects in this blog-post.

How to create a value-type with multiple values

Create it by implementing the Value<T1, T2, etc.> class:

public class ZipCode : Value<int, string>
{
    private int _postalCode;
    private string _state;

    public static ZipCode Create(int postalCode, string state)
    {
        return new ZipCode(postalCode, state);
    }

    private ZipCode(int postalCode, string state) : base(postalCode, state)
    {
        _postalCode = postalCode;
        _state = state ?? throw new ArgumentNullException(nameof(state));
    }

    // Add the methods and operators you need here

    public override string ToString()
    {
        return $"{_postalCode} ${_state}";
    }
}

How to create a numeric or a comparable type

Some values can be greater than and may need to be sorted. Assume you want to find the cheapest product. You'll need to sort by price. Prices are in Euro. Deriving the Euro class from ComparableValue<T> instead of Value<T> allows sorting and comparing.

ComparableValue<T> supports:

  • Comparison using ==, != and .Equals(x)
  • Comparison using the <, >, <= and => operators
  • Ordering collections by using collection.OrderBy(x => x...)
  • Getting the smallest and biggest value in a collection using collection.Min() or collection.Max()

Example:

public class Euro : ComparableValue<double>
{
    public static Euro Create(double value)
    {
        return new Euro(value);
    }

    private Euro(double value) : base(value)
    {
    }

    // Add the methods and operators you need here
}

Now may be used like this:

var cheap = Euro.Create(1);
var expensive = Euro.Create(100000);

if(cheap < expensive)
{
    Console.WriteLine("That makes sense...");
}

Note that the class is called NumericValue. In most cases it's used with numbers. But it does not explicitly require a number. It requires the type parameter to derive from IComparable.

Creating Boolean value-objects

When the value-object is of type Boolean, use the BooleanValue object. It supports:

  • Comparison using ==, != and .Equals(x)
  • Comparison with bool
  • Using the if(booleanValue) statement to compare
public class AgreedToLicense : BoolValue
{
    public static AgreedToLicense Create(bool value)
    {
        return new AgreedToLicense(value);
    }

    private AgreedToLicense(bool value) : base(value)
    {
    }
}

Works like this:

var agreed = AgreedToLicense.Create(true);

if(agreed)
{
    Console.WriteLine("Eureka!");
}

How to create an entity

This package contains a base-class for a entities. It supports:

  • Entity comparison using .Equals(x), == and !=
  • Ids

Create an entity by implementing the Entity<T> base-class. It provides a public property called ID on every entity. Always construct an entity with an Id. Entities are more than just an Id. Add the properties you need to the constructor of the entity. Don't pass it to the base class. Example:

public class Order : Entity<Order>
{
    public Euro Price { get; }

    // Don't forget to either give the class a public constructor
    // or add a factory method to instantiate an instance of the class..
    public Order(Id<Order> id, Euro price) : base(id)
    {
        Price = price;
    }
}

Use it like this:

var id = Id<Order>.New();
var price = Euro.Create(3.5f);

var order = new Order(id, price);
Console.WriteLine(order.Id);
Console.WriteLine(order.Price);

Comparing entities (and aggregates)

Entities are compared by id. Not by value or reference. The same applies for aggregates. That means that different entities, with the same id are considered equal. For example:

var id = Id<Order>.New();

var order = new Order(id, Euro.Create(2));
var sameOrderWithDifferentValues = new Order(id, Euro.Create(3));

if (order == sameOrderWithDifferentValues)
{
    Console.WriteLine("This is true!");
}

Creating an entity that does not use a GUID as id

This code is easiest to use with entities that use GUIDs for ids. However, it supports any other type of id, too. To make that work, implement the id yourself. Like this:

public class BookId : Id<Book, int>
{
    public static BookId Create(int id)
    {
        return new BookId(id);
    }

    private BookId(int id) : base(id)
    {
    }
}

The entity base-class has an overload that has two type parameters. The first being the type of the entity. The second one being the type of Id to use:

public class Book : Entity<Book, BookId>
{
    public Number Number { get; }

    public static Book Create(BookId id, Number number)
    {
        return new Book(id, number);
    }

    protected Book(BookId id, Number number) : base(id)
    {
        Number = number;
    }
}

How to create an aggregate

This package contains a base-class for aggregates. It supports:

  • Aggregate comparisons using .Equals(x), == and !=
  • Ids

Create an aggregate by implementing the Aggregate<T> base-class. It provides a public property called ID on every aggregate. Always construct an entity with an Id. Aggregates are more than just an Id. Add the properties you need to the constructor of the aggregate. Don't pass it to the base class. Example:

public class OrderAggregateRoot : Aggregate<OrderAggregateRoot>
{
    public Euro Price { get; }
    public Id<Order> OrderId { get; }

    public OrderAggregateRoot(Id<OrderAggregateRoot> id, Id<Order> orderId, Euro price) : base(id)
    {
        OrderId = orderId;
        Price = price;
    }
}

Use it like this:

var id = Id<OrderAggregateRoot>.New();
var orderId = Id<Order>.Create(new Guid("6001300f-8c49-402a-9545-027c8917557d"));
var price = Euro.Create(3.5f);

var order = new OrderAggregateRoot(id, orderId, price);
Console.WriteLine(order.Id);
Console.WriteLine(order.OrderId);
Console.WriteLine(order.Price);

Creating an aggregate that does not use a GUID as id

This code is easiest to use with aggregates that use GUID for ids. However, it supports any other type of id, too. To make that work, implement the id yourself. Like this:

public class BookId : Id<BookAggregate, int>
{
    public static BookId Create(int id)
    {
        return new BookId(id);
    }

    private BookId(int id) : base(id)
    {
    }
}

The aggregate base-class has an overload that has two type parameters. The first being the type of the aggregate. The second one being the type of Id to use:

public class BookAggregate : Aggregate<BookAggregate, BookId>
{
    public Number Number { get; }

    public static BookAggregate Create(BookId id, Number number)
    {
        return new BookAggregate(id, number);
    }

    private BookAggregate(BookId id, Number number) : base(id)
    {
        Number = number;
    }
}

See it in action!

Use this repository as a reference. See how to use the DomainDrivenDesign.DomainObjects NuGet package in this solution, in this project.

Sponsor me

Buy me a coffee!

domainobjects's People

Contributors

appie2go avatar dependabot[bot] avatar mjackson5 avatar mkeinemans avatar stacycash avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

domainobjects's Issues

I can't create an entity that has an ID of type int or string

The following code shows that id must always be a Guid. In my project i need to use a string.

    public sealed class Id<T> : Value<Guid> where T : Entity<T>
    {
        private readonly Guid _id;

        public static Id<T> New()
        {
            return new Id<T>(Guid.NewGuid());
        }

        public static Id<T> Create(Guid id)
        {
            return new Id<T>(id);
        }

        private Id(Guid id) : base(id)
        {
            _id = id;

            if (id == Guid.Empty)
            {
                throw new ArgumentException(nameof(id), $"{id} is not a valid identifier.");
            }
        }

Can this be changed to a solution in which we choose the type of id we'd like to use per entity?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.