-
Notifications
You must be signed in to change notification settings - Fork 85
Closed
Description
Problem
Even with only GitHub integration, we have been having difficulties with the sync up/down to the external service.
- Writes are coupled to the external API
- Multiple
taskandcommentrecords are created - Records become easily out of sync
Our basic goals:
- Eventual consistency to the destination records in our database
- Eventual consistency to the external APIs
- Events to/from the external APIs are fully concurrent:
- enqueued in order
- dropped when more recent events come in
- restartable when errored
Each Event can have a:
direction-:inbound | :outboundintegration_fieldsintegration_external_id- theidof the integration resource from the external providerintegration_updated_at- the last updated at timestamp of the integration resource from the external providerintegration_record_id- theidof our cached record for the resourceintegration_record_type- thetypeour cached record for the resource as the table name
record_fieldsrecord_id- theidof the record for the resource connected to this integrationrecord_type- thetypeof the record for the resource connected to this integration as the table name
canceled_by- theidof theEventthat canceled this oneduplicate_of- theidof theEventthat this is a duplicate ofignored_for_id- theidof the record that caused this event to be ignoredignored_for_type- thetypeof the record (table name) that caused this event to be ignoredstate-:queued | :processing | :completed | :errored | :canceled | :ignored | :duplicate | :disabled
We may want our own writes to our own records, even without integrations, to also go through this process. Not sure.
When an event comes in we should:
- check if there is any event for the
integration_external_idwhere:- the
integration_updated_atis after our event's last updated timestamp (limit 1)- if yes, set state to
:ignoredand stop processing, setignored_for_idto theidof the event in thelimit 1query andignored_for_typeto this event table's name
- if yes, set state to
- the
integration_updated_attimestamp for the relevantrecord_is equal to our event's last updated timestamp (limit 1)- if yes, set state to
:duplicateand stop processing, setduplicate_ofto theidof the event in thelimit 1
- if yes, set state to
- the
modified_attimestamp for the relevantrecord_is after our event's last updated timestamp- if yes, set state to
:ignoredand stop processing, setignored_for_idto therecord_idandignored_for_typeto therecord_type
- if yes, set state to
- the
- check if there are any events for the
integration_external_idwhere:integration_updated_atis before our event's last updated timestamp- if yes, set state of those events to
:canceledand setcanceled_byto theidof this event
- if yes, set state of those events to
- check if there is any other
:queuedevent or:processingevent for theintegration_external_id- if yes, set state to
:queued
- if yes, set state to
- when
:processing, create or update the relevant record matchingrecord_idandrecord_typethrough the relationship on the record forintegration_record_idandintegration_record_type - when
:completed, kick off process to look for next:queueditem where theintegration_updated_attimestamp is the oldest
We would also need within the logic for updating the given record to check whether the record's updated timestamp is after the event's timestamp. If it is, then we need to bubble the changeset validation error and mark the event :ignored as above.