refactor(svcs): SeedWork Entity & ValueObject

This commit is contained in:
Collin M. Barrett 2021-11-26 13:50:07 -06:00
parent b01c932a6d
commit 43c2036a7e
6 changed files with 160 additions and 28 deletions

View file

@ -1,6 +1,8 @@
namespace FilterLists.Directory.Domain.Aggregates.Changes;
using FilterLists.SharedKernel.Domain.SeedWork;
public abstract class Change
namespace FilterLists.Directory.Domain.Aggregates.Changes;
public abstract class Change : Entity
{
public string? Reason { get; protected init; }
public DateTime SubmittedAt { get; protected init; } = DateTime.UtcNow;

View file

@ -1,9 +1,10 @@
using FilterLists.Directory.Domain.Aggregates.Changes;
using FilterLists.Directory.Domain.Aggregates.Licenses;
using FilterLists.SharedKernel.Domain.SeedWork;
namespace FilterLists.Directory.Domain.Aggregates.FilterLists;
public class FilterList : IRequireChangeApproval<FilterListChange>
public class FilterList : Entity, IRequireChangeApproval<FilterListChange>
{
protected FilterList() { }

View file

@ -1,6 +1,8 @@
namespace FilterLists.Directory.Domain.Aggregates.FilterLists;
using FilterLists.SharedKernel.Domain.SeedWork;
public class FilterListViewUrl
namespace FilterLists.Directory.Domain.Aggregates.FilterLists;
public class FilterListViewUrl : Entity
{
protected FilterListViewUrl() { }

View file

@ -1,6 +1,8 @@
namespace FilterLists.Directory.Domain.Aggregates.Licenses;
using FilterLists.SharedKernel.Domain.SeedWork;
public class License
namespace FilterLists.Directory.Domain.Aggregates.Licenses;
public class License : Entity
{
protected License() { }

View file

@ -0,0 +1,73 @@
namespace FilterLists.SharedKernel.Domain.SeedWork;
/// <summary>
/// https://github.com/vkhorikov/CSharpFunctionalExtensions/blob/master/CSharpFunctionalExtensions/Entity/Entity.cs
/// </summary>
public abstract class Entity<TId>
{
public virtual TId Id { get; protected set; } = default!;
protected Entity()
{
}
protected Entity(TId id)
{
Id = id;
}
public override bool Equals(object? obj)
{
if (obj is not Entity<TId> other)
return false;
if (ReferenceEquals(this, other))
return true;
if (ValueObject.GetUnproxiedType(this) != ValueObject.GetUnproxiedType(other))
return false;
if (IsTransient() || other.IsTransient())
return false;
return Id is not null && Id.Equals(other.Id);
}
private bool IsTransient()
{
return Id is null || Id.Equals(default(TId));
}
public static bool operator ==(Entity<TId>? a, Entity<TId>? b)
{
if (a is null && b is null)
return true;
if (a is null || b is null)
return false;
return a.Equals(b);
}
public static bool operator !=(Entity<TId>? a, Entity<TId>? b)
{
return !(a == b);
}
public override int GetHashCode()
{
return (ValueObject.GetUnproxiedType(this)?.ToString() + Id).GetHashCode();
}
}
public abstract class Entity : Entity<long>
{
protected Entity()
{
}
protected Entity(long id)
: base(id)
{
}
}

View file

@ -1,23 +1,22 @@
namespace FilterLists.SharedKernel.Domain.SeedWork;
/// <summary>
/// https://enterprisecraftsmanship.com/posts/value-object-better-implementation/
/// https://github.com/vkhorikov/CSharpFunctionalExtensions/blob/master/CSharpFunctionalExtensions/ValueObject/ValueObject.cs
/// </summary>
public abstract class ValueObject
[Serializable]
public abstract class ValueObject : IComparable, IComparable<ValueObject>
{
protected abstract IEnumerable<object> GetEqualityComponents();
private int? _cachedHashCode;
protected abstract IEnumerable<object?> GetEqualityComponents();
public override bool Equals(object? obj)
{
if (obj == null)
{
return false;
}
if (GetType() != obj.GetType())
{
if (GetUnproxiedType(this) != GetUnproxiedType(obj))
return false;
}
var valueObject = (ValueObject)obj;
@ -26,27 +25,66 @@ public override bool Equals(object? obj)
public override int GetHashCode()
{
return GetEqualityComponents()
.Aggregate(1, (current, obj) =>
{
unchecked
_cachedHashCode ??= GetEqualityComponents()
.Aggregate(
1,
(current, obj) =>
{
return (current * 23) + (obj?.GetHashCode() ?? 0);
}
});
unchecked
{
return current * 23 + (obj?.GetHashCode() ?? 0);
}
});
return _cachedHashCode.Value;
}
public virtual int CompareTo(object? obj)
{
var thisType = GetUnproxiedType(this);
var otherType = GetUnproxiedType(obj);
if (thisType != otherType)
return string.CompareOrdinal(thisType?.ToString(), otherType?.ToString());
var other = (ValueObject?)obj;
var components = GetEqualityComponents().ToArray();
var otherComponents = other?.GetEqualityComponents().ToArray() ?? Array.Empty<object?>();
return components.Select((t, i) => CompareComponents(t, otherComponents[i]))
.FirstOrDefault(comparison => comparison != 0);
}
private static int CompareComponents(object? object1, object? object2)
{
if (object1 is null && object2 is null)
return 0;
if (object1 is null)
return -1;
if (object2 is null)
return 1;
if (object1 is IComparable comparable1 && object2 is IComparable comparable2)
return comparable1.CompareTo(comparable2);
return object1.Equals(object2) ? 0 : -1;
}
public virtual int CompareTo(ValueObject? other)
{
return CompareTo(other as object);
}
public static bool operator ==(ValueObject? a, ValueObject? b)
{
if (ReferenceEquals(a, null) && ReferenceEquals(b, null))
{
if (a is null && b is null)
return true;
}
if (ReferenceEquals(a, null) || ReferenceEquals(b, null))
{
if (a is null || b is null)
return false;
}
return a.Equals(b);
}
@ -55,4 +93,18 @@ public override int GetHashCode()
{
return !(a == b);
}
internal static Type? GetUnproxiedType(object? obj)
{
const string EFCoreProxyPrefix = "Castle.Proxies.";
const string NHibernateProxyPostfix = "Proxy";
var type = obj?.GetType();
string? typeString = type?.ToString();
if (typeString is not null && (typeString.Contains(EFCoreProxyPrefix) || typeString.EndsWith(NHibernateProxyPostfix)))
return type?.BaseType;
return type;
}
}