Platform Engineering

Building a desktop platform is like building an operating system

Most engineers think desktop platforms are about windows and toolbars. The hard parts are identity, communication, deployment, versioning and convincing hundreds of applications to coexist without setting fire to each other.

· 10 min read

Editorial cover: a row of desktop application windows floating above a single platform runtime slab, joined by context, messaging and deployment channels Four small desktop application windows, each drawn with a title bar and three dots, float across the upper half. They are linked to a central node marked shared context by teal lines, and to each other by faint crimson dashed messaging lines. Below them runs a single wide slab labelled platform runtime, carrying the platform services: window management, interop, identity, discovery, deployment and security. Upward arrows mark deployment flowing from the slab into the windows. The whole arrangement reads as a small operating system, with the windows as the easy, visible part resting on the slab that does the real work. THE WINDOW IS THE EASY BIT THE PLATFORM IS THE OPERATING SYSTEM shared context PLATFORM RUNTIME window mgmt interop identity discovery deployment security the one runtime they all stand on context messaging deployment It just happens to be written, mostly, in JavaScript.

Most engineers spend their careers building applications. A much smaller number spend theirs building the thing those applications run on, and the two jobs look far more alike than they are.

From the outside a desktop runtime sounds like a weekend project. Open a few windows, render some content, bolt on a menu bar, ship it. That illusion tends to survive until roughly the first week in production, at which point the actual shape of the problem arrives, usually at an inconvenient hour.

I spent years working alongside front-office tools on a trading floor, which is one of the last places where the desktop never went away. A trader runs twenty applications at once, from six vendors and four internal teams, and expects every one of them to agree on which client they are currently looking at. Make that work and nobody notices. Get it wrong and you find out immediately, because someone just acted on a stale ticket.

The browser window, it turns out, is the easy bit.

Everything arranged around it is where the engineering lives.

The problem nobody sets out to have

No sane organisation wakes up and decides to build a desktop platform for the joy of writing infrastructure. The platform is what you are left with after the alternatives fail.

It usually starts with applications. Twenty of them, then fifty, then a number nobody can state with confidence. Different teams, different frameworks, different release cycles, all built in good faith and total isolation. Then a user does the most reasonable thing in the world: they try to move a piece of information from one application into another.

And the questions start.

How do these things talk to each other? How does picking a client in one window update the other six? Where do windows open, and what happens when the monitor they remember has been unplugged? How does any of this get upgraded without a fortnight of downtime? Who is allowed to read what?

What looked like a folder full of applications has, without anyone deciding it, started behaving like an operating system. At that point you have two options: pretend the problem isn't there, or build the platform. Most organisations spend a year on the first option before committing to the second.

The runtime was never the interesting part

Whenever desktop platforms come up, the conversation goes straight to the runtime. Electron. OpenFin. CEF. Whatever native shell someone is championing this quarter.

Choosing a runtime matters, in the way choosing a foundation matters. But it answers exactly one question: how do I execute applications consistently? That is the first question, not the hard one. The hard questions all turn up afterwards, once the runtime has been picked and everyone has moved on assuming the difficult part is behind them.

It is not. It has barely started.

A vertical stack with a small box labelled Application window on top and a box labelled Runtime, Electron, OpenFin or CEF at the bottom. Between them sits a large band labelled Platform services holding six pills: window management, interop and shared context, identity and trust, discovery, deployment and versioning, security and permissions. A note marks the top and bottom as the part people picture, and the middle band as the part that takes ten years.

Window management is deceptively nasty

Every engineer believes they understand windows, because opening one is a single line of code. Managing a few hundred of them across a few thousand machines is a different discipline entirely, and it is unreasonably full of edge cases.

Which monitor should this window open on, and what is the correct behaviour when that monitor no longer exists? Should the layout restore itself, and to what state? Can one trader hand their entire screen arrangement to another? What happens to all of it after a runtime upgrade nudges every coordinate by a few pixels?

The naive version is famous:

window.open("/orders");

The version that survives contact with real users looks less like a window and more like a small workflow engine:

platform.windows.open({
  id: "orders",
  workspace: "trading",
  restoreState: true,
  monitor: "current",
});

The first call creates a window. The second tries to manage a human being's working day, the same one I once spent my time keeping under a hundred milliseconds. Those are not the same problem, and most of the cost lives in the gap between them.

Context matters more than messaging

Here is the lesson that takes longest to learn and saves the most pain once learned: application communication is not the problem you are actually trying to solve.

The problem is shared context.

Picture a trader selecting a client. A chart cares. An order ticket cares. A risk dashboard, a news feed and a P&L blotter all care. The naive instinct is to fire a message and let everyone sort themselves out:

bus.publish("selectedClient", client);

This works beautifully in a demo and rots steadily in production. Every application ends up knowing the names, quirks and message shapes of every other application. Change one and you risk all of them. Within a year the system has acquired the property every engineer recognises and dreads: nobody will touch it, because nobody can predict what falls over when they do.

Two panels, each with five application nodes labelled chart, ticket, risk, news and blotter. On the left, under point-to-point messaging, every node is wired to every other node by a tangle of ten crossing lines, annotated change one, risk all of them. On the right, under shared context, every node connects only to a single central hub labelled client number 12345, annotated change the fact, nobody else moves.

The fix is to stop publishing to each other and start agreeing on context.

platform.context.set({ type: "client", id: "12345" });

Now no application cares who set the context. They care only about the context that currently exists. The chart redraws, the ticket repopulates, the blotter refilters, and not one of them holds a reference to the others. This is the entire idea behind FDC3, the interop standard that grew out of exactly this pain on exactly these desks, and the distinction runs deeper than it first looks.

Coupling applications to a shared fact is survivable. Coupling them to each other is not.

The runtime APIs quietly become a product

There is a moment, usually arriving by accident, when the platform team realises it is no longer building infrastructure. It is building an API, and an API has customers.

Application developers do not want to think about window lifecycles, message routing, runtime compatibility or discovery services. They want capability, and they want it in one line:

platform.notifications.show(notification);
platform.windows.open(spec);
platform.context.set(context);
platform.interop.publish(channel, payload);

The instant developers depend on those four lines, everything changes. Documentation stops being a nicety. Backwards compatibility stops being optional. A breaking change is no longer a refactor - it is an outage with someone else's name on it. The platform team finds itself facing the exact problems of an external software vendor, except the customers sit two desks away and can walk over.

I have argued before that the best platforms make the right thing the easy thing, and a desktop runtime is that argument with the volume turned up. The API is the golden path. If it is pleasant, people build on it. If it is not, they route around it, and a platform nobody builds on is just an expensive way to open windows.

Deployment is where the theory meets the floor

Architecture diagrams give deployment about half a box, which is roughly the inverse of where the difficulty lives. A desktop platform stacks several versioning problems on top of each other, and each layer insists on evolving to its own schedule.

Four stacked rows that each ship independently. The top row, runtime, advances through versions 8 to 12. The second, platform APIs, advances through versions 3 to 5. The third, applications, holds a mix of apps built against runtime versions 8, 11, 9 and 12. The bottom, user workspaces, holds saved layouts that assume older versions. A brace notes each layer ships on its own clock, above a callout: the hard question is never what is the latest version, it is what still has to keep working.

So answer the questions that keep platform teams honest. Can runtime 12 still run an application built against runtime 8? Can one application upgrade without dragging the others with it? How fast can you roll the whole estate back when a release goes wrong at half past eight on a Monday?

These read like operations questions. They are really architecture questions - the answers were decided years earlier, in the design, whether or not anyone realised they were deciding them. A platform people trust to upgrade cleanly gets adopted. One that has ever eaten a trader's layout gets avoided for a decade, and they will remind you of it at every opportunity.

Maintaining old behaviour you would love to delete is also where most of the bill ends up, which is the same point I keep making about technical debt usually being organisational debt, arriving from a different direction.

Security stops being a boundary and becomes a question

Most web applications live inside a fairly clean security perimeter. The browser draws a hard line and you mostly reason within it. Desktop platforms erase that comfort. The entire point is that applications coexist, share context and call shared capabilities, which moves every interesting question to what is allowed to flow between them.

Which applications may talk to which? Who can read the shared context? Can one application impersonate another, and would you ever know? How is a capability granted, revoked and audited?

The job is to balance two failure modes that pull in opposite directions. Lock it down hard enough to be safe and nobody adopts the platform, because it has become useless. Open it up enough to be useful and you have built a lovely environment for one compromised application to read the entire floor's activity. The correct setting sits somewhere in the middle, it is never obvious, and it moves.

The platform becomes the product

Something inverts as a desktop platform matures. The applications, the visible things everyone assumed were the point, start to matter less. The platform underneath them starts to matter more.

Developers begin to judge you on deployment speed, runtime stability, documentation quality, API consistency, tooling and observability. Users, who never see any of that, judge you on startup time, whether their windows land where they left them, and whether the thing stays up during the European open. The platform becomes the floor every other team stands on, and floors get noticed only when they give way.

Success here is invisible. Failure is the only part anyone sees.

The same problems, all the way down

The genuinely strange thing about building a desktop platform is how little of it is about desktops. Set the runtime aside and the problem list could belong to almost any platform ever built.

Whether you are assembling a desktop runtime, an internal developer platform, a cloud, a Kubernetes setup or an actual operating system, the same themes keep surfacing. Identity. Discovery. Communication. Versioning. Security. Deployment. Backwards compatibility. The vocabulary changes with the domain. The trade-offs do not move an inch.

Swap every noun in that list for its cloud or Kubernetes equivalent and the sentences still hold. The implementation details are where teams spend their days, and they genuinely differ. But the shape of the problem was fixed long before anyone chose a runtime, and it is the same shape underneath all of them. New surface, old structure.

Final thoughts

Desktop platforms get written off as glorified browser containers, and the description is accurate about precisely the least interesting part. The window is a rounding error. The real work is building somewhere stable enough that applications written by different teams, in different years, to different assumptions, can coexist, communicate, evolve and ship safely for a decade without setting fire to one another.

Do that for long enough and the truth gets hard to avoid. You are not building a desktop platform any more.

You are building a small operating system. It just happens to be written, for reasons nobody is entirely proud of, mostly in JavaScript.

Further reading

  • FDC3 (FINOS): the interop standard that exists because every trading floor independently invented the same context bus and eventually got tired of doing it badly. Read the bit on intents and the context diagram above will click.
  • The Electron process model: a clear tour of the main and renderer split, which is the one runtime decision you will still be living with long after you have forgotten making it.
  • Raymond Chen, The Old New Thing: decades of war stories about keeping ancient applications running on new versions of Windows. If you ever want to feel something about backwards compatibility, this is the place to go.

Human (you) in the loop

New writing, now and then

Occasional notes on platform engineering, building dependable software and that constant buzz word we doom scroll past on LinkedIn! No cadence promised.

Prefer a reader? Subscribe via RSS.

← All Articles

Jump to

32 articles