TagServer Integration
Overview
TagServer is an in-house developed timing system used for race events. The EMS synchronises event, participant, and timing data to TagServer to enable:
-
Timing point configuration
-
Participant lookup by bib number or RFID tag
-
Real-time results capture
-
Start group management
Entities Synced to TagServer
The following 8 entity types are synchronised to TagServer:
| Main Entity | Sync Entity | Purpose |
|---|---|---|
|
|
Event details (name, dates) |
|
|
Participant personal information |
|
|
Organiser information |
|
|
Race configuration within an event |
|
|
Start wave/group definitions |
|
|
Participant-to-start-group assignments |
|
|
Event entry details (bib, payment status) |
|
|
RFID tag to participant mapping |
Architecture
Package Structure
admin-service/src/main/java/za/co/idealogic/event/admin/service/sync/tagserver/
├── BaseSyncService.java # Generic sync orchestration
├── SyncController.java # Scheduler and coordinator
├── ApiApiImpl.java # REST API client (RestTemplate)
├── ApiConfig.java # API client configuration
├── AuthRemoteLoginService.java # Authentication handling
├── AuthTokenInterceptor.java # JWT token injection
│
├── EventSyncService.java # Event sync implementation
├── PersonSyncService.java # Person sync implementation
├── OrganisationSyncService.java # Organisation sync implementation
├── RaceSyncService.java # Race sync implementation
├── StartGroupSyncService.java # StartGroup sync implementation
├── StartGroupParticipantSyncService.java
├── EventParticipantSyncService.java
└── BibTagMappingSyncService.java
database/src/main/java/za/co/idealogic/event/
├── domain/
│ ├── BaseSyncEntity.java # Abstract sync entity base
│ ├── EventSyncTagServer.java
│ ├── PersonSyncTagServer.java
│ ├── OrganisationSyncTagServer.java
│ ├── RaceSyncTagServer.java
│ ├── StartGroupSyncTagServer.java
│ ├── StartGroupParticipantSyncTagServer.java
│ ├── EventParticipantSyncTagServer.java
│ └── BibTagMappingSyncTagServer.java
│
└── repository/
├── BaseSyncEntityRepository.java
├── EventSyncTagServerRepository.java
├── PersonSyncTagServerRepository.java
└── ... (one per entity)
Class Relationships
┌─────────────────────────────┐
│ SyncController │ Coordinates all sync services
│ - scheduledSync() │ Runs on cron schedule
│ - addService() │
└──────────┬──────────────────┘
│ manages
▼
┌─────────────────────────────┐
│ BaseSyncService<E, S> │ Generic sync orchestration
│ - syncToRemote() │
│ - syncToMain() │ ← Not implemented
│ - handleDeletions() │
│ - processEntity() │
└──────────┬──────────────────┘
│ extends
▼
┌─────────────────────────────┐
│ EventSyncService │ Entity-specific implementation
│ - createInRemote() │
│ - updateInRemote() │
│ - deleteFromRemote() │
│ - convertToDTO() │
│ - calculateRemoteHash() │
└─────────────────────────────┘
BaseSyncEntity
The base class for all sync entities:
@MappedSuperclass
public abstract class BaseSyncEntity<E> {
private Instant lastModifiedMain; // When synced TO remote
private Instant lastModifiedRemote; // When synced FROM remote
public abstract E getMainEntity();
public abstract void setMainEntity(E entity);
public abstract int getDataHash(E entity);
}
The current implementation uses lastModifiedMain and lastModifiedRemote instead of entitySyncedOn and remoteSyncedOn from the spec.
|
Hash Calculation
Each sync entity implements getDataHash() to calculate a content hash for change detection.
Event
Objects.hash(
entity.getId(),
entity.getName(),
entity.getStartDateTime(),
entity.getEndDateTime()
)
Person
PersonWrapper wrapper = new PersonWrapper(entity);
Objects.hash(
wrapper.getFirstName(),
wrapper.getLastName(),
wrapper.getContactNumber(),
wrapper.getEmail()
)
API Client
The current implementation uses RestTemplate via ApiApiImpl:
| Operation | Method |
|---|---|
Create Event |
|
Update Event |
|
Get Event |
|
Delete Event |
|
Similar patterns for other entities |
Sync Service Implementation
Each entity has a corresponding sync service extending BaseSyncService:
@Service
public class EventSyncService extends BaseSyncService<Event, EventSyncTagServer> {
@Override
protected void createInRemote(Event entity, EventSyncTagServer syncEntity) {
EventDTOApiDTO dto = (EventDTOApiDTO) convertToDTO(entity);
String xOrgId = getOrgId(entity);
apiClient.createEvent(dto, xOrgId);
}
@Override
protected void updateInRemote(Event entity, EventSyncTagServer syncEntity) {
EventDTOApiDTO dto = (EventDTOApiDTO) convertToDTO(entity);
apiClient.partialUpdateEvent(entity.getId(), dto, getOrgId(entity));
}
@Override
protected void deleteFromRemote(EventSyncTagServer syncEntity) {
Event entity = syncEntity.getMainEntity();
apiClient.deleteEvent(entity.getId(), getOrgId(entity));
}
@Override
public void syncToMain() {
log.info("Remote changes to main not implemented yet for events");
}
}
Scheduling
The SyncController manages scheduling:
@Scheduled(cron = "${sync.cron.expression:0 */1 * * * *}")
public void scheduledSync() {
syncAllToRemote();
// syncAllToMain(); // Currently disabled
}
Default: Every minute (0 */1 * * * *)
Implementation Status
| Feature | Status | Details |
|---|---|---|
Outbound sync |
Implemented |
CREATE, UPDATE, DELETE for all 8 entities |
Inbound sync |
Not implemented |
All |
Deletion detection |
Implemented |
Orphaned sync records detected and remote entities deleted |
Hash comparison |
Implemented |
Local vs remote hash comparison determines UPDATE vs SKIP |
Per-entity transactions |
Implemented |
|
Error handling |
Basic |
Exceptions logged but no retry mechanism |
Authentication |
Implemented |
JWT token-based with interceptor |
Known Issues and Gaps
Critical
-
No inbound sync - All
syncToMain()methods are stubs. Changes made in TagServer are not synchronised back to EMS. -
Missing
remoteIdfield - Cannot track TagServer’s entity IDs. Currently assumes IDs are identical on both systems. -
Hardcoded credentials - Username, password, and base URL are in source code.
Important
-
No retry mechanism - Failed syncs are logged but not retried. Network issues can cause permanent desynchronisation.
-
No bounce-back detection - While the infrastructure exists, inbound sync is not implemented, so bounce-backs cannot be detected.
-
Missing audit log - No record of sync operations for troubleshooting.
Database Tables
Sync tables are created via Liquibase changelog:
20251027173329_add_sync_Entities.xml<createTable tableName="event_sync_tag_server">
<column name="id" type="bigint" autoIncrement="true">
<constraints primaryKey="true"/>
</column>
<column name="event_id" type="bigint">
<constraints nullable="false"/>
</column>
<column name="last_modified_main" type="timestamp"/>
<column name="last_modified_remote" type="timestamp"/>
</createTable>
Similar structure for all 8 sync entity tables.
Source Code References
| File | Purpose |
|---|---|
|
Generic sync orchestration (~344 lines) |
|
Scheduler and service registry |
|
REST client implementation (~217 lines, 25 methods) |
|
JWT authentication |
|
Entity-specific sync implementations |
| File | Purpose |
|---|---|
|
Abstract sync entity base (~30 lines) |
|
Common repository queries |
|
Entity-specific sync tables |
|
Liquibase changelog |