Skip to main content

Architecture

┌─────────────────────────────────────────────┐
│ kafka-connect-salesforce (repo) │
├─────────────────────────────────────────────┤
Salesforce │ sf-core (shared library) │ Kafka
────────── │ • Auth: Client Credentials, JWT Bearer │ ─────
Pub/Sub gRPC ◄───┤ • Pub/Sub client (Subscribe/Publish/ │
Bulk API 2.0 ◄───┤ GetSchema/GetTopic, replay handling) ├──► SourceRecord
REST/SOQL ◄───┤ • Bulk 2.0 client (query + ingest jobs) │ (any converter)
CometD (opt) ◄───┤ • REST/SOQL client + describe cache │
│ • Avro/describe → Connect schema mapping │
│ • Rate-limit governor (/limits polling) │
├─────────────────────────────────────────────┤
│ Connectors (thin): │
│ • SalesforceSourceConnector/Task │◄── SinkRecord
│ • SalesforceSinkConnector/Task │
│ • SalesforcePlatformEventSinkConnector │
│ • SalesforceStreamingSourceConnector (opt) │
└─────────────────────────────────────────────┘

Design principles​

  • One shared sf-core library owns all Salesforce protocol logic — auth and session refresh, the Pub/Sub gRPC client, Bulk 2.0 job lifecycles, REST/SOQL with describe caching, schema mapping, retry classification, and quota governance. The connectors stay thin and testable.
  • Checkpointing via Kafka Connect's offset store. The Pub/Sub replayId (opaque bytes, stored base64) and Bulk SystemModstamp cursor live in each record's sourceOffset, exactly as the framework intends. No external state.
  • Per-SObject isolation. The source runs an independent state machine per SObject — its own subscription, schema cache, Bulk jobs, and offsets — so one failing object can't stall the rest.
  • Pluggable converters. Records are standard Connect Structs (or schemaless maps for the CometD source); use any key/value converter — JSON, Avro with Schema Registry or Apicurio, Protobuf.
  • Clean room. Everything is implemented from Salesforce's public API documentation with an independent sf.* config namespace. No Confluent code or config strings.

The source state machine​

Each SObject pipeline moves through up to three modes, persisted in its offset so restarts resume in the right place:

SNAPSHOT (Bulk 2.0) ──► EVENT_DRIVEN (Pub/Sub CDC)
│ ▲ gap events → REST re-query / Bulk resync
└──────────► POLLING (Bulk 2.0 + SystemModstamp cursor)

The snapshot→stream handoff uses a replayId probe plus commit-timestamp dedup to make the transition lossless and duplicate-free.

Salesforce API cheat sheet​

APIUsed forKey limits
OAuth token endpointAll authClient Credentials requires My Domain
Pub/Sub API (gRPC)CDC + platform-event streaming, event publish72h retention, num_requested ≤ 100
Bulk API 2.0Snapshot, polling, high-volume writes150M records/24h, 150MB/job, 10k query jobs/24h
REST/SOQLDescribe, gap re-query, low-latency writes, /limits/Counts against the 24h API pool
Streaming API (CometD)Legacy fallback only24h/72h retention, being retired