#!/usr/bin/env python3 # erzeuge_architektur_diagramme.py """ Erzeugt die beiden Architekturdiagramme fuer den Weg vom Skript zum Betrieb: bilder_04/kap_praxisfallen_schichten.svg / .png Schichten von or_kern.py bilder_04/kap_testing_dienst.svg / .png der Dienst und sein Ablauf Beide bilden ab, was in den Programmen tatsaechlich steht - nicht ein allgemeines Architekturschema: Schichten - die sechs Abschnitte von or_kern.py, in der Reihenfolge ihrer Abhaengigkeiten. Die Aussage steckt in der Pfeilrichtung: Alles zeigt nach innen auf die Domaene, und die Domaene zeigt auf nichts. Genau deshalb laesst sich der Solver austauschen, ohne das Fachmodell anzufassen. Dienst - der Ablauf in Optimierungsdienst.py. Die Aussage ist die 202-Antwort: Wer eine Optimierung synchron beantwortet, baut einen Dienst, der unter Last umkippt. Beide entstehen mit Graphviz, in derselben Palette wie die Entscheidungsdiagramme (siehe erzeuge_solverwahl_diagramme.py). Aufruf (aus dem Repository-Wurzelverzeichnis): python3 bilder_04/erzeuge_architektur_diagramme.py Benoetigt: graphviz (Python-Paket) und das Kommandozeilenwerkzeug 'dot' """ from __future__ import annotations import os import graphviz BASIS = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) BILDER = os.path.join(BASIS, "bilder_04") INDIGO, CYAN, VIOLETT, GRUEN, AMBER = ( "#4338ca", "#0891b2", "#7c3aed", "#059669", "#b45309") TINT = {INDIGO: "#eef2ff", CYAN: "#ecfeff", VIOLETT: "#f5f3ff", GRUEN: "#ecfdf5", AMBER: "#fffbeb"} def neuer_graph(titel: str, richtung: str = "TB") -> graphviz.Digraph: punkt = graphviz.Digraph(format="svg") punkt.attr(rankdir=richtung, splines="polyline", nodesep="0.35", ranksep="0.5", fontname="DejaVu Sans", label=titel, labelloc="t", fontsize="13", bgcolor="transparent") punkt.attr("node", fontname="DejaVu Sans", fontsize="10") punkt.attr("edge", fontname="DejaVu Sans", fontsize="9", color="#5c6380") return punkt def kasten(punkt: graphviz.Digraph, name: str, text: str, farbe: str, form: str = "box") -> None: punkt.node(name, text, shape=form, style="filled,rounded", fillcolor=TINT[farbe], color=farbe, penwidth="2.0", margin="0.16,0.09") def schreibe(punkt: graphviz.Digraph, name: str) -> None: os.makedirs(BILDER, exist_ok=True) for endung in ("svg", "png"): punkt.format = endung pfad = punkt.render(os.path.join(BILDER, name), cleanup=True) print(f"geschrieben: {pfad}") # --- 1. Die Schichten von or_kern.py ---------------------------------------- def schichten() -> None: punkt = neuer_graph("Die Schichten von or_kern.py — alle Pfeile zeigen nach innen") kasten(punkt, "io", "Ein- und Ausgabe\n" "lade_produktionsproblem() · schreibe_ergebnis()\n" "Excel, CSV — die Wirklichkeit des Anwenders", AMBER) kasten(punkt, "bau", "Modellbauer — austauschbar\n" "loese_mit_glop() · loese_mit_scipy()\n" "je Solver einer, gleiche Signatur", VIOLETT) kasten(punkt, "pruef", "Abnahmeprüfung\npruefe_loesung()\n" "prüft die Lösung OHNE Solver", GRUEN) kasten(punkt, "erg", "Ergebnis — solverunabhängig\n" "SolverStatus · Loesung\n" "fünf Bibliotheken, eine Sprache", CYAN) kasten(punkt, "dom", "Domäne\nProdukt · Produktionsproblem\n" "validiert (Pydantic), kennt keinen Solver", INDIGO) punkt.edge("io", "dom", label="liest ein, schreibt zurück") punkt.edge("bau", "dom", label="baut das Modell aus") punkt.edge("bau", "erg", label="liefert") punkt.edge("pruef", "dom", label="prüft gegen") punkt.edge("pruef", "erg", label="prüft") punkt.edge("erg", "dom", style="invis") # haelt die Ebenen sauber with punkt.subgraph() as gleich: gleich.attr(rank="same") gleich.node("bau") gleich.node("pruef") punkt.node("regel", "Die Domäne importiert nichts von außen.\\l" "Deshalb kostet ein Solverwechsel eine Funktion —\\l" "und kein einziges Fachdetail.\\l", shape="note", style="filled", fillcolor="#f8fafc", color="#94a3b8", fontsize="9", margin="0.18,0.12") punkt.edge("dom", "regel", style="dashed", color="#94a3b8", arrowhead="none") schreibe(punkt, "kap_praxisfallen_schichten") # --- 2. Der Optimierungsdienst ---------------------------------------------- def dienst() -> None: punkt = neuer_graph("Optimierungsdienst: warum die Antwort 202 lautet, nicht 200") kasten(punkt, "klient", "Aufrufer\n(curl, Fachanwendung)", CYAN) kasten(punkt, "post", "POST /plaene\nJSON mit dem Problem", INDIGO) kasten(punkt, "valid", "Pydantic prüft\ndie Eingabe", VIOLETT, form="diamond") kasten(punkt, "fehler", "422\nFehler benannt,\nbevor ein Solver läuft", AMBER) kasten(punkt, "an", "202 Accepted\n+ Auftrags-ID", GRUEN) kasten(punkt, "rechne", "Hintergrundrechnung\nrechne() → or_kern", VIOLETT) kasten(punkt, "get", "GET /plaene/{id}\nlaeuft · fertig · fehlgeschlagen", INDIGO) kasten(punkt, "gesund", "GET /gesundheit\nfür die Überwachung", CYAN) punkt.edge("klient", "post") punkt.edge("post", "valid") punkt.edge("valid", "fehler", label="ungültig") punkt.edge("valid", "an", label="gültig") punkt.edge("an", "rechne", label="startet") # constraint=false: Die Rueckmeldung darf die Rangfolge nicht umsortieren, # sonst wandert der Aufrufer unter den Dienst. punkt.edge("an", "klient", label="sofort zurück", style="dashed", constraint="false") punkt.edge("klient", "get", label="fragt nach") punkt.edge("rechne", "get", label="hinterlegt das Ergebnis", style="dashed") punkt.edge("klient", "gesund", style="dotted") with punkt.subgraph() as gleich: gleich.attr(rank="same") gleich.node("fehler") gleich.node("an") punkt.node("regel", "Eine Optimierung dauert Sekunden bis Minuten.\\l" "Synchron beantwortet, blockiert sie den Dienst\\l" "und läuft in jedes Gateway-Timeout.\\l", shape="note", style="filled", fillcolor="#f8fafc", color="#94a3b8", fontsize="9", margin="0.18,0.12") punkt.edge("rechne", "regel", style="dashed", color="#94a3b8", arrowhead="none") schreibe(punkt, "kap_testing_dienst") if __name__ == "__main__": schichten() dienst()