Konzepte
Paginierung und Synchronisierung
Listen antworten seitenweise mit einem Cursor; updatedSince ruft nur ab, was sich seit Ihrem letzten Lauf geändert hat.
Seiten und Cursor
Jede Liste antwortet auf dieselbe Weise: mit den Einträgen, dem Cursor für die nächste Seite und der Angabe, ob es eine gibt.
{
"data": [ { "sessionId": "b3ac0004-0000-4000-8000-000000000000", "title": "Registration and coffee" } ],
"nextCursor": "YzE6MDYzOTI3NjEwODQ4MjgyMDAwMHxiM2Fj...",
"hasMore": true
}Geben Sie nextCursor als cursor zurück, mit denselben Filtern, bis er null ist. limit legt die Seitengröße fest: standardmäßig 100, höchstens 500. Ein Cursor ist undurchsichtig: Übernehmen Sie ihn so, wie er kam, und bauen Sie nie selbst einen. Ein Cursor, den nicht der Server erzeugt hat, wird mit invalidCursor abgelehnt, statt stillschweigend von vorn zu beginnen.
Listen haben eine feste Reihenfolge, die älteste Änderung zuerst, bei Gleichstand entscheidet die ID. Eine Seite überspringt oder wiederholt also nie einen Eintrag, der sich während des Lesens nicht geändert hat. Ein Eintrag, der sich während des Blätterns ändert, wandert ans Ende, und Sie sehen ihn erneut. Genau das will eine Synchronisierung.
Die Listen der Teilnehmenden, Bestellungen und Check-ins nehmen außerdem noch page und pageSize an, wie vor der Einführung der Cursor. Das funktioniert weiterhin; bevorzugen Sie aber den Cursor, der nie zählen muss.
Synchronisieren: nur, was sich geändert hat
Listen mit veränderlichen Einträgen geben in updatedUtc an, wann sich jeder Eintrag zuletzt geändert hat, und nehmen updatedSince an. Eine Synchronisierung merkt sich den neuesten updatedUtc-Wert, den sie gesehen hat, und fragt beim nächsten Mal ab dort:
- Lesen Sie jede Seite mit
updatedSince, gesetzt auf den Wert, den Sie sich beim letzten Mal gemerkt haben (beim ersten Mal ohne). - Speichern oder aktualisieren Sie jeden Eintrag anhand seiner ID.
- Merken Sie sich den neuesten
updatedUtc-Wert des Laufs für das nächste Mal.
updatedSince ist inklusiv: Ein Eintrag, der in derselben Millisekunde wie Ihre Marke geändert wurde, kommt erneut, statt verloren zu gehen. Einen Eintrag zu aktualisieren, den Sie schon haben, schadet nicht; einen zu verpassen schon.
const base = "https://attendbeacon.com/api/v1/events/b3ac0000-0000-4000-8000-000000000001/attendees";
const headers = { Authorization: `Bearer ${process.env.ATTENDBEACON_KEY}` };
let since = await loadMark(); // null on the first run
let newest = since;
let cursor = null;
do {
const url = new URL(base);
url.searchParams.set("limit", "500");
if (since) url.searchParams.set("updatedSince", since);
if (cursor) url.searchParams.set("cursor", cursor);
const page = await fetch(url, { headers }).then((r) => r.json());
for (const attendee of page.data) {
await upsert(attendee); // by attendee.attendeeId
if (!newest || attendee.updatedUtc > newest) newest = attendee.updatedUtc;
}
cursor = page.nextCursor;
} while (cursor);
await saveMark(newest);var since = await LoadMarkAsync(); // null on the first run
var newest = since;
string? cursor = null;
do
{
var query = $"events/b3ac0000-0000-4000-8000-000000000001/attendees?limit=500"
+ (since is null ? "" : $"&updatedSince={Uri.EscapeDataString(since)}")
+ (cursor is null ? "" : $"&cursor={cursor}");
var page = await client.GetFromJsonAsync<JsonElement>(query);
foreach (var attendee in page.GetProperty("data").EnumerateArray())
{
await UpsertAsync(attendee); // by attendeeId
var updated = attendee.GetProperty("updatedUtc").GetString()!;
if (newest is null || string.CompareOrdinal(updated, newest) > 0) newest = updated;
}
cursor = page.GetProperty("nextCursor").GetString();
}
while (cursor is not null);
await SaveMarkAsync(newest);base = "https://attendbeacon.com/api/v1/events/b3ac0000-0000-4000-8000-000000000001/attendees"
headers = {"Authorization": f"Bearer {os.environ['ATTENDBEACON_KEY']}"}
since = load_mark() # None on the first run
newest = since
cursor = None
while True:
params = {"limit": 500}
if since: params["updatedSince"] = since
if cursor: params["cursor"] = cursor
page = requests.get(base, params=params, headers=headers).json()
for attendee in page["data"]:
upsert(attendee) # by attendee["attendeeId"]
if newest is None or attendee["updatedUtc"] > newest:
newest = attendee["updatedUtc"]
cursor = page["nextCursor"]
if cursor is None:
break
save_mark(newest)$base = "https://attendbeacon.com/api/v1/events/b3ac0000-0000-4000-8000-000000000001/attendees"
$since = Get-Content mark.txt -ErrorAction SilentlyContinue # nothing on the first run
$newest = $since
$cursor = $null
do {
$query = "?limit=500"
if ($since) { $query += "&updatedSince=" + [uri]::EscapeDataString($since) }
if ($cursor) { $query += "&cursor=$cursor" }
$page = Invoke-RestMethod -Uri ($base + $query) -Headers $headers
foreach ($attendee in $page.data) {
Save-Attendee $attendee # by attendeeId
if (-not $newest -or $attendee.updatedUtc -gt $newest) { $newest = $attendee.updatedUtc }
}
$cursor = $page.nextCursor
} while ($cursor)
Set-Content mark.txt $newestZeitangaben sind ISO 8601 in UTC, mit drei Dezimalstellen und einem Z. Deshalb lassen sie sich auch als Text korrekt vergleichen.
Was keine Änderungszeit hat
Räume, Tracks, Vortragende, Sponsoren und Sponsorenstufen sind kleine Listen ohne eigene Änderungszeit: Lesen Sie sie vollständig, gern mit dem Cursor. Ihre einzelnen Ressourcen und die Veranstaltung selbst antworten mit einem ETag. Fragen Sie erneut mit If-None-Match, lautet die Antwort 304 Not Modified ohne Body, wenn sich nichts geändert hat.
Check-ins ändern sich nach ihrer Erfassung nie: Blättern Sie per cursor durch sie oder per after, der letzten checkInId, die Sie haben. Diese wird nur größer.
Löschungen und Datenlöschung
Ein storniertes Ticket bleibt mit "status": "cancelled" in der Liste, damit eine Synchronisierung die Stornierung sieht. Löscht jemand seine Daten in der Veranstaltungs-App, bleibt der Eintrag der teilnehmenden Person mit "erased": true und ohne personenbezogene Daten bestehen. Der Webhook attendee.erased informiert Sie sofort, damit Sie Ihre eigene Kopie löschen können. Siehe Datenschutz und Löschung.
Webhooks oder Polling
Polling mit updatedSince alle paar Minuten ist einfach und erfasst alles. Webhooks informieren Sie innerhalb von Sekunden. Viele Integrationen machen beides: Webhooks für die Geschwindigkeit, eine nächtliche Synchronisierung für die Sicherheit.