External Sync Config
Overview
The External Sync system in DataCollect provides a unified interface for synchronizing data with external third-party systems. It implements the Strategy pattern to support multiple external system integrations through pluggable adapters.
The system aims to:
This configuration is typically used by sync server instances to synchronize data with external systems. The sync server reads this configuration to understand the data structure, authentication requirements, and sync endpoints needed to properly exchange data with external systems like OpenSPP, OpenFn, or other third-party platforms.
Sample Configuration
Here's a complete example of an external sync configuration with detailed field explanations:
{
"externalSync": {
"type": "openfn-adapter",
"url": "https://api.openfn.org/workflow/trigger/abc123",
"extraFields": [
{
"name": "apiKey",
"value": "sk_prod_1234567890abcdef"
},
{
"name": "workflowId",
"value": "wf_1234567890"
},
{
"name": "batchSize",
"value": "50"
},
{
"name": "timeout",
"value": "30000"
}
]
}
}
Field Explanations
type (Required)
- Purpose: Specifies which adapter to use for synchronization
- Values:
"mock"- For testing and development"openfn-adapter"- For OpenFn workflow integration- Custom adapter types as defined in your system
- Example:
"openfn-adapter"
url (Required)
- Purpose: The endpoint URL of the external system for data synchronization
- Format: Full URL including protocol, domain, and path
- Examples:
"https://api.openfn.org/workflow/trigger/abc123""http://localhost:3000/mock-sync""https://your-external-system.com/api/sync"
extraFields (Required Array)
-
Purpose: Additional configuration parameters specific to the adapter type
-
Structure: Array of objects with
nameandvalueproperties -
Common Fields:
For OpenFn Adapter:
apiKey: Your OpenFn API key for authenticationworkflowId: The specific workflow ID to triggerbatchSize: Number of records to process in each batch (default: 100)timeout: Request timeout in milliseconds (default: 30000)
For OpenSPP Adapter:
database: OpenSPP/Odoo database name (required)username: Username for authentication (required)password: Password for authentication (required)batchSize: Number of entities per batch (default: 50)batchDelayMs: Delay between batches in milliseconds (default: 1000)maxRetries: Maximum retry attempts for failed entities (default: 2)fieldMappings: JSON array of field mappings with transformers (see Field Mapping section)
For Mock Registry Server:
clientId: OAuth2 client ID registered on the mock registry (required)clientSecret: OAuth2 client secret (required)identifierScheme: Identifier scheme URI (default:urn:mock:vocab:id-type)identifierType: Identifier type for DC-pushed entities (default:system_id)timeout: HTTP request timeout in milliseconds (optional)
For Custom Adapters:
- Any adapter-specific configuration parameters
- Database connection strings
- Custom headers or authentication tokens
- Sync frequency settings
Configuration Examples by Adapter Type
Mock Registry Server Configuration
{
"type": "mock",
"url": "http://localhost:9999",
"adapterConfig": {
"clientId": "mock-client",
"clientSecret": "mock-secret",
"identifierScheme": "urn:mock:vocab:id-type",
"identifierType": "system_id"
}
}
Start the reference server from the monorepo:
docker compose -f docker/docker-compose.dev.yaml --profile mock up -d
pnpm seed # provisions a 'demo-mock-registry' config wired to http://localhost:9999
The pnpm seed script auto-seeds 2 households and 5 persons into the mock server (via python -m mock_server seed) and uploads a DC app config with externalSync.type = "mock", ready for the admin UI to trigger.
OpenFn Adapter Configuration
{
"type": "openfn-adapter",
"auth": "api-key",
"url": "https://api.openfn.org/workflow/trigger/def456",
"extraFields": [
{ "name": "apiKey", "value": "sk_prod_abcdef123456" },
{ "name": "workflowId", "value": "wf_abcdef123456" },
{ "name": "batchSize", "value": "50" },
{ "name": "timeout", "value": "60000" }
]
}
Custom External System Configuration
{
"type": "custom-adapter",
"auth": "basic",
"url": "https://your-system.com/api/sync",
"extraFields": [
{ "name": "username", "value": "sync_user" },
{ "name": "password", "value": "secure_password" },
{ "name": "database", "value": "production_db" },
{ "name": "syncInterval", "value": "300000" }
]
}
Architecture
Design Pattern
The External Sync system uses the Strategy pattern to handle different external system integrations:
- Context:
ExternalSyncManageracts as the context that manages the synchronization strategy - Strategy: Each adapter (e.g.,
MockRegistrySyncAdapter,OpenFnSyncAdapter) implements theExternalSyncAdapterinterface - Registry: The
adaptersMappingobject serves as a registry of available strategies
Key Components
const adaptersMapping = {
"mock": MockRegistrySyncAdapter,
"openfn-adapter": OpenFnSyncAdapter,
};
Configuration
ExternalSyncConfig Interface
The configuration is defined by the ExternalSyncConfig type:
type ExternalSyncConfig = {
type: string; // Adapter type identifier
auth?: string; // Authentication method
url: string; // External system URL
extraFields: { name: string; value: string }[]; // Additional configuration
};
Configuration in Admin UI
The Admin interface provides a user-friendly way to configure external sync settings.
Available Adapters
1. Mock Registry Server Adapter
Type: mock
OAuth2 HTTP client for the reference mock registry server (Python + Litestar + SQLite). Used as the canonical reference V2 adapter and for end-to-end sync testing without OpenSPP.
- Pull:
GET /v1/personsandGET /v1/groupswithupdated_sincewatermark - Push:
POST/PATCHof individuals and groups,system_ididentifier for DC-originated entities - Auth: OAuth2 client credentials (JWT, 1-hour TTL, auto-refresh)
- Conflict handling: maps HTTP 412 Precondition Failed to
ConflictError
Configuration:
{
"type": "mock",
"url": "http://localhost:9999",
"adapterConfig": {
"clientId": "mock-client",
"clientSecret": "mock-secret",
"identifierScheme": "urn:mock:vocab:id-type",
"identifierType": "system_id"
}
}
See also: Building a V2 Adapter — uses this adapter as the worked example.
2. OpenFn Adapter
Type: openfn-adapter
Integration with OpenFn workflow automation platform:
- Push: Sends entities to OpenFn workflows via API
- Authentication: Uses API key authentication
- Pull: Not yet implemented (planned for future)
Configuration:
{
"type": "openfn-adapter",
"url": "https://api.openfn.org/workflow/trigger",
"extraFields": [
{ "name": "apiKey", "value": "your-api-key" }
]
}
3. OpenSPP Adapter
Type: openspp-adapter
Integration with OpenSPP social protection platform:
- Push: Sends entities to OpenSPP with field mapping and transformation
- Pull: Retrieves updates from OpenSPP and applies them locally
- Authentication: Uses basic authentication (username/password)
- Field Mapping: Visual interface for mapping form fields to OpenSPP fields
- Data Transformers: Automatic format conversion (dates, IDs, multi-select, boolean)
- Batch Processing: Configurable batch sizes and delays
Configuration:
{
"type": "openspp-adapter",
"url": "https://openspp.example.com",
"auth": "basic",
"extraFields": [
{ "name": "database", "value": "openspp" },
{ "name": "username", "value": "admin" },
{ "name": "password", "value": "password" },
{ "name": "batchSize", "value": "50" },
{ "name": "batchDelayMs", "value": "1000" },
{ "name": "maxRetries", "value": "2" }
]
}
For detailed OpenSPP adapter documentation, see the OpenSPP Adapter Guide.
Usage Examples
Basic Initialization
import { ExternalSyncManager } from './components/ExternalSyncManager';
import { EventStore } from './interfaces/types';
import { EventApplierService } from './services/EventApplierService';
// Configuration
const config: ExternalSyncConfig = {
type: 'mock',
url: 'http://localhost:3000/sync',
extraFields: []
};
// Initialize manager
const manager = new ExternalSyncManager(
eventStore,
eventApplierService,
config
);
// Initialize adapter
await manager.initialize();
// Check if adapter was loaded
if (manager.isInitialized()) {
await manager.synchronize();
}
With Authentication
const credentials: ExternalSyncCredentials = {
username: 'sync_user',
password: 'secure_password'
};
await manager.synchronize(credentials);
Error Handling
try {
await manager.initialize();
if (manager.isInitialized()) {
await manager.synchronize();
console.log('Sync completed successfully');
} else {
console.error('No adapter available for type:', config.type);
}
} catch (error) {
if (error.message === 'Adapter not initialized') {
console.error('Please call initialize() first');
} else {
console.error('External sync failed:', error.message);
}
}
API Reference
Constructor
constructor(
eventStore: EventStore,
eventApplierService: EventApplierService,
config: ExternalSyncConfig
)
Parameters:
eventStore: Store for managing events and audit logseventApplierService: Service for applying events to entitiesconfig: Configuration object specifying the external system type and settings
Methods
initialize(): Promise<void>
Initializes the external sync manager by instantiating the appropriate adapter.
Behavior:
- Looks up the adapter class based on configuration type
- Creates an instance with provided dependencies
- If adapter type not found, manager remains uninitialized
Throws: Error when adapter instantiation fails
synchronize(credentials?: ExternalSyncCredentials): Promise<void>
Performs synchronization with the external system using the configured adapter.
Parameters:
credentials: Optional authentication credentials for the external system
Behavior:
- Delegates sync operation to the loaded adapter
- Handles system-specific integration logic
- Typically involves pulling/pushing data and applying changes
Throws: Error when adapter is not initialized or sync operation fails
isInitialized(): boolean
Checks if the external sync manager has been properly initialized with an adapter.
Returns: true if an adapter is loaded and ready for synchronization
Integration with DataCollect System
Event Sourcing Integration
The External Sync system integrates with DataCollect's event sourcing architecture:
- Event Store: Tracks all form submissions and maintains sync timestamps
- Event Applier Service: Applies pulled events to local entities
- Sync Levels: Manages synchronization state (LOCAL, REMOTE, EXTERNAL)
Sync Process Flow
-
Push Phase:
- Retrieve events since last push timestamp
- Send events to external system in batches
- Update push timestamp after successful batch
-
Pull Phase:
- Retrieve data from external system since last pull
- Convert external data to FormSubmission events
- Apply events using EventApplierService
- Update pull timestamp
Timestamp Management
The system maintains separate timestamps for:
lastPushExternalSyncTimestamp: Last successful push to external systemlastPullExternalSyncTimestamp: Last successful pull from external system
This enables incremental synchronization and prevents data loss.
Field Mapping and Transformers
For adapters that support field mapping (like OpenSPP), you can configure field mappings with data transformers to handle format conversion between form data and external system formats.
Field Mapping Structure
Field mappings define how form fields map to external system fields, with optional transformers for data conversion:
{
"formField": "first_name",
"opensppField": "firstname",
"transformer": {
"type": "text",
"options": {}
}
}
Transformer Types
The system supports several transformer types:
- text: Pass-through or string conversion (default)
- date: Date format conversion with configurable input/output formats
- id: ID value handling for relation fields
- multiselect: Array-to-delimited-string conversion
- boolean: Boolean normalization with configurable truthy/falsy values
For detailed transformer documentation, see the OpenSPP Adapter Guide.
Configuring Field Mappings
Field mappings can be configured:
- Via Admin UI: Use the visual field mapping dialog in the configuration editor
- Via JSON Config: Include mappings in the
fieldMappingsextraField as a JSON string
Example field mappings configuration:
{
"name": "fieldMappings",
"value": "[{\"formField\":\"birth_date\",\"opensppField\":\"birthdate\",\"transformer\":{\"type\":\"date\",\"options\":{\"inputFormat\":\"auto\",\"outputFormat\":\"YYYY-MM-DD\"}}},{\"formField\":\"gender\",\"opensppField\":\"gender_id\",\"transformer\":{\"type\":\"id\"}}]"
}
Extending the System
Adding New Adapters
To add support for a new external system:
- Create Adapter Class:
class CustomSyncAdapter implements ExternalSyncAdapter {
constructor(
private eventStore: EventStore,
private eventApplierService: EventApplierService,
private config: ExternalSyncConfig
) {}
async sync(credentials?: ExternalSyncCredentials): Promise<void> {
// Implement sync logic
}
}
- Register in Adapters Mapping:
const adaptersMapping = {
"mock": MockRegistrySyncAdapter,
"openfn-adapter": OpenFnSyncAdapter,
"openspp-adapter": OpenSppOdooSyncAdapter,
"custom-adapter": CustomSyncAdapter, // Add new adapter
};
- Update Admin UI:
<v-select
v-model="form.externalSync.type"
:items="[
{ title: 'Mock Registry Server', value: 'mock' },
{ title: 'OpenFn', value: 'openfn-adapter' },
{ title: 'OpenSPP', value: 'openspp-adapter' },
{ title: 'Custom System', value: 'custom-adapter' }, // Add new option
]"
label="Type"
/>
Configuration Schema
Each adapter can define its own configuration schema by extending ExternalSyncConfig:
interface CustomSyncConfig extends ExternalSyncConfig {
database?: string;
timeout?: number;
retryAttempts?: number;
}
Best Practices
Error Handling
- Always check
isInitialized()before callingsynchronize() - Implement proper error handling for network failures
- Use try-catch blocks around sync operations
Performance
- Use batch processing for large datasets
- Implement incremental sync using timestamps
- Consider implementing retry logic for failed operations
Security
- Store sensitive credentials securely
- Use HTTPS for external system communication
- Implement proper authentication mechanisms
Monitoring
- Log sync operations for debugging
- Track sync success/failure rates
- Monitor sync performance metrics
Related Components
- ConfigCreateView.vue: Admin UI for configuring external sync
- FieldMappingDialog.vue: Visual interface for field mapping configuration
- MockRegistrySyncAdapter: V2 OAuth2 HTTP client for the reference mock registry (examples/mock-server)
- OpenFnSyncAdapter: OpenFn platform integration
- OpenSppOdooSyncAdapter: OpenSPP platform integration with field mapping
- EventStore: Event storage and timestamp management
- EventApplierService: Event application to entities
- FieldTransformers: Data transformation utilities (text, date, id, multiselect, boolean)
Troubleshooting
Common Issues
-
Adapter Not Found
- Ensure the adapter type is correctly registered in
adaptersMapping - Check that the adapter class is properly imported
- Ensure the adapter type is correctly registered in
-
Authentication Failures
- Verify credentials are correctly formatted
- Check that the external system accepts the authentication method
-
Sync Failures
- Check network connectivity to external system
- Verify the external system URL is correct
- Review error logs for specific failure reasons
-
Data Loss
- Ensure timestamps are properly maintained
- Check that incremental sync is working correctly
- Verify event ordering and processing
Debug Mode
Enable debug logging to troubleshoot sync issues:
// Enable detailed logging
console.log('Sync configuration:', config);
console.log('Adapter initialized:', manager.isInitialized());
console.log('Sync timestamps:', {
lastPush: await eventStore.getLastPushExternalSyncTimestamp(),
lastPull: await eventStore.getLastPullExternalSyncTimestamp()
});