1

Context

I'm currently developing a notification system with DDD which needs to be able to send the same notification to multiple users. For example, a group of party goers might all be notified about the lineup of a certain festival.

To accommodate this I've created two domain classes (which are on their own seperate aggregate):

  • Notification (which contains the content)
  • NotificationDelivered (which contains a reference to the Notification, the user and if the user read it or not).

By doing this I'm able to create a big amount of notifications with same content but individually tracked by each user.

Problem

My main problem now is that a lot of times I have to use both classes at the same time. For example, if I want to show the user its notifications, I first have to get its NotificationDelivered and then the corresponding Notification for each one. This leads my to believe that my domain might be too coupled, however I'm struggling to find a way in which a lot of messages can be sent without replicating it's contents in every instance.

This is also complicating the use of repositories, since I have a repository for each aggregate and this would always involve two, possibly costly, transitions with the DB.

0

2 Answers 2

3

It all depends on how you define a "notification". Most commonly, it is considered to be a specific message sent to a specific user. This would lead you to a setup akin to:

User 1->* Notification

But you seem to be defining a notification as a reusable message, no matter how many people it is send to, i.e.:

User *<->* Notification

Or, more in detail:

User 1->* UserNotification *<-1 Notification

Notice I didn't use NotificationDelivered as I don't like that name. It suggests it's an event, but you're using it as a cross table, so I picked the default cross table name.

This leads my to believe that my domain might be too coupled

Actually, it's quite the opposite. Your domain is very uncoupled, which is why you're having to perform an additional step, which takes additional overhead. Domains which are too coupled tend to have the "benefit" that it's much easier to jump from one to the other, but it comes are many different costs down the line.

There's nothing wrong with your approach, but it does add an additional layer of complexity in favor of saving some space.


This is also complicating the use of repositories, since I have a repository for each aggregate and this would always involve two, possibly costly, transitions with the DB.

That is always the case when you deal with a many-to-many, especially when the cross table contains more data than just the two references. It's always going to either be one large join query across three tables, or two smaller queries, one of which is a join to the cross table.

There's different ways of approaching this, but it always boils down to roughly the same db interaction.

Remember that pure DDD does not prioritize DB performance.

In pure DDD, an aggregate is always fetched in its entirety, no matter how many tables it's spread across, and no effort is made towards writing many varied queries which each fetch a different subset of the aggregate's information.

This isn't always the most db-performant, as you may be fetching data from a related table that you don't end up using, but it does dramatically simplify the domain logic itself (no null checks and subsequent additional data fetching, little to no NRE possibilities), which is the prime focus of (pure) DDD.

In other words, DDD favors clarity of the domain logic over performance of the database (but there are always reasonable limits of course), and the decision you've made to separate UserNotification from Notification is a db-performance decision, so you've slightly realigned your DDD priorities for yourself.


Next up: how do you define your aggregates? Is UserNotification part of User, Notification, or is it its own root?

That's not something I can give you a universal answer to. It very much depends on what your domain contains and primarily focuses on. If either aggregate root is already very heavy or deeply involved in your domain, you're going to want to err towards not also pushing UserNotification into that domain.

Let's briefly discuss how I'd approach it in all three scenarios:

[User + UserNotification] + [Notification]

Here, when fetching the user you already know the notification references involved, so the only additional query to launch would be notificationService.GetByIds()

[User] + [UserNotification + Notification]

Here, the user doesn't know anything about its notifications, that's under the purview of the notification subdomain. So at the very least, you'll have to call something along the lines of notificationService.GetForUser().

Internally, that method will either be launching two queries, but more likely you're going to launch a single query that joins the UserNotification and Notification tables.

[User] + [UserNotification] + [Notification]

Here, you've painted yourself in the corner of always having to do two separate calls, as the UserNotification and Notification are in two separate subdomains, and therefore should not be joined in a single query (by intention of having made them separate aggregates)

You'll have to call something along the lines of userNotificationService.GetForUser() and then a second call along the lines of notificationService.GetByIds().


I can't tell you how to draw these lines, because it's so very contextual based on which features your application has and how you expect them to evolve. Just as a few examples:

  • For a HR application, I would expect there to be many user-related features, and therefore I'd be inclined to not stack every reference in the User aggregate.
  • For an alerting/monitoring tool, I would expect there to be many notification-related features (not all of them relating to users specifically) and therefore I'd be inclined to not stack every reference in the Notification aggregate.
  • If a UserNotification has a significant lifetime, e.g. containing an entire structure of statuses, a persistent comment thread, ... then I'd be inclined to make UserNotification its own aggregate root so as not to push that complex root into the existing User or Notification subdomain.

The choice is yours.


As an aside, given the tendency for "notification" to mean a specific message to a specific user, I'd personally be inclined to change the names here to something like :

User 1->* Notification *<-1 NotificationTemplate

But that's purely a naming argument, and not relevant for the structure of your code.

0

if I want to show the user its notifications, I first have to get its NotificationDelivered and then the corresponding Notification for each one

Most apps using DDD benefit greatly from applying CQRS - Command Query Responsibility Segregation.

DDD is really well suited for the command side of your application, but often less suited for queries. That seems to me the problem you're facing here as well.

CQRS doesn't have to be a very complicated system using event sourcing or separate databases. A really simple solution could look something like this:

CQRS with DDD

You have to make sure that the query side cannot change anything* in the database. That would defeat the entire purpose of applying DDD.

*anything related to business logic, updating a visitor count would be fine.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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