Domain-Driven Design is a software development approach in which it utilizes concepts and good practices related to object-oriented programming.
Domain-Driven Design: Tackling Complexity in the Heart of Software - Eric Evans | Implementing Domain-Driven Design - Vaughn Vernon |
---|---|
[TestClass]
public class Tests
{
[TestMethod]
public void Email()
{
Assert.IsTrue(new Email("domain.com").Invalid);
Assert.IsTrue(new Email(string.Empty).Invalid);
Assert.IsTrue(new Email("email@domain.com").Valid);
}
[TestMethod]
public void Order()
{
var customer = CustomerFactory.Create("Luke", "Skywalker", "luke.skywalker@starwars.com");
var product = ProductFactory.Create("Millennium Falcon", 500_000_000);
var item = OrderItemFactory.Create(product, 1);
var order = OrderFactory.Create(customer);
order.AddItem(item);
var discount = new DiscountService().Calculate(order.Total, DiscountType.Large);
order.ApplyDiscount(discount);
Assert.AreEqual(250_000_000, order.Total.Value);
}
}
public sealed record Customer(FullName FullName, Email Email) : Entity
{
public Email Email { get; private set; } = Email;
public FullName FullName { get; private set; } = FullName;
public void ChangeEmail(Email email) => Email = email;
public void ChangeFullName(FullName fullName) => FullName = fullName;
}
public static class CustomerFactory
{
public static Customer Create(string firstName, string lastName, string email)
{
return new(new FullName(firstName, lastName), new Email(email));
}
}
public sealed record Product(string Description, Amount Price) : Entity
{
public string Description { get; private set; } = Description;
public Amount Price { get; private set; } = Price;
public void ChangeDescription(string description) => Description = description;
public void ChangePrice(Amount price) => Price = price;
}
public static class ProductFactory
{
public static Product Create(string description, decimal price)
{
return new(description, new Amount(price));
}
}
public sealed record Order(Customer Customer) : Entity
{
public Amount Discount { get; private set; } = new(0);
public IReadOnlyList<OrderItem> Items { get; private set; } = new List<OrderItem>();
public Amount Total => new(Items.Sum(item => item.SubTotal.Value) - Discount.Value);
public void AddItem(OrderItem item) => Items = new List<OrderItem>(Items) { item };
public void ApplyDiscount(Amount discount) => Discount = discount;
}
public static class OrderFactory
{
public static Order Create(Customer customer) => new(customer);
}
public sealed record OrderItem(Product Product, Quantity Quantity) : Entity
{
public Amount SubTotal => new(Product.Price.Value * Quantity.Value);
}
public static class OrderItemFactory
{
public static OrderItem Create(Product product, decimal quantity)
{
return new(product, new Quantity(quantity));
}
}
public enum DiscountType
{
Small = 1,
Medium = 2,
Large = 3
}
public sealed class DiscountService
{
public Amount Calculate(Amount amount, DiscountType type)
{
var discount = Factory.Get<IDiscount>(x => x.IsApplicable(type));
if (discount is null) { return amount; }
return discount.Calculate(amount);
}
}
public interface IDiscount
{
Amount Calculate(Amount amount);
bool IsApplicable(DiscountType type);
}
public sealed class SmallDiscount : IDiscount
{
public Amount Calculate(Amount amount) => new(amount.Value * 0.1M);
public bool IsApplicable(DiscountType type) => type == DiscountType.Small;
}
public sealed class MediumDiscount : IDiscount
{
public Amount Calculate(Amount amount) => new(amount.Value * 0.25M);
public bool IsApplicable(DiscountType type) => type == DiscountType.Medium;
}
public sealed class LargeDiscount : IDiscount
{
public Amount Calculate(Amount amount) => new(amount.Value * 0.5M);
public bool IsApplicable(DiscountType type) => type == DiscountType.Large;
}
public sealed record Amount(decimal Value)
{
public override string ToString() => Value.ToString();
}
public sealed record Email(string Value)
{
public bool Invalid => !Valid;
public bool Valid
{
get
{
if (string.IsNullOrWhiteSpace(Value)) return false;
const string regex = @"^([a-z0-9_\.\-]{3,})@([\da-z\.\-]{3,})\.([a-z\.]{2,6})$";
return new Regex(regex).IsMatch(Value);
}
}
public override string ToString() => Value.ToString();
}
public sealed record FullName(string FirstName, string LastName)
{
public override string ToString() => $"{ FirstName } { LastName }";
}
public sealed record Quantity(decimal Value)
{
public override string ToString() => Value.ToString();
}