Eine URL wie /senaite/clients/acme-labs/samples/H2O-2026-0042
läuft nicht durch einen Router. Jedes Segment ist ein echtes
Python-Objekt in der Datenbank, und die URL ist ein Gang durch
den Objektbaum. Dieses Verhalten hat SENAITE nicht gebaut. Es
kommt von Zope, das das seit 1998 tut.
Der Rest von SENAITE ist genauso zusammengesetzt: Bausteine aus langlebigen Open-Source-Projekten, mit einer Laborschicht darüber. Wer die drei Technologien darunter versteht, liest die API anders, plant Backups anders und erweitert das System anders.
Zope: der Webserver, der URLs auf Objekte abbildet
Zope war eines der ersten Python-Web- Frameworks. Siebenundzwanzig Jahre später liefert es weiter reguläre Releases aus und tut immer noch eine Sache, die kein populäres modernes Framework tut: es veröffentlicht Objekte direkt über URLs. Wo Flask oder Django Sie bitten, Routen zu deklarieren, bittet Zope Sie, einen Baum aus Python-Objekten zu bauen. Die URL ist ein Pfad durch diesen Baum.
Jedes Segment ist ein echtes Python-Objekt in der ZODB. Zope läuft den Pfad ab, prüft auf jedem Schritt die Permissions und übergibt das finale Objekt an einen View zum Rendern. Ein Segment mehr heisst ein Kindobjekt mehr.
Das hat für SENAITE drei praktische Konsequenzen:
- Traversal ist umsonst. Ein Client mit hundert Proben mit je fünfzig Analysen ist von Haus aus ein Hundert-mal-Fünfzig tiefer URL-Raum. Keine Route-Tabelle zu pflegen.
- Security ist per Objekt. Jedes Objekt im Baum trägt seine eigene Access-Control-List. Eine Nutzerin, die die Proben eines Clients sehen kann, kann von einer bestimmten Probe ausgeschlossen werden, ohne dass Code geändert werden muss.
- Die Zope Component Architecture (ZCA) ist der Weg, wie Add-ons in den Kern eingreifen, ohne ihn zu patchen. Wenn ein neues SENAITE-Feature ein Marker-Interface, einen Adapter oder einen Subscriber registriert, ist das die Mechanik dahinter. SENAITE zu erweitern heisst zum grössten Teil, die richtige ZCA-Komponente zu registrieren.
ZODB: eine Objektdatenbank ohne Schema
ZODB ist die Speicherschicht von Zope, und
sie nimmt einer Anwendung wie SENAITE eine ganze Problemklasse
ab. Python-Objekte werden gespeichert, wie sie sind — kein
ORM dazwischen, keine Schema-Migration, keine Übersetzung in
Zeilen und Spalten. Ein Sample mit Referenzen auf einen
Client, einer Liste von Analysis-Objekten und einer Batch
wird auf Platte in derselben Form abgelegt, in der es im Speicher
liegt.
Daraus folgen drei Dinge:
- Persistenz ist transparent. Alles, was von einer
persistenten Wurzel erreichbar ist, wird gespeichert. Es gibt
kein
save()am Ende eines Request-Handlers. - Jeder Request ist eine Transaktion. Wirft der Request,
rollt die Transaktion zurück und kein Teilzustand überlebt.
Zwei kollidierende Writes erzeugen einen
ConflictErrorund die unterlegene Seite versucht es erneut. Deshalb bleiben parallele Bearbeitungen eines Worksheets sicher. - Ein Feld hinzuzufügen ist eine Code-Änderung, keine Migration. Bestehende Objekte bekommen das neue Feld mit seinem Default beim nächsten Zugriff.
Der Preis ist real. Es gibt keine SQL-Joins. Alles, was in einer relationalen Datenbank eine Query wäre, ist hier ein Katalog- Lookup — ein Zope-Katalog ist eine indizierte Sicht, die neben den Objekten geführt wird, die sie referenziert. Deshalb tauchen «Katalog» und «Index» in SENAITE-Gesprächen so oft auf, und deshalb ist ein schlecht indiziertes Feld hier ein deutlich teureres Problem als in einem System, in dem man einen neuen Index nachträglich draufsetzen kann.
Plone: Contenttypes, Workflows, Rollen
Zope allein ist eher ein Toolkit als ein System. Plone ist das opinionierte Framework darauf, und es liefert die Bausteine, die den Bau eines LIMS auf diesem Stack überhaupt handhabbar gemacht haben.
- Ein Content-Type-Framework (Dexterity)
zum Definieren von Domänenobjekten mit Schema, Formular-
Generierung und Validierung. Jeder
AnalysisRequest,Client,WorksheetundReportTemplateist ein Dexterity-Type. - Eine Workflow-Engine mit benannten Zuständen, geguardeten Transitions, per-State-Permission-Maps und einer vollständigen Transition-Historie auf jedem Objekt. Deshalb ist «sample received → to be verified → verified → published» ein erstklassiges Konzept, keine Status-Spalte.
- Ein Rollen- und Permission-Modell, das feingranular pro
Objekt, Transition und Feld arbeitet. Eine Sampler-Rolle kann
eine Probe von
scheduled_samplingnachsample_receivedbewegen, aber kein Ergebnis daran editieren; das Modell drückt das direkt aus, nicht über verstreute Permission-Checks im Anwendungscode.
Plone bringt zwanzig-plus Jahre Security-Reviews mit, ein reifes User-und-Gruppen-Modell und das Vertrauen, das aus einem System kommt, das von Regierungen, Verlagen und Universitäten produktiv eingesetzt wird. Für ein Laborsystem, in dem Audit und Zugriffskontrolle nicht optional sind, arbeitet dieses Erbe schwer mit.
Was der Stack in der Praxis heisst
Für Integratoren ist der Objektbaum der Grund, warum das
JSON-API-URL-Schema so aussieht, wie es aussieht
(/@@API/senaite/v1/analysisrequest/<UID>), und warum Referenzen
zwischen Objekten UIDs sind, keine Foreign Keys. Den Baum zu
lesen heisst die API-Oberfläche zu lesen.
Für Administratoren ist die ZODB der Grund, warum ein voll-
ständiges Backup eine einzige Datei ist (Data.fs), und warum
Datenbank-Pack ein geplanter Wartungslauf ist, nichts, was man
vergisst, bis die Platte voll ist. Wachstumstrends und Pack-
Zeitpläne verdienen echte Aufmerksamkeit.
Für Entwicklerinnen, die SENAITE erweitern, ist das Muster: Marker-Interface für eine neue Fähigkeit, Adapter für Verhalten, Dexterity-Type für neuen Content, XML plus Python-Guards für neuen Workflow. Alles Weitere komponiert sich daraus.
Nichts davon ist exotisch. Es ist ein Stack, der seit zwei Jahrzehnten produktiv läuft. Und das ist genau der Punkt — ein Laborsystem ist ein schlechter Ort, um Early Adopter zu sein.
Weiterführend
Offizielle Dokumentation zu den Bausteinen: