2

When modeling bigger domain entities you will probably have some kind of relation between these entities. These relations will usually result in entities referencing different entities. If i am not mistaken, there are two ways of modeling these relations:

  • Aggregation ( A uses B )
  • Composition ( A owns B )

When modeling Composition the root entity manages the lifetime of it's composited entities. That also applies to persistence, where you have to clean up the composited entities, when the root entity is deleted.

What i am having trouble with is the same scenario, just using Aggregation. How do you model the relation between entities, when they can exist independently and the same entity could be aggregated by multiple root entities? This gets even more complicated when both entities are supposed to be saved to a database.

Take these requirements for example:

We want to model recipes, which consist of different ingredients and their quantities. Recipes and ingredients can be provided with additional information, like descriptions, pictures and user comments. Ingredients can also be provided with links to substitute ingredients and recipes to make the ingredients (bread can be made using a recipe for example).

I would start by separating the different domain entities, specified by the requirements and add the fields that are owned by the entities (composition). That would be information like comments, pictures and descriptions. But i don't know how to correctly model the aggregation between the entities, so that independent handling makes sense and i can easily integrate it in my Data Access Layer (database).

Could you provide me with a short code example that demonstrates how i should model the relation between Recipe and Ingredient, so that i can use it easily in my application and don't have a nightmare in my persistence code? Use your language of choice!

2 Answers 2

4

Preliminary remark

I have the feeling that an important concept his still hidden in the "ingredients" in your requirement. Because there is:

  • the ingredient in general (e.g. Sugar, apple, Egg), with the picture, and recipe independent information;
  • the use of an ingredient in a recipe (e.g. 20g of sugar, 1kg of apples).

By the way, I'd think that the replacement ingredient is related to the use in a recipe (e.g. you can replace sugar with honey when you make a tart, but my mother uses to put a pinch of sugar in carrot salad and I'm not sure that nonay could make it here ).

In this case you'd best model this situation with an association class:

enter image description here

Composition

Lets focus on the relation between Recipe and IngredientUse and forget about the raw Ingredient.

This is clearly a composition relation: The IngredientUse makes only a sense inside of a Recipe. No recipe, no use of ingredient. That's clearly a unique ownership.

enter image description here

In C++ I would implement this relation as a complete ownership: the IngredientUse would be contained in a vector in the Recipe. When the Recipe is destroyed, the vecotr and its content is destroyed with it:

class IngredientUse { ... }; 

class Recipe {
protected: 
    vector<IngredientUse> ingredients; 
...
};

Only the Recipe can access its used ingredients. The ingredients could be identified by their index in the vector.

Another variant would be to have a list of pointers. But in this case I'd use a smart pointer that expresses adequately the ownership:

class Recipe {
protected: 
    list< unique_ptr<IngredientUse> > ingredient_pointers;  //ownership clearly expressed in code
...
};

When Recipe is destroyed, so is the list, and the unique_ptr will make sure that the owned object is deleted as well.

In a relational database, I'd use a unique ID for example ID-RECIPE as primary key for the Recipe. But for IngredientUse, instead of using a unique ID, I would use a primary key composed of a foreigh ID-RECIPE key and a sequential number in the Recipe:

  • The ownership is then clearly expressed (e.g. the primary key makes only sense together with the owning Recipe).
  • RDBMS that enable to declare referential integrity constraints can event take care of cascading deletes.

Agregation

The aggregation is different, because it's a shared ownership. Imagine that your application is no longer about the exact composition of a recipe, but only about the generic ingredients, in order to prevent allergies, or help to make choices on base of favorite tastes.

In this case the relation between Recipe and Ingredient would not be a composition: a Recipe could disappear, but the generic Ingredients would still be used by other recipes.

In this case in my C++ code, I could no longer hold the ingredients by value in my Recipe. I would need to have a shared ownership. For example:

class Recipe {
protected: 
    vector< shared_ptr<Ingredient> > ingredient_pointers;  //shared ownership obvious
...
};

So the aggregated elements could not be stored inside the holding object. They must be stored in a way to be potentially reused. The smart pointer that I used above expresses shared ownership. The ingredient is kept alive until no Recipe uses it anymore.

In the database model, Recipe and Ingredient would both have unique IDs in their respective table (e.g. ID-RECIPE and ID-INGREDIENT). To represent the association, I'd then add a table INGREDIENTS-IN-RECIPE that holds the relevant combinations of ID-RECIPE and ID-INGREDIENT. Referential integrity and cascading delete would then only delete the association records for recipes that were deleted, without never deleting the master ingredient.

3
  • 1
    Introducing this "IngredientUse" type is clearly the way to go. I'm working on a problem with a very similar structure at this very moment, and I've concluded that wrapping those persistent records into a second, more transient entity keeps the associated data in its proper place and lets the Ingredients be reused across recipes. (The bit I'm puzzling over is where to store the "unit": it's part of the recipe that the honey is measured in mL instead of g, but it's still useful information the next time the ingredient is used anywhere.)
    – jscs
    Commented Jul 3, 2016 at 22:03
  • The IngredientUse class is a good idea. I would have needed to save the additional information anyways, so i might aswell put it in a separate class. If you want to combine the generic Ingredient and IngredientUse approach (encapsulate recipe specific ingredient data, but still be able to create generic ingredient descriptions, pictures, etc), how would you do that? Save the id of the generic Ingredient in your IngredientUse class and query it, if needed? Commented Jul 4, 2016 at 1:26
  • @JoshCaswell You could encapsulate measurement unit data in its own class aswell. That would give you the advantage, that you could easily translate between different unit systems. You would model the Ingredient "Water" with the UnitType "Fluid" and measure it in litres, or fluid ounces. Based on the users preferences you could then translate between the two. Commented Jul 4, 2016 at 1:30
1

I think this comes down.to whether your recipe has

List<string> IngredientIds

Or

List<Ingredient> Ingredients

With the former you have to fetch the ingredients seperately from your persistence layer. Pehaps using a different repository to the Recipe

With the latter both the ingredients and recipe are persisted when the Recipe is saved

3
  • I actually like the first approach a lot, but have never tried it on any project of mine. Does it work well? Commented Jul 4, 2016 at 1:33
  • yes, it is my preferred approach for the business layer. Although, if you are going for a no-sql style approach over RMDB I would take the second
    – Ewan
    Commented Jul 4, 2016 at 7:44
  • Understandable. I will keep this in mind, thank you for the advice! Commented Jul 4, 2016 at 7:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.