Best Practices

Caching, Fehlerbehandlung, Pagination, Rate-Limits, Logging - was eine produktive Integration von einer unterscheidet, die dreimal neu gebaut werden muss.

Die go~mus-API ist offen, mit ihr lässt sich fast alles bauen. Damit du nicht in den gleichen Fallen landet wie andere vor dir, hier eine kompakte Liste an Praktiken, die sich bewährt haben.

Caching ist Pflicht, nicht Empfehlung

Stammdaten täglich pollen und cachen. Verfügbarkeiten live, aber nicht pro Endkunden-Request. Direkt-Live-Traffic auf Stammdaten wird abgelehnt und kommt nicht in Produktion. Mehr Details unter Datenaktualität: Pull statt Push.

Konkrete Cache-Layer, die sich bewährt haben:

  • HTTP-Cache mit korrekten max-age-Headern auf Reverse-Proxy-Ebene (Varnish, Cloudflare, nginx)
  • Application-Cache (Redis, Memcached) mit expliziter Invalidierung beim täglichen Sync-Job
  • ETag- oder Last-Modified-basiert, wenn du conditional GETs nutzen willst

Fehler graziös behandeln

Order-Erstellung kann jederzeit fehlschlagen, auch nach erfolgreicher Reservation: Kapazität wurde reduziert, anderer Kanal hat schneller verkauft, Preisberechnung weicht ab. Du musst vorbereitet sein.

  • 4xx von go~mus: Body lesen, Fehler dem Endkunden klar machen, ggf. Schritt zurück gehen
  • 5xx von go~mus: retry mit exponential backoff, max 3 Versuche, dann freundliche Fehlermeldung
  • Reservation läuft ab: redirect zur Auswahl, neue Verfügbarkeit prüfen

Eine Order kann nicht nachträglich bestätigt werden, wenn sie nie erstellt wurde. Niemals.

Pagination

Jeder Index-Endpoint paginiert. Default 25, kann pro Instanz variieren. Lest meta.total_count, meta.page, meta.per_page aus und blättert konsequent durch.

async function fetchAllTickets(token) {
  const all = [];
  let page = 1;
  while (true) {
    const res = await fetch(`https://demo.gomus.de/api/v4/tickets?page=${page}`, {
      headers: { Authorization: `Bearer ${token}` }
    });
    const data = await res.json();
    all.push(...data.tickets);
    if (page >= Math.ceil(data.meta.total_count / data.meta.per_page)) break;
    page++;
  }
  return all;
}

Multi-Locale in einem Roundtrip

Wenn DE und EN gebraucht werden, hängst du nicht zwei Calls hintereinander. Ein Call mit ?locale=de liefert beide.

Tokens server-seitig halten

Niemals Tokens im Browser-Bundle, nicht in Mobile-Apps, nicht in Frontend-JS. Tokens auf eurem Server, API-Calls proxen.

Total selbst berechnen, Server validiert

Bei der Order-Erstellung wird total als Summe aller Item-Preise mitgeschickt. go~mus rechnet selbst gegen, und nur wenn dein total zur Server-Berechnung passt, wird die Order angelegt. Diese Doppelberechnung schützt vor Preismanipulation und falsch konfigurierten Discounts.

Heißt: du berechnst den Preis client-/server-seitig auf deiner Seite, aus den Preisinformationen, die go~mus in den jeweiligen Stammdaten-Endpunkten liefert.

Wo die Preise pro Produkttyp herkommen:

  • Tickets: price_cents, discount, vat_pct, tax_included direkt aus dem Ticket-Objekt - GET /api/v4/tickets oder GET /api/v4/tickets/:id. Der /capacity-Endpoint liefert nur Verfügbarkeiten, keine Preise.
  • Events (Termine): prices-Array im Termin-Detail unter GET /api/v4/events/:event_id/dates/:id. Zwei Typen: Default-Price und Scale-Price (z.B. „regulär", „ermäßigt", mit scale_price_id und title). Pro Termin können mehrere Optionen vorliegen.
  • Tours: dedizierter Preis-Endpunkt GET /api/v4/tours/:id/prices. Default-, Scale- und Entgelttabellen-basierte Preise plus Zuschläge (Fremdsprache, Sonntag etc.). Pro Preis-Objekt geben group (Pauschal vs. pro Teilnehmer) und optional (Pflicht vs. Wahl) das Verhalten an. Preise hängen ab von Datum, Uhrzeit, Teilnehmerzahl, Kunden-Kategorie und Sprache.

Wenn der Server deinen total ablehnt: Stammdaten frisch holen (Cache-Invalidierung), neu berechnen, neuer Versuch. Häufig veränderte Preise oder Discount-Konfigurationen sind die Ursache.

foreign_id für Customer-Mapping

Wenn du ein eigenes CRM haben, hängt eine foreign_id an Customer-Records. Beim nächsten Update referenzierst du via foreign_id statt go~mus-ID. Spart Mapping-Tabelle. Mehr in foreign_id-Pattern.

Logging

Logge jeden API-Call: Timestamp, Endpoint, HTTP-Status, Response-Time, Token-Identifier (nicht Token selbst). Bei Support-Tickets brauchst du das, um die Fehlerursache rückwärts nachzuvollziehen.

Was nicht loggen: Tokens, Customer-PII, Bestellungen mit Klartext-Daten. Logs sind Audit-Material, kein Datenarchiv.

Idempotenz und Wiederholungen

Bei Netzwerkfehlern weißt du nicht, ob die Order durch ist. Was du weißt musst:

  • Reservation: nicht idempotent. Gleicher Aufruf erzeugt eine zweite Reservation. Reservations können nicht via API abgefragt werden. Bei Timeout nicht versuchen, die alte wiederzufinden, sondern sie nach 5 Minuten von selbst auslaufen lassen und einen neuen Capacity-Check + Reservation starten.
  • Order anlegen: nicht idempotent, aber go~mus storniert nicht-finalisierte Orders nach kurzer Zeit automatisch (Auto-Cancel). Bei Timeout also einfach den Vorgang neu starten, doppelte halb-erstellte Orders verschwinden von selbst.
  • Finalize: idempotent, kannst du wiederholen.

Beispielcode-Repos

Was kaputt gehen wird

Pläne, die in der Praxis schiefgehen:

  • „Wir cachen die Verfügbarkeiten 5 Minuten, das wird reichen." → Ausverkaufte Termine werden weiterverkauft, Kunden sind sauer.
  • „Wir berechnen den Preis selbst aus Listenpreisen." → Bei Discounts, Sonderaktionen, Dynamic Pricing greift die Server-Validierung und du hast einen Bug auf eurer Seite.
  • „Wir holen die Tokens über AJAX im Browser." → Token im Browser = Token public. Sofortige Rotation nötig.

Wenn du nicht weiterkommt

Schreibe mit dem konkreten API-Call, Timestamp, erwartetem vs. tatsächlichem Verhalten an support@giantmonkey.de. Häufige Ursache von Fehlern: ein alter oder falscher Token. Hilfreich daher: die letzten vier Zeichen des verwendeten Tokens mitschicken, oder gleich den vollen Token - wir rotieren ihn danach. Je präziser die Frage, desto schneller die Antwort.