Data Synchronisation
Introduction
Information for an event must reside in multiple locations. The primary location is always the Event & Membership Management System (EMS). It is the master for all data.
At some point the data must be shared with:
-
Registration platforms (e.g., RunSignup)
-
Timing systems (e.g., TagServer)
-
Supporting devices on event day
Information must flow in both directions, because data could be changed on any system. This documentation describes the design to track what has changed and when to sync data.
Sync Targets
The framework supports multiple sync targets, each with different characteristics:
| Target | Direction | Purpose |
|---|---|---|
TagServer |
Outbound only |
In-house timing system - receives event, race, participant, and tag data |
Mautic |
Outbound only |
Marketing automation - receives contact and event data |
MailChimp |
Outbound only |
Email marketing - receives contact and list data |
RunSignup |
Bi-directional |
Registration platform - syncs participant registrations both ways |
ChatWoot |
Bi-directional |
Customer support - syncs contact interactions both ways |
Conflict Resolution
EMS always wins. When conflicts occur (both systems modified the same entity):
-
For outbound-only targets: No conflict possible - EMS data simply overwrites remote
-
For bi-directional targets: EMS data takes precedence, based on timestamp comparison
This simplifies the design significantly - EMS is the master system, and remote systems receive authoritative data from EMS.
Entity-Target Mapping
Different targets sync different entity subsets:
| Target | Entities |
|---|---|
TagServer |
Event, Person, Organisation, Race, StartGroup, StartGroupParticipant, EventParticipant, Tag |
RunSignup |
Event, Race, EventParticipant, Person (to be confirmed) |
Mautic/MailChimp |
Person, Contact preferences (to be confirmed) |
ChatWoot |
Person, Conversation history (to be confirmed) |
Key Concepts
The synchronisation framework is built around four key concepts:
Entity to be Synced
A JPA entity which should be synchronised, in part or in full, with a remote system. At minimum the entity requires:
-
A primary key (typically
Long) -
A
modifiedOnfield (part of the Auditable pattern)
The modifiedOn field must be updated whenever the entity changes. This is critical for change detection.
|
Remote Entity
The corresponding record in the remote system. The remote entity:
-
Tracks a copy of the main entity’s data
-
Can also be updated, resulting in its state being synchronised back to the main entity
-
Must track its own
modifiedOnfield -
Needs a notification or query mechanism to return records changed after the last sync
Sync Record
Since there can be many synchronisation processes attached to an entity (one per sync target), a SyncRecord maintains a ManyToOne JPA relationship with the entity. This is a unidirectional association from the SyncRecord side only.
| Field | Description |
|---|---|
|
Identifies the process managing this sync (e.g., TAGSERVER, RUNSIGNUP) |
|
Foreign key reference to the main entity |
|
Timestamp when the entity was last synchronised TO the remote system |
|
Timestamp when the remote system record was last synchronised back to the main entity |
|
Optional reference to the remote entity’s unique identifier |
An indirect relationship exists between the SyncRecord and the RemoteEntity via a reference. The RemoteEntity will either reference the Entity ID (if it has provision to do so), or the SyncRecord stores a unique key of the RemoteEntity.
Change Detection
The two syncedOn properties allow the synchronisation process to identify when the associated entity was last synchronised in each direction.
When entitySyncedOn < Entity.modifiedOn
→ Entity is due to be synchronised to the remote system
When remoteSyncedOn < Remote.modifiedOn
→ Remote system record must be synchronised back to the Entity
Content Hash
To detect whether a change is a bounce-back (our own change echoing back) versus an actual remote change, the framework uses a content hash:
-
Load the entity from DB
-
Calculate the hash on that entity (from a method within the SyncProcess)
-
Apply the changes from the RemoteEntity via a mapping function
-
Calculate the hash again on the updated entity
-
Compare the two hash values to determine change
If the hash differs, the content is copied over and persisted.
Synchronisation Flows
Outbound Sync - New Entity Records
When the sync process is first initiated, there will be no SyncRecords attached to the Entities for the given SyncProcess. A filter mechanism determines which entities should be synchronised.
-
Apply filter to determine eligible entities
-
Prepare and transmit data to remote system
-
Create SyncRecord with:
-
Reference to Entity and RemoteEntity
-
entitySyncedOnset to the Entity’smodifiedOnvalue
-
This ensures that any subsequent change to the Entity can be detected later.
Outbound Sync - Updated Entity Record
When an Entity changes, the modifiedOn field changes. This allows a query to find entities where Entity.modifiedOn > SyncRecord.entitySyncedOn.
SELECT e.id as IDToSync
FROM entity e
LEFT JOIN sync_entity sr ON e.id = sr.entity_id
WHERE <FILTER ON e>
AND (sr.id IS NULL OR e.modified_on > sr.entitySyncOn);
The Entity is transmitted to the remote system and entitySyncedOn is set to the Entity’s modifiedOn value.
Inbound Sync - Bounce-back Detection
Each time an outbound sync is triggered, it could result in the remote system subsequently indicating that a change has been made. It is important to determine if such events are actually due to a change in the remote system.
This is done by comparing the content hash of the Entity and RemoteEntity data elements:
-
Retrieve the RemoteEntity
-
Calculate hash on its data (excluding
modifiedOn) -
Compare with corresponding fields in the Entity
-
If hash differs, copy content and persist
-
Set
entitySyncedOnto the Entity’smodifiedOnvalue -
Set
remoteSyncedOnto the RemoteEntity’smodifiedOn(or polling timestamp)
Inbound Sync - Polling for Remote Changes
For supporting remote systems, the sync process polls to determine any changes:
-
Use the latest
SyncRecord.remoteSyncedOnas a time reference -
Find all remote records changed after that time
-
For each record, calculate and compare content hash
-
If hash differs, apply changes to the local Entity
-
Update both
entitySyncedOnandremoteSyncedOntimestamps
Inbound Sync - Unchanged Fields
There could be instances where the remote entity was changed, but not on a field that has to be synced back. In this case, the process works the same as bounce-back detection - the hash comparison will show no difference, and only the remoteSyncedOn timestamp is updated.
Current Implementation
The current implementation targets the TagServer as the first sync target. See TagServer Integration for detailed implementation documentation.
Framework Architecture
| Class | Purpose |
|---|---|
|
Abstract base class providing sync orchestration (admin-service) |
|
Abstract base class for sync record entities (database) |
|
Base repository with common sync queries (database) |
|
Scheduler coordinating all sync services |
Implementation Status
| Feature | Status | Notes |
|---|---|---|
Outbound sync (main → remote) |
Implemented |
Full CREATE, UPDATE, DELETE support |
Inbound sync (remote → main) |
Stub only |
All |
Deletion handling |
Implemented |
Orphaned sync records cleaned up |
Hash-based change detection |
Implemented |
Compares local vs remote hash |
Per-entity transactions |
Implemented |
Prevents batch rollback on single failure |
Scheduled sync |
Implemented |
Cron-based, default every minute |
Testing Considerations
| Ensuring the hash function works reliably is critical. If the same data is not correctly detected as being similar, it will result in an endless loop of updates. |
Design comprehensive tests around:
-
Content hash checking for each entity type
-
All sync flows (outbound new, outbound update, inbound bounce-back, inbound change)
-
Edge cases (null fields, empty strings, timezone handling)
Target-Specific Documentation
Outbound-Only Targets
-
TagServer Integration - In-house timing system sync
-
MailChimp Integration - Future
-
Mautic Integration - Future