Im April 2020 wurden alle deutschen Healthcare-Startups plötzlich wichtig. Apotheken waren überwältigt. Ärzte machten zum ersten Mal Videosprechstunden. Patienten schickten Papierrezepte per Post ein. Das ganze Land wachte auf und merkte, dass die Rezept-Pipeline — der Weg von einem ausgestellten Rezept bis zur Medikamentenabgabe — fast vollständig analog war. In Hamburg schloss ich mich meinrezept.online an, um das Backend zu bauen, das in der Mitte davon sitzen sollte.
Das hier ist kein “wir haben Leben gerettet”-Post. Es ist ein “so sah die echte Engineering-Arbeit aus”-Post — denn die war tatsächlich interessant, und vieles davon ist von außen unsichtbar.
Das Rezeptformat-Problem
Bevor du ein Rezept an eine Apotheke routen kannst, musst du es lesen. In Deutschland hieß das 2020: mehrere Formate bewältigen. Das Muster-16-Papierformular, frühe digitale Rezeptformate, die noch nicht standardisiert waren, Fax-Bilder und gelegentlich ein PDF, das die Arztpraxis aus welcher Praxissoftware auch immer exportiert hatte.
Jedes davon hat einen anderen Extraktionspfad. Das Papierformular hat ein bekanntes Layout, kommt aber als Scan an. Die digitalen Formate haben strukturierte Daten, aber nicht alle Apotheken konnten alle davon verarbeiten. Das PDF aus der Praxissoftware ist der schlimmste Fall: strukturierte Daten, eingesperrt in einem Dokumentformat, das nie zum programmatischen Parsen gedacht war.
Die Aufgabe des Backends: all das in eine einzige interne Repräsentation normalisieren, die der Rest des Systems — der Apotheken-Routing-Layer, der Order-Tracking-Service, der Patienten-Notification-Service — konsumieren kann, ohne sich um das Eingabeformat zu kümmern. Das ist ein überraschend hartes Problem, wenn sich die Eingabeformate in Echtzeit unter dir verändern — was sie 2020 taten, als die Bundesregierung aktiv den Rollout des Elektronischen Rezepts vorantrieb.
Apotheken-Integration ist by Design legacy-schwer
So sieht der Apotheken-Integrations-Bereich vom Backend aus: Jede größere Apothekenkette hat ihr eigenes Bestellsystem, viele unabhängige Apotheken laufen auf WINAPO, ProFit oder ADG — Verwaltungssystemen, die Jahrzehnte alt sind und Bestellschnittstellen über Protokolle exponieren, die in einem modernen Backend-Kontext niemand kennt. HTTP/JSON ist nicht das dominierende Wire-Format. EDIFACT und HL7-Varianten tauchen auf. Manche Apotheken haben ein Webportal und ein inoffizielles API, das jemand reverse-engineered hat. Manche haben gar nichts, und du bist wieder beim Verschicken einer strukturierten E-Mail.
Das ist keine Beschwerde. Das ist einfach, wie Healthcare-Integration auf der consumer-facing Seite aussieht. Die EHR-Arbeit im Krankenhausmaßstab, die ich anderswo gemacht habe (AFAQ, HAKEEM), operiert auf einer anderen Abstraktionsebene — da arbeitest du mit HL7 FHIR, hast dedizierte Integration-Engines, einen technischen Ansprechpartner auf Apotheken- oder Labor-Seite. Auf der Ebene der Consumer-Apotheken-App arbeitest du oft mit dem, was die Apotheke tatsächlich hat — was häufig deutlich weniger ist als du dir wünschen würdest.
RabbitMQ war der Message Broker, was für diesen Maßstab Sinn macht. Wir machten kein Kafka-Volumen; wir machten zuverlässige Zustellung zwischen einer handhabbaren Anzahl Services mit Retry-Semantik, Dead-Letter-Handling und der Fähigkeit, eingehende Order-Annahme von ausgehender Apotheken-Dispatch zu entkoppeln. RabbitMQ erledigt das sauber. Die Exchange-Topologie — Topic Exchanges, die nach Apothekenregion, Rezepttyp und Erfüllungspfad routen — ließ uns Routing-Logik hinzufügen, ohne jeden Consumer anfassen zu müssen.
Microservices mit drei Sprachen und sechs Wochen
Der Stack war PHP 7, Python und Node.js. Das ist die reale Welt. Du erbst die Sprache, in der dein erster Service geschrieben wurde, du wählst das beste Tool für das nächste Problem (Python für die Extraktionsarbeit, Node.js für das Echtzeit-Status-API), und du landest bei einer polyglotten Codebase, die durch die Message-Contracts zwischen Services zusammengehalten wird und nicht durch Sprachkonsistenz.
Die Message-Contract-Disziplin ist ehrlich gesagt wichtiger als Sprachkonsistenz.
Wenn jeder Service sich darüber einig ist, wie ein order.received-Event
aussieht — das Schema, die Versionierung, die Pflichtfelder — dann ist die
Tatsache, dass der Producer PHP und der Consumer Node.js ist, ein
Implementierungsdetail. Der gefährliche Failure-Mode ist, jeden Service sein
Domainverständnis unabhängig weiterentwickeln zu lassen, sodass order.received
für den Apotheken-Dispatch-Service etwas leicht anderes bedeutet als für den
Patienten-Notification-Service. Das merkst du zum denkbar schlechtesten Zeitpunkt
— nämlich wenn ein Patient beim Support anruft und sagt, er habe eine
“Rezept verschickt”-Benachrichtigung erhalten, aber die Apotheke sagt, es
existiere kein Datensatz über die Bestellung.
ReactJS auf dem Frontend, wofür ich nicht primär verantwortlich war, aber das über das Status-API mit demselben Domain-Modell verbunden war. Konsistenz zwischen dem, was der Patient im UI sieht, und dem, was das Backend-Event-Log sagt, ist ein unterschätztes Integrationsproblem — das sehr sichtbar wurde, als das Pandemievolumen Timing-Lücken aufdeckte, die wir nicht bei dem Maßstab getestet hatten.
Die Pandemie-Timeline verstärkte alles
Im April 2020 einzusteigen bedeutete, einem Team beizutreten, bei dem das Produkt plötzlich durch echte Nutzernachfrage load-getestet wurde, in einer Art, die keine Staging-Umgebung vorhergesehen hatte. Healthcare-Angst ist ein echter Traffic-Treiber. Die eingehenden Order-Spitzen rund um jedes Nachrichtenereignis — eine Lockdown-Ankündigung, geänderte Apothekenöffnungszeiten, ein Nachrichtenartikel über Medikamenten-Engpässe. Man lernt sehr schnell, welche Teile eines Service stateful sind und welche nicht — weil die stateful Teile die sind, die bei Lastspitzen nicht graceful versagen.
Was mich das sehr konkret gelehrt hat: die Extraktions-Pipeline musste vom Order-Acceptance-Pfad entkoppelt werden. Eine langsame Apotheken-Integration sollte nicht verhindern, dass das Rezept eines Patienten empfangen und bestätigt wird. Die Bestätigung und die Erfüllung sind verschiedene Concerns und sollten unabhängig voneinander fehlschlagen können. Diese Änderung haben wir unter Last gemacht — was nicht ideal ist, aber eine echte Engineering-Erinnerung ist.
Die Bandbreite über Healthcare-Domains
Ich habe Healthcare-Software auf zwei sehr unterschiedlichen Skalen geshippt: große Enterprise-EHR- und klinische Workflow-Systeme (Krankenhausmaßstab, hunderttausende Patientenakten, komplexe Clinical Decision Support) und consumer-facing Healthcare-Apps (Rezeptbestellung, Patienten-Notifications, Apotheken-Routing). Sie teilen regulatorische Anforderungen — Patientendaten- Handhabung, Rezeptgültigkeit, Audit Trails — aber sonst kaum etwas.
Die consumer-facing Seite hat schnellere Iterationszyklen, weniger formale Integrationspartner-Beziehungen und mehr Druck, den Long Tail der Edge Cases in den Daten zu bewältigen (das merkwürdige Rezeptformat, die Apotheke, die am Wochenende nicht antwortet). Die Enterprise-Seite hat strukturiertere Integration, formale HL7- und FHIR-Contracts und langsamere Änderungszyklen. Beides ist schwer. Die Bandbreite ist wertvoll.
Ich baue Fulcrum — eine local-first Agent-Control-Plane — auch deshalb, weil die Koordinationsmuster aus verteilten Healthcare-Backends direkt auf Agent-Orchestration anwendbar sind. Zuverlässige Zustellung, Dead-Letter-Handling, Schema-versionierte Event-Contracts: die Probleme sind dieselben, die Domain ist eine andere.