Zum Behandeln von HTTP Anfragen muss man für Flask jeweils Eine Funktion
schreiben die eine Anfrage auf eine konkrete Ressource behandelt.
Diese Funktionen werden oft View-Functions genannt.
Ich finde den begriff treffend und werde ihn daher auch benutzen.
Eine View-Function sollte m.E. immer eine Art von Abfrage behandeln.
Negativbeispiel sind hier die viel zu findenden Beispiele zum Handhaben von
Formularen.
- @app.route("/login", methods=["GET", "POST"])
- def login():
- if request.method == 'POST':
- username = request.form['username']
- password = request.form['password']
- if password == username + "_secret":
- id = username.split('user')[1]
- user = User(id)
- login_user(user)
- return redirect(request.args.get("next"))
- else:
- return abort(401)
- else:
- return Response('''
- <form action="" method="post">
- <p><input type=text name=username>
- <p><input type=password name=password>
- <p><input type=submit value=Login>
- </form>
- ''')
Die erfahrung zeigt, dass mangels anderer Beispiele diese Beispiele schon aus
pragmatischen Erwägungen (
get things done) übernommen werden. Dadurch wird
weiter ausufernden Verzweigungen innerhalb einer View-Function vorschub
geleistet. Es entsteht schnell schwer lesbarer, und noch unübersichtlicher und
damit schwerer testbarer Code. Resultat, änderungen werden irgendwann schon
allein deshalb vermieden, weil man fast immer etwas kaputt macht.
Das obige Beispiel lässt sich recht leicht umbauen:
- @app.route("/login", methods=["POST"])
- def login():
- username = request.form['username']
- password = request.form['password']
- if password == username + "_secret":
- id = username.split('user')[1]
- user = User(id)
- login_user(user)
- return redirect(request.args.get("next"))
- else:
- return abort(401)
-
- @app.route("/login", methods=["GET"])
- def get_login_form
- return Response('''
- <form action="" method="post">
- <p><input type=text name=username>
- <p><input type=password name=password>
- <p><input type=submit value=Login>
- </form>
- ''')
Das schaut schon besser aus ist für mich aber noch nicht wirklich befriedigend.
Es verleibt ein
if .. else
Block in der login Funktion die
request
und
redirect
integriert. Das ist eine Verletzung vom
IOSP.
Ralf Westphal hat hierzu ein sehr schönes ebook verfasst:
Messaging as a Programming Model.
Nach meinem Verständis befindet sich eine Flask View-Function am Stamm des von
Ralf Westphal in diesem Buch skizzierten Baums der Funktionsaufrufe.
Oder wenn man sich das in Schichten vom Aufruf des Programms bis zu den ganz unten liegenden Operationen vorstellt stellt die View-Function einen Teil der
Oberfläche dar.
Wichtig bei den unten liegenden Operationen ist, dass sie dem IOSP oder noch
besser dem
Prinzip der gegenseitigen Nichtbeachtung (PoMO) folgen.
Aber ich interessiere mich gerade nicht für die Blätter, wie Herr Westphal die
Operationen in seinem Buch nennt, sondern für die Integrationsteile.
Kleine Abschweifung zum Thema bessere Ausbildung von Fachkräften: spoiler
Ich gebe offen zu mich nicht hinreichend Ausgebildet zu fühlen, denn sonst würde
ich das Refactoring des oben stehenden Beispieles ebenso einfach aus dem Ärmel
schütteln wie das Beispiel selbst. Es wäre ebenso naheliegend.
In der Berufsschule hatte ich genau einen Lehrer, der uns Clean-Code
prinzipien ans Herz gelegt hat. Das war im dritten Lehrjahr. Er war auch der
erste, der mit uns ausführlich Design-Pattern
besprochen hat. Das halte ich für reichlich spät. Programmieren lernen reicht
nicht um gut anwendungen zu entwickeln. Nach der Berufsausbildung bleibt im
Berufsalltag oft nur wenig Zeit sich mit diesen Themen explorativ
auseinander zu setzen. Dabei ist der explorative Anteil dabei besonders wichtig,
denn eine Tätigkeit wird eben nur über das Machen zur Routine.
Hier liefert mir das Buch von Herrn Westphal einen guten Ansatz, der mir aber
so noch nicht ausreicht. Schön zum Entwerfen auf Papier, alles klar, aber leider
etwas zu Abstrakt.
Aber er bezieht sich auf Steve Bate mit dem der zu dem Thema auch eine
Korrespondenz geführt hat.
Steve Bate hat zu
Messaging as a programming modeleinen sehr langen und guten Blog Artikel geschrieben, zu dem es auch einen
zweiten Teil gibt.
Hier bekomme ich Lösungsansätze für das Problem.
Ich vermute man kann in der View-Function sehr gut eine Pipeline nach dem
Pattern
Pipes and Filtersintegrieren. Wir geben aber zwei unterschiedliche Flask-Respnse Objekte zurück.
Entweder, wenn der Login erfolgreich ist einen Redirect oder einen Abort.
Ich sehe zwei Herangehensweisen für das Problem.
- Es muss einen Filter geben, der aus dem egebnis der Login Prüfung einen der
beiden Responses erzeugt. Damit muss dieser dann aber wieder wisse über die Kontrolllogik für die empfangende Nachricht und das Response Object haben.
Damit wäre mein Problem nicht gelöst sondern nur in eine Tiefere Ebene
Verschoben. - Es muss einen Filter geben der die Login Nachricht bei Fehlschlag an
einen Filter weiter leitet der den Response für einen Abort und bei
Erfolg an einen Filter weiter leitet der den Response für einen Redirect
erzeugt.
Ansatz 1 ist vor allem besonders Unbefriedigend, da er auch noch gegen
"Simple is better than complex." aus
"The Zen of Python" verstößt.
Das müssen wir bei Lösung zwei zar auch hinnehmen. Aber der Ansatz 1 ist nicht
einfach nur komplexer, denn er löst das Problem nicht in unserem Sinne.
Er verkompliziert die Dinge auch. Damit ist
"Complex is better than complicated." aus
"The Zen of Python" ebenfalls
verletzt.
Die gestiegene Komplexität für ansatz 2 wäre ein notwendiges Übel.
Ich würde sogar behaupten, dass es den Ansatz
"Flat is better than nested." aus
"The Zen of Python" unterstützt.
Ich kann damit nämlich definitiv stark verschachtelte (tiefe) Verzweigungen
leichter vermeiden.
Steve Bate bezieht sich in seinem blog übrigens auf das Buch
"Enterprise Integration Patterns: Designing, Building, and Deploying
Messaging Solutions" von Gregor Hohpe und Bobby Woolf
(
Link zum buch auf smile.amazon.de).
Auch Sehr Informativ in diesem Kontext dürfte "Patterns für Enterprise
Application-Architekturen" von Martin Fowler
(
Link zum Buch auf smile.amazon.de) sein.