A layered architecture is used to build applications based on domain-driven design. It consists of three main layers:
- Domain
- Application
- Infrastructure
A layered architecture is used to build applications based on domain-driven design. It consists of three main layers:
Domain service is used to encapsulate business logic, which could not sit comfortably within an entity or aggregate in the system. Usually, a domain service is created then multiple entities are involved and behavior can't be owned by a single entity.
public class PricingService : DomainService, IPricingService { public Money CalculateTotalPriceFor(IList<item> items, Coupon coupon) { // ... } }
public interface ProfileService : ApplicationService, IProfileService { public Task<Profile> CreateProfileAsync(string username) { // ... } public Task<Profile?> GetProfileAsync(int id, CancellationToken cancellationToken) { // ... } }
public class MailService : InfrastructureService, IMailService { public virtual async Task SendMailAsync(MailMessage mailMessage) { // ... } }
One of the domain modeling building blocks is a value object. Value objects are an entity's state, describing something about the entity or the things it owns.
Value objects are always preferred over entities because they are immutable and lightweight. Due this reason, it is easy to work with them.
In the .NET world, records are good candidates to implement value objects. Below, you can see how a value object can look implemented in C#.
public record Money { public decimal Amount { get; init; } public Money(decimal amount) { if (amount % 0.01m != 0) { throw new ArgumentException("More than two decimal places", nameof(amount)); } Amount = amount; } public Money Add(Money value) { return new Money(Amount + value.Amount); } public Money Substract(Money value) { return new Money(Amount - value.Amount); } }
Every value object has to match such characteristics:
Now let's dive deep into each value objects characteristics.
Value objects have no identity.
Value objects are considered equal if they have the same value.
var a = new Money(10M); var b = new Money(10M);
Console.WriteLine(a == b); // True
Once created, a value object can not be changed.
var a = new Money(10M);
a.Amount = 12M; // Can't be assigned
As much as possible, value objects should expose expressive domain‐oriented behavior and encapsulate the state.
var a = new Money(10M); var b = a.Substract(new Money(2M));
Console.WriteLine(b); // Money { Amount = 8 }
Value objects should never be in an invalid state.
var a = new Money(10.0348M); // Throws exception "More than two decimal places"