Rich Domain Model with Spring Boot and Hibernate
Tags: java, hibernate, jpa, springboot
Published: 2023-09-07 19:26:14

The article is a **_long read_**. I recommend you to look through the `Table of contents` in advance. Perhaps some parts can be more intriguing than others.

---

I like [Hibernate](https://hibernate.org/). Even though it's complicated (and [error-prone sometimes](https://www.stemlaur.com/blog/2021/03/30/tech-hibern-hate/)), I consider it an extremely useful framework. If you know how to cook it, it shines.

> Though Hibernate is in implementation of JPA, I'm going to say _Hibernate_ every time I mean JPA. Just for the sake of simplicity.

Lots of Java developers use Hibernate in their projects. However, I’ve noticed a strange tendency. Java devs apply [Anemic Domain Model pattern](https://martinfowler.com/bliki/AnemicDomainModel.html) almost every time. Whenever I ask about reasons for picking up that strategy, I get answers like:

1. We've always done it before and it works fine.
1. Our developers got used to such architecture.
1. Do we have any other options?

That's why I've decided to come up with this article. I want to reconsider the [status quo](https://en.wikipedia.org/wiki/Status_quo#:~:text=Status%20quo%20is%20a%20Latin,religious%2C%20scientific%20or%20military%20issues.) of Anemic Domain Model usage with Hibernate and propose you something different. And that is [Rich Domain Model pattern](https://www.baeldung.com/java-anemic-vs-rich-domain-objects).

In this article, I'm telling you:

1. What is Rich Domain Model?
1. What's wrong with Anemic Domain Model and how can Rich one fix the issues?
1. Step by step solution.
1. What disadvantages does Rich Domain Model have?

![article cover](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/me7tt7mwp4yqe6blbpzw.jpg)

> You can check out the entire repository with code examples by [this link](https://github.com/SimonHarmonicMinor/rich-domain-model-with-hibernate-example).

## Table of contents

1. The problems with Anemic Domain Model
    1. Too smart services
    1. Possible invariants' violation
    1. Lack of encapsulation
    1. Testing is harder
1. Rich Domain Model principle
1. Don't add setters, getters, and public no-args constructor
    1. No-args constructor allows incostistent object instantiation
    1. Setters break encapsulation
    1. Getters break encapsulation
    1. Current result
1. Aggregate and Aggregate root
1. Evolution of requirements
    1. Each Pocket must possess at least one Tamagotchi
    1. Every Tamagotchi name has to be unique within a Pocket
    1. If a user deletes a Tamagotchi, they can restore it by name
1. Querying data
    1. Manual queries
    1. Introducing toDto method
1. Unit testing entities
    1. Pocket must always possess at least one Tamagotchi
    1. If you delete Tamagotchi, you can restore it by name
    1. If you delete multiple Tamagotchi with the same name, you can only restore the last one
1. Integration testing
    1. Create Pocket
    1. Create Tamagotchi
    1. Update Tamagotchi
1. Performance implications
    1. Optimization of queries
    1. Pinpointing optimized checks
1. Database generated ID
    1. Manually fill the id
    1. Introducing business key
1. Is Rich Domain Model always worth it?
1. Conclusion
1. Resources
    

## The problems with Anemic Domain Model

Firstly, let’s discuss the domain. We’re going to develop the Tamagotchi application. Pocket may have many `Tamagotchi` instances, but each `Tamagotchi` belongs to a single `Pocket`. Therefore, the relationship is `Pocket --one-to-many-> Tamagotchi`.

Most likely the Anemic Domain Model solution would be implemented in this way:

![Anemic domain model example](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j9tyfqsznif3bhs3rlpq.png)

I bet you’ve seen a lot of similar Java code. But the solution contains many problems. Let’s discuss them one by one.

### Too smart services

Anemic Domain Model requires the service layer to possess all business logic. While entities act as dummy data structures. But entities are not static. They develop during the time. We may add some fields and delete others. Or we can combine existing fields in `Embeddable` object.

Here, services have to know every minor detail of the entity they are working with. Because any operation may require access to different fields. Meaning that even a slight change in an entity may lead to major restructuring in many services. Actually, that breaks [Open-Closed principle](https://en.wikipedia.org/wiki/Open%E2%80%93closed_principle). The code becomes not object-oriented but procedural. We don’t use benefits of OOP paradigm. Instead, we bring additional difficulties.

### Possible invariants' violation

[Invariant](https://khorikov.org/posts/2022-06-06-validation-vs-invariants/) is a business rule that allows only certain changes to entities. It guarantees that we won’t transmit entities to the wrong state. For example, suppose that `Pocket` may contain only three `Tamagotchi` by default. If you want to have more, you need to buy premium subscription. That’s an invariant. The code has to disallow adding fourth `Tamagotchi` to `Pocket`, if user don’t purchase the additional feature.

If we choose the Anemic Domain Model approach, it means that services are obligatory to check invariants and cancel operation if needed. But invariants are also not static. Imagine that the rule of three `Tamagotchi` within `Pocket` has not been introduced from the start of the project. But we want to add it now. It means that we have to check every method and function that might create a new `Tamagotchi` and add corresponding checks.

It becomes even worse if changes are broader. Suppose that `Tamagotchi` has become a part of [Saga pattern](https://microservices.io/patterns/data/saga.html). Now it contains `status` field that has a value of `PENDING`. If `Tamagotchi` is `PENDING`, you can neither delete it nor update it. Do you see where I’m going? You have to check _every_ piece of code that updates or deletes `Tamagotchi` and make sure you don’t miss any check for `PENDING` status.

### Lack of encapsulation

[Encapsulation](https://en.wikipedia.org/wiki/Encapsulation_(computer_programming)) in OOP is a mechanism that restricts direct access to certain data. That makes sense. Entity might have several fields, but it doesn’t mean we want to allow changing each of them. We might change only simultaneously concrete fields. Other ones are allowed to be updated only if the entity transmits to a specific state.

Anemic Domain Model forces us to give up encapsulation and put `@Getter` and `@Setter` annotations from [Lombok](https://projectlombok.org/) without considering the consequences.

And the biggest problem with violating encapsulation is that code becomes more _dangerous_ to work with. You cannot just call `setName` or `setStatus` methods. But you have to make sure that you check specific conditions in advance. Again, invariants aren’t static. So, every mutation call to an entity is like a land mine. You don’t know what breaks next, if you miss a single condition check.

### Testing is harder

Mostly developers use Hibernate in combination with Spring Boot. Meaning that services are regular Spring beans with [@Transactional annotation](https://www.baeldung.com/transaction-configuration-with-jpa-and-spring). Usually those services contain [spaghetti code](https://en.wikipedia.org/wiki/Spaghetti_code#:~:text=Spaghetti%20code%20is%20a%20pejorative,with%20insufficient%20ability%20or%20experience.) of entities, repositories, and other services invocations. When it comes to testing, I see developers choose one of options:

1. Integration testing.
1. [Mocking](https://site.mockito.org/) everything.

Don’t get me wrong. I think that integration testing is crucial. And [Tescontainers](https://testcontainers.com/) library especially helped to make the process smooth. However, I think that the count of integration tests should be as minimum as possible. If you can validate something with a simple unit test, do it this way. Bringing too much integration tests into the project also leads to certain difficulties:

1. Integration tests are harder to maintain.
1. There is always a shared resource (database, in this case). So, tests might unexpectedly become dependent on each other. Tests might get [flaky](https://www.datadoghq.com/knowledge-center/flaky-tests/#:~:text=A%20flaky%20test%20is%20a,with%20each%20individual%20test%20run.).
1. It's tough to run integration tests in parallel.
1. Those tests are much slower. If your project is old enough and you have many integration tests, a regular CI build can run in 30 minutes or even more.

![Too many integration tests](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8ed2hm7jbnscywdoepn3.png)

What about mocking? I think such tests are almost useless. I don’t mean that mocking in general is a bad idea. But if you try to mock every call to Spring Data JPA repository and other service, it may occur that [you don’t test the behaviour](https://semaphoreci.com/blog/unit-testing). You just verify the correct order of mocks’ invocations. So, tests become fragile and an enormous burden to maintain.

## Rich Domain Model principle

On the contrary, Rich Domain Model pattern proposes a different approach. Look at the diagram below.

![Rich domain model principle](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/31spxo3rfuwg4c6s1a9h.png)

As you can see, entities hold the required business logic. While services act like a thin layer that delegates call to repositories and entities.

Rich Domain Model correlates with [tactical patterns of Domain Driven Design](https://learn.microsoft.com/en-us/azure/architecture/microservices/model/tactical-ddd). The one that we're interested in is [aggregate](https://martinfowler.com/bliki/DDD_Aggregate.html).

Aggregate is a cluster of domain objects that you can treat as a whole unit. For example, `Pocket` has many `Tamagotchis`. Meaning that `Pocket` and `Tamagotchi` can be a single aggregate. Aggregate root is the entity that allows direct access to aggregate and guarantees invariants’ correctness. Therefore, if we want to change something in `Tamagotchi`, we should only interact with `Pocket`.

By introducing Rich Domain Model, I want to solve these problems:

1. Code should become more _business-oriented_. If logic is divided between many services, it's hard to understand the actual operation flow (especially for newcomers).
1. Let the compiler validate your code. If an entity has a setter for every field, you should put additional check whenever you invoke it. But if an entity provides only certain amount of methods that mutates its state, it means that incorrect transition is not possible due to compiler check. In another words, if a method doesn't exist, you cannot call it. So, it's better to provide only those operations that are needed.
1. Reduce the amount of integration tests. If it's possible, it's better to test business logic with simple unit tests. So, I want to replace some integration tests with unit ones without violating the quality assurance level.

Let's start our journey with refactoring `Pocket` and `Tamagotchi` to Rich Domain Model.

## Don't add setters, getters, and public no-args constructor

Firstly, look at the initial approach of designing `Pocket` and `Tamagotchi` entities following Anemic Domain Model:

```java
@Entity
@NoArgsConstructor
@Setter
@Getter
public class Pocket {
  @Id
  private UUID id;

  private String name;

  @OneToMany(mappedBy = "pocket")
  private List<Tamagotchi> tamagotchis = new ArrayList<>();
}

@Entity
@NoArgsConstructor
@Setter
@Getter
public class Tamagotchi {
  @Id
  private UUID id;

  private String name;

  @ManyToOne(fetch = LAZY)
  @JoinColumn(name = "pocket_id")
  private Pocket pocket;
}
```

> Here I’m using UUID as a primary key. I understand that there are some performance implications for it. But now client-side generated ID is crucial for a smooth transition to the Rich Domain Model. Anyway, later I’ll give you some examples with other ID types.

I bet this looks familiar. Perhaps your current project contains lots of similar declarations. What problems are there?

### No-args constructor allows incostistent object instantiation

Hibernate [demands each entity to provide a no-args constructor](https://stackoverflow.com/questions/2808747/why-does-jpa-require-a-no-arg-constructor-for-domain-objects). Otherwise, the framework doesn’t work properly. And it’s one of the edgy cases that can make your code less straight-forward and more [buggy](https://www.quora.com/What-does-it-mean-to-write-a-buggy-code).

Thankfully, there is a solution. Hibernate doesn’t need a _public_ constructor for an entity. Instead, it can be _protected_. So, we can add a _public static_ method to instantiate the entity, and leave _protected_ constructor for Hibernate specifically. Look at the code example below:

```java
@Entity
@NoArgsConstructor(access = PROTECTED)
@Setter
@Getter
public class Pocket {
  @Id
  private UUID id;

  private String name;

  @OneToMany(mappedBy = "pocket")
  private List<Tamagotchi> tamagotchis = new ArrayList<>();

  public static Pocket newPocket(String name) {
    Pocket pocket = new Pocket();
    pocket.setId(UUID.randomUUID());
    pocket.setName(name);
    return pocket;
  }
}

@Entity
@NoArgsConstructor(access = PROTECTED)
@Setter
@Getter
public class Tamagotchi {
  @Id
  private UUID id;

  private String name;

  @ManyToOne(fetch = LAZY)
  @JoinColumn(name = "pocket_id")
  private Pocket pocket;

  public static Tamagotchi newTamagotchi(String name, Pocket pocket) {
    Tamagotchi tamagotchi = new Tamagotchi();
    tamagotchi.setId(UUID.randomUUID());
    tamagotchi.setName(name);
    tamagotchi.setPocket(pocket);
    return tamagotchi;
  }
}
```

As you can see, business code (that is likely to be in a different package) cannot instantiate `Tamagotchi` or `Pocket` with no-args constructor. It has to invoke dedicated methods `newTamagotchi` and `newPocket` that accept a specific amount of parameters.

### Setters break encapsulation

I think _public_ setters aren’t much different from regular _public_ fields. Well, you could put some checks in a setter because it’s a method. But in reality, people tend not to go this way. Usually we just put `@Setter` annotation from Lombok library on top of class and that’s it.

I consider using setters in an entity a bad approach due to these reasons:

1. Possible invariants’ violation. Some fields cannot be updated. Other ones can be updated only if the entity is being transmitted to a particular state. Pure setters forces developer to put all those checks in services.
1. If `Tamagotchi.name` is `String`, it doesn't mean that every `String` value is allowed. Therefore, you also have to perform those checks outsided of an entity.
1. A field can be a part of implementation detail. Maybe it's forbidden to update it directly. But a _public_ setter allows this operation.

The main point is that _public_ setters breaks the principle of compiler validation that I mentioned previously. You just provide too many options that can be called differently.

What’s the alternative? I suggest adding `changeXXX` methods for specific behaviour. Also, those methods should contain validation logic and throw exception if needed.

Suppose that `Tamagotchi` entity has a `status` field that can have the value of `PENDING`. If `Tamagotchi` is `PENDING`, it cannot be modified. Look at the code example below:

```java
@Entity
@NoArgsConstructor(access = PROTECTED)
@Getter
public class Tamagotchi {
  @Id
  private UUID id;

  private String name;

  @ManyToOne(fetch = LAZY)
  @JoinColumn(name = "pocket_id")
  private Pocket pocket;

  @Enumerated(STRING)
  private Status status;

  public void changeName(String name) {
      if (status == PENDING) {
          throw new TamagotchiStatusException("Tamagotchi cannot be modified because it's PENDING");
      }
      if !(nameIsValid(name)) {
          throw new TamagotchiNameInvalidException("Invalid Tamagotchi name: " + name);
      }
      this.name = name;
  }

  public static Tamagotchi newTamagotchi(String name, Pocket pocket) { /* entity creation */ }
}
```

The `Tamagotchi.changeName` method guarantees that you cannot change `name` if certain preconditions are violated. The code that invokes the method doesn’t need to know about specific rules. You just have to deal with exceptions.

### Getters break encapsulation

Well, the previous paragraph about setters is more or less obvious. There are dozens of articles and opinions on the Internet about problems with setters. Anyway, eliminating getters sounds ridiculous, isn’t it? They don’t mutate the state of an entity. So, what’s the deal?

The problem with getters is that they also allow to break encapsulation and perform unnecessary or wrong checks. Suppose that we also want to restrict updating the name of `Tamagotchi` if its status is `ERROR`. That's the possible solution you might see during the code review:

```java
@Service
@RequiredArgsConstructor
public class TamagotchiService {
    private final TamagotchiRepository repo;

    @Transactional
    public void changeName(UUID id, String name) {
        Tamagotchi tamagotchi = repo.findById(id).orElseThrow();
        if (tamagotchi.getStatus() == ERROR) {
            throw new TamagotchiStatusException("Tamagotchi cannot be modified because its status is ERROR");
        }
        tamagotchi.changeName(name);
    }
}
```

Though `Tamagotchi` provides a dedicated method `changeName`, the check is still implemented in the service layer. I've noticed that even experienced senior developers tend to fall into **_anemic model mindset_** when there is a possibility. Because they've been working for years on different projects and most likely each one has applied Anemic Domain Model pattern. So, developers just choose the simpler and more obvious way.

However, a decision has some consequences. Firstly, the logic is divided between `Tamagotchi` entity and `TamagotchiService` (that’s the one thing we’ve wanted to avoid). Secondly, checks might be duplicated and you can miss it during the code review. And finally, some checks can be outdated in time. For example, this validation of `ERROR` status might become obsolete later. If you forget to eliminate it here, your code won’t act expectedly.

As I mentioned before, if you don’t need a method, just don’t add it. Getters aren’t required to perform business logic. You can put validations inside `Tamagotchi.changeName` method. If a getter is not present, it cannot be invoked and such a scenario won’t happen.

> What about querying, then? Usually we use Hibernate entities to `SELECT` data, transform it into DTO, and return the result to the user. How can we do it without getters? Don’t worry, we’ll discuss this topic later in the article.

There is also one exception for this rule. You can add getters for ID. Sometimes it’s necessary to know the entity id in runtime. Later you’ll see an example of that.

### Current result

We've already discussed three points:

1. No-args constructor.
1. Setters.
1. Getters.

If we remove those pieces, the code will look like this:

```java
@Entity
@NoArgsConstructor(access = PROTECTED)
public class Pocket {
  @Id
  private UUID id;

  private String name;

  @OneToMany(mappedBy = "pocket")
  private List<Tamagotchi> tamagotchis = new ArrayList<>();

  public static Pocket newPocket(String name) {
      Pocket pocket = new Pocket();
      pocket.setId(UUID.randomUUID());
      pocket.setName(name);
      return pocket;
  }
}

@Entity
@NoArgsConstructor(access = PROTECTED)
public class Tamagotchi {
  @Id
  private UUID id;

  private String name;

  @ManyToOne(fetch = LAZY)
  @JoinColumn(name = "pocket_id")
  private Pocket pocket;

  @Enumerated(STRING)
  private Status status;

  public void changeName(String name) {
      if (status == PENDING) {
          throw new TamagotchiStatusException("Tamagotchi cannot be modified because it's PENDING");
      }
      if (!nameIsValid(name)) {
          throw new TamagotchiNameInvalidException("Invalid Tamagotchi name: " + name);
      }
      this.name = name;
  }

  public static Tamagotchi newTamagotchi(String name, Pocket pocket) {
      Tamagotchi tamagotchi = new Tamagotchi();
      tamagotchi.setId(UUID.randomUUID());
      tamagotchi.setName(name);
      tamagotchi.setPocket(pocket);
      tamagotchi.setStatus(CREATED);
      return tamagotchi;
  }
}
```

## Aggregate and Aggregate root

Previously I’ve mentioned the Aggregate pattern. Speaking about our domain, the `Pocket` entity should be the Aggregate root. However, existing API allows us to access `Tamagotchi` entity directly. Let’s fix that.

Firstly, let's add simple `CREATE/UPDATE/DELETE` operations. Look at the code example below:

```java
@Entity
@NoArgsConstructor(access = PROTECTED)
public class Pocket {
  @Id
  private UUID id;

  private String name;

  @OneToMany(mappedBy = "pocket", cascade = PERSIST, orphanRemoval = true)
  private List<Tamagotchi> tamagotchis = new ArrayList<>();

  public UUID createTamagotchi(TamagotchiCreateRequest request) {
      Tamagotchi tamagotchi = Tamagotchi.newTamagotchi(request.name(), this);
      tamagotchis.add(tamagotchi);
      return tamagotchi.getId();
  }

  public void updateTamagotchi(UUID tamagotchiId, TamagotchiUpdateRequest request) {
      Tamagotchi tamagotchi = tamagotchiById(tamagotchiId);
      tamagotchi.changeName(request.name());
  }

  public void deleteTamagotchi(UUID tamagotchiId) {
      Tamagotchi tamagotchi = tamagotchiById(tamagotchiId);
      tamagotchis.remove(tamagotchi);
  }

  private Tamagotchi tamagotchiById(UUID tamagotchiId) {
      return tamagotchis
              .stream()
              .filter(t -> t.getId().equals(tamagotchiId))
              .findFirst()
              .orElseThrow(() -> new TamagotchiNotFoundException("Cannot find Tamagotchi by ID=" + tamagotchiId));
  }

  public static Pocket newPocket(String name) {
      Pocket pocket = new Pocket();
      pocket.setId(UUID.randomUUID());
      pocket.setName(name);
      return pocket;
  }
}

@Entity
@NoArgsConstructor(access = PROTECTED)
class Tamagotchi {
  @Id
  @Getter
  private UUID id;

  private String name;

  @ManyToOne(fetch = LAZY)
  @JoinColumn(name = "pocket_id")
  private Pocket pocket;

  @Enumerated(STRING)
  private Status status;

  public void changeName(String name) {
      if (status == PENDING) {
          throw new TamagotchiStatusException("Tamagotchi cannot be modified because it's PENDING");
      }
      if (!nameIsValid(name)) {
          throw new TamagotchiNameInvalidException("Invalid Tamagotchi name: " + name);
      }
      this.name = name;
  }

  public static Tamagotchi newTamagotchi(String name, Pocket pocket) {
    Tamagotchi tamagotchi = new Tamagotchi();
    tamagotchi.setId(UUID.randomUUID());
    tamagotchi.setName(name);
    tamagotchi.setPocket(pocket);
    tamagotchi.setStatus(CREATED);
    return tamagotchi;
  }
}
```

There are a lot of nuances. So, I’ll point out each of them one by one. Firstly, `Pocket` entity provides methods `createTamagotchi`, `updateTamagotchi`, and `deleteTamagotchi` as-is. You don’t retrieve any information from `Tamagotchi` or `Pocket`. You just invoke the required functionality.

> I’m aware that such a technique also has performance penalties. We’ll also discuss some approaches to overcome these problems later.

Then goes `Tamagotchi` entity. The first thing I want you to notice is that the entity is _package-private_. Meaning that nobody can access `Tamagotchi` outside of the package. Therefore, calling `Pocket` directly is the only way.

> Now you may think that its profit isn’t so obvious. But soon we’ll discuss the evolution of aggregate and you’ll see the benefits.

Neither `Pocket` nor `Tamagotchi` entity provides regular setters or getters. One can only invoke public methods of `Pocket` entity.

## Evolution of requirements

As I said before, entities aren't static. Requirements change and invariants as well. So, let's look through a hypothetical process of implementing new requirements and see how it goes.

### Each Pocket must possess at least one Tamagotchi

It means that we should create a `Tamagotchi`, when a new `Pocket` is instantiated. Also, if you want to delete a `Tamagotchi`, you have to check that it’s not the single one within the `Pocket`. Look at the code example below:

```java
@Entity
@NoArgsConstructor(access = PROTECTED)
public class Pocket {
  /* fields and other methods */

  public void deleteTamagotchi(UUID tamagotchiId) {
      Tamagotchi tamagotchi = tamagotchiById(tamagotchiId);
      if (tamagothis.size() == 1) {
          throw new TamagotchiDeleteException("Cannot delete Tamagotchi because it's the single one"); 
      }
      tamagotchis.remove(tamagotchi);
  }

  public static Pocket newPocket(String name) {
      Pocket pocket = new Pocket();
      pocket.setId(UUID.randomUUID());
      pocket.setName(name);
      pocket.createTamagotchi(new TamagotchiCreateRequest("Default")); // creating default tamagotchi
      return pocket;
  }
}
```

As you can see, invariants’ correctness is guaranteed within an aggregate. Even if you want to, you cannot create a `Pocket` with zero `Tamagotchi` or delete `Tamagotchi` if it’s a single one. And I think that it’s great. Code becomes less error-prone and easier to maintain.

### Every Tamagotchi name has to be unique within a Pocket

To implement this requirement, we need to alter `createTamagotchi` and `updateTamagotchi` methods a bit. Look at the code example below:

```java
@Entity
@NoArgsConstructor(access = PROTECTED)
public class Pocket {
  /* fields and other methods */

  public UUID createTamagotchi(TamagotchiCreateRequest request) {
      Tamagotchi tamagotchi = Tamagotchi.newTamagotchi(request.name(), this);
      tamagotchis.add(tamagotchi);
      validateTamagotchiNamesUniqueness();
      return tamagotchi.getId();
  }

  public void updateTamagotchi(UUID tamagotchiId, TamagotchiUpdateRequest request) {
      Tamagotchi tamagotchi = tamagotchiById(tamagotchiId);
      tamagotchi.changeName(request.name());
      validateTamagotchiNamesUniqueness();
  }

  private void validateTamagotchiNamesUniqueness() {
      Set<String> names = new HashSet<>();
      for (Tamagotchi tamagotchi : tamagotchis) {
          if (!names.add(tamagotchi.getName()) {
              throw new TamagotchiNameInvalidException("Tamagotchi name is not unique: " + tamagotchi.getName());
          }
      }
  }
}
```

> You’ve probably noticed that I added a getter for `Tamagotchi.name` field. Because `Pocket` and `Tamagotchi` form a single aggregate, it’s fine to provide getters. Because `Pocket` might need this information. However, `Tamagotchi` should not request anything from `Pocket`. It’s also better to mark this getter as package-private. So, no one can access it outside of the package.
> 
> I understand that `validateTamagotchiNamesUniqueness` doesn't perform well. Don't worry, we'll discuss workarounds later in the `Performance implications` section.

Once again, the domain model guarantees that each `Tamagotchi` name is unique within a `Pocket`. What is interesting is that API hasn’t changed a bit. The code that invokes those public methods (likely domain services) doesn’t have to change logic.

### If a user deletes a Tamagotchi, they can restore it by name

This one is tricky and involves soft deletion. It also has additional points:

1. If `Tamagotchi` with the same name already exists, a user cannot restore the one they've deleted.
1. If a user deletes multiple `Tamagotchis` with the same name, they can only restore the last one.

I'm not a fan of soft deletion that involves adding `isDeleted` column by [many reasons](https://stackoverflow.com/questions/2549839/are-soft-deletes-a-good-idea). Instead, I will introduce a new entity `DeletedTamagotchi` that contains the state of deleted `Tamagotchi`. Look at the code example below.

```java
@Entity
@NoArgsConstructor(access = PROTECTED)
@Getter
class DeletedTamagotchi {
  @Id
  private UUID id;

  private String name;

  @ManyToOne(fetch = LAZY)
  @JoinColumn(name = "pocket_id")
  private Pocket pocket;

  @Enumerated(STRING)
  private Status status;

  public static DeletedTamagotchi newDeletedTamagotchi(Tamagotchi tamagotchi) {
      DeletedTamagotchi deletedTamagotchi = new DeletedTamagotchi();
      deletedTamagotchi.setId(UUID.randomUUID());
      deletedTamagotchi.setName(tamagotchi.getName());
      deletedTamagotchi.setPocket(tamagotchi.getPocket());
      deletedTamagotchi.setStatus(tamagotchi.getStatus());
      return deletedTamagotchi;
  }
}
```

`Tamagotchi` entity is rather simple, so `DeletedTamagotchi` contains the same fields. However, if the original entity were more complicated, it couldn’t be the case. For example, you could save the state of `Tamagotchi` in `Map<String, Object>` fields that transforms to [JSONB in the database](https://www.postgresql.org/docs/current/datatype-json.html).

Also, `DeletedTamagotchi` entity is package-private like `Tamagotchi`. So, the presence of this entity is an implementation detail. The other parts of the code don’t need to know this and interact with `DeletedTamagotchi` directly. Instead, it’s better to provide a single method `Pocket.restoreTamagotchi` without additional details.

Now let's alter `Pocket` entity to the new requirements. Look at the code example below:

```java
@Entity
@NoArgsConstructor(access = PROTECTED)
public class Pocket {
  /* fields and other methods */

  @OneToMany(mappedBy = "pocket", cascade = PERSIST, orphanRemoval = true)
  private List<DeletedTamagotchi> deletedTamagotchis = new ArrayList<>();

  public void deleteTamagotchi(UUID tamagotchiId) {
      Tamagotchi tamagotchi = tamagotchiById(tamagotchiId);
      if (tamagothis.size() == 1) {
          throw new TamagotchiDeleteException("Cannot delete Tamagotchi because it's the single one"); 
      }
      tamagotchis.remove(tamagotchi);
      addDeletedTamagotchi(tamagotchi);
  }

  private void addDeletedTamagotchi(Tamagotchi tamagotchi) {
      Iterator<DeletedTamagotchi> iterator =  deletedTamagotchis.iterator();
      // if Tamagotchi with the same has been deleted,
      // remove information about it
      while (iterator.hasNext()) {
          DeletedTamagotchi deletedTamagotchi = iterator.next();
          if (deletedTamagotchi.getName().equals(tamagotchi.getName()) {
              iterator.remove();
              break;
          }
      }
      deletedTamagotchis.add(
          newDeletedTamagotchi(tamagotchi)
      );
  }

  public UUID restoreTamagotchi(String name) {
      DeletedTamagotchi deletedTamagotchi = deletedTamagotchiByName(name);
      return createTamagotchi(new TamagotchiCreateRequest(deletedTamagotchi.getName()));
  }
}
```

The `deleteTamagotchi` method also creates or replaces a `DeletedTamagotchi` record. Meaning that every other code that calls the method for whatever reason doesn't violate the new requirement about soft deletion because it's been implemented internally.

To perform the required business operation, you should just invoke `Pocket.restoreTamagotchi`. We hid all the complex details behind the scenes. What’s even better is that `DeletedTamagotchi` is not a part of public API. Meaning that it can be easily modified or even removed, if it’s not needed anymore.

---

As you can see, placing business logic within an aggregate has significant benefits. However, it's not the end of the story. There are still some concerns we need to deal with. And the next one is querying data.

## Querying data

When we deal with Hibernate, usually we use public getters to transform entity into DTO and return it to the user. However, only `Pocket` entity is public now, and it doesn’t provide any getters (aside from `Pocket.getId()`). How do we perform queries in this case? I can suggest several approaches.

### Manual queries

The obvious solution is just writing regular [JPQL](https://thorben-janssen.com/jpql/) or SQL statements. Hibernate uses reflection and doesn’t demand public getters for fields. This may work if you start a project from scratch. But if you already relying on getters to retrieve information from the entity and put it into DTO, then transition might be overwhelming. That’s why we have a second option.

### Introducing toDto method

An entity can provide `toDto` or similar method that returns its internal representation as a separate data structure. It’s similar to [Memento design pattern](https://www.geeksforgeeks.org/memento-design-pattern/). Look at the code example below:

```java
@Entity
@NoArgsConstructor(access = PROTECTED)
@Setter(PRIVATE)
public class Pocket {
  /* other fields and methods */

  public PocketDto toDto() {
    return new PocketDto(
        id,
        name,
        tamagotchis.stream()
            .map(Tamagotchi::toDto)
            .toList()
    );
  }
}

@Entity
@NoArgsConstructor(access = PROTECTED)
@Setter(PRIVATE)
@Getter
class Tamagotchi {
  /* other fields and methods */

  public TamagotchiDto toDto() {
    return new TamagotchiDto(id, name, status);
  }
}
```

The returned DTO is an immutable object that couldn’t affect entities’ state. Besides, the approach is also helpful for unit testing. Let’s move on to this part.

## Unit testing entities

We're going to test these scenarios:

1. `Pocket` must always possess at least one `Tamagotchi`.
1. If you delete `Tamagotchi`, you can restore it by name.
1. If you delete multiple `Tamagotchi` with the same name, you can only restore the last one.

> The entire test suite is available by [this link](https://github.com/SimonHarmonicMinor/rich-domain-model-with-hibernate-example/blob/master/src/test/java/com/example/demo/domain/PocketTest.java).

### Pocket must always possess at least one Tamagotchi

Look at the unit tests below.

```java
class PocketTest {

  @Test
  void shouldCreatePocketWithTamagotchi() {
    Pocket pocket = Pocket.newPocket("My pocket");

    PocketDto dto = pocket.toDto();

    assertEquals(1, dto.tamagotchis().size());
  }

  @Test
  void shouldForbidDeletionOfASingleTamagotchi() {
    Pocket pocket = Pocket.newPocket("My pocket");
    PocketDto dto = pocket.toDto();
    UUID tamagotchiId = dto.tamagotchis().get(0).id();

    assertThrows(
        TamagotchiDeleteException.class,
        () -> pocket.deleteTamagotchi(tamagotchiId)
    );
  }
}
```

The first one checks that `Pocket` is being created with a single `Tamagotchi`. Whilst the second one validates that you cannot delete `Tamagotchi` if it’s a single one.

What I like about those tests is that they are _unit_ ones. No database, no Testcontainers, just regular JUnit and we've validated business logic successfully. Cool! Let's move forward.
 
### If you delete Tamagotchi, you can restore it by name

This one is a bit more complicated. Look at the code example below.

```java
class PocketTest {

  @Test
  void shouldDeleteTamagotchiById() {
    Pocket pocket = Pocket.newPocket("My pocket");
    UUID tamagotchiId = pocket.createTamagotchi(new TamagotchiCreateRequest("My tamagotchi", CREATED));

    pocket.deleteTamagotchi(tamagotchiId);

    PocketDto dto = pocket.toDto();
    assertThat(dto.tamagotchis())
        .noneMatch(t -> t.name().equals("My tamagotchi"));
  }

  @Test
  void shouldRestoreTamagotchiById() {
    Pocket pocket = Pocket.newPocket("My pocket");
    UUID tamagotchiId = pocket.createTamagotchi(new TamagotchiCreateRequest("My tamagotchi", CREATED));
    pocket.deleteTamagotchi(tamagotchiId);

    pocket.restoreTamagotchi("My tamagotchi");

    PocketDto dto = pocket.toDto();
    assertThat(dto.tamagotchis())
        .anyMatch(t -> t.name().equals("My tamagotchi"));
  }
}
```

The `shouldDeleteTamagotchiById` checks that deletion works as expected. The other one validates `restoreTamagotchi` method behaviour.

### If you delete multiple Tamagotchi with the same name, you can only restore the last one

This one is the most challenging. Look at the code example below.

```java
class PocketTest {

  @Test
  void shouldRestoreTheLastTamagotchi() {
    Pocket pocket = Pocket.newPocket("My pocket");
    UUID tamagotchiId = pocket.createTamagotchi(new TamagotchiCreateRequest("My tamagotchi", CREATED));
    pocket.deleteTamagotchi(tamagotchiId);
    tamagotchiId = pocket.createTamagotchi(new TamagotchiCreateRequest("My tamagotchi", PENDING));
    pocket.deleteTamagotchi(tamagotchiId);

    pocket.restoreTamagotchi("My tamagotchi");

    PocketDto dto = pocket.toDto();
    assertThat(dto.tamagotchis())
        .anyMatch(t ->
            t.name().equals("My tamagotchi")
            && t.status().equals(PENDING)
        );
  }
}
```

Here we do these steps:

1. Create `Pocket`.
1. Create `Tamagotchi` with name of `My tamagotchi` and status `CREATED`.
1. Delete `Tamagotchi`.
1. Create `Tamagotchi` with name of `My tamagotchi` and status `PENDING`.
1. Restore `Tamagotchi` by name `My tamagotchi`.
1. Verify that the last one `Tamagotchi` has been restored (with status of `PENDING`).

---

Here are tests' run result:

![Unit tests results](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aafywc3f9rprapis45e2.png)

Rich Domain Model pattern allows us to test complex business scenarios with simple unit tests. I think it’s outstanding. However, integration tests are also important because we need to store data in DB not in RAM. Let’s discuss this part of the equation.

## Integration testing

We use entities with a conjunction of repositories (Spring Data ones usually). Let’s write some use cases and test them:

1. Create `Pocket`.
1. Create `Tamagotchi`.
1. Update `Tamagotchi`.

> The entire test suite is available by [this link](https://github.com/SimonHarmonicMinor/rich-domain-model-with-hibernate-example/blob/master/src/test/java/com/example/demo/service/PocketServiceIntegrationTest.java).

### Create Pocket

Look at the service example below:

```java
@Service
@RequiredArgsConstructor
public class PocketService {

  private final EntityManager em;

  @Transactional
  public UUID createPocket(String name) {
    Pocket pocket = Pocket.newPocket(name);
    em.persist(pocket);
    return pocket.getId();
  }
}
```

Time to write some integration tests. Look at the code snippet below:

```java
@Testcontainers
@DataJpaTest
@AutoConfigureTestDatabase(replace = NONE)
@Transactional(propagation = NOT_SUPPORTED)
@Import(PocketService.class)
class PocketServiceIntegrationTest {

  @Container
  @ServiceConnection
  public static final PostgreSQLContainer<?> POSTGRES = new PostgreSQLContainer<>("postgres:13");

  @Autowired
  private TransactionTemplate transactionTemplate;
  @Autowired
  private TestEntityManager em;
  @Autowired
  private PocketService pocketService;

  @BeforeEach
  void cleanDatabase() {
    // there is cascade constraint in the database deleting tamagotchis and deleted_tamagotchis
    transactionTemplate.executeWithoutResult(
        s -> em.getEntityManager().createQuery("DELETE FROM Pocket ").executeUpdate()
    );
  }

  @Test
  void shouldCreateNewPocket() {
    UUID pocketId = pocketService.createPocket("New pocket");

    PocketDto dto = transactionTemplate.execute(
        s -> em.find(Pocket.class, pocketId).toDto()
    );
    assertEquals("New pocket", dto.name());
  }
}
```

I use Testcontainers library to start PosgtreSQL in Docker. [Flyway](https://flywaydb.org/) migration tool creates tables before tests run.

> You can check out the migrations by [this link](https://github.com/SimonHarmonicMinor/rich-domain-model-with-hibernate-example/blob/master/src/main/resources/db/migration/V1__create_tables.sql).

I guess this snippet is not that complicated. So, let's go next.

### Create Tamagotchi

Look at the code service implementation below:

```java
@Service
@RequiredArgsConstructor
public class PocketService {
  /* other fields and methods */
  
  @Transactional
  public UUID createTamagotchi(UUID pocketId, TamagotchiCreateRequest request) {
    Pocket pocket = em.find(Pocket.class, pocketId);
    return pocket.createTamagotchi(request);
  }
}
```

As you can see, the Rich Domain Model pattern demands to declare services as thin layer that are easy to understand and test. And here is the test itself:

```java
/* same Java annotations */
class PocketServiceIntegrationTest {
  /* initialization... */  

  @Test
  void shouldCreateTamagotchi() {
    UUID pocketId = pocketService.createPocket("New pocket");

    UUID tamagotchiId = pocketService.createTamagotchi(
        pocketId,
        new TamagotchiCreateRequest("my tamagotchi", CREATED)
    );

    PocketDto dto = transactionTemplate.execute(
        s -> em.find(Pocket.class, pocketId).toDto()
    );
    assertThat(dto.tamagotchis())
        .anyMatch(t ->
            t.name().equals("my tamagotchi")
                && t.status().equals(CREATED)
                && t.id().equals(tamagotchiId)
        );
  }
}
```

This one is a bit more interesting. Firstly, we create a `Pocket` and then add a `Tamogotchi` inside it. Assertions checks that expected `Tamagotchi` is present in result DTO.

### Update Tamagotchi

This one is the most intriguing. Check out the implementation below:

```java
@Service
@RequiredArgsConstructor
public class PocketService {
  /* other fields and methods */

  @Transactional
  public void updateTamagotchi(UUID tamagotchiId, TamagotchiUpdateRequest request) {
    UUID pocketId = em.createQuery(
            "SELECT t.pocket.id AS id FROM Tamagotchi t WHERE t.id = :tamagotchiId",
            UUID.class
        )
        .setParameter("tamagotchiId", tamagotchiId)
        .getSingleResult();

    Pocket pocket = em.find(Pocket.class, pocketId);
    pocket.updateTamagotchi(tamagotchiId, request);
  }
}
```

API demands to pass `tamagotchiId`. But the domain model allows us to update `Tamagotchi` only through `Pocket` because the latter is the aggregate root. So, we determine `pocketId` with additional query to DB and then select `Pocket` aggregate by its id. Test is also quite interesting:

```java
/* same Java annotations */
class PocketServiceIntegrationTest {
  /* other fields and methods */

  @Test
  void shouldUpdateTamagotchi() {
    UUID pocketId = pocketService.createPocket("New pocket");
    UUID tamagotchiId = pocketService.createTamagotchi(
        pocketId,
        new TamagotchiCreateRequest("my tamagotchi", CREATED)
    );

    pocketService.updateTamagotchi(
        tamagotchiId,
        new TamagotchiUpdateRequest("another tamagotchi", PENDING)
    );

    PocketDto dto = transactionTemplate.execute(
        s -> em.find(Pocket.class, pocketId).toDto()
    );
    assertThat(dto.tamagotchis())
        .anyMatch(t ->
            t.name().equals("another tamagotchi")
                && t.status().equals(PENDING)
                && t.id().equals(tamagotchiId)
        );
  }
}
```

The steps are:

1. Create `Pocket`.
1. Create `Tamagotchi`.
1. Update `Tamagotchi`.
1. Validate the result DTO.

---

Here is the execution result for all integration tests:

![Integration test run results](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g93luqtm229n49j5zhnb.png)

Nothing complicated, don't you think?

## Performance implications

Rich Domain Model brings overhead for sure. However, there are 
some workarounds to reach compromise.

### Optimization of queries

Firstly, let's have a look at `PocketService.updateTamagotchi` method again:

```java
@Service
@RequiredArgsConstructor
public class PocketService {
  /* other fields and methods */

  @Transactional
  public void updateTamagotchi(UUID tamagotchiId, TamagotchiUpdateRequest request) {
    UUID pocketId = em.createQuery(
            "SELECT t.pocket.id AS id FROM Tamagotchi t WHERE t.id = :tamagotchiId",
            UUID.class
        )
        .setParameter("tamagotchiId", tamagotchiId)
        .getSingleResult();
    
    Pocket pocket = em.find(Pocket.class, pocketId);
    pocket.updateTamagotchi(tamagotchiId, request);
  }
}
```

The problem is that we retrieve all existing `Tamagotchi` instances for a specified `Pocket` when we actually want to update a single one. Look at the log below:

```sql
select t1_0.pocket_id from tamagotchi t1_0 where t1_0.id=?

select p1_0.id,p1_0.name from pocket p1_0 where p1_0.id=?

select t1_0.pocket_id,t1_0.id,t1_0.name,t1_0.status
from tamagotchi t1_0 where t1_0.pocket_id=?
```

We can change queries to restrict the transmission of unnecessary data. Look at the code example below:

```java
@Service
@RequiredArgsConstructor
public class PocketService {
  /* other fields and methods */

  @Transactional
  public void updateTamagotchi(UUID tamagotchiId, TamagotchiUpdateRequest request) {
    Pocket pocket = em.createQuery(
            """
                SELECT p FROM Pocket p
                LEFT JOIN FETCH p.tamagotchis t
                WHERE t.id = :tamagotchiId
                """,
            Pocket.class
        ).setParameter("tamagotchiId", tamagotchiId)
        .getSingleResult();
    pocket.updateTamagotchi(tamagotchiId, request);
  }
}
```

Instead of selecting all existing `Tamagotchi` instances for the specified `Pocket`, we retrieve `Pocket` and the only associated `Tamagotchi` instance by specified id. Log also looks differently:

```sql
select 
p1_0.id,
p1_0.name,
t1_0.pocket_id,
t1_0.id,
t1_0.name,
t1_0.status
from pocket p1_0
left join tamagotchi t1_0 on p1_0.id=t1_0.pocket_id
where t1_0.id=?
```

Even if `Pocket` contains thousands of `Tamagotchi`, it won’t affect the performance of the application. Because it will retrieve only a single one. If you run test cases from the previous paragraph, they will also pass successfully.

### Pinpointing optimized checks

Nevertheless, the previous technique has limitations. To understand this, let's write another test. As we've already discussed, the business rule demands that each `Tamagotchi` must have a unique name within `Pocket`. Let's test this behaviour. Look at the code snippet below:

```java
@Test
void shouldUpdateTamagotchiIfThereAreMultipleOnes() {
    UUID pocketId = pocketService.createPocket("New pocket");
    UUID tamagotchiId = pocketService.createTamagotchi(
        pocketId,
        new TamagotchiCreateRequest("Cat", CREATED)
    );
    pocketService.createTamagotchi(
        pocketId,
        new TamagotchiCreateRequest("Dog", CREATED)
    );

    assertThrows(
        TamagotchiNameInvalidException.class,
        () -> pocketService.updateTamagotchi(tamagotchiId, new TamagotchiUpdateRequest("Dog", CREATED))
    );
}
```

There are two `Tamagotchi` with names of `Cat` and `Dog`. We try to rename `Cat` to `Dog`. Here, we expect to get `TamagotchiNameInvalidException`. Because business rule should validate this scenario. But if you run the test, you’ll get this result:

```
Expected com.example.demo.domain.exception.TamagotchiNameInvalidException to be thrown, but nothing was thrown.
```

Why is that? Look again at `Pocket.updateTamagotchi` method declaration:

```java
public void updateTamagotchi(UUID tamagotchiId, TamagotchiUpdateRequest request) {
    Tamagotchi tamagotchi = tamagotchiById(tamagotchiId);
    tamagotchi.changeName(request.name());
    tamagotchi.changeStatus(request.status());
    validateTamagotchiNamesUniqueness();
}

private void validateTamagotchiNamesUniqueness() {
    Set<String> names = new HashSet<>();
    for (Tamagotchi tamagotchi : tamagotchis) {
      if (!names.add(tamagotchi.getName())) {
        throw new TamagotchiNameInvalidException(
            "Tamagotchi name is not unique: " + tamagotchi.getName());
      }
    }
}
```

As you can see, `Pocket` aggregate _expects_ to have access for all `Tamagotchi` to validate the business rule. But we changed the query to select only a single `Tamagotchi` (the one we want to update). That’s why the exception is not raised. Because there is always a single `Tamagotchi` on the list and we cannot violate the uniqueness.

I see people trying to remove such validations from an aggregate. But I think you shouldn't do that. Instead, it's better to perform another optimized check in the service level in advance. To understand this approach, look at the schema below:

![Pinpointing optimized check](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sr9b70xdwrugu4kl2nqq.png)

Aggregate should always be valid. You can’t predict all likely future outcomes. Maybe you’ll call `Pocket` in another scenario. So, if you drop a check from an aggregate completely, you may accidentally violate business rule.

Nevertheless, we live in a real world where performance matters. It’s much better to execute a single `exists` SQL statement then retrieve all `Tamagotchi` instances from the database. So, you put optimized check _specifically_ where it’s needed. But you also leave the aggregate untouched.

Look at the final code snippet of `PocketService.updateTamagotchi` method:

```java
@Transactional
public void updateTamagotchi(UUID tamagotchiId, TamagotchiUpdateRequest request) {
    boolean nameIsNotUnique = em.createQuery(
            """
                SELECT COUNT(t) > 0 FROM Tamagotchi t
                WHERE t.id <> :tamagotchiId AND t.name = :newName
                """,
            boolean.class
        ).setParameter("tamagotchiId", tamagotchiId)
        .setParameter("newName", request.name())
        .getSingleResult();

    if (nameIsNotUnique) {
      throw new TamagotchiNameInvalidException("Tamagotchi name is not unique: " + request.name());
    }

    UUID pocketId = em.createQuery(
            "SELECT t.pocket.id AS id FROM Tamagotchi t WHERE t.id = :tamagotchiId",
            UUID.class
        )
        .setParameter("tamagotchiId", tamagotchiId)
        .getSingleResult();

    Pocket pocket = em.find(Pocket.class, pocketId);
    pocket.updateTamagotchi(tamagotchiId, request);
}
```

Firstly, we check that any other `Tamagotchi` (aside from the one we want to update) already has the same name. If that’s true, we throw an exception. If you run the previous test again and check log, you’ll see that only `COUNT` query has been invoked:

```sql
select count(t1_0.id)>0
from tamagotchi t1_0
where t1_0.id!=? and t1_0.name=?
```

Anyway, I don't recommend you to overuse this approach. You should treat it like an accurately pinned patch. In other words, put it only where it's needed. Otherwise, I'd prefer relying on domain logic and leave code in services as simple as possible.

## Database generated ID

Previously I’ve mentioned that I’ll show you examples of client generated ID. However, sometimes we want to use other ID types. For example, [sequence generated ones](https://www.postgresql.org/docs/current/sql-createsequence.html). Is this Rich Domain Model pattern also applicable to these ID types? It is, but there are also some concerns.

Firstly, have a look at `Pocket` and `Tamagotchi` entities using [IDENTITY generation strategy](https://www.baeldung.com/hibernate-identifiers):

```java
@Entity
@NoArgsConstructor(access = PROTECTED)
@Setter(PRIVATE)
public class Pocket {
  @Id
  @GeneratedValue(strategy = IDENTITY)
  private Long id;

  /* other fields aren't important */

  public Long createTamagotchi(TamagotchiCreateRequest request) {
    Tamagotchi newTamagotchi = Tamagotchi.newTamagotchi(request.name(), request.status(), this);
    tamagotchis.add(newTamagotchi);
    validateTamagotchiNamesUniqueness();
    // always returns null
    return newTamagotchi.getId();
  }

  /* other methods aren't important */

  public static Pocket newPocket(String name) {
    Pocket pocket = new Pocket();
    pocket.setName(name);
    pocket.createTamagotchi(new TamagotchiCreateRequest("Default", CREATED));
    return pocket;
  }
}

@Entity
@NoArgsConstructor(access = PROTECTED)
@Setter(PRIVATE)
@Getter
class Tamagotchi {
  @Id
  @GeneratedValue(strategy = IDENTITY)
  private Long id;
  
  /* other fields and methods aren't important */
  
  public static Tamagotchi newTamagotchi(String name, Status status, Pocket pocket) {
    Tamagotchi tamagotchi = new Tamagotchi();
    tamagotchi.setName(name);
    tamagotchi.setPocket(pocket);
    tamagotchi.setStatus(status);
    return tamagotchi;
  }
}
```

As you can see, we don’t assign ID directly anymore. Instead, we leave the field with `null` value and let Hibernate fill it later. Unfortunately, this decision breaks the logic of `Pocket.createTamagotchi` method. We do not set ID during the creation of `Tamagotchi` object. So, the invocation of `Tamagotchi.getId` always returns `null` (until you [flush](https://www.baeldung.com/spring-jpa-flush) changes to the database).

There are several ways to fix this issue.

### Manually fill the id

You can eliminate `@GeneratedValue` annotation usage and pass the ID value directly in the constructor. In this case, you have to invoke `SELECT nextval('mysequence')` statement and pass its result to an entity. Look at the code example below:

```java
@Entity
@NoArgsConstructor(access = PROTECTED)
@Setter(PRIVATE)
public class Pocket {
  @Id
  private Long id;

  /* other fields aren't important */

  public Long createTamagotchi(long tamagotchiId, TamagotchiCreateRequest request) {
    Tamagotchi newTamagotchi = Tamagotchi.newTamagotchi(tamagotchiId, request.name(), request.status(), this);
    tamagotchis.add(newTamagotchi);
    validateTamagotchiNamesUniqueness();
    // always returns null
    return newTamagotchi.getId();
  }

  /* other methods aren't important */

  public static Pocket newPocket(long id, String name) {
    Pocket pocket = new Pocket();
    pocket.setId(id);
    pocket.setName(name);
    pocket.createTamagotchi(new TamagotchiCreateRequest("Default", CREATED));
    return pocket;
  }
}

@Entity
@NoArgsConstructor(access = PROTECTED)
@Setter(PRIVATE)
@Getter
class Tamagotchi {
  @Id
  private Long id;
  
  /* other fields and methods aren't important */
  
  public static Tamagotchi newTamagotchi(long id, String name, Status status, Pocket pocket) {
    Tamagotchi tamagotchi = new Tamagotchi();
    tamagotchi.setId(id);
    tamagotchi.setName(name);
    tamagotchi.setPocket(pocket);
    tamagotchi.setStatus(status);
    return tamagotchi;
  }
}
```

The advantage is that your entity classes do not depend on some Hibernate magic and you can still validate business cases with regular unit tests. But you also make your code more verbose. Because you to have pass IDs manually.

Anyway, this approach is worth considering.

> I found this option in [this article](https://www.stemlaur.com/blog/2021/03/30/tech-hibern-hate/). Actually, the author demands to stop use Hibernate at all. Even though I like Hibernate, I found some arguments intriguing.

### Introducing business key

Sometimes passing IDs manually is nearly impossible. Maybe it requires too much refactoring that is unbearable. Or maybe your application works with MySQL which [doesn't support sequences but only auto increment columns](https://dev.mysql.com/doc/refman/8.0/en/example-auto-increment.html).

> Though you can emulate sequences in MySQL by creating a regular table, this approach is not well performant.

In this case, you can introduce [business key](https://www.mssqltips.com/sqlservertip/5431/surrogate-key-vs-natural-key-differences-and-when-to-use-in-sql-server/). That is a separate value that can identify an entity uniquely. Though it doesn’t mean that the business key must be globally unique. For example, if you point to `Tamagotchi` by `name` and it’s only unique within a `Pocket`, then you can identify `Tamagotchi` by a combination of `(pocket_business_key, tamagothic_name)`.

> Nevertheless, each business key should be _unmodifiable_. Otherwise, you can compromise the identity of your entities. So, pay good attention to this point.

Also, a good example of a business key is a slug. Look at the URL of this article. Do you see that it contains its name and some hash value? That is the slug. It assigns only once when the article is created but never changes (even if I change the article’s name). So, if your entities don’t have an obvious candidate for business key, introducing a slug might be an option.

## Is Rich Domain Model always worth it?

There is no ultimate decision in software development. Every approach is just a compromise. Rich Domain Model pattern is no exception.

I started my article by explaining the problems of the Anemic Domain Model to you. They all valid and make sense. But it doesn’t mean that the Rich Domain model has no disadvantages. I can think of these:

1. If you work with Hibernate, then the Rich Domain Model pattern is not so popular. It’s just the reality. There are dozens of articles on the Internet with Hibernate examples and total absence of the Rich Domain Model. People got used to the Anemic Domain Model and you have to take it into account.
1. Rich Domain Model pattern may also bring some performance penalties. Some of them can be easily fixed. But others might become a headache. If your application is supposed to be high loaded, you have to make sure that invariants’ check won’t slow the response time too much.
1. Rich Domain Model usage often leads to [god object entities](https://en.wikipedia.org/wiki/God_object). Of course, it makes maintenance harder. There are ways to fix that. For example, Vaughn Vernon [wrote 3 articles about effective aggregate design](https://www.dddcommunity.org/library/vernon_2011/). However, if your entity is already a god object, it’s will be tough to refactor it.

## Conclusion

In the end, I can say that I think that the Rich Domain Model acts better than the Anemic one. But don’t apply it blindly. You should also consider possible consequences and make decisions wisely.

Thank you very much for reading this long piece. I hope you've learnt something new. If you found it interesting, please share it with your friends and colleagues, press the like button, and leave your comments down below. I'll be glad to hear your opinions and discuss questions. Have a nice day!

## Resources

1. [Hibernate](https://hibernate.org/)
1. [Stop Using JPA/Hibernate](https://www.stemlaur.com/blog/2021/03/30/tech-hibern-hate/)
1. [Anemic Domain Model pattern](https://martinfowler.com/bliki/AnemicDomainModel.html)
1. [Status quo](https://en.wikipedia.org/wiki/Status_quo#:~:text=Status%20quo%20is%20a%20Latin,religious%2C%20scientific%20or%20military%20issues.)
1. [Rich Domain Model pattern](https://www.baeldung.com/java-anemic-vs-rich-domain-objects) 
1. [The entire repository with code examples](https://github.com/SimonHarmonicMinor/rich-domain-model-with-hibernate-example)
1. [Open-Closed principle](https://en.wikipedia.org/wiki/Open%E2%80%93closed_principle)
1. [Validation VS invariants](https://khorikov.org/posts/2022-06-06-validation-vs-invariants/)
1. [Saga pattern](https://microservices.io/patterns/data/saga.html)
1. [Encapsulation (computer programming)](https://en.wikipedia.org/wiki/Encapsulation_(computer_programming))
1. [Lombok library](https://projectlombok.org/)
1. [@Transactional annotation](https://www.baeldung.com/transaction-configuration-with-jpa-and-spring)
1. [Spaghetti code](https://en.wikipedia.org/wiki/Spaghetti_code#:~:text=Spaghetti%20code%20is%20a%20pejorative,with%20insufficient%20ability%20or%20experience.)
1. [Mockito library](https://site.mockito.org/)
1. [Tescontainers library](https://testcontainers.com/)
1. [Flaky tests](https://www.datadoghq.com/knowledge-center/flaky-tests/#:~:text=A%20flaky%20test%20is%20a,with%20each%20individual%20test%20run.)
1. [My article about Unit Testing](https://semaphoreci.com/blog/unit-testing)
1. [Tactical DDD](https://learn.microsoft.com/en-us/azure/architecture/microservices/model/tactical-ddd)
1. [DDD Aggregate](https://martinfowler.com/bliki/DDD_Aggregate.html)
1. [Why does JPA require No-Args Constructor for Domain Objects](https://stackoverflow.com/questions/2808747/why-does-jpa-require-a-no-arg-constructor-for-domain-objects)
1. [What does it mean to write a buggy code](https://www.quora.com/What-does-it-mean-to-write-a-buggy-code)
1. [Are soft deletes a good idea?](https://stackoverflow.com/questions/2549839/are-soft-deletes-a-good-idea)
1. [PostgreSQL, Datatype JSON](https://www.postgresql.org/docs/current/datatype-json.html)
1. [JPQL](https://thorben-janssen.com/jpql/)
1. [Memento design pattern](https://www.geeksforgeeks.org/memento-design-pattern/)
1. [Flyway migration tool](https://flywaydb.org/)
1. [PostgreSQL, create sequences](https://www.postgresql.org/docs/current/sql-createsequence.html)
1. [Hibernate, IDENTITY generation strategy](https://www.baeldung.com/hibernate-identifiers)
1. [Auto increment columns in MySQL](https://dev.mysql.com/doc/refman/8.0/en/example-auto-increment.html)
1. [Surrogate key VS natural key differences](https://www.mssqltips.com/sqlservertip/5431/surrogate-key-vs-natural-key-differences-and-when-to-use-in-sql-server/)
1. [God object](https://en.wikipedia.org/wiki/God_object)
1. [Effective aggregate design by Vaughn Vernon](https://www.dddcommunity.org/library/vernon_2011/)
1. [JPA flush](https://www.baeldung.com/spring-jpa-flush)