Safeguarding against concurrent modifications - fieldenms/tg GitHub Wiki

Overview

This wiki does not apply to concurrent modifications of any single entity. This aspect is already well handled by the TG platform automatically through versioning and the conflict resolution mechanism.

This wiki covers one-2-many entity relationships, and specifically the concurrent modifications of the “many” side of the relationship which affect either the “one” side or each other.

Potentially vulnerable entities

Entities, which contain neither a Date property nor an auto-generated number, GUID or something similar as one of their key members, are typically not vulnerable to concurrent modifications.

Examples of such entities:

  • PmXref or PmTimeXref

  • InventorySupplier

  • WorkActivityExpendable

In all these cases, concurrency issues are prevented by the model, specifically — by the business key.

The need for safeguarding against concurrent modifications, therefore, should be carefully considered for entities which include a Date property, a User property, or other automatically assigned value as one of their key members.

Examples of such entities:

  • InventoryIssue -- transactionDate is auto-assigned

  • <R extends AbstractReceipt<R, ?>>

  • PositionAllocation

  • LocationSubsystemTimeline

Safeguarding mechanism

If the “one” side has a persistent property that represents some kind of aggregation of the “many” side, it can be used for synchronisation of the “many” side. To activate such synchronisation, it is necessary to turn the automatic conflict resolution off for such persistent property by setting the autoConflictResolution parameter to false for the @MapTo annotation:

public class WorkActivity ...

    ...

    @MapTo(autoConflictResolution = false)
    private BigDecimal actualDuration;

It is also important that the instance of the “one” side, which needs to be modified as the result of saving the “many” side, must be retrieved before saving the “many” side:

public WorkActivityRepairPeriod save(final WorkActivityRepairPeriod repairPeriod) {

        ... // misc validation and processing

        // To safeguard against concurrent modifications, it is necessary to re-fetch an instrumented instance of master WorkActivity first, before saving this instance of repairPeriod. 
        // This instrumented instance will then be passed into recalcWaDuration() to be updated.
        final WorkActivity wa$ = co$(WorkActivity.class).findByEntityAndFetch(WorkActivityCo.FETCH_MODEL_FOR_EDITING, repairPeriod.getWorkActivity());

        final var savedPeriod = super.save(repairPeriod);

        recalcWaActualDuration(wa$); // Recalculates and updates WorkActivity.actualDuration

        return savedPeriod;
}

For the complete implementation, please refer to RMS Issue-#599 if it still exists, otherwise develop.

If the “one” side has a property that represents some kind of aggregation of the “many” side, but it is a @Calculated property, it will need to be converted into a persistent property, similar to WorkActivity.actualDuration illustrated above. Care should be taken to recalculate the value both upon save() and batchDelete(). Care should be taken to make sure that all overridden batchDelete* methods are adjusted to perform recalculation in each.

In a rare case where the “one” side does not already have a property that represents some kind of aggregation of the “many” side, it will need a separate property added just for synchronisation purposes. This property could be a simple Integer version counter:

    @MapTo(autoConflictResolution = false)
    private Integer manySideVersion;

It will only need to be updated on save of the “many” side, not the deletion.

In the unlikely extreme case where many such helper properties are required, a separate one-2-one synchronisation entity WorkActivitySync should be introduced to contain all these helper properties and thus avoid polluting the “one” side entity.

⚠️ **GitHub.com Fallback** ⚠️