E-Commerce Domain Examples
ProductId — strongly typed identifier (struct)
Using a struct wraps a primitive in a type-safe shell with zero overhead.
using ZeroAlloc.ValueObjects;
[ValueObject]
public readonly partial struct ProductId
{
public Guid Value { get; }
public ProductId(Guid value) => Value = value;
public static ProductId New() => new(Guid.NewGuid());
public static ProductId Parse(string s) => new(Guid.Parse(s));
public override string ToString() => Value.ToString();
}
var id1 = ProductId.Parse("a1b2c3d4-e5f6-7890-abcd-ef1234567890");
var id2 = ProductId.Parse("a1b2c3d4-e5f6-7890-abcd-ef1234567890");
id1 == id2 // true
id1.Equals(id2) // true
var catalog = new Dictionary<ProductId, Product>();
catalog[id1] = product;
var found = catalog[id2]; // works — same hash, equal
Money — value with currency
[ValueObject]
public partial class Money
{
public decimal Amount { get; }
public string Currency { get; }
public Money(decimal amount, string currency)
{
if (amount < 0) throw new ArgumentException("Amount cannot be negative");
if (string.IsNullOrWhiteSpace(currency)) throw new ArgumentException("Currency required");
Amount = amount;
Currency = currency.ToUpperInvariant();
}
public Money Add(Money other)
{
if (Currency != other.Currency)
throw new InvalidOperationException($"Cannot add {Currency} and {other.Currency}");
return new Money(Amount + other.Amount, Currency);
}
public Money Multiply(decimal factor) => new(Amount * factor, Currency);
public static Money Zero(string currency) => new(0m, currency);
}
var subtotal = new Money(49.99m, "USD");
var shipping = new Money(5.00m, "USD");
var total = subtotal.Add(shipping);
// Deduplication in a set of prices
var uniquePrices = new HashSet<Money>
{
new Money(9.99m, "USD"),
new Money(9.99m, "USD"), // duplicate, not added
new Money(14.99m, "USD"),
};
// uniquePrices.Count == 2
ShippingAddress — partial equality
Shipping addresses should be compared on delivery-relevant fields only.
[ValueObject]
public partial class ShippingAddress
{
[EqualityMember] public string Line1 { get; }
[EqualityMember] public string? Line2 { get; }
[EqualityMember] public string City { get; }
[EqualityMember] public string State { get; }
[EqualityMember] public string PostalCode { get; }
[EqualityMember] public string Country { get; }
// Cosmetic / operational — not part of domain identity
public string? DeliveryInstructions { get; }
public bool IsDefault { get; }
public ShippingAddress(
string line1, string? line2, string city,
string state, string postalCode, string country,
string? deliveryInstructions = null,
bool isDefault = false)
{
Line1 = line1; Line2 = line2; City = city;
State = state; PostalCode = postalCode; Country = country;
DeliveryInstructions = deliveryInstructions;
IsDefault = isDefault;
}
}
var addr1 = new ShippingAddress("123 Main St", null, "Springfield", "IL", "62701", "US",
deliveryInstructions: "Ring bell");
var addr2 = new ShippingAddress("123 Main St", null, "Springfield", "IL", "62701", "US",
deliveryInstructions: "Leave at door");
// Same delivery address, different instructions — equal for dedup purposes
addr1 == addr2 // true
var saved = new HashSet<ShippingAddress> { addr1, addr2 };
// saved.Count == 1
Discount — opt-out with audit fields
[ValueObject]
public partial class Discount
{
public string Code { get; }
public decimal Percentage { get; }
public DateTime ValidFrom { get; }
public DateTime ValidTo { get; }
[IgnoreEqualityMember] public Guid IssuedBy { get; }
[IgnoreEqualityMember] public DateTime CreatedAt { get; }
public Discount(string code, decimal percentage, DateTime validFrom, DateTime validTo, Guid issuedBy)
{
Code = code; Percentage = percentage;
ValidFrom = validFrom; ValidTo = validTo;
IssuedBy = issuedBy; CreatedAt = DateTime.UtcNow;
}
public bool IsValid(DateTime at) => at >= ValidFrom && at <= ValidTo;
}
Two discounts with the same code, percentage, and validity window are equal — regardless of who issued them or when the record was created.