Aggregate (DDD) isn’t hierarchy & relationships

How do you design an aggregate in domain-driven design? An aggregate in a cluster of related objects and used to manage the complexity of business rules and data consistency. Designing aggregates often incorrectly because of the focus on the relationship between entities.

YouTube

Check out my YouTube channel, where I post all kinds of content accompanying my posts, including this video showing everything in this post.

Relationships

I believe most developers designing aggregates are thinking about data, hierarchy & relationships. As an example, here’s an aggregate diagramed by its relationships. It has an entity that is the Aggregate Root which any consuming code calls, and the relationship from the root to to other entities and value objects.

To put more context to this, I’ll be using an example in this post that came from a member of my Patreon that was asked on my private Discord. The scenario was you have a Route and many different locations for a Route. You can think of this as a bus route, and all the locations are bus stops. A location (bus stop) has a single vending machine.

This vending machine would be emitting event and telemetry data, such as temperature and alarming when it is out of a certain range.

Aggregate

Besides just making the statement that someone can purchase something from a vending machine, I’ve only talked about the entities and their relationships. This is precisely where things go wrong, only thinking about entities, data, and their relationships.

Yes, an aggregate is a cluster of related objects, but those objects should contain behavior.

If I were to model the above in code, it might look like this.

The above code only deals with creating the relationships between entities, and the only real behavior is from the vending machine that has the ability to trigger an Alarm.

We don’t have any business logic or any behavior. All the code we have is to build up an object model hierarchy. While this is a simple example, the point I’m trying to get across is that we’re modeling the relationships. Sure you can have “business logic” that is based on the rules of these relationships, but that’s pretty much it.

Do you need an aggregate for that? Well depending on what type of database you’re using, you could be enforcing these relationship constraints at the database level. You could also just be using transaction scripts and data models. For more on transaction scripts, check out my post Domain Logic: Where does it go?

Invariants

Why do you want to use an aggregate? To enforce invariants and to be a consistency boundary. Hierarchy & relationships are an aspect but not only within that context. An aggregate is a cluster of related objects, but related how? Related based on the invariants you need to enforce.

So if invariants are a key part of an aggregate, when do you need to enforce them? Only when you’re making state changes. If data within an entity is not related to any invariants or must be consistent within an aggregate, then it doesn’t serve a purpose within the aggregate.

Taking this further leads you to realize that exposing data such as the AlarmCount and LastAlarmDate serves no value.

CQRS

Now you’ve landed on CQRS because the model for commands and the model for queries are different. The model for your commands to make state changes can be an aggregates, and your queries can choose a completely different path.

As mentioned many times, CQRS isn’t about different data stores, it’s about different paths for reads and writes. Your queries can use the same underlying database, but they might not use an aggregate, rather they could query the data store directly.

Behavior

If we stop thinking about relationships and start thinking about behaviors and the invariants we need to enforce, the consistency we need within an aggregate, our model would look different.

The Vending Machine is the one that had the ability to create an alarm. It can be on its own and when an alarm is triggered it could be publishing an event.

We could have another aggregate that contains the Route and the Location that could consume that event if it actually needs to know the alarm was triggered.

What it likely cares about the event is for query purposes to know if there are routes that have any locations with vending machines that are in an alarmed state.

Aggregates

It’s typical to think about aggregates based on their hierarchy & relationships between entities. I think this comes from the nature of thinking about data first and not behaviors. But the point of an aggregate is a consistency boundary and enforcing invariants. The data that drives those invariants is what you care about based on the behaviors being exposed. If you don’t know what the behaviors are, you can’t know what invariants to enforce. If you don’t know that, then you’re just building an object model hierarchy, not an aggregate. Don’t add unneeded complexity when you simply can use a data model and a transaction script.

Join!

Developer-level members of my Patreon or YouTube channel get access to a private Discord server to chat with other developers about Software Architecture and Design and access to source code for any working demo application I post on my blog or YouTube. Check out my Patreon or YouTube Membership for more info.

Follow @CodeOpinion on Twitter

Software Architecture & Design

Get all my latest YouTube Vidoes and Blog Posts on Software Architecture & Design

Building a system that Junior Developers can be productive in

How do you get junior developers or someone new up and being productive within your system? Never mind domain knowledge. There is a lot of technical tribal knowledge about how you handle logging, persistence, validation, and various patterns and practices you apply within your specific systems architecture. Here’s how I think about it by creating silos.

YouTube

Check out my YouTube channel, where I post all kinds of content accompanying my posts, including this video showing everything in this post.

Coupling

In a simplistic view, you might think you have a web application or HTTP API with a controller that invokes a service layer that interacts with your database.

You could think of A and B as types, layers, components, whatever. The point is they illustrate the coupling between them.

In reality, however, it’s not a single usage. Typically, you will have a high degree of coupling to some type/layers, etc.

This is typical when you have some abstraction or layer around data access. Yes A is coupled to B, but at a very high degree, making “B” more fragile to change. If you introduce a bug or non-backward compatible change to “B”, it can affect many different usages from A.

Check out my post, Write Stable Code using Coupling Metrics, as I’m describing Afferent and Efferent coupling.

Another common situation is having a lot of indirection where a request goes through various layers and types.

Pile on top of that a high degree of coupling, and you’re in a system that will be hard to change.

If you’re a junior or someone new on a team, you’ll likely feel overwhelmed by the number of places you need to make changes to (layers) or will be so afraid of making a change because you don’t know the implications.

What often happens to combat this is developers will look at existing functionality and how it’s implemented. They then will re-create the same type of changes throughout various layers or piggyback off another feature, which ultimately conflates different features.

Silos

You want to be working in isolation. You want someone new on your team or junior developers to have the freedom to work in isolation without having to worry about every part of the system. To do that, you need to manage coupling. You want to be able to add functionality to your system without affecting any other part of your system. I like to think of this as working in silos.

A silo is independent and oftentimes will resemble other silos.

Their structure is the same. However, internally what they implement can be different. This makes them great for being a template to kick off a new feature. The template will show all the different patterns, practices, and tools you use to start quickly. Think of it as scaffolding a new feature.

This allows new people on your team to instead of piggybacking on existing features or having to change and touch code through different layers, they can create their changes in isolation. You’re minimizing coupling as much as you can.

This means that one silo won’t affect another. Because you aren’t coupled between them. The best way to illustrate this is with the publish-subscribe pattern.

In an e-commerce system, you publish an OrderPlaced event when an order is placed. You then can have 0 or many different consumers of that OrderPlaced event.

In the example above, there are two consumers. One is for sending out a confirmation email, and another consumer is used for sending HTTP requests to other systems as a webhooks system.

They are independent and have no coupling between them. This means we can add a new consumer at any time, completely independent of the other consumers.

This is the type of independence you’re after. However, not everything is going to be decoupled by the publish-subscribe pattern, but the intent is still the same: limit coupling.

Silos may share some common concerns. For example, they may share the same underlying data model, but they likely don’t all work on the same data. Or if they do, you’ll likely share a domain model in that case.

They aren’t share nothing. You will have s some cross-cutting concerns, but generally, those are more technical concerns rather than business concerns (excluding a domain model).

Because there’s limited coupling, this means you can add silos and remove silos without affecting others, similar to the illustrating with publish-subscriber consumers.

Extensible

Ultimately this makes your system extensible. You can leverage templates that illustrate the common structure but need the implementation details created. Every team has various coding standards, how they use certain libraries, frameworks, and tools. This allows you to define those and have the focus simply be on the implementation of the functionality required.

You might be thinking, “Are you just describing vertical slice architecture?”. Yes, to a degree. Vertical slices are a great way to think about silos. Meaning a feature or a feature set is a silo. It’s also helpful to think about CQRS in accomplishing this as well. However, the end goal is controlling coupling. You’re junior developers (and everyone on your team) will thank you.

Join!

Developer-level members of my Patreon or YouTube channel get access to a private Discord server to chat with other developers about Software Architecture and Design and access to source code for any working demo application I post on my blog or YouTube. Check out my Patreon or YouTube Membership for more info.

Follow @CodeOpinion on Twitter

Software Architecture & Design

Get all my latest YouTube Vidoes and Blog Posts on Software Architecture & Design

Implementing Undo functionality

You’re writing an email and click “Send”. Then immediately think, “oh no!” because you sent it to the wrong person. Or maybe you’re checking out of an e-commerce website, click “Place Order” then a couple of minutes go by, and you think, “I shouldn’t have ordered that,” and have buyer’s remorse. You want to cancel or undo the action you performed in both situations. Now put your developer hat on. How would you implement undo functionality in these types of cases?

YouTube

Check out my YouTube channel, where I post all kinds of content accompanying my posts, including this video showing everything in this post.

Undo

A good example of undo functionality is canceling the sending of an email. If you’re familiar with Gmail, it has a feature to undo a recently sent email.

Gmail sending undo functionality

Once you send your message, you have the option to undo your sent message.

Undo Functionality

To illustrate this in our implementation, I also realized that Twitter could not undo sending a tweet. So I’m going to re-create a simple example of that.

I’ve created a Blazor server app allowing you to enter text and click send now. In the example below, I sent a message “this is a test message”.

On our backend, we are generating a Tweet message (command) that is being sent from our API to a message broker.

Send Command to Broker

We have a consumer that asynchronously handles the Tweet command and persists that to our database. It then also publishes a Tweeted event back to our broker.

Process command from broker

Then there is a consumer for the Tweeted event that handles that event from our message broker and, using SignalR, pushes that message back to the client.

Consume event to push to client UI update

Here’s what the code looks like when processing the Tweet command.

So that’s how it works currently. How do we implement an Undo? So that our “Tweet” can be canceled for a window of time after we send it.

Implementation

All the code for this demo is available to Developer-level members of my Patreon or YouTube channel.

To implement undo functionality, we can use delayed delivery from the Tweet command we initially sent to our message broker. This prevents the command from being consumed until the delay period has passed. It acts as a timeout. Once the timeout has passed, then a consumer will process our Tweet command.

During that timeout period, our API can expose another command to cancel the original Tweet command.

Delayed delivery has a bunch of different use cases. Check out my post Avoiding Batch Jobs by a message in the FUTURE for more examples.

We can now implement a TweetWithDelay command and a new UndoTweet command. And if a Tweet is undone, we will publish a TweetUndone event.

The above code uses an NServiceBus saga. When a TweetWithDelay is handled, it sets the Saga State to capture the incoming text/message of the Tweet. Delayed Delivery occurs when it calls RequesTimeout for 10 seconds. Once 10 seconds has passed, the Timeout method will be invoked, and we’ll create a regular Tweet command and send it. That will invoke the code above that we already had. If however the UndoTweet command is consumed, it will complete the Saga and publish our TweetUndone event. When a saga is completed, the Timeout won’t be executed.

Notice we didn’t have to change any of the existing code. This is all brand-new functionality.

In our front-end Blazor, we now can have a “Send with Delay” button that will send our new command to the broker.

What this looks like visually, I will send a tweet with “send now,” and then I will send another with “send with delay”.

When that occurs, we can show a cancel/X on the far right to allow the user to send the UndoTweet command. If you don’t within 10 seconds, as mentioned earlier, the timeout will occur, and the SendTweet will be sent, and we will remove the cancel/X on the far right.

Now if I send another tweet with a delay

However, with this tweet, I did click the undo button, which sent the UndoTweet command, which happened before the timeout occurred, so it completed our Saga, and the tweet was never processed, and we removed it from the UI via SignalR.

Client Side

You may wonder why not implement undo functionality exclusively on the front end? Why not just implement the timeout in the front end and make the API call after the timeout?

You could, but the downside is that no request would ever be sent if you close the browser before the timeout occurs. In the example of Gmail, do you want to click “Send” and then wait 30 seconds to close your browser/tab? Not really.

Join!

Developer-level members of my Patreon or YouTube channel get access to a private Discord server to chat with other developers about Software Architecture and Design and access to source code for any working demo application I post on my blog or YouTube. Check out my Patreon or YouTube Membership for more info.

Follow @CodeOpinion on Twitter

Software Architecture & Design

Get all my latest YouTube Vidoes and Blog Posts on Software Architecture & Design