Product, Category, Attributes modelling in DDD - domain-driven-design

I'm trying to model online shop catalog using Domain Driven Design.
There are three main concepts I have right now: Product, Category, Attribute.
Attribute is a characteristic of a product. For instance things such as color, weight, number of CPU cores etc. There are attributes which possible values are fixed, for instance "condition" - can be new or used. Some of them are within some range of values, for instance "number of CPU cores". Some are freely created like "color".
Category have required attributes which every product within that category needs to have, and optional ones. Categories can have parent categories.
Product belongs to a single category which needs to be a leaf category(no children categories).
Now the problem I have is to model these three concepts as aggregates.
One option is to have three different aggregates: Product, Attribute, Category.
Product will have it's attribute values(each with parent id to Attribute AR). Attribute will be in different types(fixed, freely choosen, range). Category will have a list of IDs of Attributes which are required, and list of IDs
The issue here is that whenever I need to create a new product I would need to check if it has all of the required attributes, check the values, and then store the product. This validation would span three aggregates. Where should it go ? It should be domain service ?
Other option is to have 2 AR. Category, with it's products and Attributes. The issue here is again validation of correct values for a single attribute added to a product. The other huge issue I see here, is that I should fetch the whole aggregate from the repository. Given that category can have hundreds of products, I don't think that's a good idea. However it makes sense as a conceptual whole, as If I would like to delete a category, all of it's products should be deleted as well.
What I am missing here ?

In "Implementing Domain Driven Design", Vaugh Vernon uses the "specification pattern" to handle entity/aggregate validation. Without quoting the entire chapter, you have different possibilities : (Java is used in my example, I hope you get the overall idea)
Validating Attributes / Properties
If it is a simple validation process field by field, then validate each attribute separately inside the setter method.
class Product {
String name;
public Product(String name) {
setName(name);
}
public void setName(String name) {
if(name == null) {
throw new IllegalArgumentException("name cannot be null");
}
if(name.length() == 0) {
throw new IllegalArgumentException("name cannot be empty");
}
this.name = name;
}
}
Validating Whole Object
If you have to validate the whole object, you can use a kind of specification to help you. To avoid having the entity having too much responsibilities (dealing with the state, and validate it), you can use a Validator.
a. Create a generic Validator class, and implement it for your Product Validator. Use a NotificationHandler to deal with your validation error (exception, event, accumulating errors and then sending them ? up to you) :
public abstract class Validator {
private ValidationNotificationHandler notificationHandler;
public Validator(ValidationNotificationHandler aHandler) {
super();
this.setNotificationHandler(aHandler);
}
public abstract void validate();
protected ValidationNotificationHandler notificationHandler() {
return this.notificationHandler;
}
private void setNotificationHandler(ValidationNotificationHandler aHandler) {
this.notificationHandler = aHandler;
}
}
NotificationHandler is an interface, that you could implement given your requirements in term of validation error handling. Here is the interface proposed by Vaugh Vernon :
public interface ValidationNotificationHandler {
public void handleError(String aNotificationMessage);
public void handleError(String aNotification, Object anObject);
public void handleInfo(String aNotificationMessage);
public void handleInfo(String aNotification, Object anObject);
public void handleWarning(String aNotificationMessage);
public void handleWarning(String aNotification, Object anObject);
}
b. Implements this class with a specific validator ProductValidator:
public class ProductValidator extends Validator {
private Product product;
public ProductValidator(Product product, ValidationNotificationHandler aHandler) {
super(aHandler);
this.setProduct(product);
}
private void setProduct(Product product) {
this.product = product;
}
#Override
public void validate() {
this.checkForCompletness();
}
private void checkForCompletness() {
if(product.getName().equals("bad name") && anotherCondition()) {
notificationHandler().handleError("This specific validation failed");
}
...
}
}
And then, you can update your entity, with a validate method, that will call this validator to validate the whole object:
public class Product {
private String name;
public Product(String name) {
setName(name);
}
private void setName(String name) {
if (name == null) {
throw new IllegalArgumentException("Name cannot be null");
}
if (name.length() == 0) {
throw new IllegalArgumentException("Name cannot be empty");
}
this.name = name;
}
// Here is the new method to validate your object
public void validate(ValidationNotificationHandler aHandler) {
(new ProductValidator(this, aHandler)).validate();
}
}
Validating multiple aggregates
And finally, which is your direct concern, if you want to validate multiple aggregates to have something coherent, the recommendation is to create a Domain Service and a specific validator. The domain services can either have injected the repositories to look up for the different aggregates, or I everything is created by the application layers, then inject the different aggregates as method parameter:
public class ProductCategoryValidator extends Validator {
private Product product;
private Category category;
public ProductCategoryValidator(Product product, Category category, ValidationNotificationHandler aHandler) {
super(aHandler);
this.setProduct(product);
this.setCategory(category);
}
private void setCategory(Category category) {
this.category = category;
}
private void setProduct(Product product) {
this.product = product;
}
#Override
public void validate() {
this.checkForCompletness();
}
private void checkForCompletness() {
// Count number of attributes, check for correctness...
}
}
And the domain service that will call the Validator
public class ProductService {
// Use this is you can pass the parameters from the client
public void validateProductWithCategory(Product product, Category category, ValidationNotificationHandler handler) {
(new ProductCategoryValidator(product, category, handler)).validate();
}
// Use This is you need to retrieve data from persistent layer
private ProductRepository productRepository;
private CategoryReposiory categoryReposiory;
public ProductService(ProductRepository productRepository, CategoryReposiory categoryReposiory) {
this.productRepository = productRepository;
this.categoryReposiory = categoryReposiory;
}
public void validate(String productId, ValidationNotificationHandler handler) {
Product product = productRepository.findById(productId);
Category category = categoryReposiory.categoryOfProductId(productId);
(new ProductCategoryValidator(product, category, handler)).validate();
}
}
Like I said, I think you might be interested into the solution 3. As you have guessed it, you can use a Domain Service. But, add a specific validator to ensure the "responsibilities" are not mixed.

The issue here is that whenever I need to create a new product I would need to check if it has all of the required attributes, check the values, and then store the product. This validation would span three aggregates. Where should it go ? It should be domain service ?
The usual answer is that the retrieval of information (aka I/O) is done in an application service. Copies of that information are then passed, like other inputs, into the domain model.
A single "transaction" might include multiple calls to aggregate methods, as we fetch inputs from different places.
These copies of information are generally treated as data on the outside - we have an unlocked copy of the data here; while we are using that copy, the authoritative copy might be changing.
If you find yourself thinking that "the authoritative copy of the data over there isn't allowed to change while I use it over here" - that's a big red flag that either (a) you don't actually understand your real data constraints or (b) that you've drawn your aggregate boundaries incorrectly.
Most data from the real world is data on the outside (Bob's billing address may change without asking your permission - what you have in your database is a cached copy of the Bob's billing address as of some point in the past).

Related

What are the real world, production grade cases for domain events (understood as events within same bounded context & process)?

If we have a bounded context with lets say 2 aggregates where aggregate1 publishes event1 and aggregate2 wants to react to it, we have 1 ways of doing it:
in process raising event1 > aggregate2 reacting to it
publish event1 to message bus and have some separate process pick it up & invoke aggregate2 method(s)
regardless of being within the same bounded context, if we want to make sure we don't lose event1 (application crashes between aggregate1 is saved, and aggregate2 is saved in reaction to event1, e.g.) i have a hard time finding examples of when would option 1 be better than option 2 (beyond maybe validation)?
i must be missing something but at this point of my knowledge, it seems like a pure theoretical concept to me without some real world value in terms of reliability and ability to maintain correct state.
of course that publishing a message and having separate process listen/react to it might seem like an overkill but is there any practical use of domain events that are not persisted somewhere (even within local DB which gets polled in which case i'd call that a primitive message bus)?
what am i missing?
What is a real world application of domain events within a bounded
context and process?
Requirements:
User can create categories.
Category names must be unique.
User can rename categories.
(Category will have a number of other properties unrelated to naming).
DDD Concepts:
A Category aggregate should be responsible for its own internal invariants, but cannot know the details about other Category aggregates.
How are you going to ensure that the Category Name for the current Category is globally unique without the Category having access to all other categories?
Answer: Domain Events
DomainEvent
public CategoryRenamed : DomainEvent
{
public Category Category { get; }
internal CategoryRenamed(Category category)
{
this.Category = category;
}
}
DomainEventHandler
public CategoryRenamedHandler : IDomainEventHandler<CategoryRenamed>
{
public CategoryRenamedHandler(CategoryRenamed domainEvent)
{
string proposedName = domainEvent.Category.Name;
// query database to ensure that proposedName is not already in use
if (inUse)
throw new Exception($"Name {proposedName} already in use." ;
}
}
Entity
public abstract class Entity
{
List<DomainEvent> _domainEvents = new List<DomainEvent>();
protected AddDomainEvent(DomainEvent domainEvent)
{
_domainEvents.Add(domainEvent);
}
public List<DomainEvent> DomainEvents => _domainEvents;
}
Category
public class Category : Entity
{
public Guid Id { get; private set; }
public string Name { get; private set; }
public Category(Guid id, string name)
{
Id = id;
SetName(name);
}
public Rename(string name)
{
SetName(name);
}
void SetName(string name)
{
// Local Invariants
if (string.IsNullOrWhitespace(name))
throw new Exception("Invalid name");
Name = name;
// Add a domain event for the infrastructure to process
AddDomainEvent(new CategoryRenamed(this));
}
}
Command
public class AddCategoryCommand
{
public Guid Id { get; set; }
public string Name { get; set; }
}
CommandHandler
public class CommandHandler : ICommandHandler<AddCategoryCommand>
{
readonly ICategoryRepository _categoryRepository;
public CommandHandler(ICategoryRepository categoryRepository)
{
_categoryRepository = categoryRepository;
}
public void HandleCommand(AndCategoryCommand command)
{
Category newCategory = new(command.Id, command.Name);
// Check for domain events before committing to repository
DomainEventDispatcher.DispatchEvents(newCategory.DomainEvents);
// Dispatcher will find the CategoryRenamed event and send 'in-process'
// to CategoryRenamedHandler
// If name was is in use an error will be thrown by the handler (see above)
_categoryRepository.Add(newCategory);
}
}
Outcome
Your Category aggregate has enforced its own local invariants and the domain command and domainevent handling infrastructure has been leveraged to ensure uniqueness of name across all categories.

Domain events are customer defined, not hard-coded

Requirements for our SaaS product are to build a domain layer where any attribute or combination of attributes that are changed could trigger a domain event - and subsequently kick off a custom process, or notification.
So, I am hesitant to add tons of code to the domain layer that kicks off tons of DomainEvent objects which may not make sense to many tenants.
Each tenant will have the ability to (through a UI screen):
1. define which attributes they care about (e.g. "amount") and why (e.g. amount is now greater than $100)
2. define what happens when they change (e.g. kick off an approval process)
This seems like a business rules engine integration to me along with a BPMS. Does anyone have thoughts on a more lighter-weight framework or solution to this?
You could publish a generic event that has its constraints/specification defined against a unique Name. Let's call the event SpecificationEvent. Perhaps you would have a SpecificationEventService that can check you domain objects that implement a ISpecificationValueProvider and return a populated event that you could publish:
public interface ISpecificationEventValueProvider
{
object GetValue(string name);
}
public class SpecificationEventService
{
IEnumerable<SpecificationEvent> FindEvents(ISpecificationEventValueProvider provider);
}
public class SpecificationEvent
{
private List<SpecificationEventValue> _values;
public string Name ( get; private set; }
public IEnumerable<ISpecificationEventValue> Values
{
get { return new ReadOnlyCollection<ISpecificationEventValue>(_values); }
}
}
public class SpecificationEventValue
{
public string Name { get; private set; }
public object Value { get; private set; }
public SpecificationEventValue(string name, object value)
{
Name = name;
Value = value;
}
}
So you would define the custom events in some store. Possibly from some front-end that is used to defined the constraints that constitute the event. The SpecificationEventService would use that definition to determine whether the candidate object conforms to the requirements and then returns the event with the populated values that you can then publish.
The custom code could be registered in an endpoint where you handle the generic SpecificationEvent. Each of the custom handlers can be handed the event for handling but only the handler that determines that the event is valid for it will perform any real processing.
Hope that makes sense. I just typed this up so it is not production-level code and you could investigate the use of generics for the object :)

What layer is responsible for implementing a LazyLoading strategy for children objects of an entity

Let's say you have an order as an aggregate root. An order contains one or more line items.
It is my understanding that it's the repository's responsibility to instantiate an order object when asked.
The line items can be loaded at the time of the order object's creation (eager loaded), or the line item collection can be populated when it is accessed by the client code (lazy loaded).
If we are using eager loading, it's seems that the repository code would take responsibility with hydrating the line items when the order is created.
However if we are using lazy loading, how is the repository called when the LineItems collection is accessed without creating a dependency on the repository from the order domain class?
Main problem is in Repository's ability to get only aggregate roots (presenting aggregates), thus you cannot use Repository to get line items. This can lead to aggregate encapsulation violation.
I propose something like:
//Domain level:
public interface IOrderItemList {
IEnumerable<OrderItem> GetItems();
}
public class Order {
private IOrderItemList _orderItems;
public IEnumerable<OrderItem> OrderItems
{ get { return _orderItems.GetItems() } };
public Order(IOrderItemList orderItems)
{
_orderItems = orderItems;
}
}
public class OrderItemList : IOrderItemList
{
private IList<OrderItem> _orderItems;
public IEnumerable<OrderItem> GetItems() {
return _orderItems; //or another logic
}
//other implementation details
}
//Data level
public class OrderItemListProxy : IOrderItemList
{
//link to 'real' object
private OrderItemList _orderItemList;
private int _orderId;
//alternatively:
//private OrderEntity _orderEntity;
//ORM context
private DbContext _context;
public OrderItemListProxy(int orderId, DbContext context)
{
_orderId = orderId;
_context = context;
}
public IEnumerable<OrderItem> GetItems() {
if (_orderItemList == null)
{
var orderItemEntities = DbContext.Orders
.Single(order => order.Id == _orderId).OrderItems;
var orderItems = orderItemEntites.Select(...);
//alternatively: use factory to create OrderItem from OrderItemEntity
_orderItemList = new OrderItemList(orderItems);
}
return _orderItemList.GetItems();
}
}
public class OrderRepository
{
//ORM context
private DbContext _context;
Order GetOrder(int id)
{
var orderEntity = _context.Single(order => order.Id == id);
var order = new Order(new OrderItemListProxy(id, _context))
//alternatively:
//var order = new Order(new OrderItemListProxy(orderEntity, _context))
...
//init other fields
...
}
//other methods
...
}
Most important here is that IOrderItemList corresponds to domain layer, but OrderItemListProxy corresponds to data layer.
Finally,
You may use IList<OrderItem> instead of custom IOrderItemList or another appropriate interface.
Proxy implementation may differ.
I don't provide best practicies for using db context, it may depend on technologies you use.

Object creation events in ServiceStack's OrmLite

I need to set an event handler on objects that get instantiated by OrmLite, and can't figure out a good way to do it short of visiting every Get method in a repo (which obviously is not a good way).
To give some background - say I have a class User, which is pulled from database; it also implements INotifyPropertyChanged. I want to assign a handler to that event. Having it auto-populated from Funq would be ideal, but of course OrmLite doesn't ask Funq to hydrate the new object.
So I'm stuck.
Any hints in a right direction would be appreciated.
It sounds to me like you're mixing in presentation logic with your data access logic. If I was in your position I would not attempt to implement INotifyPropertyChanged on a model (such as your User class). Instead I would create a ViewModel and place the databinding logic there (MVVM Style).
Having INotifyPropertyChanged on the data model is not quite logical when you get down to it. If I were to update the database record it would not fire this event for example (but the property has changed). It makes a lot more sense on a ViewModel.
Beyond solving your original issue it also makes building complex screens a lot easier by letting you aggregate, compose, and filter data for display purposes. If you need to pull in information from your database, a RSS feed, a stock ticker web API, and twitter you can do so in your ViewModel.
public class User
{
[AutoIncrement]
public int Id { get; set; }
public string Name { get; set; }
}
public class UserViewModel : INotifyPropertyChanged
{
private string _name;
public UserViewModel(User user)
{
_name = user.Name;
}
public string Name
{
get { return _name; }
set {
if (value == _name) return;
_name = value;
OnPropertyChanged("Name");
}
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Small Note: This answer was written in the context of display data on a screen with a ViewModel, however, the same concept applies to observing model changes for any purpose.

Add behavior to existing implementation - C# / Design Pattern

My current implementation for service and business layer is straight forward as below.
public class MyEntity { }
// Business layer
public interface IBusiness { IList<MyEntity> GetEntities(); }
public class MyBusinessOne : IBusiness
{
public IList<MyEntity> GetEntities()
{
return new List<MyEntity>();
}
}
//factory
public static class Factory
{
public static T Create<T>() where T : class
{
return new MyBusinessOne() as T; // returns instance based on T
}
}
//Service layer
public class MyService
{
public IList<MyEntity> GetEntities()
{
return Factory.Create<IBusiness>().GetEntities();
}
}
We needed some changes in current implementation. Reason being data grew over the time and service & client cannot handle the volume of data. we needed to implement pagination to the current service. We also expect some more features (like return fault when data is more that threshold, apply filters etc), so the design needs to be updated.
Following is my new proposal.
public interface IBusiness
{
IList<MyEntity> GetEntities();
}
public interface IBehavior
{
IEnumerable<T> Apply<T>(IEnumerable<T> data);
}
public abstract class MyBusiness
{
protected List<IBehavior> Behaviors = new List<IBehavior>();
public void AddBehavior(IBehavior behavior)
{
Behaviors.Add(behavior);
}
}
public class PaginationBehavior : IBehavior
{
public int PageSize = 10;
public int PageNumber = 2;
public IEnumerable<T> Apply<T>(IEnumerable<T> data)
{
//apply behavior here
return data
.Skip(PageNumber * PageSize)
.Take(PageSize);
}
}
public class MyEntity { }
public class MyBusinessOne : MyBusiness, IBusiness
{
public IList<MyEntity> GetEntities()
{
IEnumerable<MyEntity> result = new List<MyEntity>();
this.Behaviors.ForEach(rs =>
{
result = rs.Apply<MyEntity>(result);
});
return result.ToList();
}
}
public static class Factory
{
public static T Create<T>(List<IBehavior> behaviors) where T : class
{
// returns instance based on T
var instance = new MyBusinessOne();
behaviors.ForEach(rs => instance.AddBehavior(rs));
return instance as T;
}
}
public class MyService
{
public IList<MyEntity> GetEntities(int currentPage)
{
List<IBehavior> behaviors = new List<IBehavior>() {
new PaginationBehavior() { PageNumber = currentPage, }
};
return Factory.Create<IBusiness>(behaviors).GetEntities();
}
}
Experts please suggest me if my implementation is correct or I am over killing it. If it correct what design pattern it is - Decorator or Visitor.
Also my service returns JSON string. How can I use this behavior collections to serialize only selected properties rather than entire entity. List of properties comes from user as request. (Kind of column picker)
Looks like I don't have enough points to comment on your question. So, I am gonna make some assumption as I am not a C# expert.
Assumption 1: Looks like you are getting the data first and then applying the pagination using behavior object. If so, this is a wrong approach. Lets say there are 500 records and you are showing 50 records per fetch. Instead of simply fetching 50 records from DB, you are fetching 500 records for 10 times and on top of it you are adding a costly filter. DB is better equipped to do this job that C# or Java.
I would not consider pagination as a behavior with respect to the service. Its the behavior of the presentation layer. Your service should only worry about 'Data Granularity'. Looks like one of your customer wants all the data in one go and others might want a subset of that data.
Option 1: In DAO layer, have two methods: one for pagination and other for regular fetch. Based on the incoming params decide which method to call.
Option 2: Create two methods at service level. One for a small subset of data and the other for the whole set of data. Since you said JSON, this should be Restful service. Then based on the incoming URL, properly call the correct method. If you use Jersey, this should be easy.
In a service, new behaviors can be added by simply exposing new methods or adding new params to existing methods/functionalities (just make sure those changes are backward compatible). We really don't need Decorator or Visitor pattern. The only concern is no existing user should be affected.

Resources