Skip to content

Architecture

This page explains the reasoning behind Conflux's architectural decisions — the why behind the choices that shape how the system is built and how it evolves.

Identifiers: UUIDs

All models use UUIDs instead of auto-incrementing integer IDs. This makes IDs safe to expose in URLs and API responses without leaking information about record counts, and simplifies data migration between systems without collision risk.

Soft Deletes

Most records are soft-deleted rather than permanently removed. The deleted_at timestamp is set on deletion, and records are filtered out by default via Eloquent's global scope. Hard deletes are allowed in exceptional cases — for ephemeral data or append-only audit logs where permanent removal is intentional — but must be confirmed explicitly.

One such sanctioned exception is the webhook_calls table (see Webhook Receiver): it is a transient inbound buffer that is mass-pruned on a schedule, so it intentionally omits softDeletes().

Related records are left untouched when a parent is soft-deleted. There are no cascade deletes anywhere in the system, which avoids accidental data loss and keeps deletion behaviour explicit and auditable.

Migrations: Forward-Only

Migrations never implement a down() method. Rolling back by reversing the original migration is destructive — it can drop columns or tables that contain production data. Instead, rollbacks are handled by writing a new migration that undoes the change. This keeps the migration history linear and makes every schema change an explicit, reviewable step.

No Class Names in the Database

Fully-qualified class names are never stored as database values. Storing class names couples the database to the application's internal namespace — a rename or move breaks existing data silently.

Instead, backed enums are used with short alias values (e.g. SystemDriver::WooCommerce = 'woocommerce'). The database stores the alias; the enum owns the mapping to the implementation class via a driverClass(): string method.

Encrypted Credentials

Sensitive per-system-connection secrets (API keys, tokens, passwords) are stored as an encrypted JSON blob using Laravel's built-in encrypted:json cast. Encryption uses AES-256-CBC, keyed by APP_KEY. This means secrets are never readable directly from the database.

Non-secret fields such as base_url are stored in plain text. This allows them to be displayed and filtered without the overhead of decryption on every read.

Async-First Processing

Queue-based, asynchronous processing is preferred over synchronous handling wherever possible. This keeps HTTP response times predictable and makes the system more resilient to downstream failures — a slow external platform call does not block a user-facing request.

All jobs use the rabbitmq queue driver. Failed jobs are tracked via the database-uuid driver. Redis is never used as a queue driver.

Internal Communication

Internal service communication follows REST conventions. This keeps the interface consistent and makes it straightforward to test and reason about without a custom protocol layer.

Data Flow: Both Directions

Conflux handles data in both directions:

  • Pull (outbound) — Conflux fetches data from external platforms on demand or on a schedule. A BulkSyncJob drives the fetch: for each requested capability it calls requestBulkExport() on the platform integration, then dispatches a PersistBulkDataJob on the rabbitmq connection with the returned DTOs. PersistBulkDataJob resolves the correct transformer from TransformerRegistry and calls transform() to write the records to the unified Eloquent models.
BulkSyncJob (fetch) → PersistBulkDataJob (persist via TransformerRegistry) → Eloquent models
  • Push (inbound) — External platforms send webhooks to POST /api/webhooks/{connection}. The endpoint validates the HMAC signature and dispatches the payload to the platform integration asynchronously. See the Webhook Receiver reference.

Integration Architecture

Each platform integration consists of two paired classes registered as singletons:

  • An integration (AbstractIntegration subclass) registered in IntegrationRegistry — responsible for communicating with the external platform and returning normalised DTOs.
  • A transformer (AbstractTransformer subclass) registered in TransformerRegistry — responsible for persisting those DTOs to Conflux's unified Eloquent models.

Both registries follow the same pattern: register(SystemDriver, class-string) in AppServiceProvider::boot(), resolved via the service container at runtime. New integrations should follow this pattern and use existing integrations as reference. Do not introduce new abstraction layers prematurely.

API Versioning

Not implemented yet. Versioning will be added only when demand arises — introducing it speculatively adds complexity that nobody benefits from yet.

Multi-Tenancy

Conflux is being extended with a Tenant entity that groups a company's connections across every integrated system, replacing the current App boundary. The model is decided but not yet built — see Tenant Model and Cross-System Identity for the full concept and the linked ADRs for the individual decisions.

Error Handling and Retries

No strategy is defined yet. The approach will be kept simple until the requirements are clear.

Application Layer

All proxies are trusted. X-Forwarded-* headers are honoured because nginx sits in front of PHP-FPM in all environments.

API routes live in routes/api.php, including the webhook route (/api/webhooks/{connection}), which opts out of the global auth:connection + ensure.connection.active middleware via withoutMiddleware() — authentication there is via HMAC signature instead (see Webhook Receiver). Scribe is configured to pick up api/* routes and generate the API Reference.

Distributed Tracing

Every request, queued job, and CLI command in Conflux is assigned a trace context that follows the W3C Trace Context standard. This makes it possible to correlate a single logical operation across multiple log entries, queue workers, and downstream HTTP calls without custom log-scraping heuristics.

Conflux uses the OpenTelemetry PHP SDK (open-telemetry/api and open-telemetry/sdk) rather than a hand-rolled solution. The OTel SDK provides the correct data model (spans, trace IDs, span IDs, flags) and the W3C propagation format out of the box, which means the implementation stays compatible with any future observability backend without changing application code.

Spans can be exported via OTLP/HTTP (open-telemetry/exporter-otlp) to VictoriaTraces (see ADR-0011), which runs as a docker-compose.yml service locally and exposes a built-in UI for browsing traces. Export is opt-in: config/telemetry.php's otlp_endpoint is null by default (spans are created and tracked in-process but not exported), and is set via OTEL_EXPORTER_OTLP_TRACES_ENDPOINT in .env.

See Tracing Reference for headers, payload keys, and log fields.

Background Processing

Two long-running processes are managed by supervisor in a dedicated container:

Process Command Purpose
Queue worker queue:work Processes internal jobs from the rabbitmq queue
Scheduler schedule:work Runs Laravel scheduled tasks every minute