Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/sm-g/byvalue
DDD ValueObject implementation helper
https://github.com/sm-g/byvalue
c-sharp ddd domain-driven-design dotnet netstandard
Last synced: about 2 months ago
JSON representation
DDD ValueObject implementation helper
- Host: GitHub
- URL: https://github.com/sm-g/byvalue
- Owner: sm-g
- License: mit
- Created: 2018-11-04T09:26:02.000Z (about 6 years ago)
- Default Branch: master
- Last Pushed: 2022-02-05T06:27:00.000Z (almost 3 years ago)
- Last Synced: 2024-11-07T15:06:48.255Z (about 2 months ago)
- Topics: c-sharp, ddd, domain-driven-design, dotnet, netstandard
- Language: C#
- Homepage:
- Size: 73.2 KB
- Stars: 9
- Watchers: 2
- Forks: 0
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# ByValue
[![Build status](https://ci.appveyor.com/api/projects/status/k6nmr1mdixf7xho6/branch/master?svg=true)](https://ci.appveyor.com/project/sm-g/byvalue/branch/master) [![Build Status](https://travis-ci.org/sm-g/ByValue.svg?branch=master)](https://travis-ci.org/sm-g/ByValue) [![NuGet](http://img.shields.io/nuget/v/ByValue.svg)](https://www.nuget.org/packages/ByValue/)
This library helps to create ValueObjects with properly implemented equality behavior:
1. Provides base `ValueObject` class.
2. Gives extension `ByValue()` for comparing collections with semantic of ValueObject (`IReadOnlyCollection`, `IReadOnlyDictionary`, `IDictionary` and `ISet` are supported).## Example
```cs
public class MultilineAddress : ValueObject
{
public MultilineAddress(IReadOnlyCollection addressLines, string city, string postalCode)
{
AddressLines = addressLines ?? throw new ArgumentNullException(nameof(addressLines));
City = city ?? throw new ArgumentNullException(nameof(city));
PostalCode = postalCode ?? throw new ArgumentNullException(nameof(postalCode));if (addressLines.Count < 1 || addressLines.Count > 3)
throw new ArgumentOutOfRangeException(nameof(addressLines), addressLines, "Multiline address should have from 1 to 3 address lines");
}public IReadOnlyCollection AddressLines { get; }
public string City { get; }
public string PostalCode { get; }// here you should return values, which will be used in Equals() and GetHashCode()
protected override IEnumerable Reflect()
{
// by default collections compared with not strcit ordering
yield return AddressLines.ByValue(Ordering.Strict);// you can transform object's properties when return them
yield return City.ToUpperInvariant();yield return PostalCode;
}
}
```### SingleValueObject
Inherit from `SingleValueObject` to boost performance when ValueObject has only one property:
```cs
public class UserId : SingleValueObject
{
public UserId(int value)
: base(value)
{
if (value == 0)
throw new ArgumentOutOfRangeException(nameof(value));
}public static explicit operator UserId(int value)
{
return new UserId(value);
}public static implicit operator int(UserId userId)
{
return userId == null ? 0 : userId.Value;
}public static implicit operator int? (UserId userId)
{
return userId == null ? (int?)null : userId.Value;
}
}
```### Custom EqualityComparer
When using `ByValue()` to compare collections, elements will be compared using default `EqualityComparer`. Sometimes this is not acceptable.
Here is `AddressBook` which implements value object semantic:
```cs
public class AddressBook : ReadOnlyCollection
{
public AddressBook(IList list)
: base(list)
{
}public override bool Equals(object obj)
{
if (obj is null)
return false;
if (ReferenceEquals(this, obj))
return true;
if (obj.GetType() != GetType())
return false;var other = obj as AddressBook;
var thisByValue = this.ByValue(); // not strict ordering
var otherByValue = other.ByValue();
return thisByValue.Equals(otherByValue);
}public override int GetHashCode()
{
return Items.Count;
}
}
```But you need address book which will not treat address with city "Foo" equal to address with city "fOO". Possible solution is to use custom `EqualityComparer` for `MultilineAddress` in derived `EnhancedAddressBook`:
```cs
public class EnhancedAddressBook : AddressBook
{
public EnhancedAddressBook(IList list)
: base(list)
{
}public override bool Equals(object obj)
{
if (obj is null)
return false;
if (ReferenceEquals(this, obj))
return true;
if (obj.GetType() != GetType())
return false;var other = obj as EnhancedAddressBook;
var thisByValue = this.ByValue(x => x.UseComparer(EnhancedAddressComparer.Instance));
var otherByValue = other.ByValue(x => x.UseComparer(EnhancedAddressComparer.Instance));
return thisByValue.Equals(otherByValue);
}public override int GetHashCode()
{
return base.GetHashCode();
}private class EnhancedAddressComparer : IEqualityComparer
{
public static EnhancedAddressComparer Instance => new EnhancedAddressComparer();public bool Equals(MultilineAddress x, MultilineAddress y)
{
if (ReferenceEquals(x, y))
return true;
if (ReferenceEquals(x, null) || ReferenceEquals(y, null))
return false;return x.AddressLines.ByValue(Ordering.Strict).Equals(y.AddressLines.ByValue(Ordering.Strict))
// do not ignore case of chars
&& x.City == y.City
&& x.PostalCode == y.PostalCode;
}public int GetHashCode(MultilineAddress obj)
{
return 1;
}
}
}```
More examples could be found in [tests](https://github.com/sm-g/ByValue/tree/master/test/ByValue.Tests/Samples).