“Value object is an object whose equality is based on the value rather than identity.”.
“A Value object has no identity and it’s immutable.”
Entities are defined by their identity and they represent the fundamental component of a domain. However not all objects may be “identified” and it’s not desirable to do so. As an example a Person is identified by an Id, but if a person has a budged and this budget is made of “Money”, the Money is a Value-Object. We don’t identify the money, we just quantify the amount of it.
Another example is to encapsulate the use of primary values into a class that actually represents it’s purpose : instead of using a string Id why not use an Id Value-Object? This is valid for all primary types. Using primary values (e.g. strings for names, emails, id’s ) can be a source of human error as this implies that any string can be either a name, an email, id.
Bad Composition:
class Person { public string Id { get; set; } public string Name { get; set; } } class Program { static void Main(string[] args) { Person person = new Person(); person.Id = Guid.NewGuid().ToString(); person.Name = Guid.NewGuid().ToString(); } }
Designing a Value Object:
I want to provide as much functionality as possible to a value object to create a “natural” feeling when handling this object. For this I will override the basic operators and equality methods. I could leave this as a task of the child object, but I want my child object to be as light as possible.
IComparable – usefull for overriding the greater and smaller operator.
IEquatable – usefull for overriding the equal and not equal operator.
TNew – I want to enforce a functional creation method for fluent declaration usage. This static Create method is defined also in the parent class, as I said before, I want the child class to be as light as possible
public abstract class ValueObject<TValue, TNew> : IComparable<ValueObject<TValue, TNew>>, IEquatable<ValueObject<TValue, TNew>> where TNew : ValueObject<TValue, TNew>, new()
Full Implementation
This is my implementation of a Value Object. It can be refined with more intuitive methods added but for my project it will do.
public abstract class ValueObject<TValue> : IComparable<ValueObject<TValue>>, IEquatable<ValueObject<TValue>>, ISerializable { [ExpandoKey(nameof(Value))] public TValue Value { get; protected set; } protected Type Type => typeof(TValue); [BsonIgnore] public ValidationResult<string> Validation { get { if (_validator == null) { _validationBuilder = new ValidationBuilder<TValue, string>(); Validators(_validationBuilder); _validator = _validationBuilder.GetValidation(); } return _validator.Validate(Value); } } private IValidation<TValue, string> _validator; private ValidationBuilder<TValue, string> _validationBuilder; protected ValueObject() { } protected abstract void Validators(ValidationBuilder<TValue, string> validators); /* * Contracts */ protected abstract bool EqualsCore(object obj); protected abstract int GetHashCodeCore(); public abstract int CompareTo(ValueObject<TValue> other); public bool Equals(ValueObject<TValue> other) => this.EqualsCore(other); /* * Methods */ public override bool Equals(object obj) => this.EqualsCore(obj); public override int GetHashCode() => this.GetHashCodeCore(); public void GetObjectData(SerializationInfo info, StreamingContext context) { this.Value = (TValue)info.GetValue(nameof(Value), this.Type); } public static bool operator ==(ValueObject<TValue> first, ValueObject<TValue> second) { if (ReferenceEquals(first, null) && ReferenceEquals(second, null)) return true; if (ReferenceEquals(first, null) && !ReferenceEquals(second, null)) return false; if (!ReferenceEquals(first, null) && ReferenceEquals(second, null)) return false; return first.Equals(second); } public static bool operator !=(ValueObject<TValue> first, ValueObject<TValue> second) { return !(first == second); } public static bool operator >(ValueObject<TValue> first, ValueObject<TValue> second) { if (ReferenceEquals(first, null) && ReferenceEquals(second, null)) return false; if (ReferenceEquals(first, null) && !ReferenceEquals(second, null)) return false; if (!ReferenceEquals(first, null) && ReferenceEquals(second, null)) return true; return first.CompareTo(second) > 0; } public static bool operator <(ValueObject<TValue> first, ValueObject<TValue> second) { if (ReferenceEquals(first, null) && ReferenceEquals(second, null)) return false; if (ReferenceEquals(first, null) && !ReferenceEquals(second, null)) return true; if (!ReferenceEquals(first, null) && ReferenceEquals(second, null)) return false; return first.CompareTo(second) < 0; } public static bool operator >=(ValueObject<TValue> first, ValueObject<TValue> second) { if (ReferenceEquals(first, null) && ReferenceEquals(second, null)) return true; if (ReferenceEquals(first, null) && !ReferenceEquals(second, null)) return false; if (!ReferenceEquals(first, null) && ReferenceEquals(second, null)) return true; return first.CompareTo(second) >= 0; } public static bool operator <=(ValueObject<TValue> first, ValueObject<TValue> second) { if (ReferenceEquals(first, null) && ReferenceEquals(second, null)) return true; if (ReferenceEquals(first, null) && !ReferenceEquals(second, null)) return true; if (!ReferenceEquals(first, null) && ReferenceEquals(second, null)) return false; return first.CompareTo(second) <= 0; } public static implicit operator TValue(ValueObject<TValue> valueObject) { return (!(valueObject is null)) ? valueObject.Value : default(TValue); } }
Let’s also see an actual implementation of this. Creating a child of the ValueObject is simple, the declaration will enforce implementing a parameterless constructor and the needed methods for the base class
public class Id : ValueObject<string> { public Id() { //for expando deserializer } public Id(SerializationInfo info, StreamingContext context) { //The special constructor is used to deserialize values. this.GetObjectData(info, context); } private Id(string id) { this.Value = id; } public static Id Create(string id) { return new Id(id); } public override int CompareTo(ValueObject<string> other) { if (other is null) return 1; return this.Value.CompareTo(other.Value); } protected override bool EqualsCore(object obj) { if (obj is null) return false; if (obj is Id valueObject) return this.Value == valueObject.Value; return false; } protected override int GetHashCodeCore() { return this.Value.GetHashCode(); } protected override void Validators(ValidationBuilder<string, string> validators) { validators.Add(x => (!string.IsNullOrEmpty(x)) ? ValidationResult<string>.Success() : ValidationResult<string>.Fail("Id cannot be empty or null"), 0); } }
Creating an object value is straight forward, we can use the Create static method. One of the advantages of this method is that it provides a fluent approach to instantiate an object and validations can be made before the object is actually created.
Id id = Id.Create(Guid.NewGuid().ToString()); var idvalue = id.Value;