Visuelle Programmierung macht einfach Spaß. Programmierung mit Komponenten aus einem Baukasten macht einfach Spaß. Ich fühle mich an meine Kindheit erinnert, als ich stundenlang mit Lego gespielt habe.
Wenn ich so in die Entwicklergemeinde hineinhorche, scheinen mir visuelle, komponentenbasierte Tools allerdings kein so großes Thema zu sein. Geredet wird über Microservices, Docker, Kubernetes und was dergleichen Verteilungskonzepte bzw. -infrastruktur mehr sind. Dass auf einer Entwicklerveranstaltung aber make.com oder pipedream.com oder Zapier vorgestellt würden… Ich kann mich daran nicht erinnern.
Dabei finde ich den Produktivitätsgewinn durch sie so offensichtlich:
Tausende existierende Komponenten einfach nutzen, statt sie selbst zu bauen, spart Zeit, Geld und Nerven.
Diese Komponenten visuell zu Datenflüssen zu verbinden, statt Code zu schreiben, gibt Überblick, spart Zeit, Geld und Nerven.
Das ganze automatisch skalierbar in der Cloud zu haben, spart vor allem Nerven.
Die Verbindungen sind standardbasiert, vor allem kommen JSON und HTTP zum Einsatz.
Datenflüsse können hierarchisch sein, in dem integrierende Workflows andere per REST-Aufruf einbinden.
Und wer will oder muss, der kann zwischen all das eigene “Funktionen” setzen, die entweder direkt im Workflow stehen (wie bei Pipedream) oder per REST aufgerufen werden. Das Hosting dafür ist mit Deno Deploy, val.town, Vercel, Netlify heute ja trivial. Ganze virtuelle Maschinen aufzusetzen, kommt mir heutzutage schwerfällig vor, selbst mit Docker.
Klar, diese Einfachheit und Flexibilität hat ihren Preis: Es mag eine gläserne Performancedecke geben oder es kann teurer sein, als alles handzuklöppeln. Solange aber die Verhältnisse unklar sind und Wandelbarkeit an allen Ecken und Ende nötig ist, scheinen mir höhere Kosten durchaus gerechtfertigt. Weniger Kosteneffizienz für mehr Flexibilität.
Sobald sich Muster abzeichnen, können Teile eines Softwaresystems ja “calcifiziert” werden: denn “codifizieren” bedeutet, Lösungen zu verhärten. In C# oder Typescript zu codieren und in Docker laufen zu lassen, ist für mich (in vielen Fällen) eine Optimierung — die nicht leichtfertig und zu früh vorgenommen werden sollte.
Ein Workflowbeispiel - sogar mit KI
Anlass für diesen Begeisterungsausdruck ist eine Lösung, die ich gerade für den internen Gebraucht gebastelt habe. Klar, die ist klein. Für sich genommen sind die Unix Programme wie ls oder grep aber auch klein. Die Power von Unix kommt aus der Möglichkeit zu ihrer Integration.
Insofern ist auch meine kleine Lösung nur ein Teil von etwas Größerem. Eine Integration ist jederzeit über Schnittstellen möglich.
Mein Use Case
Meine Frau und ich wollen Rechnungen schreiben. Dafür wollen wir nicht auf das angewiesen sein, was uns Fakturierungslösungen wie Zoho & Co bieten. Die sind uns zu groß, zu schwerfällig. Außerdem macht es Spaß, sich etwas selbst zu basteln.😉
Die Rechnungen schreiben wir mit Excel, an die Kunden versenden wir sie als PDF. Dafür könnten wir uns ein “Excel-Formular” im passenden Layout basteln. Doch irgendwie finden wir das starr und es werden die Aspekte Datenerfassung und Layout vermischt. Ein bisschen Prinzipienreiterei darf auch sein, oder?😉 Die führt auch auf direktem Weg zu mehr Bastelspaß.😁
Also: Eine ganz einfache Excel-Tabelle, die rein auf die Erfassung der nötigen Daten konzentriert ist. Und das Layout der Rechnung, das “Briefpapier”, soll in Word gestaltet werden. Außerdem sollen die Rechnungsnummern automatisch vergeben werden, egal wer von uns auf welchem Rechner eine Rechnung schreibt.
Der Lösungsentwurf
Was braucht es für die Umsetzung dieses “Traums”?
Die Daten in der Excel-Rechnung müssen irgendwie in die Word-Dokumentenvorlage gebracht werden. Geht das mit der Serienbrieffunktion? Hm…? Das kommt mir altbacken vor. Zu wenig Bastelspaß!😉
Stattdessen schwebt mir ein “echter” Reportgenerator vor. Ach, MS Access, wenn ich an dich denke…
Für den Reportgenerator müssen die Rohdaten aus Excel extrahiert und in eine passende Form (CSV, JSON) gebracht werden. Dann kann der Reportgenerator sie mit der Word-Dokumentenvorlage mischen und das Ergebnis als PDF speichern.
Natürlich darf die automatische Rechnungsnummernvergabe (inkl. Protokollierung) nicht vergessen werden.
Außerdem sollte der Prozess automatisch ablaufen.
Mein Entwurf für den groben Flow sieht so aus:
Excel-Rechnungen, die in einem Dropbox-Folder abgelegt werden, werden automatisch verarbeitet.
Die Excel-Daten werden nach JSON extrahiert; dabei kann die Hierarchie schön erhalten bleiben (ein Rechnungskopf, viele Rechnungspositionen).
Eine Rechnungsnummer wird erzeugt.
JSON-Daten und Rechnungsnummer gehen an den Reportgenerator, der sie mit einem hinterlegten Template nach PDF “rendert”.
Das PDF wird in einem Dropbox-Folder mit dem Original abgelegt.
Außerdem gibt es eine Benachrichtigung per Email.
Wenn ich Lust habe, kann ich später einen alternativen Trigger bauen, z.B. über das Kontextmenü oder eine Email.
Und ich könnte natürlich auch den sofortigen Versand der PDF-Rechnung an den Kunden hinzufügen.
Die Lösungsimplementation
Bei Make sieht meine Lösung nun so aus:
Was, so ein Aufwand für so ein kleines Problem?🤯 Ja! Bastelspaß. Aus Prinzip.😁 Und: Gelegenheit zum Lernen.
Bei genauer Betrachtung ist das aber kein großer Aufwand. Im Gegenteil!
Dropbox beobachten und nacheinander die Excel-Dateien der Rechnungen herunterladen, sind zwei Make “Module”, in die 2-3 Parameter einzutragen sind. Das war’s.
Die Umwandlung in JSON? Der einfachste Teil daran war, CSV nach JSON mit GPT-3.5 zu wandeln. Ein wenig überlegen musste ich hingegen, wie ich die Daten aus der heruntergeladenen Excel-Datei nach CSV wandle. Datei öffnen und den Inhalt ins Clipboard nehmen, wie auf dem Desktop, kommt ja nicht in Frage. Wie geht das automatisiert? Die Lösung bietet z.B. convertapi.com: da geht Excel rein und CSV kommt heraus. Am Ende muss das von GPT produzierte JSON nur noch deserialisiert werden, um downstream in allen Einzelteilen zur Verfügung zu stehen.
Die Ermittlung der nächsten Rechnungsnummer habe ich ausgelagert in einen separaten Workflow (oder sollte ich “Service” sagen?). Den rufe ich per HTTP auf. Er liest eine Protokolldatei, erhöht die letzte eingetragene Rechnungsnummer um 1 und hängt einen neuen Protokolleintrag für die aktuelle Rechnung an.1 Mit solchen eigenen REST-Workflows lassen sich Make Automations gut modularisieren. Das hätte ich auch noch weiter treiben können.
Jetzt ist alles beieinander für die Erzeugung der PDF-Rechnung auf der Basis des Word-Templates. Die JSON-Daten und die Rechnungsnummer schicke ich an den Reportgenerator carbone.io. Den find ich klasse: Platzhalter für Daten in eine Vorlage einzusetzen ist total einfach für einmalige Daten (hier: Rechnungskopf mit Rechnungsnummer, Adresse usw.) und Listen (hier: die Rechnungspositionen mit Leistungsbeschreibung, Menge, Preis). Außerdem bietet carbone pfiffige Funktionen, um die Daten bzw. das Layout während des “Renderns” noch anzupassen. Absätze können z.B. ausgeblendet und Tabellenzeilen gelöscht werden.2
Das PDF als Ergebnis von carbone wird dann in der Dropbox neben der Excel-Datei in einem Output-Verzeichnis gespeichert. Die “Queue”, die im ersten Schritt beobachtet wird, leert sich dadurch.
Schließlich lasse ich mir das PDF noch zuschicken. Ich warte nicht auf seine synchrone Generierung. Der Make Workflow beobachtet die Dropbox nur alle 15 Minuten. Das Intervall reicht mir — und so kann ich die Kosten bei Make reduzieren (derzeit sind sie für die zwei Workflows 0€ pro Monat).
Meine Lösung besteht im Grunde nur aus 25 Funktionsaufrufen. Sie integriert vorhandene Bausteine. Lediglich das Prompt zur Umwandlung von CSV nach JSON habe ich “programmiert”; doch selbst dort konnte ich deklarativ formulieren.3
Fazit
Oberflächlich gesehen, hat das Projekt einfach Spaß gemacht. Mehr Spaß, als hätte ich es rein mit C# oder Typescript lösen müssen. Und dann auch noch so, dass ich kein Deploymentproblem bekomme, nur weil meine Frau und ich die Lösung auf unterschiedlichen Rechnern einsetzen wollen.
Doch ich glaube, in der Herangehensweise steckt mehr. Workflow-Plattformen zu nutzen, um mehr Wiederverwendung zu betreiben in Form von Nutzung existierender 3rd-Party Komponenten/Dienste, scheint mir sehr hilfreich. Weniger selbst machen (build), mehr nutzen, was andere schon besser gemacht haben (buy), als man es selbst mit vertretbarem Aufwand machen könnte.
Workflows geben Überblick, Workflows laden zur Modularisierung ein, Workflows machen es einfach, zwischendrin eigene Bausteine zu platzieren, wo es nichts Passendes auf dem Markt gibt.
Für mich ist das manifestiertes Flow-Design:
Die Integration ist einfach. Bausteine gibt es von der Stange. Und wenn nicht, dann konzentriere ich mich hier und mal auf die Implementation einer eigenen Operation (Integration Operation Segregation Principle).
Ich kann nur ermuntern, es selbst mal mit Make & Co zu probieren. Da ist für jeden etwas dabei - und schafft bestimmt Perspektiven für höhere Produktivität und Verlässlichkeit. Dafür sollte sich kein Entwickler zu schade sein.
Wahrscheinlich ginge das noch einfacher mit einer richtigen Datenbank. Doch ich wollte das Rechnungsprotokoll für uns beide lesbar in der Dropbox haben.
Das brauche ich, weil manche Rechnungen mit und andere ohne MwSt abgerechnet werden und dann auch andere Zahlungshinweise bekommen.
Es wäre ohne KI-Einsatz gegangen. Ich hätte das CSV auch anders nach JSON transformieren können, zur Not mit ein wenig Typescript-Code. Doch ich hatte Lust, GPT einzusetzen. Es ist so trivial, mir die Datentransformation “zu wünschen”.
Guter Ansatz den Workflow mit fertigen Komponenten aufzubauen, bevor man mit eigenem Code optimiert. Durch die Lösungsansätze mit fertigen Bausteinen kann man schneller lernen wie man das Business-Problem am besten löst. Und Anpassungen sind fix umzusetzen. Gerade in neu aufgesetzten Softwareprojekten wird gleich zu Anfang mit Kanonen auf Spatzen geschossen. Ohne große Enterprise-Frameworks, Microservices und Kubernetes fängt man gar nicht erst an. Und ja: Technisch sind die spannend, aber schlussendlich geht es darum, für die Nutzer eine Lösung bereitzustellen, die optimal und bezahlbar einen "Job to be done" erledigt.
Hm, und wie löst man das Problem, wenn ein Dienst ausfällt oder vom Netz genommen wird? Oder der Hersteller die Schnittstellen umbaut? Die Einfachheit lässt natürlich zu, dass man flux eine andere Komponente sucht und integriert, das ist sicherlich ein gewisser Vorteil, aber wenn die Lösung möglichst ohne Ausfall funktionieren soll, müsste man noch ein paar Sicherungen einbauen.