0

Context: Currently designing a product with a DDD mindset. Currently in a monolithic approach. However I'm refactoring a few things so I can change to a more loosely coupled monolith.

We have an entity called "Risk" (which is an abstract class with N subtypes). Currently have two aggregate roots who have a list of risks: Contract and Damage. This is demanded by the business logic, as when you create a damage from a specific risk in a contract, you need to take an exact copy of it at that given moment in time and add it to the damage.

However there's a new business rule that allows Customers (yet another aggregate root) to have a list of risks as well, without having any contract or damage (=> A prospect customer). The risk entity that would live under the Customer aggregate is an exact copy of the risks entity under contract & damage. The business itself is required to have the same data properties & behavior to the risk.

I'm not really a fan of sharing entities all over different aggregate roots, especially with the mindset of splitting it up to a more loosely coupled monolith. Plus the fact that the risk entity itself is an abstract with multiple subtypes.

Would a better approach be to make Risk an aggregate root as well and keep a reference ID to either a Customer, Contract or Damage?

Any advice is welcome!

6
  • 1
    Would it be fair to consider a Risk a value object, rather than an entity? Does it have an identity and life cycle of its own (= entity), or is it represented purely by its contained values (= value object)? In other words, if both a customer and a contract have a "risk of ABC", is it important that they both refer to the same risk, as opposed to two separate risks which (coincidentally) have the same values?
    – Flater
    Jun 29, 2021 at 23:31
  • @Flater good point, someone else also suggested this. To be fair: I'm not sure. A risk also has multiple guarantees and guarantees have multiple deductibles (this is the business itself). Which means it isn't really a ValueObject right? A risk cannot exist without something, so upon creating you need to have a reference to either a Customer, Contract or Damage. A risk can and will be updated over time as well..
    – Jesse
    Jun 29, 2021 at 23:36
  • @Flater A risk can even change "owners" . Like you could add a risk to a potential customer without a contract.. but when he becomes an actual customer, the risk should move below the contract for that customer. (Talking on how the business works.)
    – Jesse
    Jun 29, 2021 at 23:36
  • 1
    Value objects do not have an upper limit on how much data they can contain. They are conceptually different from entities. A very common example here is an Address. Your system may have several entities which all have an address, but that doesn't mean that these entities' addresses therefore have a unified lifecycle. For example, if you track both people and cars (and their registered addresses), just because I move house doesn't mean that this change of address also applies to the car. These addresses have different life cycles and just "happen" to contain the same values sometimes.
    – Flater
    Jun 29, 2021 at 23:40
  • 1
    In short, none of the arguments you mention in the comments here are relevant to consider something a value object or not. Your interpretation of what a value object is or how it can/should be used is... misguided. I presume you may have misinterpreted (or have been misexplained) the conceptual purpose.
    – Flater
    Jun 29, 2021 at 23:41

2 Answers 2

1

Without knowing exactly what you are building, it seems you've come to the natural conclusion that makes the most sense:

Would a better approach be to make Risk an aggregate root as well and keep a reference ID to either a Customer, Contract or Damage?

Based on what you shared, it makes the most sense.

There are several cases where aggregates are associated with other aggregates, but the business logic surrounding it really suggests that it is it's own aggregate root.

For example. I have a system where we maintain metadata on a corpus of information, and users can make different types of requests against that corpus. In this example, we'll call the corpus a "Collection", and the request types being "Information" or "Services". All of the requests are children of the "Collection", but there are whole workflows around fulfilling those requests.

So in this almost contrived example, we have:

  • Collection (1..n)
    • Request: Information (1..1)
    • Request: Service (1..1)

We found it worked better to have "Requests" and "Collections" be peers, and the "Request" was associated with a "Collection".

The requests have their own approval and fulfillment workflows independent of the collection's approval workflow. We've gone to a full microservices architecture already, and this approach of promoting an aggregate element to be it's own root just made the whole design easier to comprehend.

Some of the clues that suggest something is a candidate to be promoted to it's own aggregate root and simply referenced by it's logical parent include:

  • The customer talks about that thing independently of the parent. (in your case risks)
  • The same aggregate type can be associated with multiple types of parents. (Contract, Damage, Customers)
  • There are independent workflows/business logic surrounding the child and the parent

Those are just a few reasons, and there could be more rational reasons to promote something to be it's own aggregate root. By doing this, you essentially decouple the concepts--but maintain the referential link you need.

1
  • Thanks! I like this approach. I'm not yet sure on how to solve it though as someone else pointed out it would maybe be better to create it as a ValueObject. But I'm not entirely sure on how that is possible.
    – Jesse
    Jun 29, 2021 at 23:30
1

Here are a few recommendations depending on the actual domain:

  • Whether Risk should be an aggregate by itself, or part of the Customer, Contract, or Damage depends on whether it is an invariant in those aggregates. If the others cannot exist without a Risk, it makes sense to make Risk an entity of those aggregates.

  • If Risk has its own lifecycle, entities, business logic and can change over time, it probably should be modeled as an Aggregate. In which case, it cannot be part of transactions of the other aggregates. The Risk aggregate must be created first and then referenced by others by its ID in their own transactions.

  • If you want a snapshot of the Risk to be taken when other aggregates are created, you will have to create an entity under each of Customer, Contract, and Damage. The entity should be populated with data from the master Risk record during aggregate creation.

  • There is a middle path between creating an independent copy of Risk across aggregates vs. referring to a Risk by its ID. You can create denormalized Risk records as entities under other aggregates (or even across different bounded contexts), which are updated by listening to Domain Events published by the Risk aggregate during its lifecycle.

12
  • A risk is an invariant in those aggregates, it's always the same. The others can perfectly exist without a risk. Risks have multiple entities below them as well (guarantees and guarantees can have deductibles => this is just how the business is). If the risk is linked to a customer, it should also be able to move under a contract.
    – Jesse
    Jun 29, 2021 at 23:27
  • 1
    I think you are confused about what an Invariant is. An invariant is a business rule that can never be broken. It looks like the presence of Risk is not an invariant (a.k.a mandatory) if other aggregates can exist without it.
    – Subhash
    Jun 30, 2021 at 13:19
  • 1
    If Risk is an aggregate, I am assuming it changes over time. Will all the other aggregates get the update? If yes, you should store a reference to the Risk and not the Risk itself. If now, they are looking at an older version of the Risk, so you have to create a copy (a snapshot) of the Risk within the aggregates.
    – Subhash
    Jun 30, 2021 at 13:22
  • 1
    To clarify the last point, does the risk connected to damage change over time, or is it a snapshot captured at the time of damage aggregate creation?
    – Subhash
    Jun 30, 2021 at 14:36
  • 1
    I understand. I would go with the last option in my answer and preserve a copy of the Risk under Damage aggregate. You will listen to Risk change events and update the entity when you have to (based on one or more rules you just laid out).
    – Subhash
    Jun 30, 2021 at 18:20

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.