Architecture Decision Record · 0027

SF-Publication-Sync via REST-Pull statt Platform-Event-Subscription oder Boomi-Hop

TL;DR — Drupal pollt SF im 15-Minuten-Takt mit einem SOQL-Delta-Query gegen TR1__Job__c. Nicht gewählt: Subscription auf das vom Kunden gelieferte Platform-Event SC_Publication_Changed_Out__e, und nicht: indirekter Empfang über einen Boomi-Webhook. Gründe: SOLCOM-Eigner-Schaft, Sandbox-Kompatibilität, fehlende Real-Time-Anforderung und der direkte Service-OAuth-Zugang, den der Kunde uns gegeben hat.

Kontext

Der Kunde lieferte zur Story F-290 zwei scheinbar widersprüchliche Inputs:

  1. Den Story-Titel „Salesforce / Bullhorn Integration (REST-Pull)", eine SF-Sandbox (FCSB), einen Service-Account (website.integrationuser@solcom.de.fcsb), ein Refresh-Token und eine SOQL-Sample-Query.
  2. Eine Excel-Spec mit Header „Boomi Canonical Data Model" und einem Sample-Payload im Salesforce-Platform-Event-Format (EventApiName: SC_Publication_Changed_Out__e, replayId, schema).

Das ist nicht ein Widerspruch im Kunden-Setup — beides existiert parallel. SF feuert ein Platform-Event, das Boomi konsumiert und in eine Canonical-Form transformiert. Andere Konsumenten (Boomi-seitig) bekommen dann diesen Canonical-Payload. Für die SOLCOM-Website ist die Frage: tritt Drupal als (a) eigener Event-Subscriber, (b) Boomi-Downstream-Konsument oder (c) direkter REST-Konsument von SF auf?

Charakter der Daten: Projekt-Ausschreibungen mit Veröffentlichungs-Window von typischerweise Tagen bis Wochen. Kein Echtzeit-Druck (B2B-Backend für Freiberufler-Suche, kein Bid-Stream, kein Live-Inventar). Akzeptable End-to-End-Latenz: ≤15 Minuten — dasselbe, das Cron-getriebener Pull leistet.

Aus der Tatsache, dass der Kunde Drupal aktiv Service-OAuth-Credentials für die FCSB-Sandbox gegeben hat, schließen wir: Direkter SF-Zugriff von Drupal ist gewünscht und genehmigt. Wenn Boomi-Vermittlung gewollt wäre, hätten wir stattdessen einen Boomi-Webhook-Endpoint und Webhook-Signing-Secret erhalten.

Entscheidung

Drupal pollt SF direkt via REST. Inbound = SOQL-Delta-Query alle 15 Minuten, nightly Reconciliation-Sweep. Outbound = ein Feld pro Publish/Unpublish via PATCH.

PfadMechanismusFrequenzBeleg
Inbound · Delta SOQL WHERE LastModifiedDate >= :since 15 Min (QueueWorker) state.solcom_sf.last_pull_at
Inbound · Reconciliation SOQL Full-Id-List → Diff gegen field_sf_id 1× täglich (03:00) Hard-Delete-Erkennung
Inbound · Bootstrap Drush solcom-sf:bootstrap einmalig / on-demand Initial-Load + Re-Sync
Outbound · URL-Push PATCH /sobjects/TR1__Job__c/<Id> async via QueueWorker hook_node_update auf publish-Wechsel

Lifecycle-Auswertung in Drupal: ein Node ist publiziert ⇔ publish_start ≤ now < publish_end AND SC_PublicationStatus__c == 'Published'. SF-seitige Hard-Deletes werden vom Reconciliation-Sweep entdeckt. Sample-Payload-Format (CDC/Platform-Event) wird bewusst nicht gelesen.

Rejected alternatives

Platform-Event-Subscription auf SC_Publication_Changed_Out__e (CometD oder Pub/Sub-API) rejected

Vorteile: Near-Realtime, kein Polling-Overhead, SF-native. Verworfen, weil:

  • Long-running Subscriber — Drupal müsste einen Daemon hosten (CometD-Client oder gRPC für Pub/Sub-API). Mittwald-Webhosting ist nicht für Long-Running-PHP-Processes optimiert (Worker-Lifecycle, Reconnect, Restart-Logik wären eigenes Sub-System).
  • Replay-Management — durable Subscription mit replayId-Persistierung, Gap-Detection bei Disconnects.
  • Sandbox-Restriktionen — Platform-Events haben Sandbox-Limits (Event-Delivery-Allocations), die Tests irritieren können.
  • Mehrwert kostet mehr — 15-Minuten-Latenz ist für B2B-Projektbörse akzeptabel; Near-Realtime wäre Over-Engineering.

Revisit-Anlass: Kunde verlangt explizit <1 Min Latenz, ODER es kommen ≥5 weitere Event-Streams aus SF — dann lohnt sich der Daemon-Aufwand.

Boomi-Webhook-Endpoint in Drupal rejected

Vorteile: kein OAuth in Drupal, schlanke Drupal-Surface (ein POST-Endpoint + Webhook-Signature). Boomi kümmert sich um SF-Integration, schickt Canonical-Form an Drupal. Würde zum Excel-Header „Boomi Canonical Data Model" passen.

Verworfen, weil:

  • Kunde gab Drupal direkten SF-Zugriff — Indikator für Pull-Wunsch. Würde Boomi-Vermittlung gewollt sein, hätten wir Webhook-Secret und URL erhalten.
  • Boomi-Verfügbarkeit ist nicht in SOLCOMs Scope — Outage in Boomi wäre Drittsystem-Risiko ohne Visibility.
  • Push-vs-Pull-Konsistenz — Outbound (URL-Push) muss ohnehin direkt nach SF (Boomi macht in der Regel keine Inbound-Writes von Konsumenten). Dann ist die Asymmetrie „Push direkt, Pull über Boomi" mehr Komplexität, nicht weniger.
  • Single-Source-of-Truth — der Reconciliation-Sweep braucht Read-Access auf SF; mit Pull haben wir den schon, mit Webhook müsste er separat aufgebaut werden.

Revisit-Anlass: Kunde stellt SF-Zugang aus Compliance-Gründen ab und liefert nur noch über Boomi.

Hybrid — Bootstrap-Pull, danach Platform-Event-Push für Delta rejected

Zwei Codepfade, gleicher Mapper. Plausibel für ein System mit hoher Schreib-Frequenz und striktem SLA. Verworfen, weil F-290 weder eine noch das andere hat — beide Anforderungen würden hypothetisch sein.

SOAP-API von SF nutzen rejected

SOQL gibt es auch via SOAP. Verworfen, weil REST/JSON deutlich Drupal-Stack-näher (Guzzle, Json-Decoding, Symfony-Cache) und der vom Kunden gelieferte Sample-Endpoint (/services/data/v66.0/query/) bereits REST ist.

Consequences