Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions java/change-tracking.md
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,71 @@ public class ChangeTrackingHandler implements EventHandler {

You can query the change log entries via CQN statements, as usual.

## Tips and Tricks

### Advanced Identifiers for Associated Entities

By default, the identifier is read from the association when the feature captures images of the data.
Additional joins might be expensive or impossible under some circumstances.

:::warning Configuration change required!
Enable [optimization for path expressions](/releases/2025/aug25#optimized-path-expressions).
:::

Let's take the following model as an example:

```cds
entity User {
key ID : UUID;
...
}

entity Entity {
key ID : UUID;
user : Association to User;
}
```

Let's assume that `User` is impossible to join with the standard identifier or requires an identifier depending on the context of the user who reads the change log.

You model the association like this:

```cds
entity Entity {
key ID : UUID;
user : Association to User @changelog: [user.ID]; // [!code focus]
}
Comment on lines +469 to +472
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm trying to understand this example and what it does. Let me try and make it a bookshop example to show how I'd understand it right now:

User = User
Entity = Orders

So if I'm logged in and for some reason have the authorization to read all orders, I'd nevertheless only see my orders when reading the Orders entity due to this modelling (and custom code). Is that correct?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is simpler: you have an entity that is somehow is expensive to join. E.g. it is external or federated. Should be typical for users or so we think.

You can avoid join by using PK as an identifier (or combination of keys when the entity has more than one) and later fill the value on read when needed.

```

The change log will contain one entry for the field `user` just like the other associations with [human-readable values](#human-readable-values-for-associations), but the identifier will be its primary key.

You need a custom handler to fetch your own custom identifier.

Here's the sketch for the handler to adapt the change log after read:

```java
@Component
@ServiceName(CatalogService_.CDS_NAME)
class ChangeLogHandler implements EventHandler {

@After(event = CqnService.EVENT_READ)
//NB: Handler is executed before the standard conversion of changelog
@HandlerOrder(HandlerOrder.EARLY + HandlerOrder.EARLY)
void enhance(List<EntityChanges> result) {
result.stream().map(l -> l.getChange()).forEach(change -> {
if (change.getAttribute().equals(Entity.USER)) {
// Use either path or existing values to find out the primary key of the user
// and do your own conversion.
change.setValueChangedFrom("...");
change.setValueChangedTo("...");
}
});
}
}
```

Always consider performance implications with cases like this. The sequential read always needs proper batching and caching, otherwise you lose the performance advantage.

## Things to Consider when Using Change Tracking

- Consider the storage costs of the change log. The change log can grow very fast and can consume a lot of space
Expand Down
Loading