Skip to content

Data Flow

How data moves in and out of the Feyenoord platform: synchronous request/response paths, the API gateway and GraphQL router, client SDKs, and asynchronous event flows.

For the full node map and external integrations, see Platform Overview.

Request paths

Client apps reach backend services through a single edge endpoint (Traefik). Which path a request takes depends on the API it targets.

Federated GraphQL (v2)

Used for new domain services composed into one graph.

Client → Traefik (/v2/graphql) → Apollo Router → subgraph(s) → database / external API

The router receives a single GraphQL operation, builds a query plan, and dispatches field fetches to the relevant subgraphs: Identity, Notifications, and Loyalty today. It stitches the results into one response. Subgraphs own their domain's schema, resolvers, and data access.

See Federation Patterns for entity design and cross-subgraph extension rules.

Legacy monolith (v1)

The existing platform API.

Client → Traefik (/api/v1, /api/graphql) → Legacy Monolith API → PostgreSQL / Redis / external APIs

The monolith exposes both REST (/api/v1/...) and GraphQL (/api/graphql). It remains the primary backend for web and native until domains migrate to federated subgraphs.

Subgraph REST (OpenAPI)

Identity and Notifications also expose typed REST endpoints for direct service-to-service or tooling access.

Client / tool → Traefik (/identity, /notifications) → Dapr sidecar → service → database

Traefik rewrites these paths to Dapr service-invocation calls on each app's sidecar.

API Gateway (Traefik)

Traefik is the only entry point clients need to know about. It handles:

  • Path-based routing: /api/v1 and /api/graphql to the legacy API; /v2 to the Apollo Router; /identity and /notifications to subgraph REST via Dapr
  • TLS termination in production
  • traceparent propagation: W3C trace context forwarded to downstream services

Clients stay unaware of internal topology. See ADR-001.

GraphQL Router

The Apollo Router composes a supergraph from each subgraph's schema.graphql at startup (in CI via rover supergraph compose).

  • Clients query one endpoint and one schema
  • The router plans and parallelizes fetches across subgraphs
  • No query namespacing: all fields live on the root Query type for single-pass planning

Client SDKs

Shared packages in packages/sdks/ give web and native typed access to backend APIs. Each SDK matches the transport of its backend.

SDKTransportGenerated from
@repo/api-sdkGraphQL (Apollo Client)Legacy API schema (apps/api/generated/schema.graphql)
@repo/identity-sdkREST (openapi-fetch)OpenAPI spec at /v2/identity/openapi.json via openapi-typescript
@repo/notifications-sdkREST (openapi-fetch)OpenAPI spec at /v2/notifications/openapi.json via openapi-typescript
@repo/sanity-sdkSanity clientSanity schema

The legacy GraphQL SDK uses a deliberately tuned Apollo InMemoryCache (possibleTypes + typePolicies) generated from the monolith schema. See API SDK (legacy).

OpenAPI SDKs regenerate types with:

bash
# identity-sdk
pnpm dlx openapi-typescript http://localhost:8888/v2/identity/openapi.json -o ./src/schema.ts

# notifications-sdk
pnpm dlx openapi-typescript http://localhost:8888/v2/notifications/openapi.json -o ./src/schema.ts

Federated GraphQL (v2) clients typically use Apollo Client pointed at /v2/graphql directly. Subgraph schemas are composed at the router, not consumed individually from clients.

Async event flow

Not all data flows are request/response. Live match events use an event-driven path:

StatsPerform SDDP → Match Gateway → Dapr pub/sub (Redis Streams) → Notifications → OneSignal → device
  1. Match Gateway connects to the StatsPerform SDDP WebSocket feed during live matches
  2. It normalizes incoming events (goals, lineups, cards, etc.) and publishes them to Dapr pub/sub topics
  3. Notifications subscribes via its Dapr sidecar (GET /dapr/subscribe) and handles incoming events
  4. Push notifications are delivered through OneSignal; in-app notifications are stored in MongoDB

Dapr decouples publishers from subscribers. Swapping the message broker in production requires only a Dapr component change, not application code. See ADR-005: Message Queue and Docker Compose (Dapr Sidecars).

Sanity webhooks and the legacy API also trigger background work (Algolia indexing, content sync). Those paths are domain-specific and documented in their respective service guides.