网页
综合
更新时间 2026-11-13 15:39:26
How Trax keeps aircraft maintenance real-time with Quarkus - Quarkus
自动蜘蛛
quarkus.io
正文约 6000 字
Blog How Trax keeps aircraft maintenance real-time with Quarkus
Quarkus turns up in places most people never think to look for it. This one lives on the ramp, in the hangar, and in the hands of the mechanic standing under a wing at 3am.
Trax builds MRO software — Maintenance, Repair and Overhaul — for airlines, cargo operators and independent maintenance providers. If an aircraft is being worked on, something has to track it: the work orders, the task cards, the parts consumed, the sign-offs, the airworthiness records that a regulator can ask to see years later. Trax has been doing that for over 25 years, and today its systems cover thousands of aircraft for customers across dozens of countries.
Historically this was a heavyweight desktop application backed by a very large Oracle database. The modern push is paperless maintenance: get the mechanic off the clipboard and onto an iPad at the aircraft, and give the maintenance controller a live web dashboard instead of a report that was true ten minutes ago. That product line is called eMobility, and the technology behind it is a suite of Quarkus applications.
These services have to serve two very different front ends:
A React web application used by maintenance control and line control staff on desktops.
A Swift/SwiftUI iOS application used by mechanics on iPads out at the aircraft.
Both need the same data, and both need it to be current. When a work order changes status, when a flight’s maintenance crew assignment moves, when a defect is raised against an aircraft, every connected client should know within seconds — without hammering the database with polling.
The wrinkle is that the system of record is the existing Oracle ERP database, and a great deal of what writes to it is not our service. Other Trax applications, batch jobs, and integrations all write to the same tables. We cannot simply fire an event from our own code after a transaction commits, because most of the interesting changes do not originate in our code at all.
So the requirement became: react to changes in the database itself, regardless of who made them, and fan them out to every connected client.
The answer was change data capture, and Quarkus made every hop of it boring in the best possible way:
Debezium tails the Oracle redo log and publishes change events to Kafka. The Quarkus service consumes those topics with Kafka Streams, refreshes whatever cached view the change affects, and then broadcasts a compact "something you care about changed" message to every open WebSocket. Clients re-fetch only what they need over REST.
What I like about this shape is that the payload on the wire is deliberately thin. We are not trying to replicate the database into the browser — we are telling the client which slice of its state is now stale.
The quarkus-kafka-streams extension gives you a Topology producer and Quarkus handles the lifecycle. We wanted adding a new captured table to be a one-class change, so the topology is assembled by discovering CDI beans:
Every handler extends a small base class that takes care of the serdes, the error handling and the logging context, so the subclass only has to say which topic it wants and what to do with a record:
A concrete handler for the work order table is then almost entirely business logic:
Adding capture for a new table is: write one handler, add one topic property. CDI discovery does the rest — there is no registry to remember to update, which is exactly the kind of thing that rots in a long-lived codebase.
The topic names live in configuration rather than in the handlers, so a new capture never touches Java that is already working:
The ${HOSTNAME} in the application id is the detail worth stopping on.
Kafka Streams derives the consumer group from the application id, and a consumer group divides partitions among its members. That is exactly what you want when you are processing work — and exactly what you do not want here. If every pod shared one application id, a change event would be delivered to one pod, and only the users connected to that pod would ever hear about it. We need the opposite: every pod must see every record, so it can notify the browsers and iPads holding open WebSockets against it. This is a broadcast, not a work queue, so each pod needs a consumer group of its own.
The container runtime hands us a unique value for free. Kubernetes sets HOSTNAME to the pod name, and pod names are unique within a namespace — a Deployment appends a random suffix, a StatefulSet an ordinal — so two replicas of the same Deployment land in different consumer groups without any coordination between them. Docker does the same thing with the container id. No config to template per replica, no init container to generate an identity.
Uniqueness alone is not the whole reason, though. A random UUID regenerated at boot would also be unique — and that is precisely the problem. Every restart would strand the old consumer group with its committed offsets and its state store directory, and the pod would come back as a stranger. HOSTNAME is stable for the life of the pod, so a container that crashes and restarts in place rejoins the same group and picks up where it left off. Unique across pods, stable across restarts: those are the two properties an application id actually needs, and the pod name has both.
On the other end, quarkus-websockets-next turned out to be a genuine pleasure. The endpoint is a plain CDI bean with annotated methods:
Two small details in there solved real production problems.
First, keeping a connection alive through corporate firewalls and airport Wi-Fi needs two different mechanisms, because the two clients are not equally capable. A native client can send RFC 6455 ping control frames, and @OnPingMessage / @OnPongMessage hand those to you directly. JavaScript in the browser has no API to send a ping frame at all, so the React client sends a literal "ping" text message instead and @OnTextMessage answers it. Two keepalive strateg
本页为82搜索生成的网页快照,内容来自站内索引;演示数据的原文链接仅作示例。