Aggregate Building Blocks

Aggregate Building Blocks

Aggregate Building Blocks

In my last article, we explored Domain Driven Designs Aggregate pattern concept in this article we will focus on code examples and see what aggregates look like in code, and in a working example we will start in small chunks and build the solution in a series of following articles let’s start with some concrete examples in code to build on our knowledge as we refactor our solution over time.??So, with that said let us dig in.

This solution is not complete we will build on this solution over a series of articles, you can get the code for this example at:

git clone https://github.com/Onemanwolf/Aggregate_Domain_Driven_Design.git        

Aggregates in Code

So, let us review what an Aggregate is.?An Aggregate is a collection of objects that represent a business domain concept like an Order in an e-commerce system or a Patient in a medical context.???These domain concepts materialize as models in code as an Aggregate object and consist of two types of objects Entities and Value objects. ?Entities have a unique identity and are mutable, meaning the state of an entity is expected to change as opposed to Value Objects which are identified by their properties or attributes and are immutably meaning we should not change them during run time and their state should remain constant. ?

For this example, we will use an Auction example from Scott Millett's book Patterns, Principles and Practices of Domain-Driven Design.

So, let us get started by examining entities first.

Entities

As you can see from the example below an Auction in figure 2 the Auction object/class has a unique identity and is enforced by our Entity base class figure 1 which has one property Id which is a generic implementation of type T. You can see the use of a generic type this decouples our implementation from any concrete identity and allows us to change or substitute the type of identity we use for example we could have an identity class that creates custom identities. In this example we are using a GUID (Globally Unique identifier), as our Identity but if we need to change the way we create or assign and unique identity we can now change that much more easily with the use of the generic type. If the Entity in question is the root of the Aggregate, for example, an Auction Aggregate, the root Entity would be an Auction class that implements the entity base class. Or in our example an Auction which is the root of our Aggregate all access and changes to the internal value objects or entities must flow throw the root entity.

Entity Base Class

Entity Base class in this context is defining only the Identity property of Id and we could implement this as an interface instead of a concrete class, but some may argue interfaces should only be concerned with behavior and not properties or attributes, alone. ?I would love to hear your thoughts on this in the comments section.?We see in figure 1 we implement this as a concrete class Entity of type T Entity<Tid>. ?As we mentioned previously, we can substitute the type of identity following the Substitution Principles (see note).

Link to Substitution Principle:?https://reflectoring.io/lsp-explained/

namespace Application.Infrastructur
{
???public class Entity<Tid>
???{
???????public Tid? Id { get; protected set; }
???}
}        

Figure 1 Generic Entity base class.

using Application.Infrastructure

namespace Application.Models.Auction
{
???public class Auction : Entity<Guid>
???{
???????public Auction(Guid id, Money startingPrice, DateTime endsAt)
???????{
???????????if (id == Guid.Empty)
???????????????throw new ArgumentNullException($"AuctionId cannot be null {nameof(id)}");
???????????if (startingPrice == null)
???????????????throw new ArgumentNullException($"StartingPrice cannot be null {nameof(startingPrice)}");
???????????if (endsAt == DateTime.MinValue)
???????????????throw new ArgumentNullException($"EndsAt cannot be null {nameof(endsAt)}");
???????????Id = id;
???????????StartingPrice = startingPrice;
???????????EndsAt = endsAt;
???????}

???????public Money StartingPrice { get; private set; }

???????public DateTime EndsAt { get; private set; }

???????public WinningBid WinningBid { get; private set; }


???????private bool StilInProgress(DateTime currentTime)
???????{
???????????return (EndsAt > currentTime);
???????}
?
???????public void PlaceBidFor(Offer offer, DateTime currentTime)
???????{
???????????if (StilInProgress(currentTime))
???????????{
???????????????if (FirstOffer())
???????????????{
???????????????????PlaceABidForTheFirst(offer);
???????????????}
???????????????else if (BidderIsIncreasingMaxminBidToNew(offer))
???????????????{
???????????????????WinningBid = WinningBid.RaiseMaxminBid(offer.MaximumBid);
???????????????????//PlaceABidForTheFirst(new BidPlaced(Id, offer.BidderId, offer.Amount, currentTime));
???????????????}
???????????}
???????}

???????private bool BidderIsIncreasingMaxminBidToNew(Offer offer)
???????{
???????????return WinningBid.WasMadeBy(offer.BidderId) && offer.MaximumBid
???????????.IsGreaterThan(WinningBid.MaximumBid);
???????}

???????private bool FirstOffer()
???????{
???????????return WinningBid == null;
???????}
???????private void PlaceABidForTheFirst(Offer offer)
???????{
???????????if (offer.MaximumBid.IsGreaterThanOrEqualTo(StartingPrice))
???????????{
???????????????WinningBid = new WinningBid(offer.BidderId, offer.MaximumBid, StartingPrice, DateTime.Now);
???????????????Place(WinningBid);
???????????}
???????}
???????private void Place(WinningBid newBid)
???????{
???????????if (FirstOffer() && WinningBid.WasMadeBy(newBid.Bidder))
???????????{
???????????????DomainEvents.Raise<OutBid>(new OutBid(Id, newBid.Bidder));
???????????}
???????????else
???????????{
???????????????WinningBid = newBid;
???????????????DomainEvents.Raise<BidPlaced>(new BidPlaced(Id, newBid.Bidder, newBid.CurrentAuctionPrice.Amount, DateTime.Now));
???????????}
???????}
???}
};        

Figure 2 Entity Auction Class implementing Entity base class.

Value Objects

We start building out our value objects by creating a base abstract class.?This class ensures we inspect the properties of the Value Object for equality to ensure the correct properties are present and we have a valid Value object.?We must implement the base behavior in every value object, so we create the base class that checks the properties and validates the object at creation. ?It is important to understand that a value object is not a group property in a custom type but also has the responsibility of validation and ensuring the value object is in the right state when a value object is created. ?So, then, Value Object should have all the business logic for value object creation and validation. One could consider using a specification pattern if the validation rules get complex and that would also apply to entities as well.

Link to specification pattern: https://medium.com/c-sharp-progarmming/specification-design-pattern-c814649be0ef


Value Objects Base Abstract Class

The base abstract class has a single responsibility of checking the properties or attributes and returning true or false this way we can validate the value object for correctness on creation as demonstrated in figure 4 you can see we are passing in decimal to the base class and then we also implement the GetAttributesToIncludeEqualityCheck() and the Equals() methods.

Link to single responsibility: https://deviq.com/principles/single-responsibility-principle

namespace Application.Infrastructur
{
???public abstract class ValueObject<T> where T : ValueObject<T>
???{
???????protected abstract IEnumerable<object>? GetAttributesToIncludeEqualityCheck();

???????public override bool Equals(object obj){
?
???????????????return Equals(obj as T);
???????}

???????public bool Equals(T other)
???????{
???????????if (other == null)
???????????????return false;
???????????return GetAttributesToIncludeEqualityCheck().SequenceEqual(other.GetAttributesToIncludeEqualityCheck());
???????}

???????public static bool operator == (ValueObject<T> left, ValueObject<T> right)
???????{
???????????return Equals(left, right);
???????}

???????public static bool operator != (ValueObject<T> left, ValueObject<T> right)
???????{
???????????return Equals(left, right);
???????}

???????public override int GetHashCode()
???????{
???????????int hash = 17;

???????????foreach (var item in GetAttributesToIncludeEqualityCheck())
???????????{
???????????????hash = hash * 31 + (item == null ? 0 : item.GetHashCode());
???????????}
???????????return hash;
???????}
???}
}        

Figure 3 Value object base abstract class.

?

Money Value Object

The Money value object Implements the base abstract class of ValueObject<T> and is demonstrated in figure 4 in this example we use Domain models concept of Money which has one property of type decimal. The Money object is an internal member of the Auction root Entity of our Auction Aggregate and cannot be accessed by external clients.


namespace Application.Models.Auctio
{
???public class Money : ValueObject<Money>, IComparable<Money>
???{
???????protected decimal Value { get; set; }
???????public Money() : this(0m) { }
???????public Money(decimal value)
???????{
???????????ThrowExceptionIfValueIsInvalid(value);
???????????Value = value;
???????}

???????private void ThrowExceptionIfValueIsInvalid(decimal value)
???????{
???????????if (value % 0.01m != 0) throw new MoreThanTwoDecimalPlacesException();

???????????if (value < 0) throw new MoneyCanNotBeNegativeValueException();
???????}
?
???????public Money Add(Money money)
???????{
???????????return new Money(Value + money.Value);
???????}

????????public bool IsGreaterThan(Money money)
???????{
???????????return Value > money.Value;
???????}

???????public bool IsGreaterThanOrEqualTo(Money money)
???????{
???????????return Value > money.Value || this.Equals(money);
???????}

???????public bool IsLessThanOrEqualTo(Money money)
???????{
???????????return Value < money.Value || this.Equals(money);
???????}

???????public int CompareTo(Money? other)
???????{
???????????return Value.CompareTo(other?.Value);
???????}

???????protected override IEnumerable<object> GetAttributesToIncludeEqualityCheck()
???????{
???????????return new List<object> { Value };
???????}

???????public override string ToString()
???????{
???????????return string.Format("{0}", Value);
???????}
???}
}        

Figure 4 Value object Money implements the base abstract class

?Summary

Now that we have the basic structure in the code of an Aggregate as we explored Entities and Value objects the building blocks of an Aggregates. We saw how we can use base classes to enforce the rules around Entities and Value objects to ensure we have a consistent implementation of these rules across our code base following the reuse principle. ?So, you may think, what do I do with this? ?Well, that will be our next topic for discussion.?

?What’s Next

We can now move forward to the fun part by implementing this concept of Aggregates as transactional boundaries in a working application. In my next article, we will Explore the Factory pattern and Repository pattern and demonstrate implementing or repository with Entity Framework with Azure SQL as our datastore of our Auction Aggregate. In addition, we will also build an implementation using Azure Cosmos DB (databases) for your document database fans using a Generic IRepository<T>.

?Note:

SOLID Principle of design

I highly recommend you learn the SOLID principles of design to ensure your code is testable and highly extendable. Look out for an article on this topic but in the meantime learn more:

https://medium.com/swlh/solid-principles-of-oop-c24bd6ccde77.

Suggested Reading:


Scott Millett's book Patterns, Principles and Practices of Domain Driven Design.?

https://www.amazon.com/Patterns-Principles-Practices-Domain-Driven-Design/dp/1118714709

Entity Framework Core

https://learn.microsoft.com/en-us/ef/core/

Azure SQL Managed Instance

https://learn.microsoft.com/en-us/azure/azure-sql/managed-instance/sql-managed-instance-paas-overview?view=azuresql

Welcome to Azure Cosmos DB

https://learn.microsoft.com/en-us/azure/cosmos-db/introduction

要查看或添加评论,请登录

Tim Oleson的更多文章

社区洞察

其他会员也浏览了