Podcast

Twelve-Factor App: Web-Applikationen auf die neue Art

Ein Ansatz für skalierbare, portable und leicht deploybare Web-Applikationen in der Cloud

Michael Vitz und Stefan Tilkov sprechen in dieser Folge über die Twelve-Factor App, eine Entwicklungsmethode für skalierbare Web-Applikationen in der Cloud. Michael erklärt zunächst, welche Ziele hinter dieser Methode stehen und erläutert dann die 12 Faktoren im Detail. Außerdem diskutieren Stefan und Michael über Vor- und Nachteile der Twelve-Factor App und beleuchten Gemeinsamkeiten und Unterschiede zu Microservices.
Listen to other episodes

Shownotes & Links

Methodik

Meinungen/Kritik

Beispiele

Transkript

show / hide transcript

Michael Vitz: Danke.

Stefan Tilkov: Michael, stell dich doch kurz vor.

Michael Vitz: Ja, ich bin Michael Vitz. Ich bin als Consultant bei der innoQ, jetzt seit fünf Monaten und beschäftige mich hier vor allen Dingen mit Softwarearchitektur, Softwareentwicklung im JVM-Umfeld, insbesondere Clojure aktuell.

Stefan Tilkov: Ok. Heute ist unser Thema aber nicht Clojure, sondern Twelve- Factor Apps.

Michael Vitz: Genau.

Stefan Tilkov: Haben wir uns raus gesucht oder hast du dir raus gesucht. Insofern ist die aller erste Frage offensichtlicherweise: was mag das wohl sein?

Michael Vitz: Ja, Twelve-Factor App, das ist eine Methode, die von Adam Wiggins damals aufgeschrieben wurde. Dazu kann man direkt sagen, der Adam Wiggins ist Co-Founder bei Heroku gewesen. Und deswegen beziehen sich manche Dinge, die er sagt, natürlich auch auf das, wie Heroku damals funktioniert hat.

Stefan Tilkov: Heroku, für diejenigen die das nicht kennen, ist so eine Plattform-as-a-Service (PaaS) Lösung auf Amazon-Basis.

Michael Vitz: Genau.

Stefan Tilkov: Die glaube ich mittlerweile zu Sales-Force gehört, ne? Ist das so?

Michael Vitz: Das kann sein. Die haben auf jeden Fall mal mit Ruby vor allen Dingen auch angefangen und bieten aber mittlerweile auch für andere Sprachen was an. Und deren Ziel war es irgendwie, eine Spachunabhängige Methode zu haben, um eben so Software-as-a-Service (SaaS) Applikationen zu entwickeln. Das heißt, das war ein Tipp für die Leute, die etwas entwickeln, was dann auf Heroku laufen sollte.

Stefan Tilkov: Das heißt, wir jeden nicht über ein konkretes Framework oder sowas, sondern wir reden über eine Menge von Empfehlungen oder Guidelines?

Michael Vitz: Ja genau. Genau genommen zwölf, daher auch Twelve-Factor App. Das sind zwölf ja Empfehlungen, Regeln, die die aufgeschrieben haben. Und wenn man sich halt an alle zwölf hält, dann ist man so eine Twelve-Factor App und profitiert davon eben in einige Belangen.

Stefan Tilkov: Ok, du hast gesagt, das ganze bezieht sich auf SaaS-Lösungen oder SaaS-Applikationen.

Michael Vitz: Genau.

Stefan Tilkov: Was ist denn das Ziel, das man verfolgt, mit diesen zwölf Faktoren?

Michael Vitz: Ja also die haben als Ziel, insbesondere fünf Punkte. Der erste Punkt ist, dass man halt neue Entwickler, die ins Team kommen, möglichst schnell eben einarbeiten kann, dass die schnell ans arbeiten kommen. Und das wollen die ermöglichen, indem die Regeln immer darauf abzielen, möglichst viel zu automatisieren, um eben möglichst wenig manuelle Schritte zu haben.

Dann der zweite Punkt ist, die App sollte einen klaren Kontrakt zum Operating System bzw. zu der Umgebung haben, wie z.B. “Welche Pakete brauche ich, um zu laufen?” Und dadurch werden die halt maximal portabel oder das ist zumindest die Hoffnung davon.

Das nächste ist, die App sollte in Cloud-Umgebungen deploybar sein. Das ist irgendwie klar, genau.

Stefan Tilkov: Klar.

Michael Vitz: Und das nächste ist dann, es sollte möglichst die Entwicklungsumgebung und die Produktionsumgebung gleich sein, weil man dadurch auch direkt sowas wie Continious Deployment testet und sich sicher ist, dass es in beiden Umgebungen läuft.

Und das letzte ist eigentlich auch klar, die App soll skalierbar sein. Das ist ja eigentlich immer Ziel, wenn man in der Cloud läuft, möglichst skalierbar zu sein.

Stefan Tilkov: Ok, aber dieser Punkt ist dann offensichtlich ganz wichtig. Diese Automatisierung und dieses Explizitmachen ist ein wesentliches Ziel von dem ganzen, ne? Dass man Dinge nicht so, ja von Hand sich irgendwie zusammen fummelt, weil man halt weiß, wie das geht, sondern versucht, das möglichst automatisiert, möglichst klar definiert und explizit darzustellen.

Michael Vitz: Ja, das ist auch bei den heutzutagigen PaaS-Diensten meistens so, dass man nur noch einen Button drückt oder nur noch in sein Git pusht und der Rest dann automatisiert abläuft. Und ich meine, dafür muss man ja irgendwie alles auch beschreiben, was man braucht.

Stefan Tilkov: Genau. Der ganze Ansatz ist aber nicht auf Heroku beschränkt, oder? Das ist nicht für eine bestimmte Lösung, sondern allgemeiner.

Michael Vitz: Nein, das ist prinzipiell überall anwendbar, denke ich. Sofern man halt die Kontrolle über all die Dinge hat, die sie fordern. Es kommt halt nur von denen und passt wahrscheinlich bei denen am besten. Oder hat am besten gepasst.

Stefan Tilkov: Ok. Gut, unsere Agenda ist heute sehr einfach, weil wir zwölf Faktoren haben. Also können wir die ja der Reihe nach mal durch gehen. Lass uns einfach mal starten.

Michael Vitz: Also im ersten Faktor geht’s um die Codebasis. Und die Codebasis sollte idealerweise genau ein Repository sein, weil die sagen, wenn man halt mehrere Codebases, also mehrere Repositories hat, dann hat man im Grunde schon eine Art Distributed System, wo man halt mehrere Twelve-Factor Apps für hat. Und es kann auch nicht sein, dass man mehrere Apps in einer Codebasis hat, sagen sie, sondern das deutet eigentlich nur darauf hin, dass man irgendwie Code wiederverwenden möchte und das soll man doch bitte über eine Shared-Library machen, die man dann über sein “wie-auch-immer-geartetes”-Dependency Management anzieht.

Stefan Tilkov: Ok, das heißt, so eine App ist prinzipiell immer relativ klein. Weil sie muss so klein sein, dass sie eben, dass es sinnvoll ist, sie in einem Repository zu verwalten, oder?

Michael Vitz: Genau, sie braucht natürlich ihren Selbstzweck, weil sonst ist es keine App mehr und sie darf auch nicht so groß sein, dass man mehrere Repositories gerne hätte, weil das mit Sicherheit dem ersten Punkt, dem Automatisieren und den Entwickler einarbeiten entgegen spricht. Weil wenn der erstmal sich aus drei verschiedenen Repositories irgendwas clonen muss oder auschecken muss, dann ist es schon wieder komplizierter, als es sein müsste.

Stefan Tilkov: Ok. Gut, Faktor Nummer zwei.

Michael Vitz: Faktor Nummer zwei, das sind die Dependencies. Und zwar soll man keine impliziten Annahmen treffen, wovon man denn abhängt. Das ist den meisten für Libraries in der Sprache, in der man entwickelt, sowieso schon klar. Also in Java benutzt man Maven oder Gradle. In Ruby Bundler bzw. Gems. Und deswegen soll man, muss man eben alle Abhängigkeiten deklarieren. Und das zweite was die wollen, man muss sie auch isolieren. Das heißt also, die Dependencies, die man anzieht, sollen nur für seine App gelten und nicht irgendwo in einem shared folder liegen, wo sie dann potentiell auch noch andere Applikationen anziehen könnten. Würde man das tun, dann hätte man wahrscheinlich wieder Angst, die abzudaten, weil man gegebenenfalls ja eine parallele App bricht. Und das vielleicht einschneidenste ist, dass die bei Dependencies auch an System-Tools, also sowas wie cURL oder ImageMagic, also Executables denken. Und die Forderung ist, dass die auch mit der App ausgeliefert werden soll.

Stefan Tilkov: Ok, also alles was – die Annahmen, die meine App über die Umgebung trifft, sind absolut minimal bzw. entweder trifft keine oder sie werden explizit gemacht.

Michael Vitz: Genau, sie sollen explizit gemacht werden, um eben deterministisch zu sein. Also ich kann die App auf A deployen, auf B deployen und ich weiß dann, ich habe da alle meine Dependencies mit dabei und nicht “Oh, plötzlich läuft es nicht mehr, weil da fehlt mir Library X.” Und es macht natürlich auch wieder das Setup einfacher, weil ein Entwickler braucht eigentlich nur die Runtime-Umgebung seiner Sprache und ein Dependency-Manager, der eben all diese Abhängigkeiten runterlädt und da kann er bei sich lokal auch schon starten, ohne vorher noch manuell etwas zu installieren.

Stefan Tilkov: Ok, aber bei einer, das Beispiel was du gerade geliefert hast mit dem ImageMagic ist ja nicht nur eine Abhängigkeit in der Programmiersprache meiner Wahl.

Michael Vitz: Richtig.

Stefan Tilkov: Also wenn ich in Ruby bin, dann habe ich eben einen Ruby-Teil, ich habe ja aber auch noch einen C-Anteil, den ich kompilieren muss, etc. Also da mache ich ja schon mehr Annahmen über meine Ablaufumgebung, als nur dass die Sprach-VM da ist.

Michael Vitz: Ja genau. Aber das ist einer der Punkte, wo ich mir auch noch unsicher bin, wie man das genau nachher umsetzen soll. Denn sein eigenes cURL ausliefern mit so einer Java-App ist eben nicht so ganz trivial. Da kommt man dann wahrscheinlich schon in Richtung Container, Docker, etc. – sind ja die Schlagworte aktuell. Damit könnte man das z. B. ganz gut machen.

Stefan Tilkov: Ok. Gut, der nächste Faktor.

Michael Vitz: Der nächste Faktor ist Config, also die Konfiguration der App. Das ist definiert als “alles, was sich in der Umgebung unterscheidet”. Also insbesondere Ressourcen, z.B. meiner Datenbankverbindeung, aber auch sowas wie Credentials, also irgendwie die Username von der Datenbank. Dann sollen die strikt vom Code getrennt werden. Und dann kommt die Forderung, dass man das nur über Umgebungsvariablen macht. Das ist damit begründet – also einmal sind Umgebungsvariablen sehr einfach zu ändern. Also einfacher schon fast, als ein File zu editieren, weil man braucht sie einfach nur exportieren, hinschreiben. Dann kann man sie nicht versehentlich mit im Code einchecken, was wiederum mehr der Forderung, dass man sie vom Code trennt, widerspricht. Und sie sind sprach- und umgebungsunabhängig. Also eigentlich jedes Betriebssystem hat in irgendeiner Form Umgebungsvariablen und jede Sprache hat auch eigentlich einen Builtin-Support dafür, kann damit also umgehen. Und das letzte, was noch gefordert wird, dass man sie nicht gruppieren soll. Also man soll keine Umgebungen, wie jetzt dev, qa und prod definieren und wenn die Umgebungsvariable enviroment auf qa steht, dann zieht man halt zehn weitere Konfigurationsparameter an, sondern man soll wirklich jeden einzelnen ändern können. Also für eine Datenbank z. B. URL, Username, Password und nicht jetzt irgendwie “Die Datenbank ist jetzt die QA-Datenbank”.

Stefan Tilkov: Warum? Was ist der Grund dafür?

Michael Vitz: Naja, man sieht halt viel deutlicher, welche Optionen man überhaupt hat. Und man kann die ja auch viel feingranularer ändern.

Stefan Tilkov: Ok. Gut, jetzt sind wir bei Nummer vier, richtig?

Michael Vitz: Genau, der vierte ist Backing Services. Backing Services kannte ich so als Begriff jetzt nicht, aber im Grunde ist es jeder Service, den ich als App benutzte, jeden anderen. Also Datenbank, E-Mail-Server, eine Queue oder ähnliches. Unterschieden wird dann in lokale Services und Third-Party-Services. Lokale Services sind die, die wir unter Kontrolle haben. Also z. B. eine zweite Twelve-Factor App, die wir bauen. Oder auch z. B. eine Datenbank, die wir selber installieren und betreiben. Und Third-Party-Services eben, wenn wir es nicht unter Kontrolle haben, also z. B. wenn ich eine Queue in AWS benutze oder die Datenbank, die AWS mir zur Verfügung stellt. Und es sollte halt so sein, dass man die lokale gegen die Third-Party einfach durch eine Konfiguration austauschen kann. Also die App sollte jetzt keine Annahme darüber treffen, ob sie mit einer lokalen MySQL-Datenbank spricht oder mit eben einer, die bei AWS gehostet ist, sondern man müsste da einfach nur eine Konfiguration ändern und man spricht dann mal mit der einen, mal mit der anderen.

Stefan Tilkov: Konfiguration ändern heißt dann wieder, eine Umgebungsvariable ändern.

Michael Vitz: Genau.

Stefan Tilkov: In dieser Logik.

Michael Vitz: Ja.

Stefan Tilkov: Ok. Gibt es da auch eine Aussage dazu, ob da irgendwie explizit gemacht werden muss, in irgendeiner Form? Also listet die Twelve-Factor App irgendwie auf, was sie an externen Services benutzt? Gibt es da so eine …

Michael Vitz: Ne, da treffen sie keine Aussage zu.

Stefan Tilkov: Ok.

Michael Vitz: Das einzige, was sie eben sagen, ist, man soll jetzt dieses Austauschen von Backing Services-Konfigurationen nicht so verstehen, dass man mal plötzlich lokal gegen eine MySQL programmiert und dann in der Produktion gegen eine Postgres umswitcht. Sondern es soll schon überall dieselbe Datenbank-Art sein oder auch dieselbe Queue. Also sie wollen jetzt nicht auch noch Vendor-unabhängig sein.

Stefan Tilkov: Ok, verstehe.

Michael Vitz: Sondern es soll nur lokal gegen – oder damit wenn die Produktionsdatenbank, wie auch immer, kaputt geht, damit man eben einfach mal eine neue Datenbank hochziehen kann und dann nur per Konfiguration gegen diese neue Datenbank geht und nicht mehr gegen die alte.

Stefan Tilkov: Ok. Gut, Nummer fünf.

Michael Vitz: Nummer fünf – Build, Release, Run – beschreibt die strikte Trennung in diese drei Phasen. Die Build-Phase, aus statisch-kompilierten Sprachen kennt man das – man baut halt aus seinem Quellcode irgendwas kompiliertes. Bei sowas wie Ruby ist das ja nicht so üblich. Da entfällt die Phase nahezu. Aber sie wollen die trotzdem. Also aus der Build-Phase soll irgendwas, ein Bundle rausfallen, was man dann nachher ausführen kann. Das ist das erste. Dann die Release-Phase. Die verbindet sozusagen dieses Bundle, was aus der Build-Phase gefallen ist, mit der Konfiguration. Dann hat man eben ein Release. Und das letzte ist dann letztendlich die Run-Phase und da wird das Release einfach nur noch hochgefahren, gestartet. Und ein Vorteil davon ist z. B., dass man in der Produktion nicht mal eben eine Änderung machen kann, weil man da häufig vergisst, dass man die mit Sicherheit bis nach vorne durchziehen muss. Das heißt, man muss für jedes Release auch durch diese drei Phasen durch und ist sich dann auch sicher, dass alles funktioniert.

Stefan Tilkov: Das bedeutet, alle Konfigurationsänderungen erfordern ein neues Release. Ich mache ein neues Release in die QA, indem ich den Prozess nochmal von vorne anfange.

Michael Vitz: Ja, alle Konfigurationsänderungen führen zu einem neuen Release, genau.

Stefan Tilkov: Ok.

Michael Vitz: Man kann ja für ein Bundle, was man gebaut hat, auch mehrere Konfigurationen haben. Z. B. eine für QA und eine für Produktion. Das sind dann zwar unterschiedliche Releases, die aber beide auf demselben Bundle, also auf derselben Codebasis basieren, nur mit verschiedenen Konfigurationsparametern. Genau, das lassen sie zu.

Stefan Tilkov: Ok. Ich habe die Nummer vergessen, wir sind glaube ich bei sechs.

Michael Vitz: Genau, wir sind bei sechs.

Stefan Tilkov: Ok.

Michael Vitz: Sechs ist Processes, also Prozesse. Das ist auch recht einfache. Also jede App soll ein einzelner Prozess sein, der gestartet werden kann. Und was dazu kommt ist, der Prozess soll keinen State haben und sich nicht auf Memory oder Filesystem verlassen. Das heißt also, alles was er im Memory hält oder auf die Festplatte abspeichert, da darf er sich nicht drauf verlassen, dass wenn er neu gestartet wird, es da immernoch liegt. Sondern er soll die Ressourcen eben als flüchtig aktzeptieren und wenn er irgendwo was speichern muss, dann soll er eben den passenden Backing Service benutzen, der das Speichern kann. Also z. B. in einer Datenbank.

Stefan Tilkov: Ok, das heißt also, mit Prozess meinst du wirklich Betriebssystem-Prozess?

Michael Vitz: Ja, genau.

Stefan Tilkov: Es muss wirklich ein Betriebssystem-Prozess sein, das heißt, wenn ich jetzt ein Ruby-Skript hätte, würde ich das eben mit dem Ruby-Interpreter starten.

Michael Vitz: Korrekt.

Stefan Tilkov: Wenn es ein Java-Ding wäre, dann hätte ich evtl. ein executable jar oder sowas.

Michael Vitz: Ja, genau.

Stefan Tilkov: Ok. Und warum soll der stateless sein? Was ist die Begründung sowas zu tun?

Michael Vitz: Naja, da kommen wir halt insbesondere auf Skalierbarkeit. Also da werden es ja – das ganze Twelve-Factor App kommt ja aus der PaaS-Ecke und da skaliert man – das kommt später auch nochmal – aber das skaliert man in der Regel darüber, dass man einfach mehrere Prozesse startet und dass vorne irgendwer das auf die verschiedenen Prozesse verteilt. Und je mehr State man hat, desto schwerer kann man eben parallele Prozesse hochfahren, weil die dann diesen State in der Regel irgendwie syncronisieren müssen. Und deswegen ist hier die Forderung einfach, man hat überhaupt keinen State.

Stefan Tilkov: Ok.

Michael Vitz: Also keinen serverseitigen State zumindest.

Stefan Tilkov: Ok, das bedeutet aber auch, dass ich so einen Backing-Service eigentlich nicht bauen kann als Twelve-Factor App. Also irgendwas widerspricht sich doch, oder? Also wenn ich keinen State in meiner Twelve-Factor App habe, aber ein Backend-Service benutzen soll, um meinen State abzulegen, dann – irgendwas stimmt doch da nicht.

Michael Vitz: Ja, das ist dann wahrscheinlich – das stimmt. Dann machen sie wahrscheinlich eine Ausnahme für Datenbanken und ähnliches.

Stefan Tilkov: Verstehe.

Michael Vitz: Also es muss schon irgendwie, klar. Also in der Regel fällt mir irgendwann mal was runter und ich will eben Sachen auch nach einem Neustart noch haben. Aber da muss man dann anscheinend für irgendeinen Backing-Service eine Ausnahme machen.

Stefan Tilkov: Ok. Oder irgendwas benutzen, was halt nicht als Twelve-Factor App realisiert ist. Wenn ich jetzt eine Standard-Datenbank benutze oder einen AWS-Service oder irgendein andere Ding benutze.

Michael Vitz: Ja, genau.

Stefan Tilkov: Ok, gut. Nummer sieben.

Michael Vitz: Nummer sieben ist Port-Binding. Und das sagt: jede App exportiert sich selber über Port-Binding. Das heißt, jede App hat halt einen oder mehrere Ports, die es nach außen frei gibt und über dies es benutzt werden kann. Und das führt automatisch dazu, dass diese App wiederum als Backing-Service benutzt werden kann, weil es kann über den Port von jemand anderem angesprochen werden. Und dazu kommt, was vielleicht nicht so ganz zu dem Punkt passt, aber was die hier explizit erwähnen: eine App muss self-contained sein. Das heißt, sie muss alles selber mitbringen, außer der Laufzeitumgebung. Also ein JDK müsste man nicht selber mitbringen, aber man sollte sich nicht auf einen Container, also einen Application-Server oder ähnliches verlassen, sondern man muss schon sich selber sozusagen auch starten können.

Stefan Tilkov: Ok. Das bedeutet, dass andere, die diese App benutzen, keinerlei Annahmen über das treffen sollen oder treffen können, was da drin passiert, richtig? Die sehen nur diesen Port, über den man das ganze ansprechen kann.

Michael Vitz: Genau, also wenn die App als Backing-Service benutzt wird, dann weiß jemand anderes nur, “An dieser Stelle ist die” und “Über den Port kommuniziert die”, also irgendeine URL und einen Port und dann spricht sie mit ihm. Die API zwischen den beiden muss man natürlich irgendwie definieren, beschreiben, wissen. Aber was die intern macht, in welcher Sprache die geschrieben ist, darüber braucht man gar keine Annahmen machen, genau.

Stefan Tilkov: Ok. Gut, Nummer acht.

Michael Vitz: Nummer acht ist Concurrency. Da kommen wir jetzt eben dazu, was ich bei Processes schon sagte. Und zwar ist der Prozess ein First-Class-Citizen in der Twelve-Factor App. Heißt also eigentlich so das wichtigeste in der Laufzeit, weil man eben über mehrere Prozesse skaliert, wie ich eben schon beschrieb. Wenn man skaliert, dann startet man einfach x mehr Prozesse und kann dann halt auch mehr Anfragen beantworten. Und was da vielleicht noch neu ist, die fordern, dass man keine Deamon-Prozesse hat, sondern man hat einfach ganz normale Prozesse und im Grunde ist dann der Prozessmanager des Systems dafür verantwortlich, dass er dafür sorgt, dass die laufen, dass er die neustartet, der kümmert sich um alles, was per stdout raus kommt usw. Also das muss die App nicht selber handhaben, sondern da kümmert sich eben dann irgendein Prozessmanager eine Ebene höher drüber.

Stefan Tilkov: Ok, den ich einfach voraussetze, wenn ich meine Twelve-Factor App schreibe. Das heißt, ich gehe davon aus, dass es irgendwas gibt, in das sich meine Twelve-Factor App einbettet. Irgendwas, das Prozesse verwalten kann.

Michael Vitz: Ja genau. Irgendwas wie Upstart oder Systemd oder sowas, unter Linux.

Stefan Tilkov: Bedeutet das, dass ich keine Threads verwenden darf, wenn ich eine Twelve-Factor App baue?

Michael Vitz: Das wird nirgendwo explizit benannt, also müsste das, eigentlich dürften Threads auch gehen, sofern sie denn dann mit sterben, wenn der Prozess stirbt. Also ich sollte auch da keine Deamon-Threads drin aufmachen.

Stefan Tilkov: Ok, ok. Das heißt, die ganze Idee ist so ein bisschen so eine Skalierung mit einem shared-nothing-Modell, ne? Wo ich statuslose Prozesse ganz, ganz viele nebeneinander stellen kann, wenn ich mehr brauche. Das passt halt ganz gut zu dem Cloud-Modell, das hast du ja eingangs schon gesagt.

Michael Vitz: Ja, ja, genau. Das ist eigentlich ja immer das Cloud-Argument. Wenn ich keinen State habe, kann ich den Prozess einfach zehn Mal starten und dann brauche ich vorher nur noch irgendwas, was die Last verteilt auf meine zehn Instanzen.

Stefan Tilkov: Und weil es Prozess und keine Threads sind, kann ich sie eben auch auf verschiedenen Maschinen starten.

Michael Vitz: Genau.

Stefan Tilkov: Ok. Gut, Nummer neun.

Michael Vitz: Nummer neun: Disposability. Das ist die Verfügbarkeit.

Stefan Tilkov: Ist Disposability nicht das genaue Gegenteil von Verfügbarkeit? Ist das nicht die “Wegschmeißbarkeit”?

Michael Vitz: Ah, ich habe bei Leo geguckt und da stand Verfügbarkeit, tatsächlich.

Stefan Tilkov: Das finde ich aber schräg. Weil to dispose heißt doch, ich schmeiße was weg.

Michael Vitz: Stimmt eigentlich.

Stefan Tilkov: Na, egal.

Michael Vitz: Und das fordert vier Dinge. Zum einen eine minimale Startzeit meines Prozesses. Also nach Möglichkeit starte ich den und der ist sofort dann auch verfügbar und kann von außen über das Port-Binding angesprochen werden. Denn man muss ihn über SIG-Term stoppen können. Also wenn ich ihn im Vordergrund habe, einfach Strg-C auf den meisten Betriebssystemen. Er muss ein graceful shutdown haben, das heißt, er muss zumindest versuchen, Ressourcen vernünftig freizugeben, die er benutzt. Und er sollte möglichst robust gegen den Tod sein. Also z. B. wenn man irgendwie größere Workloads hat, dann sollte man die in einer Queue zwischenspeichern und aus der Queue dann immer nur einen raus nehmen, damit man eben am Tod da nicht irgendwie Dinge vergisst. Und da kommen wir jetzt zu was, was du vorher gesagt hast. Das hat eben dann vor allen Dingen den Vorteil der Skalierbarkeit. Also ich kann eben auch sehr, sehr schnell skalieren. Und ich kann eben auch mal schnell von Server A auf Server B umstellen, weil ich kann den ja einfach auf Server B innerhalb von einer Sekunde oder so hochfahren, dann stoppe ich ihn auf Server A, er stirbt auch relativ schnell und schon bin ich auf einer neuen Maschine rüber gemovet.

Stefan Tilkov: Das ist vielleicht noch oder das klingt nach einer guten Begründung, warum man da vielleicht nicht einen dicken Application-Server hinpackt. Also wenn der Application-Server in einer Sekunde startet und eher eingebettet läuft, wäre das ja wahrscheinlich auch egal. Dann sehe ich das ja wieder nicht. Aber diese Idee, dass ich irgendwas zwei Minuten lang starte, das passt nicht zu dem Konzept.

Michael Vitz: Wenn er eingebettet ist, da spricht ja auch gar nichts gegen. Denn dann hat man ja im Grunde keinen Container. Also das würde der Twelve-Factor App schon genügen. Also im Java-Umfeld gibt es auch wenig Sachen, da wird eigentlich immer ein eingebetteter Application-Server benutzt. Aber man benutzt ihn eben nicht als Umgebung, die man einzeln irgendwie runterlädt, installiert und da dann nur noch sein Ding rein packt. Sondern da gehört es dann auch mit zur App. Dadurch wird z. B. auch sowas wie Konfiguration viel klarer, dass man selber als Entwickler auch dafür verantwortlich ist, diesen Application-Server zu konfigurieren und für sinnvolle Default zu sorgen und nicht dieses typische “Ich schmeiß es mal zum Betrieb rüber”, der tweakt das schon irgendwie und macht das schnell.

Stefan Tilkov: Oder auch nicht. Genau, ok. Nummer zehn?

Michael Vitz: Zehn sind wir schon, genau. Das ist die Dev/prod parity, also die Forderung, dass man seine Entwicklungsumgebung und die Produktion und auch alles, was da noch zwischen kommen mag, versucht gleich zu halten. Oder nicht versucht, sondern die sollen identisch sein. Heißt, die sollen dieselbe Datenbank benutzen. Also wir ich auch am Anfang schon mal sagte, nicht ein Mal MySQL und ein Mal Postgres und ein Mal Oracle, sondern in allen Umgebungen soll derselbe Vendor sein. Die muss dabei natürlich nicht 100%-ig gleich konfiguriert sein. Also man wird jetzt auf einem Entwickler-Notebook einer Datenbank nicht so viel Speicher geben wollen, wie man das in der Produktion macht. Aber es soll zumindest dieselbe Version oder derselber Hersteller sein. Und die haben insbesondere drei Achsen ausgemacht, wo Ungleichheiten entstehen. Und das ist ein Mal die Zeit, ein Mal das Personal, was man hat, und ein Mal die Tools. Und die Zeit wird dadurch angegangen, dass man eben sofort in Produktion deployen kann. Also jede Änderung soll sofort in Produktion gehen, weil dadurch verhindere ich, dass ich jetzt heute irgendwas mache, das ist fertig und das kommt aber erst drei Monate später und ich habe schon wieder vergessen, dass ich das gemacht habe. Sondern damit versuche ich halt, dass immer die ganzen Umgebungen möglichst gleich sind und ich weiß, was wo ist.

Stefan Tilkov: Das wäre jetzt so ein Continious Delivery-Ansatz?

Michael Vitz: Genau.

Stefan Tilkov: Ok.

Michael Vitz: Also typischerweise irgendwie “Jeder Push wird getestet und landet dann letztendlich in Produktion”. Oder vielleicht auch in der Vorproduktion, wo man dann vielleicht doch noch mal einen Button hat, aber möglichst – ich versuche möglichst alle Umgebungen gleich zu halten.

Personal, da fordern sie eben, dass die Entwickler auch für den Betrieb zuständig bzw. verantwortlich sind. Also dieser klassische DevOps Gedanke, damit man eben nicht als Entwickler sein Feature zusammen klöppt und sagt “Es ist fertig” und dann schmeiße ich das irgendwo rüber und jetzt ist es nicht mehr mein Problem und Fehler sind nicht von mir, sondern kommen von da.

Und das letzte sind eben die Tools. Und da will man eben auch Produktion und Entwicklung gleich halten. Also man sollte auch dieselben Librarys benutzen, dieselben Abhängigkeiten in denselben Versionen. Und im Grunde will man damit eigentlich diese typischen Fehler vermeiden, die man so aus klassischen Projekten – sage ich mal – kennt.

Stefan Tilkov: Ok. Nummer elf.

Michael Vitz: Nummer elf: Logs. Da gibt es gar nicht viel zu sagen.

Stefan Tilkov: Logs mit “g” oder Locks mit “ck”?

Michael Vitz: Logs mit “g”.

Stefan Tilkov: Mit “g”.

Michael Vitz: Genau. Und das ist ganz einfach. Man loggt nur per stdout.

Stefan Tilkov: Per stdout oder per stderr?

Michael Vitz: Nein, sie sagen per stdout.

Stefan Tilkov: Warum?!

Michael Vitz: Naja, also warum genau steht dort nicht. Aber es macht die ganze Application natürlich einfacher. Also es gibt nur – also jetzt nicht für deine Frage, ob stdout oder ob stderr. Ich glaube, das ist letztendlich nicht ganz entscheidend.

Stefan Tilkov: Aber ich wüsste einen Kollegen, mit dem wir da sehr lange drüber diskutieren können. Aber egal.

Michael Vitz: stderr wäre wahrscheinlich auch das richtige. Wobei eine Twelve-Factor App hat ja keine UI. Also man braucht die Konsole ja nicht, um da irgendwelche Sachen raus zu greppen und weiter zu verarbeiten. Deswegen spielt der Kanal meiner Meinung nach nicht so sehr die Rolle. Was eben das Hauptargument ist, es ist einfacher. Also alle Log-Statements kommen nur an genau einem Ort raus und nicht in fünf verschiedenen Files, die wie auch immer gruppiert sind. Sondern man hat eben nur einen Ort, an dem man suchen muss. Und jetzt kommen wahrscheinlich die ersten, die sagen “Ah, wenn ich nur per stdout, dann kann ich da ja nie wieder rein gucken.” Und das ist eben das Aber des Punktes. Aber wenn man das natürlich so macht, dann muss man sich irgendwie anders um die Logs kümmern. Ein klassischer Ansatz ist heutzutage sowas, wie ein ELK-Stack oder …

Stefan Tilkov: Das musst du kurz erklären. Was ist ein ELK-Stack?

Michael Vitz: Achso. Ein ELK-Stack, der beschreibt drei Komponenten. Das E ist ElasticSearch, das L ist Logstash und das K ist Kibana. Logstash nimmt Log-Nachrichten entgegen.

Stefan Tilkov: Auch von mehreren Prozessen?

Michael Vitz: Von mehreren Prozessen, von verteilten Prozessen, wie auch immer. Der selber füttert die dann in so ein ElasticSearch. ElasticSearch wird klassischerweise für Suche benutzt, also der indexiert einfach die Einträge. Und Kibana ist letztendlich nur eine Oberfläche über dem ElasticSearch …

Stefan Tilkov: Ok.

Michael Vitz: … womit man besonders gut nach Log-Nachrichten filtern/suchen kann. Heißt also, man braucht eine eigene Infrastruktur, die Log-Nachrichten verarbeitet. Aber wenn man sowas wie eine Twelve-Factor App macht, braucht man das sowieso, weil sich gegebenenfalls ja Requests auch über mehrere Applicationen erstrecken und wenn man da dann noch irgendwas nachvollziehen will, dann will man sich ja auch nicht auf fünf Server einloggen und da in den Files suchen und zusammensuchen.

Stefan Tilkov: Fünf Server und n Prozesse am besten noch, ne? Weil man noch ganz viele verteilte, hochskalierte, shared-nothing Prozesse hat, die alle noch ihr eigenes Log schreiben.

Michael Vitz: Ja, ja. In einer App hat man ja zehn Prozesse und man weiß ja gegebenenfalls gar nicht, wo die laufen, und da kommt man an ein Logfile sowieso nicht dran. Das heißt, man braucht sowieso jemanden, der die irgendwie sammelt und dann brauche ich auch gar nicht erst ein File schreiben, dann kann ich auch direkt nach stdout schreiben. Und das sorgt dann dafür, dass es irgendwie in diese Infrastruktur gelangt.

Stefan Tilkov: Ok. Nummer elf, wenn ich mich nicht irre.

Michael Vitz: Ne, elf war schon.

Stefan Tilkov: Elf war schon? Oh, zwölf.

Michael Vitz: Wir sind schon bei zwölf, genau. Zwölf sind die Admin processes. Das sind eben Prozesse, die nicht kontinuierlich laufen und nicht für das bestehen der App sozusagen sorgen oder die Verfügbarkeit der App, sondern das sind Dinge, wie eine Datenbankmigration, wenn ich ein neues Release einspiele. Oder sonstige Skripte oder eine Konsole, weil ich kurz irgendwas vom Status des Prozesses nachgucken möchte. Und dafür soll man die gleiche Codebasis benutzen und auch die gleiche Konfiguration. Und das vermeidet halt auch wieder Fehler. Also weil man benutzt halt dediziert dasselbe, wie die App, das heißt auch, wenn man sich jetzt zur Datenbank verbindet, die dieser Prozess benutzt, dann ist man automatisch zur richtigen Datenbank verbunden und muss nicht erst wieder suchen, mit welcher Datenbank ist der denn verbunden.

Stefan Tilkov: Ok, also dasselbe benutzen bedeutet nicht, dass man das zwingend z. B. in derselben Programmiersprache implementieren müsste, aber man behandelt es sozusagen als Teil des Produktionscodes? Kann man das so sagen?

Michael Vitz: Ja, sie fordern schon tendentiell eher, dass man es auch in derselben Programmiersprache macht, weil sie sagen, wenn man z. B. in Ruby seinen Server über bundle exec sync server oder sowas startet, dass man dann die Datenbankmigration auch über bin bundle ....

Stefan Tilkov: Verstehe.

Michael Vitz: Also man soll es idealerweise genauso machen, wie die App selber.

Stefan Tilkov: Ok.

Michael Vitz: Und das führt in der Regel dann ja auch dazu, dass man dieselbe Sprache verwenden sollte.

Stefan Tilkov: Ok. Jetzt sind wir durch zwölf Dinger durch, richtig?

Michael Vitz: Jawoll.

Stefan Tilkov: Was ist denn deine Sicht auf das ganze? Muss man immer alle diese zwölf machen, sonst wird man mit Katzendreck beworfen oder wie ist die Sicht da drauf?

Michael Vitz: Naja, erstmal kann man natürlich sagen, man muss alle zwölf machen, um eine Twelve-Factor App zu sein. Das, denke ich, ist legitim. Denn wenn die diese zwölf Faktoren gefunden haben und sagen: “Das ist eine Twelve-Factor App”, dann muss man auch alles machen, um eine zu sein. Die interessantere Frage ist: “Muss man immer eine Twelve-Factor App bauen?”

Stefan Tilkov: Ja einverstanden.

Michael Vitz: Und da bin ich der Meinung, man muss mit Sicherheit nicht, insbesondere heutzutage werden ja noch viele Applikationen gebaut, die gar nicht so richtig SaaS sind, sondern die man eher so intern komplett betreibt. Da muss man bestimmt nicht alles machen. Es gibt Faktoren, die machen denke ich immer Sinn.

Stefan Tilkov: Zum Beispiel?

Michael Vitz: Also z. B., dass man die Logs nur nach stdout schreibt, finde ich, könnte man immer machen. Ich wäre auch damit einverstanden “Oh, ich will aber noch ein File haben”, man schreibt in das File. Das wichtige finde ich da eher, dass man diese Infrastruktur hinterher hat, die die ganzen Log- Nachrichten also verarbeitet. Was kann man noch – also die Dependencies, denke ich, sollte man auch immer haben. Also man will keine impliziten Annahmen über irgendwelche Abhängigkeiten oder seine Umgebung treffen, sondern man will das immer deklarativ beschreiben. Ich persönlich würde da sogar den Abstand machen, wenn man da irgendwo noch beschreibt, was man braucht und das nicht automatisch installiert wird, fände ich das auch noch ok. Aber es geht schon in die Richtung, das zu automatisieren. Konfiguration über Environment-Variablen, denke ich, macht auch ganz oft Sinn, weil man die halt ganz schnell ändern kann. Man muss nicht immer das passende File suchen. Da würde ich allerdings sagen, wenn man so eine Gruppierung wie “Dev” und “QA” macht, sofern man jetzt nicht 50 Umgebungen hat, kann man das denke ich auch noch bedenkenlos tun. Das sind so die Sachen, die, denke ich, sollte man immer tun. Sowas wie jetzt, dass man keinen Container benutzt, also keinen Tomcat oder sowas, ich finde das muss man nicht immer tun. Es gibt bestimmt auch Modelle, wo es sich anbietet, dass man einen Container benutzt.

Stefan Tilkov: Wobei du schon eigentlich ganz richtig gesagt hast. Container benutzen bedeutet ja nicht zwingend, dass man Container und Anwendung separat installiert. Also man kann einen Tomcat benutzen oder einen Jetty oder einen Glasfish oder JBoss oder was auch immer und dabei diesen Container eben einbetten, womit er dann ja kein Container mehr ist. Also embedded container ist ja ein bisschen blödsinniger Widerspruch in sich, weil wenn man den Container einbettet eben gerade kein Container-Modell mehr fährt.

Michael Vitz: Genau. Aber auch wenn man jetzt sagt, ich installiere irgendwo einen Tomcat und schmeiße da nur noch mein War rein, finde ich, da gibt es legitime Gründe für, das so zu tun. Also das ist jetzt finde ich kein Muss, was man unbedingt machen muss. Aber gerade dieses selbst starten, man hat nachher nur ein Ding, was man zwischen Umgebungen verschieben muss, das hat mit Sicherheit seinen Charme.

Stefan Tilkov: Ok. Die Twelve-Factor App als ganzes, denkst du, sie ist sehr Cloud-spezifisch? Oder kannst du dir vorstellen, dass man das also auch in anderen Umgebungen macht?

Michael Vitz: Also den Hauptgewinn hat man mit Sicherheit, wenn man das ganze in einer Cloud macht, weil die Cloud einfach besonders skalierbar ist. Aber das heißt ja nicht, dass man nicht auch intern skalieren möchte oder weiß, dass man das muss. Und dafür muss man dann halt auch wieder eine Umgebung schaffen. Also man braucht eben vorne einen Loadbalancer oder irgendwas, was die Last verteilt. Man braucht einen Mechanismus, der erkennt, wann ich denn jetzt neue Prozesse starten muss. Aber dann fängt man ja schon an, sozusagen seine eigene Cloud in dem Sinne zu bauen. Also man braucht es wahrscheinlich nur für Cloud-Umgebungen, aber das kann eben auch intern passieren, aber dann baut man eine Art Cloud oder eine Mini-Cloud mit den benötigten Komponenten.

Stefan Tilkov: Ok, aber es müsste offensichtlich keine Public-Cloud sein.

Michael Vitz: Nein, das auf keinen Fall.

Stefan Tilkov: Das hat nicht nur Sinn in einerm PaaS-Umfeld, sondern man kann es sich auch gut vorstellen, wenn man eben diese Anforderung hat, dass man das in einem internen Deployment genauso macht.

Michael Vitz: Ja genau.

Stefan Tilkov: Ok. So ein bisschen wird man natürlich von der ganzen Diskussion heutzutage erinnern an Microservices. Teilst du das? Siehst du diesen Bezug auch oder wo siehst du das gleich und wo siehst du das anders?

Michael Vitz: Naja, das Problem ist bei Microservices immer, dass es keine eindeutige Definition gibt und das jeder das ein bisschen anders definiert. Aber ich denke, wenn man eine Twelve-Factor App gebaut hat und man fragt jemanden danach: “Ist das ein Microservice?”, dann würden wahrscheinlich fast alle Leute “Ja” sagen. Weil es hat eigentlich diese wirklich typischen Dinge. Also es ist etwas einzelnes, was man schnell startet, was nur über Port-Bindings mit anderen spricht. Dann hat man noch sowas wie Backing-Services, die ja auch wieder so eine Art kleiner Service sind. Also wenn man eine Twelve-Factor App hat, dann wird man wahrscheinlich auch einen Microservice haben. Wie auch immer man den jetzt definiert.

Stefan Tilkov: Für sind das so zwei Punkte, bei denen ich immer hadere. Das eine ist, ich bin mir nicht so ganz sicher, ob wirklich kein UI drin ist. Ich weiß nicht, ob die das ausschließen irgendwo. Ich habe das Gefühl, die sehen das durchaus als Option, dass es eine kleine Web-App ist. Dann ist eben die Twelve-Factor App eine Web-Oberfläche.

Michael Vitz: Achso, nö natürlich darf man ein UI haben.

Stefan Tilkov: Da bin ich mir nicht so sicher, ob das bei Microservices immer der Fall ist. Also da gibt es glaube ich Leute, die der Meinung sind, der Microservice hat kein UI, sondern der wird von was anderem benutzt, was dann das UI darstellt.

Michael Vitz: Ah ok.

Stefan Tilkov: Und das andere, wo ich mir auch nicht so ganz sicher bin, ist der Status. Also der Microservices haben oft diese Idee, dass sie den Status innen drin haben. Und das hast du mir vorhin erklärt, ist hier ja gezielt anders. Hier hat man den Status irgendwie draußen. Das wären so zwei Punkte.

Michael Vitz: Ja.

Stefan Tilkov: Aber ich glaube, wie du schon gesagt hast, richtig klar definiert ist das natürlich nicht und hart abgegrenzt schon gar nicht. Das ist halt irgendwie das Problem, wenn man auf einen rennenden Hasen schießt.

Michael Vitz: Also, die Twelve-Factor App sagt ja bei diesen Backing-Services auch nicht, dass ein Service, den ich benutze nicht zusätzlich auch ein UI haben darf, der sagt ja eigentlich nur “Ich komme da irgendwie an meine Daten dran.” und definiert nicht wie. Aber wenn man jetzt so ein Backing-Service hat, wie ein Datenbank, die wird da ja auch als Backing-Service – das ist ja dann im Prinzip schon eine Art Microservice. Ich benutzt einen Service, um nur an Daten zu kommen.

Stefan Tilkov: Ok. Wie sieht das aus mit Werkzeugen für das ganze? Also sollte man jetzt suchen nach dem besten Twelve-Factor App Java-Framework? Oder was hast du für eine Meinung dazu?

Michael Vitz: Also man kann das natürlich versuchen. Ich habe es ehrlich gesagt nicht versucht. Ich denke, man wird dazu nichts finden. Aber ich sagte ja ganz am Anfang, es ist eine sprachunabhängige Methode. Und dementsprechend sind die Prinzipien auch so angelegt, dass man sie in jeder Programmiersprache anwenden kann. Also was man halt braucht ist irgendeine Art Dependendy-Manager. Den hat aber heutzutage …

Stefan Tilkov: Alles.

Michael Vitz: … so gut wie jede Programmiersprache, die ich so kenne. Dann, dass man einen Prozess starten kann, aber das muss man sowieso immer, wenn man irgendwas startet in einer Programmiersprache. Und dass man auf Umgebungsvariablen zugreifen kann. Und das kann eigentlich auch jede Sprache. Und mehr fordert die Twelve-Factor App ja gar nicht. Also da braucht man kein Framework oder keine Tools für. Was natürlich nicht heißt, dass man nicht welche benutzen darf. Also z. B. bei dem Thema Logs – das ist immer so einfach zu veranschaulichen – nur, weil die nach stdout gehen, heißt das ja nicht, dass man jetzt überall in Java System.out.println schreibt, sondern auch da kann man natürlich ein vernünftiges Logging-Framework für benutzen, was Kategorien hat, was das Datum einträgt, die Zeit usw.

Stefan Tilkov: Ich weiß nicht, ob wir das raus schneiden können, deswegen falls man das nachher noch hört, wir haben ausnahmweise mal einen Hund hier im Büro, der zwischendurch ein bisschen mit diskutieren möchte.

Michael Vitz: Der hat auch seine Meinung.

Stefan Tilkov: Ok. Wir haben die URL glaube ich noch gar nicht erwähnt. Natürlich gibt es eine Website zum dem ganzen, wo im wesentlichen drauf steht, was wir gerade diskutiert haben.

Michael Vitz: Genau, das ist einfach 12factor.net. Und zwar 12factor halt mit eins, zwei, also zwölf als Zahl und einem c für factor und nicht ins Deutsche Faktor.

Stefan Tilkov: Schreibe wir natürlich auch in die Shownotes rein, klar.

Michael Vitz: Genau, steht in den Shownotes.

Stefan Tilkov: Gibt es sonst noch andere Stellen, an denen man Informationen darüber finden kann oder andere Quellen, die du nennen würdest?

Michael Vitz: Es gibt eine infoQ Präsentation, ich glaube von dem Adam Wiggins selber. Da hat er die zwölf Prinzipien vorgestellt. Das kann man sich sicher ganz gut angucken, das beschreibt eigentlich all das, was wir jetzt hier gemacht haben, nochmal auf englisch, vielleicht an einiges Stellen etwas tiefer. Das ist eigentlich das größte. Ansonsten, wenn man danach googelt, dann gibt es halt häufiger mal so Diskussionen irgendwie “Ist die Twelve-Factor App Methode überhaupt korrekt?” und “Sollte ich die machen?”. Man findet auch häufig Guides, wie mache ich das jetzt z. B. in Go oder wir mache ich eine Twelve-Factor App in Java. Das kann man sich angucken, muss man aber glaube ich nicht, denn so schwer ist es an für sich nicht.

Stefan Tilkov: Ok. Packen wir auch noch in die Shownotes rein, die Links.

Michael Vitz: Genau.

Stefan Tilkov: Alles klar. Gut, Michael, dann vielen Dank für die Episode.

Michael Vitz: Gerne.

Stefan Tilkov: Und Gruß an die Hörer raus. Tschüss.

In Memoriam ∞ CEO & Principal Consultant

Stefan was a founder and Principal Consultant at INNOQ Germany, where he spent his time alternating between advising customers on new technologies and taking the blame from his co-workers for doing so. He was a frequent speaker at conferences and author of numerous articles.

We mourn the loss of Stefan.

Senior Consultant

Michael has more than fifteen years of experience building and maintaining software for the JVM. As a senior consultant for software architecture and engineering at INNOQ, he helps clients by building well crafted and value providing software. He also contributes to open-source projects, publishes articles, delivers talks, and was announced java champion in 2021.