1

An Aggregate Root should always have a unique ID within the bounded context. Typically the examples one finds use a GUID for this to ensure global uniqueness.

However consider a bounded context for a Chat. In this case I deem messages and chats as their own individual aggregate roots. One may consider Message an entity of Chat, however if messages are to grow without bounds, this is infeasible.

Therefore a Message would hold the reference to the Chat to which it belongs, by ID. In this case I would need a large enough message Id to ensure that it is unique w.r.t. all other messages independent of Chat.

I am wondering if it is bad practice to instead make a composite key for Message of the form (ChatId, MessageId). This would ensure uniqueness, and at the same time I do not need MessageId to be as large as mentioned above, thereby saving some space.

12
  • Is a message chat really complex enough for DDD? Jan 4, 2021 at 13:16
  • 1
    I am not creating a messaging application, it is however a supporting domain in a bigger application.
    – udnes99
    Jan 4, 2021 at 15:13
  • however if messages are to grow without bounds, this is infeasible. set a buffer size. Only retain the last N entries. Make it configurable so it can scale accordingly to the resources at hand. Anyways, it depends on the kind of chat. If we were talking about an IRC-like chat, doesn't matter, the lines are persisted only on the client-side and they are volatile info we lose when close the chat.
    – Laiv
    Jan 4, 2021 at 16:18
  • But this would be a violation if I were to model Message as an entity, no? The only way to access it is through the aggregate root which should store the entire aggregate. I am currently thinking that by modeling a Message as an aggregate root, I can have methods in its repository to fetch the N last messages from offset x, which would not be problematic for resource consumption.
    – udnes99
    Jan 4, 2021 at 16:22
  • This would ensure uniqueness, and at the same time I do not need MessageId to be as large as mentioned above, thereby saving some space.false. If you are mean to persist ALL the entries, one chat might run short of message Id. then what?
    – Laiv
    Jan 4, 2021 at 16:23

2 Answers 2

2

Although a composite key is not a bad practice, I would recommend you to use a UUID.

Lets consider an UUID v4, for example, that is generated randomly:

The number of random version-4 UUIDs which need to be generated in order to have a 50% probability of at least one collision is 2.71 quintillion [...]

Source: Universally Unique Identifier - Collisions

So, even for a chat app, is really, really unlikely that a id collision will happen.

Thinking from the YAGNI perspective, I believe that, when you reach the point when you will need to worry about collision, you will already have the people and money to come up with a different solution, as your chat will be a very successful product at that point.

Useful resources:

Universally Unique Identifiers

Are UUIDs really unique?

2
  • 2
    I get your point. I just feel like it is redundant to have full UUIDv4 for the key of a message Id on its own when it must reference a chat which already has a globally unique identifier. Having maybe say a base64 string of length 8 would be more than a enough as a single chat on its own would not be expected to have say more than 10K messages at most.
    – udnes99
    Jan 4, 2021 at 15:12
  • 2
    I agreed with @muffle that UUIDs have a cost. They are heavy (in bulk volumes) and makes search by ID slower. However, If this is a problem we can not say. I would dare to say not even OP know if it's because no requirements nor constraints have been introduced in the question. So far UUID and composed id are two valid solutions. Each of which with tradeoffs. If these are relevant, nobody knows. It depends on the requirements.
    – Laiv
    Jan 4, 2021 at 16:54
1

You really need to decide:

  • Either Message is an independent aggregate root, and it has an independent unique Id,
  • Or Message belongs to the Chat aggregate, in which case it depends on Chat and is accessed via the aggregate root.

Using a composite key is in reality a mean to implement the latter. A composite key for Message that builds on Chat's Id would express a dependency on Chat. This would negate the claimed independence of Message and hence be inconsistent with your design.

More arguments: Suppose you'd go for independent aggregate roots and use a composite key: what would happen if you decide to delete a chat ?

  • Would you also delete the relevant messages? But wouldn't this be a symptom that they belong to the same unit of consistency and share a common invariant?
  • Would you keep the relevant messages? But how wouldn't this be inconsistent with your database schema?
3
  • In this hypothetical example, if a Chat was deleted, I would fire off a domain event which would eventually lead to consistency w.r.t. messages being deleted. I wouldn't necessarily consider Message and independent aggregate root because it has to contain a chat My principal understanding is that the Message should indeed be an aggregate root because the number of messages could grow out of bound. Following the logic of Vaughn Vernon's principals of effective aggregate design, a message should be an aggregate root in order to avoid the problems of modeling as an entity.
    – udnes99
    Jan 4, 2021 at 15:06
  • Yes, you could fire a domain event. Meanwhile your Chat is deleted, but some messages are still there. If you are in the same database, you'd again be inconsistent with your schema (i.e. that a component of the key relates to your chat). You could still argue that your chat component in the key does not need to be an integrity controlled foreign key. My point here was not to claim that it is impossible. It's just to highlight that low-level design (composite-key) would contradicts high-level intent (independent aggregates) and that it is therefore not a good practice :-)
    – Christophe
    Jan 4, 2021 at 15:16
  • Moreover, what are the benefits? You'd perhaps save 16 bytes per messages (i.e. the key component that you would be at the same time the chat reference). But you'll need a second component to have your full id (e.g. an integer: you would have to ensure uniqueness within the chat, whereas many participants may create a new messages for the same chat at the same time. How would you achieve this uniqueness? storing a counter on the chat (and the argument of the unit of consistency would be back like a bomerang)? Or go for an additional GUID (and then you'd have a plain Id anyway)?
    – Christophe
    Jan 4, 2021 at 15:28

Your Answer

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

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