Search code examples
architectureaggregatedomain-driven-designclean-architecture

Where are aggregate roots in this domain?


I have a small application where users can register, log in, create and manage (their) markers that can be shared among friends. So user have access to his own markers but also to markers shared for him by his friends.

Users can send a friend requests to other users and after confirmation - they become friends. Users can have unlimited number of both - markers and friends.

User can have many friends. User have many markers. Marker have one User (owner) and can be shared among owner's friends.

I can find processes like Authentication, Marker Management, Marker Sharing, Friendship.

I have problem to identify aggregate boundaries. What will be an aggregate root (or multiple aggregate roots) in this domain.

At first I thought that User is obvious aggregate root but when I started preparing diagram I saw that I would need relations between entities in aggregate root - and that's forbidden.

useraggregate

According to

Define the main entity (aggregate) used in the process, and then
build onto it with value objects

Answer beneath this question:

And this diagram:

diagramrules

I figured out that maybe I'm looking from the wrong side. So I'm starting to modelling something like this:

bad

bad2

  • red aggregate roots
  • yellow entities
  • green some value objects for better context

But I can't find a good place for SharedMarkers because in my domain is completely acceptable to create marker that is not shared to any friend. Where are aggregates in my domain and where is place for SharedMarkers in proper DDD design?


Solution

  • As a first side note, I'd like to remind you that you are allowed to reference an aggregate root from a foreign non aggregate root object. What you are not allowed to do in DDD is referring a non aggregate root from an object outside the aggregate root boundary. So both your diagrams are valid from that perspective. However, there are some interesting aspects of DDD to explore here. Let me first quote the SO answer you have linked in your question:

    Starting design from data structure is number one reason why people fail at DDD.

    Also, a lot of people try to write a single, unified, normalized domain model, but this is wrong. You should start designing your boundaries based on use cases aggregation, regrouping them by which part of the domain state they mutate consistently. Then you write a model that fits the level of detail required by your use cases within each boundary. This is called a polysemic domain model.

    Your use cases globally fit into 3 aggregates:

    • User management: register
    • Marker management: create, update, delete, share, unshare
    • Friendship management: request, accept, reject, delete

    User management

    Unless you are writing an identity provider or an Active Directory like application, authentication is usually an application layer concern rather than a domain layer one: it does not change the state of your domain model. The same reason applies as to why there is not read operations for users, markers or friendships. Otherwise, user management is quite forward ; when registering a user you don't need the marker nor the friendship concepts. I would simply model a single User entity.

    Friendship management

    Friendship management is an interesting aspect of your domain. A friend actually is a user, although you don't manipulate them as users as in user management. Actually, if use cases are extremely simple, you might want to consider regrouping user management and friendship management together in a single aggregate with single model. In that situation, you could create a Friendship entity that would relate with the existing User entity, forming an aggregate with User as the aggregate root.

    However, if you expect each feature to evolve independently, you'd better keep them separated in two different contexts. Doing so lowers the risk of regression on the friend management use cases when adding features to the user management and vice versa. In that situation, you could either model the Friend as a scalar property that refers to the user's identity, or create a new Friend class in addition to the Friendship entity.

    Your infrastructure layer could map the Friend and User entities to the same backing table, loading different column into the properties, or two different tables, specific to each entity, joined by their primary key. The latter offer better scalability opportunities, as you could easily consider moving one feature to another persistence technology without changing the other one. However, this would distribute integrity constraints, which is the beginning of a complex journey towards a distributed microservice architecture.

    Markers management

    Markers management is the most interesting feature of your application. Creating, updating or deleting a marker is quite straight forward, but sharing or unsharing a marker with a friend is not. The concept of sharing a marker depends on the user/friendship concepts but they do not belong to the user management nor the friendship management contexts: you are not changing the state of users or friends, only markers.

    Given the level of complexity for the friendship management and the markers management you describe, it does not sound reasonable to mix both concepts in a single context. You should better keep them separated in order to keep regression risk low.

    This time, however, you need to access the user/friend data, and you cannot simply refer to the user identifier. However, the level of detail you need is lower than that of the friendship management. Friendship management requires information on the friendship status, request date, response date, … Sharing a marker only needs the list of active friendship of the user.

    In that situation, I would model a Marker entity, associated to a MarkerOwner entity (the underlying user owing the marker), which would have a collection of MarkerOwnerFriend. The infrastructure would be responsible to flatten the Friend/Friendship/Friend relationship needed for friendship management when re-hydrating marker entities from the persistence store.

    This would provide a simple model that would fit the level of details needed for implementing business constraints, without overloading the model with details that are unnecessary to the use cases handled within that boundary.