I am building a small Recipe Organiser MVC project with EntityFramework Code first and Sql Server Compact edition as a way to learn more about DDD and the Repository pattern.
Here are some of my domain entities, I have simplified them for this question.
public class Recipe {
public int Id { get; set; }
[Required]
public string Name { get; set; }
/// <summary>
/// Represents a common meal type of “Breakfast, Lunch or Dinner”
/// or type of course “Appetisers, Soups, Salads, Fish, Poultry & Game, Meat, Side dishes, Desserts, Miscellaneous", etc.
/// (for scope I am not considering brunch or supper or any other type)
/// </summary>
[Required]
public Course Course { get; set; }
public virtual ICollection<Ingredient> Ingredients { get; set; }
}
public class Ingredient {
public int Id { get; set; }
[Required]
public string Name { get; set; }
}
public class Course {
public int Id { get; set; }
[Required]
public string Name { get; set; }
}
In my opinion for the scope of this project I would say that Recipe is an Aggregate Root and Ingredient and Course are aggregates of Recipe.
If I take this example and use MVC scaffolding to give me an example implementation of the repository pattern, I get the below interface and a EFRecipeRepository class that implements the interface and has a reference to the EF DB context.
public interface IRecipeRepository
{
IQueryable<Recipe> All { get; }
IQueryable<Recipe> AllIncluding(params Expression<Func<Recipe, object>>[] includeProperties);
Recipe Find(int id);
void InsertOrUpdate(Recipe recipe);
void Delete(int id);
void Save();
}
Following this guidance:
In the context of the repository pattern, aggregate roots are the only
objects your client code loads from the repository.The repository encapsulates access to child objects – from a caller’s
perspective it automatically loads them, either at the same time the
root is loaded or when they’re actually needed (as with lazy loading).
I would expect to add more to this interface, in order to obtain an Ingredient or Course via a Recipe.
The problem I have is that I want to add some features to my Views that involve just the Ingredients or Course, for example: A navigation of all the different types of Courses (not just the ones related to Recipes) and an auto-complete list for ingredients.
In the scope of my project, Ingredients don’t exist outside of a Recipe so it seems viable that it should be an aggregate of Recipe. Course however seems like it should be an Aggregate root itself, because of how it exists on its own?
I am struggling to get a vision of how this all should work, if I wanted to obtain a list of every Ingredient would that mean that I would first have to use the Recipe Repository to load up all the Recipes in order to find all the Ingredients?
Course has a similar problem except that it is used as a way to categories Recipes and so can exist outside of that boundary too.
Is there some concept I am missing? perhaps a way to create a Service that is used for such Presentation layer requirements? Does anyone have any opinion on how this should be done? or any better suggestions? Thanks
On a side note, this is a small project and DDD might not be the best approach, but I do find the repository pattern very useful for persistence ignorance in my Domain model.
Thanks
If all you need is a list of the different names of ingredients for an auto completion, I’d indeed just provide a service that extracts the necessary reports out of the database. I’m perfectly fine with reports that poke within the aggregates, because they are just that – reports. You just shouldn’t let the service return useable entities from inside an aggregate (ie. don’t return Ingredient[]) because this would open up the aggregate boundaries to the caller. Simply return a string[] or similar.
Then again, I would consider both Course and Ingredient aggregate roots in all but the most trivial applications. Can ingredients exist without a recipe? Absolutely! Is “sugar” #1 (belonging to recipe #1) something different than “sugar” #2 (belonging to recipe #2)? I don’t think so. There’s sugar. And there are recipes that use sugar. Making ingredients aggregate roots makes a lot of sense. You can easily browse your recipes by ingredient for example.
The same goes for Course, it’s a concepts that absolutely make sense on its own.
A good example for a concept that should NOT be an aggregate root is an order line (that is, the thing that says “3 Foobar TVs for $700 a piece”). Without an order, order lines make no sense. But even in this case a service that generates reports on order lines makes sense (in theory) and is legit IMO. For example, one might wants to know how many Foobar TVs are typically bought together in order to introduce the great new Foobar TV 3-pack (yeah, whatever…). This would again mean searching in the database within data that is normally not accessible directly.