Die meisten Engineers werden nie produktives MUMPS schreiben. Ich tat es — in VistA’s Pharmacy- und Billing-Packages, auf Linux-basiertem fis GT.M, in einem Maßstab, der die nationale Gesundheitsversorgung abdeckte. Jedes Mal, wenn ich das auf einer Konferenz sage, fragt jemand, ob ich einen Witz mache. Ich mache keinen Witz. So hat es wirklich ausgesehen.

VistA ist keine Abstraktion, es ist eine Codebase

VistA (Veterans Health Information Systems and Technology Architecture) ist das Open-Source-EHR der VA. Es ist seit den 1970ern in kontinuierlicher Entwicklung. Sein Pharmacy-Package ist eines der ältesten und am meisten kampferprobten Medikamentenausgabe-Systeme auf dem Planeten. Wenn ich “kampferprobt” sage, meine ich: Es hat 40+ Jahre VA-Programmierer, Budgetzyklen, MUMPS-Versionen, Hardware- Migrationen und den Kongress überlebt.

Die Codebase ist nicht schön. Sie ist nicht designed. Sie ist akkretiert — geschichtet, gewachsen, gepatcht, erweitert von Menschen, die meist tot oder im Ruhestand sind. Routinen haben Comment-Header von 1987. Du wirst ; MODIFIED BY DPT 3/12/89 — DO NOT DELETE finden, und du wirst absolut nicht löschen, was darunter steht, weil du keine Ahnung hast, was es tut, und niemand mehr, der noch angestellt ist, das auch hat.

Das ist, wie Produktion aussieht, wenn sie 40 Jahre Uptime hat.

GT.M ist MUMPS, aber es läuft auf Linux und es ist sehr schnell

Wenn Leute “MUMPS” sagen, meinen sie in der Regel eine von zwei Runtimes: Caché (jetzt IRIS) von InterSystems oder GT.M von FIS (früher Greystone Technology, daher das G). GT.M ist Open-Source, läuft auf Linux und ist das, was die meisten VistA-Installationen verwenden. Es ist die Runtime, die die VA benutzt. Es ist die Runtime, die ich benutzte.

GT.M ist — und ich möchte hier vorsichtig sein — als Storage-Engine wirklich beeindruckend. Es implementiert MUMPS-Globals als B-Tree auf der Festplatte mit Journal-Level-Crash-Safety. Ein SET ^PSDRUG(drugIen,"QTY")=qty ist kein Round- Trip zu einem Datenbank-Server. Es ist ein Write in einen lokalen B-Tree, den GT.M flusht und journalt. Der Prozess, der die MUMPS-Routine ausführt, und die Storage-Engine sind derselbe Prozess. Es gibt keinen Netzwerk-Hop. Es gibt kein ORM. Es gibt keinen Query-Planner.

Das klingt wie ein Spielzeug, bis man sich Dispensing-Throughput-Benchmarks ansieht und sich fragt, warum ein System aus den 1990ern auf bescheidener Hardware eine schicke Spring-Boot-API auf einem Write-Heavy-Workload schlägt. Dann klickt es: zwischen der Anwendungslogik und den Bits ist nichts.

Wie das Schreiben von Pharmacy-Routinen wirklich aussah

Das Pharmacy-Package verwaltet Medikamenten-Inventar, Ausgabeaufträge, Medikamenten-Interaktions-Checks und IV-Admixture-Datensätze. In MUMPS. Auf ^PS*-Globals (PS = Pharmacy System).

Eine Ausgabe-Routine sieht ungefähr so aus:

DISPENSE(DFN,DRUG,QTY) ;dispense DRUG to patient DFN
 N RESULT,AVAIL
 L +^PSDRUG(DRUG,"STOCK"):5 E  D ERRLK Q
 S AVAIL=$G(^PSDRUG(DRUG,"QTY"))
 I AVAIL<QTY D INSUF Q
 S ^PSDRUG(DRUG,"QTY")=AVAIL-QTY
 S ^PSDRUG(DRUG,"LAST")=$$NOW^XLFDT()
 S RESULT="OK"
 L -^PSDRUG(DRUG,"STOCK")
 Q RESULT

Geh ruhig und starre das an. Ich warte.

N ist NEW (lokaler Variablen-Scope). L +^GLOBAL:timeout ist ein Lock-Acquire. $G() ist GET mit Default (nie Null-Pointer-Fehler, MUMPS hat $G). S ist SET. Q ist QUIT. $$ ruft eine extrinsische Funktion auf — $$NOW^XLFDT() ruft das NOW-Label in der XLFDT-Routine auf, die einen Timestamp im FileMan-Format zurückgibt (FileMan-Datum ist ein ganzer anderer Post, lass mich nicht damit anfangen).

Das Global ^PSDRUG ist hierarchisch: der Top-Level-Subscript ist die Drug Internal Entry Number (IEN), und darunter gibt es benannte Sub-Keys wie “QTY”, “STOCK”, “LAST”. Das ist das Schema. Es gibt keine Schema-Datei. Das Schema ist implizit im Code. Man lernt es, indem man Code liest und mit dem GT.M-Utility D ^%G auf Globals starrt.

Drug-Interaction-Checking ist ein spezifisches Biest

Der angsteinflößendste Code, den ich in VistA schrieb, war rund um Drug-Interaction- Checks. Die Globals ^PSSDI und ^PSDRUG halten Interaktionsdaten, und das Pharmacy-Package hat Routinen, die feuern, bevor ein Ausgabeauftrag bestätigt wird, um zu prüfen, ob das neue Medikament mit irgendetwas in der aktiven Medikamentenliste des Patienten interagiert.

Was das stressig macht, ist nicht das MUMPS. Das MUMPS ist nur Syntax. Was das stressig macht, ist dass die Logik das Sicherheitsnetz ist. Es gibt keinen nachgelagerten Service, der deine Arbeit doppelt prüft. Die Routine läuft, der Apotheker sieht das Ergebnis, und wenn deine Interaction-Check-Logik einen Bug hat, wird eine Medikamenten-Kombination, die hätte markiert werden sollen, ausgegeben.

Ich testete diese Routinen obsessiv. GT.M’s M-Unit (ein Test-Framework, das wie JUnit aussieht, wenn JUnit in 1995 designed worden wäre) wurde mein bester Freund. Ich richtete Szenarien mit bekannten Interaktionen ein — Warfarin und Aspirin, Methotrexat und NSAIDs — und ließ sie laufen, bis die Flags konsistent waren. Das ist wahrscheinlich das Sorgfältigste, das ich in meinem professionellen Leben je bei einem Stück Code war.

Das HL7-Messaging-Package ist, wo es weird wurde

VistA’s HL7-Package datiert in manchen Fällen Jahre vor der HL7-v2-Spec-Bereinigung. Die Routinen, die HL7-Nachrichten parsen und generieren, machen String-Slicing in MUMPS — $E(MSG,start,end) zum Extrahieren von Segmenten, _ zum Konkatenieren, Piece-Funktionen zum Splitten auf Delimiter.

Was ich dort fand: Edge Cases in Patient-Demografik-Segmenten, die mit ; KLUDGE — MO WILL FIX LATER-Kommentaren von Leuten behandelt wurden, die offensichtlich nie zurückkamen, um sie zu fixen. Ich fixte einige. Für den Rest hinterließ ich Notizen. So überlebt Legacy-Code — nicht durch Aufräumen, sondern durch Anhäufen zunehmend informativer Kommentare rund um die lasttragenden Kludges.

Die GT.M-DB-Connector-Arbeit bei AFAQ

Nach meiner VistA-Zeit bei EHS wechselte ich zu AFAQ, wo wir eine EHR/EMR-Suite teilweise auf VA-VistA-Komponenten aufbauten. Die Lücke, auf die ich sofort stieß: GT.M hatte keinen modernen Datenbank-Connector. Kein JDBC. Kein REST-Adapter. Keine Möglichkeit für eine Java-App oder ein Web-Frontend, mit ihm zu reden, ohne in rohes MUMPS zu fallen oder eine uralte TCP-basierte RPC-Schicht zu verwenden, die aus den 90ern stammte.

Also baute ich neue Connectors. Die Herausforderung ist, dass GT.M’s nativer Zugriffsmechanismus entweder In-Process-MUMPS oder sein $gtm_dist C API ist — eine C-Bibliothek, die es ermöglicht, GT.M-Routinen aufzurufen und auf Globals aus C zuzugreifen. Wir packten das in eine JNI-Schicht, damit das Spring-Boot- Backend es direkt aufrufen konnte. Es war nicht schön. Das Error-Handling über die JNI-Grenze war eine besonders kreative Erfahrung. Aber es funktionierte, und es reduzierte die Round-Trip-Latency genug, dass die Chart-Load-Zeit des EHR von “peinlich” auf “akzeptabel” sank.

Die tiefere Erkenntnis aus diesem Projekt: GT.M ist schnell, weil es einfach ist. Jede Abstraktionsschicht, die man darüber legt — REST, JNI, TCP RPC — kostet einen Teil dieser Einfachheit. Der Trick ist, genau genug Abstraktion hinzuzufügen, damit das konsumierende System mit ihm reden kann, und nicht mehr.

Was ich mitnahm

Produktives MUMPS in einem nationalen Healthcare-System zu schreiben, ist nichts, das ich als Karrierepfad empfehle. Ich würde es auch nicht eintauschen.

Das ist, was es mich gelehrt hat, was moderne Backend-Arbeit nicht lehrt:

  • Storage-Kosten sind immer präsent; die meisten Sprachen verstecken sie nur. In MUMPS ist jedes S ^global(key)=value eine Storage-Operation. Man vergisst nie, dass Writes etwas kosten. In Java vergisst man es ständig, bis eine Hibernate-Session anfängt, 400 Queries pro Request zu machen.
  • Schema-Design ist immer noch Design, auch wenn es keine Schema-Datei gibt. Die Hierarchie deiner MUMPS-Global-Subscripts ist dein Datenmodell. Schlechte Key-Auswahlen kaskadieren in schlechte Zugriffsmuster, die man nicht fixen kann, ohne alles neu zu schreiben, was diese Globals liest.
  • Crash-Safety ist wichtiger, als du denkst. GT.M’s Journaling bedeutet, dass VistA-Installationen jahrelang ohne Datenkorrektionsereignisse laufen. Meine Postgres-Datenbanken erfordern sorgfältige Transaktionshygiene, um dieselbe Garantie zu erreichen. Das MUMPS-Modell macht den sicheren Pfad zum Standard-Pfad.

Ich schreibe jetzt hauptsächlich Java und TypeScript. Meine Datenbanken sind Postgres. Meine Runtimes haben GCs. Aber wenn ich ein Schema-Design reviewe oder über Transaktionsgrenzen streite, ist ein Teil meines Gehirns noch in dieser GT.M- Shell, starrt auf ^PSDRUG-Globals und denkt darüber nach, wie die Reads um 3 Uhr morgens bei einem Dispensing-Surge aussehen werden.

Das ist keine Nostalgie. Das ist Ausbildung.