Question about how to model relationships in DDD

256 views
Skip to first unread message

Sebastian P.R. Gingter

unread,
Mar 6, 2018, 10:58:34 AM3/6/18
to DDD/CQRS
Hi,

first of all, sorry if another identical post turns up. For me it seems google groups is swallowing my post, and I now try to post with another account.
I am currently in the process of learning DDD, and I want to apply what I learn to a private pet project.

Now I ran into an issue where I maybe think too much in database tables, and I don't have a clue how to design that properly.
The project is about handling data from a video game. It's a space sim, and we have star systems with stellar bodies in them.

Now, for the sake of our project, we're mainly interested in certain star system where something is found. So I'd design the star system as an entity and aggregate root, together with its stellar bodies as sub-entities.
Since we have a quite tech-savvy community, someone else built an API for all the stellar data, so we would import the actual star system and body detail data from that other API. We consider the other API as the master of that data and just reference to that and keep a copy of their data for caching purposes.

The things that can be found in these star systems are so-called signal sources. Whenever we find certain signal sources, we would update the system with the type of signal source found there, so that we can update our read models that provide stellar maps (like this: https://map.canonn.technology/nhss-data.html , zoom in a bit to see individual systems).

Now, there are other points of interests (POI) in the galaxy, and some of them do not only reference a system, but are located on a certain stellar body in that system, at specific coordinates (latitude, longitude).
Some POIs just have a category, but others are more specific and have additional information on them, like alien ruins where we provide interactive ground maps for ( https://ruins.canonn.technology/ , select ruins from the list to see examples).

I would make a general POI an aggregate too, and also a Ruin, but there is the issue: I need to reference a specific body. But the body is an entity within the star system aggregate, and telling from what I learned it is a no-no to reference anything that is not an aggregate root.

This body, however, only provides its name to the full location of any POI that is located on the ground. I.e. in the system "COL 137 SECTOR AP-Q B21-2", which is a binary star system, we have three ruins on the eighth planet of the first star (A 8), and one ruin on the fourth planet of the second star (B 4). So the full "address" of a ruin would be a composite of the name of the referenced star system, the name of the referenced body plus the coordinates. Example: COL 137 SECTOR AP-Q B21-2, A 8, 13.33, -61.55.

Since we only use the name, and won't need any other information of that body, I would strongly hesitate to make it an aggregate root of its own.
So, how would you suggest to model these kind of relationships?

Break the rule, and let the POIs that are on the surface of a body hold a reference to the body id, although the body isn't an aggregate root of its own?
A process would take this information a bit later to update read model. It would then fetch the bodies name to build the location string, and also create a link to the external API source for this body i.e. https://www.edsm.net/en/system/bodies/id/9800883/name/Col+173+Sector+AP-Q+b21-2/details/idB/2214164/nameB/Col+173+Sector+AP-Q+b21-2+A+8 ).

Or is there a different way to model that?

Sorry for that wall of text, but I wanted to explain that domain a bit to help you help me ;)

Thanks in advance,

   Sebastian

Rickard Öberg

unread,
Mar 6, 2018, 9:02:12 PM3/6/18
to ddd...@googlegroups.com
Hi Sebastian!

Great question! Since I myself considered doing a similar pet project
for ED, I've had similar thoughts.

Since we are in the context of CQRS, and not just DDD, on this list,
that needs to be accounted for. I.e. how do you design the write
model, and how do you design the read model. And what is an aggregate
anyway?

An aggregate is a function from command to a sequence of events, and
acts as the transactional boundary. That's on the write side. On the
read side, you would typically get the sequence of events and apply
them to a database. How you decide to design that database is entirely
up to you. If you use a relational database, then using foreign keys
directly to entities within aggregates is totally fine. Or if you're
like me, using a graph database (Neo4j), then entities have direct
relationships between them, so I can easily do graph queries to find
what I need.

The rule to not have direct references is, to me, only for the write
model. Simply because the way we apply commands is to load the
aggregate for the command, apply the command, and store the events. If
that aggregate load is done through event sourcing, then those events
would (by nature) only contain identifiers, so there are no hard
references, to anything (including entities). If the load is done by a
database call, then you wouldn't want it to load anything outside of
the aggregate, or it would just be too costly. Also note that if you
do a database load for the aggregate, you only need to load the
snapshot state needed to make decisions on how to translate commands
into events. And that might be a small fraction of the total state, it
certainly is for me in pretty much all cases.

So that's the basics. But in your case, and I'm familiar with the
domain as mentioned above, the main input is going to be data from
another system, let's call them events, that you want to ingest.
Events from another system ("this has happened") are turned into
commands in your system ("I want this to happen") that you can then
turn into internal events ("this has happened, as far as this system
is concerned"). You may want to do validation of these external events
(is the field format ok, are they references valid, etc.), but I would
do that in the step that converts external events into internal
commands. So when the internal commands are sent to the aggregate it
can assume they are valid, and the main concern becomes translating
into internal events, zero or more. You would store these events in an
event store, have a read model subscriber read them and apply to your
read database for queries, and that's pretty much it.

From this point of view, you are probably not using the fact that an
aggregate is a transactional boundary all that much, as there are very
few constraints you need to enforce (if you trust the source of the
events to be correct, and validation has fixed the rest). You might as
well make everything aggregates. Or not. Doesn't really matter, right?
You could go the other extreme and make the entire galaxy an
aggregate, and again, wouldn't make much difference.

Unless, of course, you want to apply a bunch of rules, which requires
a lot of state, in which case your aggregate boundary will determine
how much your aggregate has access to in order to make the decisions.
That can be worked around though, by having the event->command
translator also provide any state needed from the read database in
order to make those decisions.

So, I think your question is ultimately less related to DDD/CQRS and
more towards event sourcing, because of the (presumably) lack of
domain rules and constraints. That's going to be a more practical way
to look at it.

HTH and YMMV,
Rickard
> --
> You received this message because you are subscribed to the Google Groups
> "DDD/CQRS" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to dddcqrs+u...@googlegroups.com.
> Visit this group at https://groups.google.com/group/dddcqrs.
> For more options, visit https://groups.google.com/d/optout.

Sebastian P.R. Gingter

unread,
Mar 8, 2018, 1:25:18 AM3/8/18
to DDD/CQRS
Hi Rickard,

thanks for your answer.
In that case, I'm really going that way and design the star system with its bodies as an aggregate.
This way we can import it as a whole from the EDSM api, and we can also track the amount of commander's
visits to a system by storing an event for that particular entity. That way we can create heat maps about popular
alien structures.

The POI however still would have a reference to a body (if its a planetary based one), storing its ID.
The only thing we need of that in the read models is the name.

A POI's planet will not vanish. If it does, the POI will vanish too. But a star system or a body could be
renamed later on, i.e when it becomes famous or the game creator decides its a good place for a memorial.
So I don't want to store the actual name in the POI.

However, this only affects the read models that would list the system names, and whenever this really
happens we can create a system / body renamed event and process this accordingly.

So when we process a command that is intended to add an POI, we would determine if we already have
that star system in our store. If not, we would first add and import that system. Then we'll load the system
aggregate with all its bodies, as they are imported. Then we do our validation and store an event that
adds our POI with the references to its actual location. Some processor will then eventually update the
read model that powers our tools.

Cheers. That'll do it.

   Sebastian
Reply all
Reply to author
Forward
0 new messages