GM
EN/ES
Back to projects

Multi-tenant cloud platform / Production

SpeedFlowLink

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

Role: Backend, front end, data model and integrations 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.

SpeedFlowLink · 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
SpeedFlowLink · 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

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.
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.
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.
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.

What I worked on

Integrations, backend, front end and data model. That means writing endpoints and domain services, building Blazor screens with their ViewModel, designing and evolving tables, and — what taught me most — keeping all three consistent when a business change cuts across them.

The full path, not just implementation: gathering the requirement with whoever raised it, designing 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.

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