how to reference foreign key in Entity when separating domain model from persistence model

171 views Asked by At

I am trying out a hexagonal architecture project and separating domain model from persistence model. But I am struggling in the case when I have to reference the foreign key of another entity in the persistence model.

I have a Customer entity that looks like this and its persistence model counterpart

class Customer(val customerId: String, val name: String) {
}

@Entity(name = "customer")
data class CustomerEntity(
    var customerId: String,
    var name: String,
    @Id @GeneratedValue(strategy = GenerationType.AUTO) var id: Long = -1
)

the customerId is supposed to be the business id and the entity counterpart has additional its own database id.

Now I have another another entity Report that references the customer

class Report(val reportId: String, val date: LocalDate, val customerId: String) {
}

@Entity
data class ReportEntity(val reportId: String,
                             val date: LocalDate,
                             val customerId: String, // what should be saved here? the persistence Id (Long) of the customer or its business ID (String)?
                             @Id @GeneratedValue(strategy = GenerationType.AUTO)
                             var id: Long = -1)

If I go with saving the persistence ID of the customer, then I would have to first fetch it from the database, which means I need to query the Customer table from within the Report component. But these two are two separate bounded contexts and I am not sure if this is the right solution or there is a modeling problem here.

2

There are 2 answers

3
R.Abbasi On BEST ANSWER

I think if you don't have performance issues, you can get rid of the generated ID and use the CustomerId only.

But if you insist on having a separate ID, you will want to have the generated ID as the reference key because it's better for the performance again. On the other hand, you should consider use cases that query the Reports. Are they filtering Reports by CustomerId?

If the answer is "No", you won't have any issues because the use cases don't need the CustomerId.

But, if the answer is "Yes", then you have three options:

  1. you can bring both IDs to the Report model. In this way, you have all the information that you need on the Report model. But be aware that the Information must be unmodifiable (like these IDs because they are not going to be updated). Because any update on this information will break your data.

  2. you can duplicate the information of the Customer that the Report needs to know about. You must duplicate and manage it via integration events (CRUD events).

  3. Pull data from the Customer context. In this way, you don't need to duplicate data but you will depend on Customer context.

On the first two options, you won't depend on Customer context to pull data from but they are harder to apply, especially for the second option.

The third option is easier to apply but at the cost of coupling.

1
ArwynFr On

If I go with saving the persistence ID of the customer, then I would have to first fetch it from the database, which means I need to query the Customer table from within the Report component. But these two are two separate bounded contexts and I am not sure if this is the right solution or there is a modeling problem here.

If your two bounded context share the same persistance layer, then it is ok for your persistence layer to query the Customer table to resolve the primary key from the natural key.

If your two bounded context have separate persistance layers, then you need to maintain a copy of the customers information in the persistance layer of the report context. Let's call them Customer.Customer (source of truth in the customer context) and Report.Customer (copy in the report context). These models can have different structures, this is called a polysemic domain model.

The customers are expected to have a different primary key in both persistance layer. When persisting the report, your persistance layer can query the Report.Customer table to resolve the primary key from the natural key. You will also need some mechanism to synchronize data from the source of truth to the copy. Do not do the copy at the persistence level (database-to-database) or you will be creating some dependency at the infrastructure level. Prefer to read data from the interface of the Customer service because it is more stable and usually versioned.