14 Nov 2023
Aggregates: In Domain-Driven Design (DDD), an aggregate is a group of closely related domain objects treated as a singular unit. These aggregates comprise both entities and value objects, seen as a tightly connected whole.
Aggregates are typically composed of a root object and a number of child objects. The root object is the only object that can be referenced directly from outside the aggregate. The child objects can only be accessed through the root object.
Analogy 1: Shopping Cart and Items
Imagine a shopping cart. A shopping cart can contain many different items, such as apples, oranges, and bananas. But the shopping cart itself is a single unit. We don't add items to the apples or the oranges. We add them to the shopping cart.
public class CartItem
{
public Guid Id { get; private set; }
public string ProductName { get; private set; }
public decimal Price { get; private set; }
public int Quantity { get; private set; }
// Constructor and methods for the CartItem
public CartItem(Guid id, string productName, decimal price, int quantity)
{
Id = id;
ProductName = productName;
Price = price;
Quantity = quantity;
}
public decimal GetTotal()
{
return Price * Quantity;
}
}
public class ShoppingCart
{
public Guid Id { get; private set; }
private List<CartItem> _cartItems = new List<CartItem>();
// Constructor and methods for the ShoppingCart
public ShoppingCart(Guid id)
{
Id = id;
}
public void AddItem(string productName, decimal price, int quantity)
{
// Perform validation and business logic before adding the item to the shopping cart
var cartItem = new CartItem(Guid.NewGuid(), productName, price, quantity);
_cartItems.Add(cartItem);
}
public void RemoveItem(CartItem cartItem)
{
// Perform validation and business logic before removing the item from the shopping cart
_cartItems.Remove(cartItem);
}
public IEnumerable<CartItem> GetItems()
{
// Return the items in the shopping cart
return _cartItems.AsReadOnly();
}
public decimal GetTotal()
{
// Calculate the total price of all items in the shopping cart
return _cartItems.Sum(item => item.GetTotal());
}
}
In this example:
ShoppingCartis the aggregate root.CartItemis an entity within the aggregate.ShoppingCartmanages a list ofCartItementities.
This model represents a shopping cart that can contain various items. The ShoppingCart aggregate encapsulates the logic for managing items within the cart, and changes to the state of items (adding and removing) are controlled through the aggregate root (ShoppingCart). This aligns with the principles of DDD, where an aggregate represents a cluster of domain objects treated as a single unit.
Analogy 2: Library and Bookshelf:
Consider a library with various bookshelves. Each bookshelf can hold different books, but the bookshelf itself is a distinct unit. Just like in Domain-Driven Design (DDD), where an aggregate is a cluster of domain objects treated as a single unit, in the library analogy, the bookshelf serves as an aggregate, encapsulating and managing the books it contains.
public class Book
{
public Guid Id { get; private set; }
public string Title { get; private set; }
public string Author { get; private set; }
public bool IsAvailable { get; private set; } = true;
// Constructor and methods for the Book
public Book(Guid id, string title, string author)
{
Id = id;
Title = title;
Author = author;
}
public void BorrowBook()
{
if (!IsAvailable)
{
throw new InvalidOperationException("Book is not available for borrowing.");
}
IsAvailable = false;
}
public void ReturnBook()
{
if (IsAvailable)
{
throw new InvalidOperationException("Book is already available; cannot return again.");
}
IsAvailable = true;
}
}
public class Bookshelf
{
public Guid Id { get; private set; }
public string ShelfName { get; private set; }
private List<Book> _books = new List<Book>();
// Constructor and methods for the Bookshelf
public Bookshelf(Guid id, string shelfName)
{
Id = id;
ShelfName = shelfName;
}
public void AddBook(string title, string author)
{
// Perform validation and business logic before adding the book to the bookshelf
var book = new Book(Guid.NewGuid(), title, author);
_books.Add(book);
}
public void RemoveBook(Book book)
{
// Perform validation and business logic before removing the book from the bookshelf
_books.Remove(book);
}
public IEnumerable<Book> GetBooks()
{
// Return the books on the bookshelf
return _books.AsReadOnly();
}
}
In this example:
Bookshelfis the aggregate root.Bookis an entity within the aggregate.Bookshelfmanages a list ofBookentities.
This represents a simple model where a bookshelf contains various books. The Bookshelf aggregate encapsulates the logic for managing books on a particular bookshelf, and changes to the state of books (adding and removing) are controlled through the aggregate root (Bookshelf). This aligns with the principles of DDD, where an aggregate represents a cluster of domain objects treated as a single unit.
Analogy 3: Social Media Profile and Posts:
Think of a social media profile as an aggregate. The profile represents an individual entity, and within it, you can find various posts, photos, and comments. Each post is associated with the profile, much like how items are associated with a shopping cart. The profile acts as a boundary, ensuring that interactions and relationships are managed within the context of that specific user, aligning with the concept of aggregates in DDD.
public class Post
{
public Guid Id { get; private set; }
public string Content { get; private set; }
public DateTime CreatedAt { get; private set; }
public Guid UserId { get; private set; }
// Constructor and methods for the Post
public Post(Guid id, string content, DateTime createdAt, Guid userId)
{
Id = id;
Content = content;
CreatedAt = createdAt;
UserId = userId;
}
}
public class Photo
{
public Guid Id { get; private set; }
public string Url { get; private set; }
public DateTime UploadedAt { get; private set; }
public Guid UserId { get; private set; }
// Constructor and methods for the Photo
public Photo(Guid id, string url, DateTime uploadedAt, Guid userId)
{
Id = id;
Url = url;
UploadedAt = uploadedAt;
UserId = userId;
}
}
public class Comment
{
public Guid Id { get; private set; }
public string Content { get; private set; }
public DateTime CreatedAt { get; private set; }
public Guid UserId { get; private set; }
// Constructor and methods for the Comment
public Comment(Guid id, string content, DateTime createdAt, Guid userId)
{
Id = id;
Content = content;
CreatedAt = createdAt;
UserId = userId;
}
}
public class UserProfile
{
public Guid UserId { get; private set; }
public string UserName { get; private set; }
private List<Post> _posts = new List<Post>();
private List<Photo> _photos = new List<Photo>();
private List<Comment> _comments = new List<Comment>();
// Constructor and methods for the UserProfile
public UserProfile(Guid userId, string userName)
{
UserId = userId;
UserName = userName;
}
public void AddPost(string content)
{
var post = new Post(Guid.NewGuid(), content, DateTime.UtcNow, UserId);
_posts.Add(post);
}
public void AddPhoto(string url)
{
var photo = new Photo(Guid.NewGuid(), url, DateTime.UtcNow, UserId);
_photos.Add(photo);
}
public void AddComment(string content)
{
var comment = new Comment(Guid.NewGuid(), content, DateTime.UtcNow, UserId);
_comments.Add(comment);
}
public IEnumerable<Post> GetPosts()
{
return _posts.AsReadOnly();
}
public IEnumerable<Photo> GetPhotos()
{
return _photos.AsReadOnly();
}
public IEnumerable<Comment> GetComments()
{
return _comments.AsReadOnly();
}
}
In this example:
UserProfileis the aggregate root.Post,Photo, andCommentare entities within the aggregate.UserProfilemanages lists ofPost,Photo, andCommententities.
This example demonstrates a social media profile aggregate where various types of content (posts, photos, and comments) are associated with the user profile. The UserProfile aggregate encapsulates the logic for managing these entities, and changes to the state of posts, photos, and comments are controlled through the aggregate root (UserProfile).
Above pseudo-code snippets illustrate the basic structure and relationships in each analogy. In a real-world scenario, you would likely have more sophisticated implementations and considerations.
Here are some simple examples of aggregates:
-
An
order aggregatemight include the order itself, as well as the order items, shipping address, and billing address. -
A
product aggregatemight include the product itself, as well as its price, inventory level, and any other related data. -
A
customer aggregatemight include the customer's name, address, contact information, and order history.