Domain Driven Design under Spring Data JPA [Design]

Introduction #

Domain driven design is a rather complex subject matter as it requires a radical change in the way of thinking and approaching things.

The more a developer is engaged in an object oriented language development the better it is to absorb and implement concepts using this method. But it is not a necessary or even a sufficient precondition!

The idea is somehow related to the way ideas and concepts get translated into a working code base.

Requirements and domain scope #

Think of a simple vending machine. There are unlimited ways of describing it and modeling it. Based on the one’s needs one may focus on the products that the machine can serve or one may be interested on the internals and electric wiring of it.

Depending on the need one can describe a situation in a way that best fits these needs. The better this description serves the need the better is the description itself.

That is one of the main goals of the domain driven development and through it getting a working/simple implementation.

Simple here should not be understood in a negative way. Simplicity brings ease of understanding, more focus, less cognitive load and a supple implementation that is easy to maintain.

One other important aspect is actually focus: a working description/model of the real life situation should focus on the important things. “Important” goes back again to the stated needs: We focus on the important concepts that best serve the needs.

Let’s take a simple example:

Bank accounts.

A bank account “belongs” to a customer of the bank. A customer may withdraw money, transfer money to some other account or get money from somewhere. A bank may force the closure of a bank account. The customer may also request the opening of more accounts of other types.

This is the domain of a bank account. is all of this interesting to us? It depends on what is “Interesting”!

If my focus is only on account management stuff then I would be better off ignoring many “irrelevant” details to transactions and account entries.

Does the account really belong to the customer? It again depends on the stated need.

Once a need is stated an answer may be given to these questions.

Now I will define the scope of my future hypothetical application. This will be THE NEED:

I want an application:

  • through which my users can manage their different banks accounts: They can add/delete and view details of their accounts.
  • that enables my users to manage multiple accounts from different banks.
  • through which my users can request a new bank account.

This simple requirement can be represented in multiple ways.

One should not make the mistake of looking at the requirements from a bank point of view. The application is not a bank!

We just want to make it possible for our users to manage their different accounts while explicitly ignoring the transversal requirements regarding authentication/authorization and other otherwise important topics.

We do not even need to care about the applicability of these requirements!

A model #

One should understand that this is just a model and that other different descriptions may well serve the needs.

First the need/s again:

  • My users can manage their different bank accounts through my platform: They can add/delete and view details of their accounts.
  • A user may have multiple accounts from different banks.
  • A user can request a new bank account.

The goal would be now to make these “things” and the relationships between them into a “model”. We will use the object oriented paradigm and describe it using UML.

If we follow the traditional way of mapping sentence subjects to entities/object we can already see:

stateDiagram-v2 User Bank Account

Taking into account the verbs in the requirement sentences one can already start “wiring” these objects together:

  • The user manages its accounts
  • The user requests an account opening from its bank

stateDiagram-v2 User: User note right of User User manages its accounts and uses its bank for that purpose end note Bank: Bank note left of Bank The bank should be rather seen as a proxy here end note User --> Account Account --> Bank User --> Bank

One can see that the bank is merely aware of its customers or their accounts as it usually is in reality.

Aggregates and entities #

Let’s discuss a bit the model above:

What do we want? We want our users to manage their accounts through our platform.

Just from this requirement repetition we can see that the main entity in our design should be the user and its accounts. The bank plays almost a secondary role for the satisfaction of the needs.

In real life all interactions with the account go through the bank. The picture would look more like this:

block-beta block Bank block Customer block Accounts end end end

But we can see that the requirements do not explicitly need that. Hence, a bank which is not aware of its accounts!

So the entities of the domain may be explicitly split into 2 groups:

block-beta block Customer block Accounts end end

block-beta block Bank end

These are the aggregates of our hypothetical platform. The first one has the aggregate root User while the second one has the aggregate root Bank.

What does it mean? Accounts must be managed solely through the user entity. No direct interaction with the accounts should happen without the knowledge of the user.

There is a very subtle detail that we should not omit: The accounts in our case represent more the local entities the user manages through our platform than the bank accounts themselves: They are rather views of the remote bank accounts.

In order to stress on that the name will be changed to AccountDetails.

classDiagram User "1" *-- "*" AccountDetails : manages User "" --> Bank : requests

Operations #

Our platform should expose the following operations:

  • add_account: the user adds its bank details to the platform.
  • delete_account: delete account details from the platform. This does not imply closing the bank account!
  • view_account: view the account details
  • open_account: request the bank account opening. We assume here an instantaneous positive response from the bank.

--- title: add_account --- sequenceDiagram *->>User: add_account note left of User: add_account User-->>Bank: get_account_details Bank-)User: account_details User-->>AccountDetail: new