Confused, Aggregate Transaction Boundaries in Domain-Driven Design
Tags: ddd, go, transaction
Published: 2023-12-01 13:00:09

---
title: Confused, Aggregate Transaction Boundaries in Domain-Driven Design
published: true
description: Explore the nuances of aligning aggregates with transaction boundaries in DDD. We discuss challenges like spanning transactions across aggregates, and solutions including merging aggregates and eventual consistency, with practical examples in Golang.
tags: DDD, Golang, Transaction
---

## What is an Aggregate?

An aggregate is extracted as a unit to maintain invariant conditions and brings order to the operation of objects. It consists of boundaries and a root. The boundary of an aggregate defines what is included in the aggregate. The root of an aggregate is a specific object within it. All external operations on the aggregate are conducted via the aggregate root. By not exposing objects within the aggregate boundary to the outside, the invariant conditions within the aggregate are maintained.

It is reasonable to consider aggregates as units of the repository

e.g.

```go

// aggregates
package repository

type UserRepository interface {
	Create(user entity.User) error
	Delete(userId int) error
}

```

## Transaction Boundaries

Scope of a Transaction.

In DDD, it is ideal to consider <font style="color:red">an aggregate should be the same as a transaction boundary.</font>

Disadvantages of spanning transactions across aggregates:

- Spanning a transaction across aggregates A and B expresses the start and end of the transaction in the application layer, obscuring the consistency level of each aggregate.
- Technical changes (like ORM or storage changes) necessitate modifications in the use case/domain layer, leading to fragile code.
- Spanning aggregates naturally widens the transaction scope, increasing the likelihood of db locks.

e.g.

This sample code hides the creation/commit/rollback of tx in infrastructure, but is fragile due to its dependency on the db client in the use case/domain layer. It is sort of anti-pattern. will explain later.

```go
// Infrastructure layer

func (t *TransactionRepository) Do(ctx context.Context, runner func(tx *ent.Tx) error) error {
	// Start transaction
	tx, err := t.client.Tx(t.ctx)

	if err != nil {
		return err
	}
	err = runner(tx)

	if err != nil {
		// Rollback process
		if err := tx.Rollback(); err != nil {
			return err
		}
		return err
	}

	// Commit process
	if err := tx.Commit(); err != nil {
		return err
	}
	return nil


// Usecase layer
func (u *lessonUseCase) TransactionSample(ctx context.Context) error {
	// Has dependency with db client
	err := u.TransactionRepository.Do(ctx, func(tx *ent.Tx) error {

		// Transaction processing here
		newUser, err := userRepo.create(tx, ×××)
		if err != nil {
			return err
		}
		err := userGroupRepo.create(tx, newUser, ×××)

		return nil

	})

	if err != nil {
		return err
	}

	return nil
}
```

In real-world project, Of course, there are many challenging cases:

- e.g. Creating a User and also wanting to update a Organization etc...

## Solutions

#### 1. Merge Aggregates

If crossing aggregates with tx is problematic, merge them together (as seen in some DDD books).

Advantages:

- Maintains consistency within the aggregate
  Disadvantages:
- Larger aggregates might lead to performance issues due to increased db locks
  - Merging different aggregates into one (e.g., A and B into C) complicates the code
  - Application logic becomes more infrastructure-dependent. Hard to debug and loos testability.

#### 2. Eventual Consistency

Accept temporary data breaches.

Advantages:

- No need to change aggregates - Transaction boundaries remain within existing aggregates, keeping the scope narrow

Disadvantages:

- Temporary loss of consistency
- Implementing measures for maintaining consistency can be cumbersome
- Handling failures in mid-process (e.g. queue)
  - e.g. if creating a user succeeds but creating a organization fails, the user might be removed
- Handling failures in recovery

### 3. Transaction Across Aggregates

This is an anti-pattern.

Advantages:

- Maintains consistency
- No need to modify aggregates

Disadvantages:

- Fragile to changes
- Wider transaction scope may lead to locks

## Summary

This post delves into the intricacies of aggregate transaction boundaries within DDD, highlighting the balance between consistency, performance, and system design. Practical solutions and their trade-offs are discussed, providing insights for effective DDD implementation in real-world scenarios.

Honestly, I am still confused and don't know solution. It is really depends on you and your team.
Also I did not mention about modeling in this time. I think modeling is key factor to find better solution.

Happy coding!