Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Lesedauer 20 Min.

Newsletter-System in 20 Minuten

Der passende Dienst kann die Entwicklungszeit immens verkürzen.
© dotnetpro
Manchmal gibt es Tools und Technologien, die für mich wie die Faust aufs Auge passen. Sie erfüllen einen lang gehegten Wunsch oder sogar ein gar nicht mal so bewusstes Bedürfnis. Intelli­sense, Garbage Collection oder LINQ sind für mich Beispiele dafür. Und jetzt: Make, das früher Integromat hieß.Make hat mich vom ersten Moment an begeistert, weil darin Entwurf, Codierung und auch noch Serviceorientierung zusammenkommen. Aber der Reihe nach …

Software als Bündel von ­Datenflüssen

Für mich ist auch moderne Software lediglich ein Bündel von Kommandozeilenprogrammen. Der einzige Unterschied: Diese Kommandozeilenprogramme werden nicht direkt mit Kommandozeilenparametern gesteuert und Ergebnisse werden nicht auf der Konsole ausgegeben, sondern grafische Benutzeroberflächen (GUI) auf dem Desktop oder im Web beziehungsweise REST-APIs sind vorgeschaltet. Dahinter jedoch könnte es wie früher aussehen.Ein triviales Beispiel soll verdeutlichen, was ich meine: ein Programm, das die Zahl der Tage in einem Monat ausgibt. Wie würde das modern auf dem Desktop aussehen?Das Programm würde durch Doppelklick aufgerufen, würde einen Dialog zeigen, in dem der Benutzer einen Monat auswählt, und dafür sofort die Anzahl der Tage anzeigen. Bild 1 gibt einen Eindruck von der grafischen Oberfläche. Die Anwendung würde auf zwei Ereignisse reagieren: die Selektion eines Eintrags in der Combobox und die Veränderung der Jahreszahl. In beiden Fällen würde jedoch dasselbe geschehen: Mit Monat und Jahr würde eine Funktion die Zahl der Tage bestimmen.
Grafische Oberflächeeiner modernen Anwendung(Bild 1) © Autor
Das GUI wäre abhängig von der Funktion. Oder mit ein bisschen Struktur: Das GUI wäre von der Domäne abhängig. Oder ein bisschen allgemeiner: Das Frontend wäre vom Back­end abhängig (Bild 2).
Das Frontendist vom Backend abhängig(Bild 2) © Autor
Das ist eine saubere Arbeitsteilung; das Single Responsibility Principle (SRP) wird honoriert:
  • Entscheidungen darüber, wie das Programm mit dem Benutzer kommuniziert, sind im Modul Frontend gekapselt.
  • Entscheidungen darüber, wie Funktionalität erbracht wird, sind im Backend gekapselt.
Die Funktion im Backend wäre hier natürlich trivial (Bild 3). Dennoch wäre ihre Separation sinnvoll, weil sie damit eben unabhängig vom Front­end ist. Ob es sich dabei um ein GUI oder die Kommandozeile/Konsole handelt, ist einerlei.Dieselbe Funktion kann in einem Kommandozeilenprogramm aufgerufen werden:
Die Domänenlogikist modularisiert(Bild 3) © Autor
$ dotnet kalenderhilfe.dll 2 2020
29
$ 
Was hätte sich verändert? Nur das Frontend. Das sähe nun vielleicht wie in Bild 4 aus. Statt die Parameterwerte aus GUI-Steuerelementen auszulesen, werden sie nun von der Kommandozeile gepflückt; statt das Ergebnis einem GUI-Steuerelement zuzuweisen, wird es auf der Konsole ausgegeben.
Ein Frontendfür eine Kommandozeilenanwendung(Bild 4) © Autor
Die Oberfläche hat sich verändert. Der Kern ist gleich geblieben. Das ist für mich entscheidend. Um diesen Punkt zu betonen, kann ich das Beispiel ins Extreme treiben: Das Kommandozeilenprogramm könnte vom GUI-Programm aufgerufen werden; auf diese Weise ließe sich eine existierende Software aufhübschen (Bild 5).
Das Backendmuss nicht im selben Prozess implementiert sein wie das Frontend. Es kann als weiterer Prozess bei Bedarf instanziert werden(Bild 5) © Autor
Moderne Software als Bündel von Kommandozeilenprogrammen? Genau. Das ist meine grund­legende Sicht, die mich aus dem Feuerwerk der Front­end-Technologien rettet. Vorgestern WinForms, gestern WPF, heute HTTP-Controller, morgen MAUI (.NET Multi-platform App UI [1])? Das ist mir einerlei. Wer sich für die ein oder andere Benutzerschnittstellentechnologie interessiert, mag sich damit beschäftigen. Wer dagegen an Funktionalität interessiert ist, kann sich unabhängig von der UI-Technologie darauf konzentrieren.Egal auf welcher Technologie ein Frontend basiert, es tut immer dasselbe:
  • Collect: Es sammelt Benutzereingaben von einem Input-Medium, zum Beispiel Kommandozeile, Konsole, WinForms-Steuerelement, JSON/HTTP.
  • Project: Es projiziert Daten auf ein Output-Medium, zum Beispiel Konsole, WPF-Steuerelemente, JSON/HTTP.
Die funktionalen Anforderungen an Software könnten immer mit Kommandozeilenprogrammen realisiert werden. Der Input würde per Kommandozeilenparameter und StdIn hinfließen, der Output per StdOut geliefert werden; alle Daten könnten in textueller Form – wenn strukturiert, dann zum Beispiel als JSON-Daten – fließen. Ich nenne diesen Ansatz mal „functional core, user friendly shell“ (Bild 6). Bitte beachten Sie: Dies ist keine Referenz auf das Muster „functional core, imperative shell“ [2]! Mir geht es nicht darum, mit welchem Paradigma Core und Shell implementiert werden, sondern dass sie ganz verschiedene Aufgaben haben. Mein „functional“ steht für Funktionalität, ist das des Musters für funktionale Programmierung.
Eine universelle Architektur, die die grundlegenden Aspekte trennt(Bild 6) © Autor
Damit will ich nicht sagen, dass An­wendungen so gebaut werden sollen. Es ist für mich mehr ein Denkmodell. An­dererseits: Warum nicht? Es gibt nur ­einen Grund, der dagegen spricht: suboptimale Performance. Doch wäre die Performance wirklich so schlecht? Wenn das messbar ist, dann kann schrittweise optimiert werden. Solange es aber nur eine Ahnung bleibt, könnte bis zum Beweis des Gegenteils so strukturiert werden.Warum? Darin liegt doch ein Zusatzaufwand, weil der Functional Core Input und Output (de)serialisieren müsste. Für mich liegt der Vorteil in der deutlichen Trennung der grundlegenden Aspekte.Im Web ist das normal: Das Frontend wird mit HTML/JS ­realisiert, der Functional Core, das Backend, wird etwa als REST-Service mit .NET/C# oder Node.js/TypeScript umgesetzt. Der Performance-Verlust durch die Kommunikation wird ausgeglichen durch die Skalierbarkeit. Warum so eine strikte Trennung nicht grundsätzlich denken?Aber wie gesagt, erst einmal ist das nur ein Denkmodell. Dazu gehört für mich auch die Trennung der beiden Aspekte innerhalb des Frontends: Col­lection und Projection (Bild 7). Selbst wenn für beides dieselbe Technologie benutzt wird, ist das, worum es geht, doch sehr verschieden.
Trennungder Aspekte des Frontends(Bild 7) © Autor
Um diese Trennung deutlich zu machen, finde ich es hilfreich, die Abhängigkeit von Frontend zu Backend aufzubrechen. Die Abhängigkeit ist unnötig beziehungsweise nicht hilfreich und allenfalls ein Implementationsdetail. Entscheidend ist die Intention:
  • Dass unterschiedliche Aspekte ihre Arbeit als Funk­tionseinheiten innerhalb eines Prozesses tun.
  • Dass jede Funktionseinheit mit den passenden Daten versorgt wird.
Die passende Darstellung für einen solchen Prozess ist der Datenfluss (Bild 8).
Frontend-Aspekte und Backendals Funktionseinheiten in einem Datenfluss: Ein Prozess für die Transformation von Benutzereingaben in Programm­reaktionen(Bild 8) © Autor
  • Irgendwie sammelt das Frontend Input aus der Umwelt. Dieser Input ist das Produkt der Collection, die ihn als Prozessschritt ausstößt – zur weiteren Verarbeitung down­stream durch andere Funktioneinheiten im Prozess.
  • Das Backend wartet auf Input, um daraus ein Ergebnis herzustellen, den Output. Er ist der Ausstoß des Backends in den Prozess und wird downstream verarbeitet.
  • Das Frontend wartet darauf, etwas der Umwelt zu präsentieren. Es projiziert den Output in einer für den Benutzer nützlichen Weise.
Keiner der Prozessschritte weiß vom anderen. Alle sind unabhängig voneinander. Das dient der Flexibilität und Testbarkeit. Collection und Projection sind quasi nur Klammern um das Wesentliche einer Software (Bild 9): das Backend als Hort der Funktionalität einer Anwendung.
Die Aspekte des Frontendsklammern den Functional Core einer Software(Bild 9) © Autor
Die Funktionalität, die das Backend verkörpert, besteht natürlich aus vielen Facetten. Das API des Backends veröffentlicht viele Funktionen. Diesen vielen Funktionen stehen viele Trigger im Frontend gegenüber, die die funktionalen Facetten anstoßen; und es gibt Arten der Darstellung von Ergebnissen dieser Facetten.Im Prinzip kann deshalb der eine Block, der das Backend bisher darstellte, aufgelöst werden in viele. Jede Facette des Backends kann durch einen eigenen Block repräsentiert und mit den Aspekten des Frontends verbunden werden (Bild 10).
Funktionale Facetten(Sub-Funktionalitäten) des Backends können als eigenständige Backends angesehen und separat in Prozessen mit den Frontend-Aspekten zusammengefasst werden(Bild 10) © Autor
Wie die Darstellung suggeriert, kann jede Facette als kleineres, eigenständiges Backend angesehen werden. Das bedeutet, jede Facette könnte auch als eigenständiges Kommandozeilenprogramm realisiert werden. Ultimativ wäre das Ergebnis eine Lambda-Architektur, bei der jede Funktion des Backends für sich verpackt ist. Mit Serverless-Plattformen wie Azure Functions oder AWS Lambda ist das im Web schon lange realisiert.Warum also nicht dieses Denkmodell auf alle Anwendungen übertragen? Für mich ist es der Default; so beginne ich jedes Softwareprojekt: Ich suche nach den Backend-Facetten und versuche, sie so weit wie möglich zu separieren.Der Trennung von Frontend und Backend widerspricht die Vermutung, dass es Performance-Probleme geben könnte.Der Trennung von Backend-Facetten widerspricht die Vermutung, dass sie Zustand teilen könnten, was über Facetten-Prozesse hinweg umständlicher und auch wieder imperformanter wäre.Auch hier wende ich aber ein: Das sind Vermutungen. Vielleicht ist es so, vielleicht auch nicht. Warum für Vermutungen vorzeitige, unbegründete Optimierungen vornehmen?Backend-Facetten im selben Prozess hosten, Backend und Frontend im selben Prozess hosten: Das sind für mich vorzeitige Optimierungen für Effizienz, die massiv Flexibilität kosten, weil sie die Entkopplung zwischen Aspekten und Facetten verringern. Deshalb wäre ich damit vorsichtig. Deshalb ist für mich das primäre Denkmodell ein Bündel aus Datenflüssen zwischen Frontend- und Back­end-Services.Ausführlicher habe ich diese Sicht in zwei Blog-Artikeln beschrieben [3]  [4]. Und die Grundlage, warum Datenflüsse und nicht Abhängigkeiten das architektonische Denken beherrschen sollten, wird in [5] entwickelt.

Funktionalität im Datenfluss

Die Funktionalität einer Software wird durch den Functional Core implementiert. Der ist in mehrere Facetten getrennt, die durch unterschiedliche Trigger angestoßen werden. Die Aspekte des Frontends sind dem Functional Core nicht bekannt; er muss nur bereit sein, Input auf Aufforderung anzunehmen, zu verarbeiten und seinen Output in bestimmter Weise an die Umwelt abzugeben. Woher der Input kommt, wohin der Output geht, das ist den Funktionalitätsfacetten einerlei. Diese radikal objektorientierte Sicht leite ich ausführlich in dem Buch „Softwareentwurf mit Flow-Design“ [6] her.So sieht der Zusammenhang aus, in dem der Functional Core arbeitet. Doch wie sind seine Facetten intern strukturiert? Meine Antwort: It’s data flows all the way down.Dass eine Facette des Backends nur aus einem Prozessschritt besteht, ist nicht zu erwarten. Das bedeutet: Was in Bild 10 noch wie schwarze Monolithen aussieht, ist in Wirklichkeit eine Perlenkette vieler Schritte. In Bild 11 sei das lediglich vereinfacht als eindimensionaler Datenfluss dargestellt; tatsächlich und realistisch haben diese Datenflüsse jedoch drei Dimensionen [6].Ob diese Datenflüsse im selben Programm implementiert sind oder in verschiedenen, ist für diese grundsätzliche Sichtweise unerheblich. Das ist eine Frage der Gewichtung von Flexibilität gegenüber Effizienz.
Die Backend-Facettensind selbst Datenflüsse(Bild 11) © Autor
Als Beispiel mag ein Softwaresystem dienen, mit dem man Newsletter erstellen und versenden kann. Welche Funktionalität ist nötig?
  • Newsletter-Abonnements müssen eingetragen werden können.
  • Jeder neue Abonnent bekommt eine Begrüßungs-E-Mail.
  • Ein Newsletter soll zunächst als Entwurf erarbeitet und schließlich veröffentlicht werden.
  • Veröffentlichung bedeutet, der Newsletter wird an alle Abonnenten verschickt.
Mir ist klar, dass es das alles schon gibt. Wenn ich es hier als Beispiel nutze, dann wegen der Überschaubarkeit und einfachen Verständlichkeit des Szenarios. Außerdem dient es sich als eine serviceorientierte Implementation an, auf die ich hinauswill.Bild 12 zeigt das Softwaresystem mit einem simplen GUI als Frontend und den benötigten Ressourcen:
Softwaresystemmit GUI und Ressourcen(Bild 12) © Autor
  • Die Abonnenten brauchen einen Speicherort, der über ein Persistenz-API anzusprechen ist.
  • Der Newsletter braucht einen Speicherort, bis er verschickt wird, der über ein Persistenz-API anzusprechen ist.
  • Der Newsletter wird per E-Mail verschickt; dafür braucht es ein Email-API.
Das Frontend wird mittels eines API implementiert, ebenso erfolgt die Kommunikation mit den Ressourcen via API. Das ist von Interesse für die Softwarestruktur: APIs sind in Module zu kapseln, um sie testbar und austauschbar zu machen.Die interessanten Datenflüsse sind die für die Registrierung eines neuen Abonnenten und den Newsletter-Versand. Wie sehen die Prozesse/Datenflüsse dafür aus?Bei der Registrierung wird der neue Abonnent dem Verteiler hinzugefügt und dann eine E-Mail mit festem Begrüßungstext an ihn verschickt (Bild 13).
Datenflussfür die Registrierung eines neuen Abonnenten(Bild 13) © Autor
Bei der Veröffentlichung ist der Prozess etwas umfänglicher – siehe hierzu Bild 14:
Datenflussfür den Newsletter-Versand(Bild 14) © Autor
1. Der Newsletter-Text wird gespeichert.2. Die Abonnenten werden geladen.3. Der Newsletter-Text wird für jeden Abonnenten personalisiert.4. Der personalisierte Text wird per E-Mail verschickt.5. Der Newsletter-Text wird als versandt markiert.Diese Datenflüsse lassen sich geradlinig in einer Desktop-Software implementieren. Oder ein REST-Backend hostet sie. Die Realisierung der Funktionalität sieht in beiden Fällen gleich aus. Was sich unterscheidet, ist die Verbindung mit dem Frontend. Bild 15 skizziert, wie das für den Newsletter-Versand aussehen könnte. Eine detailliertere Modularisierung lasse ich aus; sie trägt nichts zur Überführung der Implementation nach Make bei.
Skizze der Implementationdes Datenflusses für den Newsletter-Versand(Bild 15) © Autor
Die Implementation eines Datenflussentwurfs kann denkbar simpel sein. Wie das Listing in Bild 15 zeigt, ist jeder Schritt im Prozess lediglich in einen Funktionsaufruf übersetzt worden. Die Schleife implementiert das, wo in Bild 14 fließende Daten ominös mit einem Sternchen gekennzeichnet sind; dieses steht für einen Stream, der eine Menge von Datenelementen einzeln zum verarbeitenden Prozessschritt downstream fließen lässt. Näheres dazu finden Sie in [6].So natürlich und geradlinig die Umsetzung aussehen mag, eines ist hervorzuheben: Die Aufgabe der Funktion NewsletterVeröffentlichen() ist es, die anderen Funktionen zu integrieren. Sie verdrahtet die einzelnen Funktionsbausteine zu einem Ganzen, zum Prozess. Sie stellt den Datenfluss zwischen ihnen her.Integration ist eine ganz eigene Verantwortlichkeit (Integration Operation Segregation Principle (IOSP)). Insbesondere eine Funktion sollte entweder darauf konzentriert sein oder Funktionalität mit Logik implementieren.

Datenflüsse in der Cloud mit Make

Bei Make [7] [8] gilt nomen est omen: Make ist ein Service, der ganz auf Integration von Funktionseinheiten zu Prozessen in Form von Datenflüssen konzentriert ist. Bild 16 zeigt ein Beispiel der Selbstdarstellung von Make.
Beispiel für einen mit Makeimplementierten Datenfluss(Bild 16) © Autor
Die Funktionseinheiten, die integriert werden, sind vor allem Dienste im Internet. Derzeit bietet Make knapp 900 Apps, das heißt Internetdienste, deren API in mundgerechter Weise gekapselt wurde. Dazu kommen Services, die zwischen App-Prozessschritte geschaltet werden können, um deren Daten innerhalb von Make zu verarbeiten, zum Beispiel Router oder RegEx-Filter.Was mich von Anfang an begeistert hat, ist die Nähe der Make-Notation zur Flow-Design-Datenflussnotation (Bild 17): Oben ein Beispiel aus der Make-Dokumenta­tion, unten ein nachträglicher Entwurf dazu.
Ein Datenfluss-Entwurflässt sich fast eins zu eins nach Make übertragen(Bild 17) © Autor
  • Funktionseinheiten sind Kreise und stehen für einen spezifischen API-Aufruf von App beziehungsweise Service.
  • Daten fließen von links nach rechts entlang von (gepunkteten) Linien. Welche Daten in einer Ausführung geflossen sind, zeigt der Durchfluss-Zähler in Anzahl und Inhalt – siehe Bild 18.
Welche Daten in welcher Strukturzwischen den Funktions­einheiten fließen, kann für jede Funktionseinheit nach deren ­Ausführung eingesehen werden(Bild 18) © Autor
  • Da es kein Frontend gibt, das die Prozessausführung anstoßen könnte, stehen verschiedene Trigger zur Verfügung, zum Beispiel periodische Ausführung oder Reaktion auf Veränderung innerhalb der Datenquelle einer App.
Mit Make können Softwarelösungen aus getesteten, performanten, skalierbaren existierenden Bausteinen zusammengesetzt werden. Hier und da ist ein wenig Glue Code nötig, hier und da etwas Datenflusskontrolle, doch der Aufwand dafür ist begrenzt. Und wo eine App fehlt, kann die Lücke durch eigene REST-Services geschlossen werden; Make bietet Bausteine für die Low-Level-HTTP-Kommunikation und JSON-/XML-(De-)Serialisierung. Der Charme von Make besteht aber gerade darin, Services nicht selbst zu implementieren, sondern existierende zu nutzen. Das reduziert den Entwicklungsaufwand und die ­Fehlerwahrscheinlichkeit.Der Einstieg in Make ist kostenlos möglich. Für einen professionellen Einsatz ist der Preis von der Anzahl der ausgeführten Operationen (Schritte in Datenflüssen) und der Datenmenge abhängig. Ich denke, selbst für den kleinsten Bezahlplan mit neun Euro pro Monat bekommt man genug, um viele Szenarien allemal inhouse abzudecken.

Newsletter versenden mit Make

Mit Make dreht sich das Denken oft um 180 Grad, finde ich. Ich denke nicht mehr vom Frontend aus in die Implementation hinein, sondern von den existierenden Apps zum Frontend hin.Wenn ein Szenario in der Cloud implementiert werden kann, frage ich mich:
  • Welche Ressourcen sind beteiligt?
  • Wie kann der Prozess getriggert werden?
  • Was braucht es an Glue Code dazwischen?
Wie das konkret aussehen kann, zeigt das Beispiel des Newsletter-Softwaresystems. Dass darin mindestens ein Internetdienst eine Rolle spielt, ist klar: Speicherung mag noch lokal realisiert werden, doch E-Mails brauchen Zugriff auf einen Provider. Warum also nicht das ganze Softwaresystem in die Cloud auslagern und möglichst viel Funktionalität durch existierende Dienste abdecken lassen, statt sie selbst zu implementieren?Für den E-Mail-Versand bietet Make verschiedene Apps. Sobald der personalisierte Text hergestellt ist und die E-Mail-Adresse eines Abonnenten bekannt ist, ist die E-Mail das kleinste Problem. Aber woher den Text nehmen, wo die Abonnenten speichern? Welche Ressourcen, welche Apps bieten sich an?Für die Abonnenten liegt eine Datenbank nahe. Make bietet Apps zum Beispiel für MongoDB oder MySQL an. Doch wie werden diese Datenbanken gefüllt? Für sie müsste ein Frontend geschrieben werden.Angesichts einer überschaubaren Menge von weniger als 5000 zu erwartenden Abonnenten könnte auch eine Google-Tabelle der Speicherung dienen. Wenn das am Ende nicht genug ist, können die Daten von dort immer noch automatisch in eine MongoDB oder Ähnliches übertragen werden. Für den Einstieg in den Newsletter-Versand wäre eine Google-Tabelle hingegen schon allein deshalb interessant, weil sie bereits zwei Frontends bietet: Google Forms und Google Sheets.
  • Mit Google Forms kann ich Interessenten auf einer Landing Page oder per Link in einer E-Mail anbieten, sich direkt in den Newsletter-Ver­teiler einzutragen. Google Forms erlaubt es, eingereichte Formularinhalte gleich an eine verbundene Tabelle anzuhängen.
  • Mit Google Sheets kann ich im Backoffice die Abonnenten bequem in der verbundenen Tabelle verwalten.
  • Für Endkunden ist das kein passendes Frontend, für Mit­arbeiter kann es aber ausreichen, insbesondere in einer ­frühen Phase des Softwareprojekts.
Newsletter-Texte können ja wahre Kunstwerke sein. Für das Beispiel hier will ich mich aber auf reinen Text beschränken. Eine Umstellung auf HTML-Inhalte inklusive Bildern ist ein Thema, das Make nicht aus der Ruhe bringt, hier jedoch die Komplexität unnötig erhöhen würde.Drei mögliche Implementationen für die Newsletter fallen mir ein:1. Der Newsletter wird in einem E-Mail-Client als E-Mail gestaltet und dann an eine spezielle Adresse geschickt. Für die dotnetpro könnte sie zum Beispiel publish-newsletter@dotnetpro.de lauten. Die Ressource zur Speicherung des Newsletters während des Entwurfs wäre der E-Mail-Client, die Ressource für die Veröffentlichung der E-Mail-Server.2. Der Newsletter wird in einem Trello Board geschrieben. Das Brett hat die Spalten: Entwurf, Veröffentlichen, Veröffentlicht. Je Text gibt es eine Trello-Karte mit Titel und Text. Angestoßen würde die Veröffentlichung durch Verschieben der Karte in die Spalte Veröffentlichen. Für reinen Text ist der Editor für die Kartenbeschreibung gut genug – solange der Newsletter nicht zu lang wird.3. Der Newsletter wird in einer Textdatei in der Dropbox gespeichert. Dort gibt es in einem Ordner die Unterordner Entwurf, Veröffentlichen, Veröffentlicht. Während des Entwurfs wird die Newsletter-Datei im Entwurfsordner gespeichert; sobald sie in den Veröffentlichungsordner verschoben wird, springt der Versand an. Vorteil hier: Jeder Texteditor kann benutzt werden. Nachteil: Die Texte in der Dropbox zu verwalten ist etwas weniger strukturiert als in einem Trello Board.Ich denke, Option 1 spare ich mir für später. Dort sehe ich Potenzial, das Problem der HTML-Newsletter zu lösen. Aber im Moment gibt es da zu viele Unsicherheiten. Etwas Forschungsaufwand, ein Spike wäre angezeigt. Für den jetzigen Zweck will ich es einfacher halten, also Option 2 oder 3.Nach meiner Erfahrung ist beides gleich simpel mit Make zu realisieren. Deshalb entscheide ich mich für die Trello-Variante, weil sie visueller/strukturierter ist; hier gibt es schon ein Frontend, das zum Szenario passt.Frontend und Backend sind damit wie in Bild 19 besetzt:
Das Frontend des Softwaresystemssetzt sich aus den Benutzerschnittstellen existierender Services zusammen(Bild 19) © Autor
  • Benutzerschnittstellen müssen nicht implementiert werden. Was für eine Erleichterung!
  • Das Backend besteht vor allem aus Integration in Make, wo APIs zu einem Prozess zusammengezogen werden.
Die Kommunikation zwischen Frontend-Teilen und Backend erfolgt durch zwischengeschaltete Medien (Bild 20): die Datenbanken der integrierten Services.
Die Ressourcen der integriertenServicesdienen auch der Kommunikation zwischen Make-Backend undFrontends(Bild 20) © Autor
Wenn in sie Daten eingeschrieben werden, triggert das die Verarbeitung im Backend.Der Abonnent benutzt ein Google-Formular, um sich in den Newsletter-Verteiler einzutragen (Bild 21); der Verteiler ist ein Google Sheet, das in der Tabellenkalkulation auch im Backoffice verändert werden kann.
Der Newsletterwird über ein Google-Formular abonniert(Bild 21) © Autor
Die Newsletter-Redaktion findet innerhalb des Trello Boards statt (Bild 22); die Veröffentlichung ist dort nur ein Schubs einer Karte in die Spalte Veröffentlichen. Das Back­end verschiebt den Newsletter nach dem Versand an den Verteiler automatisch in die letzte Spalte.
Ein simplerNewsletter-Redaktionsprozess in Trello(Bild 22) © Autor
Für Registrierung und Verwaltung der Newsletter muss nichts in Make getan werden. Dort sind lediglich zwei Prozesse – sogenannte Scenarios – für die Begrüßung und den Newsletter-Versand aufzusetzen. Bild 23 zeigt exemplarisch anhand des Begrüßungsprozesses, wie das geht: Das Make-Scenario soll jedem neuen Newsletter-Abonnenten eine personalisierte Willkommens-E-Mail schicken.
Schrittweise einen Prozessin Make implementieren (Bild 23) © Autor
1.Ein Make-Scenario besteht zunächst nur aus einem Platzhalter für den ersten Prozessschritt.2. Jeder Prozessschritt wird aus der Liste der Apps und Services ausgewählt.3. Apps werden mit einem Konto für ihren Service und darin mit individuellen Ressourcen verbunden. Hier: Ich verbinde Make mit meinem Google-Konto und wähle darin das Google Sheet aus, in das die Google Form aus Bild 21 Newsletter-Anmeldungen schreibt. Außerdem wähle ich als Funktion für diesen Prozessschritt die Ermittlung neuer Einträge in das Google Sheet.4. Die neuen Tabellenzeilen fließen zum zweiten Schritt, für den ich den Versand einer E-Mail wähle. Dafür binde ich den Schritt an mein
E-Mail-Konto.5. Nach jedem Prozessschritt stehen im Datenfluss alle Daten zur Verfügung, die bis dahin über Apps hineingezogen wurden. Das ist hier zwar nur eine Tabellenzeile, doch aus der kann ich
für den Adressaten der E-Mail die Spalte mit der E-Mail-Adresse wählen.6. Den Begrüßungstext trage ich fest in den Prozessschritt ein; der Vorname des Adressaten wird über einen Platzhalter eingespielt, der ebenfalls aus der Tabellenzeile des vorherigen Schrittes stammt.Die Übersetzung des Datenflusses aus Bild 13 erfolgt mit ­Make quasi mechanisch. Da Google Forms die Speicherung schon vornimmt, reduziert sich die Registrierung auf die Feststellung, ob es neue Anmeldungen gegeben hat. Deren Daten werden geladen und in den Prozess geschoben.Auch wenn der initiale Prozessschritt Watch Rows heißt, beobachtet das Scenario das Google Sheet nicht kontinuierlich und wird auch nicht von Google getriggert. Vielmehr wähle ich ein Polling-Intervall von fünf Minuten: Alle fünf Minuten startet Make das Scenario und der erste Schritt prüft, ob er neue Zeilen in der Tabelle erkennt. Die lädt er und schiebt sie einzeln in den Datenfluss. Wie viele neue Zeilen je Aufruf verarbeitet werden sollen, lässt sich festlegen.Um das Scenario zu testen, kann ich es manuell anstoßen. Ansonsten läuft es nach Einstellung des Polling-Intervalls periodisch automatisch. Neue Abonnenten bekommen also im Mittel nach 2,5 Minuten ihre Begrüßungs-E-Mail. Das scheint mir akzeptabel. Welche Daten bei einem Scenario-Lauf geflossen sind, kann ich an den Prozessschritten einsehen: Der Klick auf die Durchflussangabe öffnet ein Inspektorfenster, das für jedes geflossene/verarbeitete Datenelement Struktur und Inhalt zeigt.Zeitaufwand bis hierher:
  • Einrichtung der Google Form samt Tabelle: fünf Minuten.
  • Aufsetzen des Scenarios: fünf Minuten.
Jetzt weiter mit dem Prozess für den Newsletter-Versand. Dort ist der Trigger ein anderer: Der Versand wird gestartet, sobald eine Karte im Newsletter-Board in die Spalte Veröffentlichen gezogen wurde. Dann soll an alle nicht abgemeldeten Abonnenten in dem Google Sheet der Inhalt der Karte (Titel, Beschreibung) verschickt werden. Der Platzhalter $vorname ist dabei durch den Vornamen des Abonnenten zu ersetzen. Das finale Scenario dafür zeigt Bild 24. Hervorhebenswert hierin ist:
Der Prozessfür den Newsletter-Versand(Bild 24) © Autor
  • Die Beobachtung des Trello Boards stellt nur fest, dass eine Karte in der Spalte Veröffentlichen angekommen ist. Dadurch ist diese Karte jedoch noch nicht im Datenfluss; sie muss anschließend in einem zweiten Schritt explizit geladen werden.
  • Laut Entwurf in Bild 14 sollte die Markierung des Newsletters als versandt erst ganz am Schluss stattfinden. Das ist in der Make-Implementation nicht praktikabel, weil das bedeuten würde, dass der Schritt nach dem E-Mail-Versand liegt. Der
    E-Mail-Versand jedoch wird für jeden Abonnenten durchlaufen; dasselbe würde mit der Markierung passieren. Deshalb verschiebt schon der dritte Prozessschritt den Newsletter in die Spalte Veröffentlicht, bevor der eigentliche Versand stattfindet.
  • Aus dem Google Sheet der Abonnenten werden ­alle Einträge geladen – allerdings werden nur die weitergeleitet, die nicht als abgemeldet gekennzeichnet sind. Das wird durch einen Filter erreicht (Bild 25). Alternativ ließe sich ein solcher Filter auch am Datenfluss in Make anbringen, doch ich nehme an, dass der Filter im Prozessschritt von der App unterstützt wird und die Menge der Daten, die von Google zu Make fließen, begrenzt.
Welche Zeilender Google-Sheet-Tabelle downstream fließen, kann mit einem Filter eingeschränkt werden(Bild 25) © Autor
  • Der E-Mail-Versand funktioniert wie beim vorherigen Prozess. Allerdings kann hier der Text nicht im Prozessschritt parametrisiert werden, weil er dynamisch aus der Newsletter-Karte geladen wird. Also ist eine Ersetzung des Platzhalters nötig (Bild 26). Dafür bietet Make Funk­tionen, die innerhalb von Feldern aufgerufen werden können. Die Notation dafür ist etwas gewöhnungsbedürftig und der Editor ein wenig umständlich, doch letztlich funktioniert diese Programmierung wie gewohnt.
Ersetzen desPlatzhaltersfür den ­Vornamen im Newsletter-Text durch ­eine Funktion von Make(Bild 26) © Autor
Auch für dieses Scenario stelle ich ein Polling-Intervall von fünf Minuten ein. Alternativ könnte ich entscheiden, dass nur jeden Morgen um 7 Uhr geprüft werden soll, ob ein Newsletter zum Versand ansteht. Zeitaufwand für dieses Scenario:
  • Verteiler: null Minuten.
  • Newsletter-Verwaltung mit Trello: fünf Minuten.
  • Aufsetzen des Scenarios: 20 Minuten. Eigentlich sollte es nicht so lange dauern, doch ich war nicht ganz vertraut mit der Trello-App und bin über das Filtern in der Google-Sheet-App gestolpert.

Fazit

Insgesamt hat also die Implementation des Softwaresystems für den Newsletter-Versand weniger als 45 Minuten gekostet. Ich finde, das ist vernachlässigbar im Vergleich zu einer eigenen Implementation mit C# und vielleicht Azure.Als Vorteile sehe ich:
  • Kein Implementationsaufwand für Frontends.
  • Kein Hosting-Aufwand für die Persistenz.
  • Kein Hosting-Aufwand für Services.
  • Getestete und skalierbare Services können „out of the box“ genutzt werden, statt sie selbst entwickeln zu müssen.
Dass angesichts dieser Verteilung des Prozesses auf Schritte, die irgendwo in der Cloud laufen, Latenzen entstehen, ist klar. Ein Make-Scenario kommt nicht dort zum Einsatz, wo es um Performance geht.Es empfiehlt sich vielmehr dort, wo Skalierbarkeit und Verlässlichkeit und Entwicklungsgeschwindigkeit im Vordergrund stehen.Natürlich gibt es noch weitere Alternativen zu Make, zum Beispiel Zapier oder IFTTT. Mit denen habe ich früher gearbeitet.Seit ich Make kenne, bin ich allerdings davon begeistert, weil es so nah an dem ist, wie ich Entwürfe mit Flow-Design mache.Meine Notation finde ich fast eins zu eins bei Make als Implementationssprache wieder; die Art, wie die Daten inklusive des Stream-Konzepts [6] fließen, entspricht dem, wie ich es im Softwareentwurf seit vielen Jahren denke.In diesem Beitrag wollte ich von diesem Gefühl etwas rüberbringen: dass der Entwurf von serviceorientierter Software sehr nah an der Implementation sein kann. Die deklarative Programmierung mit Datenflüssen ist hier!Einiges, das ich nicht zeigen konnte:
  • Der Aufruf von eigenen REST-Services ist möglich. Wo in der Liste der Apps und Services etwas fehlt, kann die Lücke selbst geschlossen werden.
  • Mit sogenannten Routern lassen sich auch mehrarmige Prozesse realisieren, die alternativ oder parallel arbeiten.
  • Scenarios können auch direkt durch HTTP-Aufrufe/Webhooks getriggert werden.
  • Make bietet Variablen, die in einem Prozess gesetzt und ausgelesen werden können.
  • Scenarios können in eine Make-eigene Datenbank schreiben und daraus lesen; nicht alles muss also in der Cloud mittels App persistiert werden.
  • Bei Fehlern können Exception-Flüsse angestoßen werden.
Aber natürlich gibt es auch unschöne Eigenheiten und Lücken bei Make:
  • Die Refaktorisierung von Scenarios wird nicht gut unterstützt. Man kann sie umbauen, man kann das eine vom anderen aufrufen, doch das finde ich umständlich. Es wäre ­zumindest wünschenswert, innerhalb eines Scenarios mehrere Prozessschritte zusammenfassen und wegklappen zu können.
  • Mit Routern lassen sich Datenflüsse in mehrere Arme (fork) auffächern – leider gibt es keine ähnlich einfache Wei­se, um mehrere Flüsse zusammenzufassen (join). Es ist nicht unmöglich, doch es bedeutet Handarbeit.
  • Das Testen von Scenarios wird leider nicht wirklich unterstützt. Es wäre schön, wenn man für Scenarios unterschiedliche Umgebungen einstellen könnte, in denen etwa Variab­leninhalte und Verbindungseinstellungen hinterlegt sind.
Bisher haben mir die Nachteile den Spaß an Make nicht verderben können. Die Vorteile überwiegen für mich. Ich kann, ohne Held von Infrastrukturdiensten wie Azure, AWS oder Kubernetes sein zu müssen, serverless service-oriented implementieren. Um den Vorteil an Entwicklungsgeschwindigkeit und Entlastung beim Betrieb einstreichen zu können, muss ich bereit sein, mich auf Apps zu verlassen, Performance auf ihren Platz zu verweisen und Eventual Consistency zu akzeptieren. Ich finde, dieses Umdenken lohnt sich.

Fussnoten

  1. .NET MAUI, http://www.dotnetpro.de/SL2205Make1
  2. Kenneth Lange, The Functional Core, Imperative Shell Pattern, http://www.dotnetpro.de/SL2205Make2
  3. Ralf Westphal, Sleepy Hollow Architecture – No application should be without it, http://www.dotnetpro.de/SL2205Make3
  4. Ralf Westphal, Terminus Architecture, https://ralfw.de/terminus-architecture
  5. Ralf Westphal, Die IODA-Architektur im Vergleich, http://www.dotnetpro.de/SL2205Make4
  6. Ralf Westphal, Softwareentwurf mit Flow-Design, http://www.dotnetpro.de/SL2205Make5
  7. Integromat, https://integromat.com
  8. Make, http://www.make.com

Neueste Beiträge

DWX hakt nach: Wie stellt man Daten besonders lesbar dar?
Dass das Design von Websites maßgeblich für die Lesbarkeit der Inhalte verantwortlich ist, ist klar. Das gleiche gilt aber auch für die Aufbereitung von Daten für Berichte. Worauf besonders zu achten ist, erklären Dr. Ina Humpert und Dr. Julia Norget.
3 Minuten
27. Jun 2025
DWX hakt nach: Wie gestaltet man intuitive User Experiences?
DWX hakt nach: Wie gestaltet man intuitive User Experiences? Intuitive Bedienbarkeit klingt gut – doch wie gelingt sie in der Praxis? UX-Expertin Vicky Pirker verrät auf der Developer Week, worauf es wirklich ankommt. Hier gibt sie vorab einen Einblick in ihre Session.
4 Minuten
27. Jun 2025
„Sieh die KI als Juniorentwickler“
CTO Christian Weyer fühlt sich jung wie schon lange nicht mehr. Woran das liegt und warum er keine Angst um seinen Job hat, erzählt er im dotnetpro-Interview.
15 Minuten
27. Jun 2025
Architecture & Patterns

Das könnte Dich auch interessieren

UIs für Linux - Bedienoberflächen entwickeln mithilfe von C#, .NET und Avalonia
Es gibt viele UI-Frameworks für .NET, doch nur sehr wenige davon unterstützen Linux. Avalonia schafft als etabliertes Open-Source-Projekt Abhilfe.
16 Minuten
16. Jun 2025
Mythos Motivation - Teamentwicklung
Entwickler bringen Arbeitsfreude und Engagement meist schon von Haus aus mit. Diesen inneren Antrieb zu erhalten sollte für Führungskräfte im Fokus stehen.
13 Minuten
19. Jan 2017
Evolutionäres Prototyping von Business-Apps - Low Code/No Code und KI mit Power Apps
Microsoft baut Power Apps zunehmend mit Features aus, um die Low-Code-/No-Code-Welt mit der KI und der professionellen Programmierung zu verbinden.
19 Minuten
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige