GM
EN/ES
Back to production

Multi-tenant cloud platform / Production

Speed FlowLINK

Cloud platform for managing fuel dispensing stations — a Blazor front end, several APIs with separate responsibilities, and a shared multi-tenant data model.

Role: Full stack within the team — device channel, front end, business core, data model and centralization Duration: 5 years Speed Solutions 2021 — present

The problem

A dispensing station combines physical devices, floor operators, self-service, billing and third-party systems. Each speaks a different protocol and has its own life cycle: devices get replaced, operations cannot stop, and one customer's data must never reach another. A single monolithic service would have tied the hardware release cadence to the web interface's.

Speed FlowLINK · services grouped by audience over a shared database System map: five services grouped by the audience they serve, all resting on a shared multi-tenant database. Web interface BLAZOR SERVER Real time SIGNALR Device API HARDWARE SoftAPI SOFTWARE DEVICES Business API DOMAIN · EF CORE Integrations THIRD PARTIES Self-service KIOSK SHARED CONTRACTS Multi-tenant database · owner filter applied at the data layer
Speed FlowLINK · services grouped by audience over a shared database

Constraints

  • Strict per-customer isolation on a shared database
  • Field devices cannot be updated at the same pace as the cloud
  • 24/7 operation — the schema evolves without long maintenance windows
  • Surfaces with opposing requirements — the web tolerates latency, hardware does not

Decisions

The team chose Split the system into APIs by audience (web interface, software devices, hardware, integrations, self-service) over a common database
Instead of One API serving every surface
Because Each audience has its own release cadence and load profile. Separating them means the web can ship without touching the device channel, and the other way round.
The team chose Apply the tenant filter in the repository, taking the context from the authenticated user
Instead of Trusting every query to remember the owner identifier
Because A cross-cutting rule enforced in one place is auditable; scattered across hundreds of queries it is a leak waiting to happen.
The team chose Shared contracts in an abstractions library consumed by every service
Instead of Duplicating DTOs in each API
Because A shared contract costs coordination; duplicating it costs silent divergence between services that must understand each other.
The team chose Schema migrations reviewed and applied under control rather than generated automatically against production
Instead of Letting the migration tool operate unreviewed
Because On a live multi-tenant database, an unreviewed migration is a data incident, not a build error.

Outcome

  • Blazor Server front end consuming the APIs through injected HTTP services, with logic in ViewModels and thin views
  • Data model with per-area prefix conventions — parameters, administration, general, customers and security — that makes the schema readable without opening documentation
  • Real-time device status pushed to the interface over a messaging hub
  • Third-party integration channel isolated from the business core

How the system is split

The solution is not one large service but a set of services grouped by who they talk to, over a common database:

  • Core business API — serves the web interface and holds the domain logic, entities and schema evolution.
  • Device API — the channel to hardware devices, with direct control over them.
  • SoftAPI — a self-contained service for software-based device communication. It shares the database but deploys on its own.
  • Integrations API — the entry point for third-party systems, isolated from the core.
  • Real-time hub — pushes device status to the interface so the browser never has to poll.
  • Self-service — the surface for unattended operation.

That split is the product’s structural decision: it lets the device channel — the one that cannot fail — evolve at a different pace from the web interface.

Multi-tenancy as an invariant, not a convention

Every customer shares the database. Isolation is solved at the data layer: the repository receives the authenticated user’s context and applies the owner filter uniformly. No query decides on its own whether to filter.

It is the kind of rule that, left to each developer’s judgement, works right up until someone writes a new query in a hurry.

How the work runs

The product moves in sprints. Every requirement is refined beforehand with whoever raised it, estimated as a team and taken into the sprint with an agreed scope; what comes out of it goes through peer code review and continuous integration before it touches production.

That rhythm is what makes it possible to sustain several surfaces at once without any of them having a single owner: work rotates, decisions are recorded in the schema and the shared contracts, and cross-review keeps the judgement even across services. My own work inside that is full stack and cuts across the device channel, the interface, the business core, the data model and the data centralization with the floor application.

Each requirement is followed end to end, not just implemented: gathering it with whoever raised it, proposing the solution, building it, testing, continuous integration and seeing the release through.

Why it belongs here

It is the system I have spent the longest with, and where decisions have consequences measurable in years. It shows sustained work on a real product — not an architecture drawn on a whiteboard, but one that survived five years of changing requirements — and how several people hold up a system no one carries alone.

The source code belongs to Speed Solutions. This case study describes architecture and decisions only — no source code, customer data or business figures.