1

In Spring Data JDBC if an entity (e.g. Customer) has a value (e.g. Address) like in the example here the value has a back reference column (column customer in table address) to the entity in the db schema:

CREATE TABLE "customer" (
  "id"                  BIGSERIAL       NOT NULL,
  "name"                VARCHAR(255)    NOT NULL,
  PRIMARY KEY (id)
);

CREATE TABLE "address" (
  "customer"            BIGINT,
  "city"                VARCHAR(255)    NOT NULL
);

The problem with this is that if you use that Address value more than once in one entity or even in different entities you have to define an extra column for each usage. Only the primary id of the entity is stored in these columns and otherwise there is no way to distinguish from which entity it is. In my actual implementation I have five of these columns for the Address value:

  "order_address"            BIGINT,    -- backreference for orderAddress to customer id
  "service_address"          BIGINT,    -- backreference for serviceAddress to customer id
  "delivery_address"         BIGINT,    -- backreference for deliveryAddress to customer id
  "installation_address"     BIGINT,    -- backreference for installationAddress to provider_change id
  "account_address"          BIGINT,    -- backreference for accountAddress to payment id

I understand how it works, but I don't understand the idea behind this back reference implementation. So can someone please shed some light on that issue? Thanks!

Jens Schauder
  • 77,657
  • 34
  • 181
  • 348
Stefan
  • 67
  • 6

1 Answers1

3

As to most good questions there are many sides to the answer.

The historical/symmetry answer

When it comes to references between entities Spring Data JDBC supports 1:1 (the one you ask about) and 1:N (lists, sets and maps). For the latter anything but a back-reference is just weird/wrong. And with using a back-reference for 1:1 becomes basically the same, simplifying the code, which is a good thing.

The DML process answer

With the back-reference, the process of inserting and deleting becomes much easier: Insert the aggregate root (customer in your example) first, then all the referenced entities. And it continues to work if those entities have further entities. Deletes work the other way round but are equally straight forward.

The dependency answer

Referenced entities in an aggregate can only exist as part of that aggregate. In that sense they depend on the aggregate root. Without that aggregate root there is no inner entity, while the aggregate root very often might just as well exist without the inner entity. It therefore makes sense, that the inner entity carries the reference.

The ID answer

With this design, the inner entity doesn't even need an id. It's identity is perfectly given by the identity of the aggregate root and in case of multiple one-to-one relationships to the same entity class, the back-reference column used.

Alternatives

All the reasons are more or less based on a single one-to-one relationship. I certainly agree that it looks a little weird for two such relationships to the same class and with 5 as in your example it becomes ridiculous. In such cases you might want to look in alternatives:

Use a map

Instead of modelling your Customer class like this:

class Customer {
  @Id
  Long id;
  String name;
  Address orderAddress
  Address serviceAddress
  Address deliveryAddress
  Address installationAddress
  Address accountAddress
}

Use a map like this

class Customer {
  @Id
  Long id;
  String name;
  Map<String,Address> addresses
}

Which would result in an address table like so

CREATE TABLE "address" (
  "customer"            BIGINT,
  "customer_key"        VARCHAR(20).    NOT NULL,
  "city"                VARCHAR(255)    NOT NULL
);

You may control the column names with a @MappedCollection annotation and you may add transient getter and setter for individual addresses if you want.

Make it a true value

You refer to Address as a value while I referred to it as an entity. If it should be considered a value I think you should map it as an embedded like so

class Customer {
  @Id
  Long id;
  String name;
  @Embedded(onEmpty = USE_NULL, prefix="order_")
  Address orderAddress
  @Embedded(onEmpty = USE_NULL, prefix="service_")
  Address serviceAddress
  @Embedded(onEmpty = USE_NULL, prefix="delivery_")
  Address deliveryAddress
  @Embedded(onEmpty = USE_NULL, prefix="installation_")
  Address installationAddress
  @Embedded(onEmpty = USE_NULL, prefix="account_")
  Address accountAddress
}

This would make the address table superfluous since the data would be folded into the customer table:

CREATE TABLE "customer" (
  "id"                  BIGSERIAL       NOT NULL,
  "name"                VARCHAR(255)    NOT NULL,
  "order_city"          VARCHAR(255)    NOT NULL,
  "service_city"        VARCHAR(255)    NOT NULL,
  "deliver_city"        VARCHAR(255)    NOT NULL,
  "installation_city"   VARCHAR(255)    NOT NULL,
  "account_city"        VARCHAR(255)    NOT NULL,
  PRIMARY KEY (id)
);

Or is it an aggregate?

But maybe you need addresses on their own, not as part of a customer. If that is the case an address is its own aggregate. And references between aggregates should be modelled as ids or AggregateReference. This is described in more detail in Spring Data JDBC, References, and Aggregates

Jens Schauder
  • 77,657
  • 34
  • 181
  • 348
  • Thank you for the detailed clarification - always nice to have some background information! Actually I was first using a *Map addresses*. But I ended up in one entity using three *Address* values and two other entities using each one *Address* value. What I don't understand...why do you think that in my example the *Address* is not a true *VALUE* in a *DDD* context, but rather an *ENTITY*? It doesn't have its own identity. – Stefan Jun 22 '21 at 20:28
  • Too many cities in your ```customer``` table. – Stefan Jun 23 '21 at 06:05
  • Regarding entity vs value. It kind of depends on where you look. In the database it kind of does have an id. – Jens Schauder Jun 23 '21 at 06:37