SF-Publication-Sync via REST-Pull statt Platform-Event-Subscription oder Boomi-Hop
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:
- 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. - 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.
| Pfad | Mechanismus | Frequenz | Beleg |
|---|---|---|---|
| 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
- Maximale Latenz ist Cron-Tick. 15 Minuten Inbound, je Cron-Tick Outbound. Akzeptabel für die Story, dokumentiert für künftige PRDs.
- SF-API-Budget bleibt klein. Bei 96 Delta-Pulls/Tag + 1 Reconciliation = ~97 SOQL-Queries/Tag plus N PATCH-Calls für Outbound. SF-Sandbox-Limits werden nicht angefasst.
- Drupal hat keinen Daemon-Footprint. Standard-Drupal-Cron / Mittwald-Cronjob trägt den Sync. Keine Long-Running-Process-Discipline notwendig.
- Boomi bleibt für andere Konsumenten relevant. Wenn SOLCOM später Daten an Marketing oder externe Empfänger weiterleiten muss, geschieht das über Boomi — nicht über die Website. Dieser ADR sagt nichts gegen Boomi an sich, nur gegen Drupal als Boomi-Konsument.
- Reconciliation ist Pflicht, nicht Nice-to-have. SF-Hard-Deletes werden vom Delta-Pull nicht erfasst — der nightly Sweep schließt diese Lücke. Ohne ihn würden gelöschte Publications als Drupal-Geister weiterleben.
- Wenn Kunde Platform-Event-Subscription erzwingt, wird dieser ADR mit
superseded-by:0NNNmarkiert und ein neuer ADR über die Long-Running-Subscriber-Architektur geschrieben — inkl. Wahl der Subscription-Technologie (CometD vs. Pub/Sub-API).