Blog: Latest Entries (15):


Digitale Unabhängigkeit der EU – zwischen Anspruch und Realität

bbcode-image


Immer öfter liest man Schlagzeilen, dass Europa unabhängiger von den USA werden soll – vor allem wenn es um Cloud, Künstliche Intelligenz oder Hardware geht. Spätestens seit Trump ist das Thema präsent: seine Sprunghaftigkeit hat vielen in Europa gezeigt, wie riskant Abhängigkeiten sein können. Gleichzeitig erinnert das Ganze an Corona: Damals hat man gesehen, wie schlecht Schulen digital aufgestellt waren – viel Aktionismus, wenig greifbare Ergebnisse. Heute wirkt es ähnlich: Man startet Projekte, verteilt Fördergelder, aber am Ende bleibt oft das Gefühl, dass man versucht, das Problem einfach auszusitzen.

Politik und die ewige Symbolik

Die EU fördert viele Programme, schreibt Strategiepapiere und stellt Forschungsgelder bereit. Auf dem Papier klingt das beeindruckend. Aber oft fehlt der nächste Schritt: ein Produkt, das man wirklich nutzen kann. Anstatt pragmatisch Lösungen einzukaufen oder bestehende Anbieter zu stärken, wird lieber geforscht und diskutiert. Am Ende heißt es dann: „Wir haben viel gelernt.“ Das erinnert an die Raumfahrtprojekte der ESA – während Europa jahrelang lernt, übernimmt SpaceX die Aufträge der Industrie.

Cloud und Hyperscaler – Kopieren statt Neues wagen

Bei der Cloud will die EU eigene Hyperscaler aufbauen – also quasi europäische Versionen von AWS, Azure oder Google Cloud. Der Haken: Man will eigentlich nur beweisen, dass man das Gleiche bauen kann. Doch während man an der Kopie arbeitet, sind die US-Anbieter schon wieder einen Schritt weiter. Für viele Kunden in Europa ist das Angebot ohnehin überdimensioniert. Sie wollen meist gar kein komplexes Kubernetes-Cluster, sondern einfach einen stabilen Server, der zuverlässig läuft. Statt also auf die tatsächlichen Bedürfnisse einzugehen, versucht die EU, ein Modell nachzubauen, das hier oft gar nicht gebraucht wird.

Hardware – die europäische CPU

Ein besonders ambitioniertes Beispiel ist der Plan, eine europäische CPU für Rechenzentren zu entwickeln. Klingt spannend, aber auch hier geht die EU den Forschungsweg. Anstatt bestehende Designs zu kaufen oder mit Firmen zusammenzuarbeiten, die schon Erfahrung haben, startet man ein neues Großprojekt. Ergebnis: viel Papier, lange Laufzeiten und am Ende ein Chip, der bei Markteinführung schon veraltet ist. Offiziell heißt es dann: „Wir haben wichtige Erkenntnisse gewonnen“ – aber nutzen will die CPU keiner.

KI – Mistral als Hoffnungsträger

Im Bereich KI sieht es immerhin etwas besser aus. Mit Mistral AI aus Frankreich gibt es ein Startup, das schon einiges erreicht hat und Milliardeninvestitionen anzieht. Ein echter Hoffnungsträger für Europa. Aber auch hier fehlt es an Infrastruktur – die Rechenzentren, um wirklich auf Augenhöhe mit den USA zu arbeiten, gibt es schlicht nicht. Und während in den USA KI-Produkte direkt am Markt getestet werden, diskutiert man in Europa erst einmal lange über Risiken wie Fake News oder Copyright-Fragen. Wichtig, klar – aber zu oft wird dadurch der Fortschritt ausgebremst.

Warum es so schwer ist

Das Muster zieht sich durch alle Bereiche: Europa will unabhängiger werden, scheitert aber an langsamen Prozessen, fehlendem Kapital und politischem Zögern. Chips kommen weiterhin aus den USA, Cloud-Infrastruktur dominiert Amazon & Co., und bei KI hat man zwar Ideen, aber wenig Durchschlagskraft. Eine komplette Unabhängigkeit ist schlicht nicht machbar. Dafür hängen Lieferketten, Talente und Technologien zu stark weltweit zusammen.

Fazit – weniger kopieren, mehr eigene Wege gehen

Europa wird die USA oder China in absehbarer Zeit nicht ablösen können. Aber das ist vielleicht auch gar nicht nötig. Statt von „vollständiger Unabhängigkeit“ zu träumen, sollte die EU auf strategische Autonomie setzen: in den Bereichen, die wirklich kritisch sind, eigene Lösungen aufbauen, die im Ernstfall einspringen können. Das heißt: schneller entscheiden, mehr Geld in die Hand nehmen, Produkte statt endlose Forschungsprojekte fördern – und vor allem nicht nur kopieren, was andere schon machen.

Wenn die EU den Mut hat, eigene Wege zu gehen, kann sie tatsächlich etwas bewegen. Wenn nicht, bleibt sie beim Status quo: man lernt viel, aber kommt im globalen Wettbewerb nicht voran.

Und am Ende natürlich die Hoffnung aller, dass man die Situation aussitzen kann. Hat in der Corona-Pandemie ja auch geklappt.

Jira-Tickets und AI-Prompts

bbcode-image


Warum gute Kommunikation in beiden Welten zählt

In den letzten Monaten habe ich gemerkt: Wer saubere Jira-Tickets schreiben kann, hat auch Vorteile im Umgang mit AI-Agents wie Gemini-CLI, JetBrains AI oder ChatGPT. Klingt erstmal komisch – ist aber logisch. Denn am Ende des Tages geht es in beiden Fällen um das Gleiche: klare Kommunikation.

Jira-Ticket schreiben: Was gehört rein?

Ein Ticket ist nicht einfach nur ein To-do. Es ist eine handlungsfähige Anweisung an jemanden im Team. Damit es wirklich „ready“ ist, braucht es bestimmte Bestandteile. Zunächst sollte es einen prägnanten Titel geben, der die Aufgabe auf den Punkt bringt – beispielsweise „Implementiere User-Login mit JWT“. Danach folgt eine Beschreibung des Ziels oder Zwecks: Warum ist die Aufgabe wichtig und was soll damit erreicht werden? Zusätzlich ist es hilfreich, Kontext bereitzustellen, etwa Links zu Architektur-Dokumentationen, Business-Entscheidungen oder verwandten Tickets.

Ein gutes Ticket enthält außerdem klare Akzeptanzkriterien, die definieren, wann die Aufgabe als erledigt gilt. Ebenso gehören technische Details hinein, also welche Module oder Bibliotheken betroffen sind und angepasst werden müssen. Constraints wie Performance-Limits, Sicherheitsanforderungen oder bestimmte Architekturvorgaben dürfen ebenfalls nicht fehlen. Abgerundet wird das Ganze durch Beispiele, zum Beispiel konkrete Input- und Output-Werte, User Stories oder Skizzen, sowie durch Referenzen auf Dokumentationen, Guidelines oder APIs. Wenn ein Ticket so aufgebaut ist, kann es auch ein Junior-Entwickler umsetzen, ohne zehnmal nachzufragen.

Praxis-Anekdote: Ich erinnere mich noch an ein Ticket, das ich vor Jahren an einen neuen Kollegen geschrieben habe. Ich dachte, die Aufgabe sei selbsterklärend – aber ohne klare Akzeptanzkriterien hat er einen halben Tag damit verbracht, ein Feature zu bauen, das komplett an der Anforderung vorbeiging. Seitdem schreibe ich in jedes Ticket, woran man erkennt, dass es „fertig“ ist. Das spart Zeit und Nerven.

AI-Prompt schreiben: Fast das Gleiche

Das Lustige ist: Ein guter Prompt für eine AI braucht nahezu die gleichen Elemente. Nur dass man hier statt mit Menschen mit einer Maschine kommuniziert. Diese versteht nichts zwischen den Zeilen, also muss man noch klarer und expliziter sein.

Ein Prompt könnte zum Beispiel mit einem kurzen Titel starten: „Schreibe eine Python-Funktion zur JWT-Validierung.“ Danach folgt die Erläuterung des Zwecks, etwa: „Die Funktion wird im Auth-Flow eines FastAPI-Backends genutzt.“ Um die AI nicht im Dunkeln zu lassen, sollte man auch den Kontext beschreiben, zum Beispiel: „Wir nutzen fastapi, pyjwt und haben bereits ein User-Model.“

Genauso wichtig sind Akzeptanzkriterien, etwa: „Die Funktion soll ein JWT validieren und den User zurückgeben, oder eine Exception werfen.“ Bei den technischen Details kann man spezifizieren, welche Bibliothek genau eingesetzt werden soll, etwa: „Verwende bitte pyjwt.decode und unser Secret aus settings.py. Kein anderes JWT-Paket!“ Constraints dürfen nicht fehlen, zum Beispiel: „Muss synchron laufen, da es im Request-Lifecycle genutzt wird.“ Schließlich helfen konkrete Beispiele – wie Input-Token und erwarteter Output – sowie Links zu relevanten Dokumentationen wie der PyJWT-Doku. Im Prinzip ist das ein Jira-Ticket, nur dass der Empfänger diesmal ein AI-Agent ist.

Praxis-Anekdote: Neulich habe ich einem AI-Agenten genau so einen Prompt gegeben. Ich war in Eile und habe die Constraint „synchron“ weggelassen. Was kam zurück? Eine perfekt funktionierende, aber komplett asynchrone Implementierung. Für mein Projekt unbrauchbar. Genau dieser Moment hat mir gezeigt: Ein Prompt ohne vollständige Infos ist wie ein Ticket ohne DoR – das Ergebnis ist selten das, was man wirklich braucht.

Unterschiede: Mensch vs. Maschine

Natürlich gibt es Unterschiede. Menschen haben oft implizites Wissen über Teamkonventionen und können Dinge zwischen den Zeilen verstehen. Eine AI kennt nur das, was man ihr explizit mitteilt. Auch die Feedback-Schleife läuft anders: Mit einem Junior-Entwickler kann ich direkt reden und Fragen beantworten, während ich bei einer AI iterativ über Prompts verfeinern muss. Außerdem ist die Detailtiefe bei AI oft höher, weil sie keine impliziten Kontexte versteht.

Praxis-Anekdote: Ich hatte mal eine Situation, in der ein Junior-Entwickler einfach kurz an meinem Schreibtisch stand und fragte: „Meinst du jetzt wirklich die neue API oder die alte?“ – Problem sofort geklärt. Bei einer AI muss ich diesen Kontext von vornherein im Prompt mitliefern, sonst produziert sie Ergebnisse, die mich später doppelt Arbeit kosten.

Aber die Basis ist die gleiche: klare, strukturierte Kommunikation.

Mein Fazit

Ja, man kann es so sagen: Wer gute Jira-Tickets nach Definition of Ready schreiben kann, schreibt auch gute AI-Prompts. Denn beide verlangen Klarheit statt vager Ansagen. Sie verlangen Kontext und Ziel statt eines bloßen „mach mal“. Sie profitieren von Struktur und Nachvollziehbarkeit. Und sie brauchen ein gemeinsames Verständnis, wann etwas wirklich „fertig“ ist.

Für mich bedeutet das: Prompt Engineering ist nichts Exotisches. Es ist einfach die Weiterentwicklung einer Fähigkeit, die wir als Senior Devs sowieso jeden Tag nutzen – nämlich Aufgaben so zu formulieren, dass andere sie umsetzen können. Und ganz ehrlich: Wenn ich es schaffe, dass ein Junior nach einem Ticket sofort loslegen kann, dann kann auch eine AI ziemlich gute Ergebnisse liefern.

Praxis-Anekdote: Mittlerweile nutze ich dieselbe innere Checkliste für beides. Wenn ich ein Ticket schreibe, frage ich mich: Könnte auch eine AI damit etwas anfangen? Und wenn ich einen Prompt schreibe, frage ich mich: Könnte auch ein Junior-Dev das verstehen? Diese Haltung sorgt dafür, dass meine Kommunikation klarer wird – egal, ob ich mit Menschen oder Maschinen zusammenarbeite.


Psychologische Sicherheit im IT-Team

bbcode-image


Warum Führungskräfte von Vertrauen mehr profitieren als von Kontrolle
Viele Führungskräfte in der IT stehen unter enormem Druck. Stakeholder erwarten Ergebnisse, Deadlines sind knapp, Budgets begrenzt. In dieser Situation liegt die Versuchung nahe, Kontrolle zu verstärken und bei Fehlern mit Druck oder harschen Worten zu reagieren. Doch genau hier beginnt eine Abwärtsspirale: Unsichere Führungskräfte neigen schneller dazu, Fehler auf Mitarbeitende abzuwälzen. Das mag kurzfristig Entlastung verschaffen, führt aber langfristig zu Angst, Rückzug und Fluktuation.

Eine ganz andere Dynamik entsteht, wenn Führungskräfte psychologische Sicherheit schaffen. Darunter versteht man ein Klima, in dem Mitarbeitende keine Angst haben müssen, für Fehler bloßgestellt zu werden, sondern offen über Probleme sprechen können. Wer so führt, erlebt nicht nur weniger versteckte Fehler und Konflikte, sondern auch mehr Engagement und Innovationskraft. Denn Mitarbeitende, die sich sicher fühlen, investieren ihre Energie nicht in Selbstschutz, sondern in Zusammenarbeit und Qualität.

Besonders im IT-Bereich ist Klarheit ein zentraler Hebel. Ticket-Systeme wie Jira helfen, Aufgaben nachvollziehbar zu dokumentieren, Abhängigkeiten sichtbar zu machen und Missverständnisse zu vermeiden. Mit einer klaren Definition of Ready wissen Teams genau, was von ihnen erwartet wird. Dadurch sinkt die Unsicherheit, Fehler werden seltener und Konflikte mit anderen Abteilungen oder Stakeholdern reduzieren sich deutlich. Auch Führungskräfte profitieren: Sie müssen nicht alles im Kopf behalten, haben jederzeit einen Überblick und können Entscheidungen auf einer stabilen Datenbasis treffen. Die eigene Unsicherheit und der damit verbundene Stress nehmen ab.

Doch Prozesse und Tools sind nur eine Seite der Medaille. Entscheidend ist, wie Führungskräfte mit unvermeidbaren Fehlern umgehen. Wer in diesen Momenten Ruhe bewahrt, systemische Ursachen in den Blick nimmt und Mitarbeitende unterstützt, zeigt Stärke. Wer hingegen laut wird oder Schuldige sucht, schwächt nicht nur sein Team, sondern auch sich selbst. Denn ein verunsichertes Team gibt Unsicherheit zurück: Kommunikation stockt, Vertrauen bricht weg, Fehler werden verschwiegen – und am Ende steht die Führungskraft unter noch größerem Druck.

Das Gegenteil ist ebenfalls wahr: Ein sich sicher fühlendes Team gibt Sicherheit zurück. Mitarbeitende, die offen über Probleme sprechen, schaffen Transparenz und entlasten damit die Führung. Ein Klima, in dem Ideen wertgeschätzt und Fehler als Lernchancen behandelt werden, stärkt die Resilienz des gesamten Systems. Führungskräfte, die Vertrauen schenken, bekommen Loyalität, Offenheit und Verlässlichkeit zurück. So entsteht ein Kreislauf gegenseitiger Rückendeckung: Die Leitung gibt Sicherheit ins Team – und das Team gibt diese Sicherheit zurück.

Für IT-Unternehmen ist dieser Unterschied entscheidend. Während eine Kultur der Angst auf Dauer Kosten, Fluktuation und Qualitätsprobleme erzeugt, sorgt psychologische Sicherheit dafür, dass Talente bleiben, Software zuverlässig ist und Innovation möglich wird. Wer als Führungskraft Verantwortung übernimmt, nicht nur für Aufgaben und Deadlines, sondern auch für das Klima im Team, legt den Grundstein für nachhaltigen Erfolg.

Am Ende lässt es sich einfach zusammenfassen: Unsicherheit nach unten weiterzugeben, schwächt alle. Sicherheit vorzuleben und zu ermöglichen, stärkt alle. Führungskräfte, die diesen Unterschied verstehen, profitieren selbst am meisten.

Praktisches Fazit für Führungskräfte
Wenn Sie psychologische Sicherheit in Ihrem Team fördern möchten, können Sie sofort damit beginnen: Sprechen Sie über Fehler offen und nutzen Sie sie, um Prozesse zu verbessern, statt Schuldige zu suchen. Machen Sie Anforderungen und Ziele durch klare Tickets und Definition of Ready transparent, sodass Unsicherheit reduziert wird. Ermutigen Sie Mitarbeitende, Fragen zu stellen, und nehmen Sie jede Stimme ernst – auch die leisen. Zeigen Sie selbst Verletzlichkeit, indem Sie eigene Fehler eingestehen und daraus lernen. Und vor allem: Reagieren Sie in Stresssituationen so, dass Ihr Team merkt, dass Sie hinter ihm stehen.

So schaffen Sie ein Umfeld, in dem Ihre Mitarbeitenden sich sicher fühlen, ihre beste Arbeit leisten und Ihnen diese Sicherheit zurückgeben. Am Ende gewinnen nicht nur Sie und Ihr Team – sondern das gesamte Unternehmen.

Firebird Driver unter Linux für PHP installieren

In diesem kurzen Tutorial zeige ich euch, wie ihr den Firebird-Treiber für PHP in einem Docker-Container problemlos installieren könnt.

Voraussetzungen
* Docker-Umgebung
* PHP-Container

Installationsschritte

1. Systemaktualisierung
Zuerst aktualisieren wir das Paketverzeichnis, um sicherzustellen, dass wir die neuesten Versionen erhalten:


apt-get update


2. Firebird und Abhängigkeiten installieren
Installieren wir nun die notwendigen Pakete, einschließlich des Firebird-Entwicklungspakets und weiterer Hilfsmittel:


apt-get install -y firebird-dev libib-util apache2-utils


3. PHP PDO-Firebird-Erweiterung aktivieren
Abschließenden kompilieren und installieren wir die PDO-Firebird-Erweiterung für PHP:


docker-php-ext-install pdo_firebird


Ergebnis
Diese einfache Befehlsfolge ermöglicht es euch, den Firebird-Treiber erfolgreich in eurem Docker-PHP-Container zu integrieren. Die Installation wurde erfolgreich mit PHP-Version 2.5.9 getestet und funktioniert ohne Probleme.

Fazit
Mit nur wenigen Zeilen Code habt ihr volle Unterstützung für Firebird-Datenbanken in eurer PHP-Anwendung. Der Prozess ist unkompliziert und schnell erledigt – perfekt für Entwicklungs- und Produktionsumgebungen!

erster Test mit eternalai.org als Unterstützung beim Schreiben

Willkommen im Zeitalter der Cloud – Schluss mit E-Mail-Pingpong!

bbcode-image


Es gibt Dinge, die einfach nicht sterben wollen. Faxgeräte zum Beispiel. Oder die gute alte Tradition, Word- und Excel-Dateien per E-Mail an eine Verteilerliste zu schicken, die länger ist als die Zutatenliste auf einer Tiefkühlpizza.

Das Ritual läuft meist so ab:

* Jeder bekommt die Datei, jeder trägt irgendwo seine Änderungen ein – mal mit, mal ohne Kommentar.
* Manche schicken ihre Version nach fünf Minuten zurück, andere nach drei Wochen (inklusive „Sorry für die Verspätung“).
* Am Ende sitzt eine arme Seele da und darf Sherlock Holmes spielen: „Wer hat jetzt eigentlich was geändert, warum steht hier plötzlich 42 und wieso ist die Schrift Comic Sans?“

Das Ergebnis? Chaos mit Ansage.

Cloud statt Chaos

Und dann kam die Cloud. Eine einzige Datei, die geteilt wird. Alle arbeiten gleichzeitig darin – in Echtzeit! Keine E-Mail-Anhänge mehr, keine 15 Versionen im Postfach mit kryptischen Dateinamen wie Budget_final_final_reallyfinal_v3_neu.xlsx.

Stattdessen:

* Jeder sieht sofort, was die anderen tun (ja, auch die Tippfehler in Echtzeit).
* Änderungen werden automatisch dokumentiert – ganz ohne Excel-Krimi.
* Kommentare lassen sich direkt anfügen und lösen. Mit Benachrichtigung! Niemand kann mehr sagen: „Das hab ich gar nicht gesehen.“

Der Aha-Moment

Das Beste daran: Man muss es den Kollegen nur einmal zeigen. Einmal live erlebt, wie man in einer Cloud-Datei gleichzeitig tippt, löscht und diskutiert – schon will niemand mehr zurück zur altmodischen E-Mail-Schleuder.

Kurz gesagt: Cloud-Arbeit ist wie der Sprung vom Walkman zu Spotify. Wer einmal den Unterschied erlebt hat, fragt sich: „Warum haben wir uns das früher eigentlich angetan?“


AI: Entwickeln mit AI ist nicht immer Vibe-Coding!

bbcode-image


Wenn man sagt, man würde mit AI entwickeln, denken viele sofort, dass man einfach Vibe-Coding betreibt. Dass man ein paar Prompts eingibt, auf das Ergebnis schaut und hofft, dass dabei etwas Sinnvolles herauskommt. Das ist aber ein Trugschluss. AI-gestütztes Entwickeln erfordert genau wie klassische Softwareentwicklung Struktur, Planung und Präzision. Man kann sich nicht einfach zurücklehnen und darauf vertrauen, dass die AI schon die richtigen Entscheidungen treffen wird. Im Gegenteil: Man muss sich ständig rechtfertigen können, warum bestimmte Vorschläge der AI übernommen werden und andere nicht.

Ein entscheidender Unterschied liegt dabei in der Vorbereitung. Bevor die AI überhaupt an den Code geht, steht die Erstellung einer fundierten requirements.md. Diese Datei enthält alle Vorgaben, die Akzeptanzkriterien, die Use-Cases und konkrete Beispiele. Die Erstellung dieser Dokumentation dauert leicht zwei Stunden oder mehr, weil jedes Detail klar definiert sein muss. Erst wenn die AI genau weiß, welche Aufgabe sie erfüllen soll, welche Bibliotheken genutzt werden dürfen und welche Funktionen zum Einsatz kommen sollen, kann sie effizient arbeiten. Ziel ist nicht, dass die AI kreativ wird, sondern dass sie genau das umsetzt, was man selbst tun würde – nur schneller und mit Unterstützung von Tests und Dokumentation.

Ein großer Vorteil der AI liegt in ihrer Geschwindigkeit. Während man selbst Stunden damit verbringen könnte, Boilerplate-Code, Dokumentation und Unit-Tests zu schreiben, generiert die AI diese Elemente in einem Bruchteil der Zeit. Doch auch hier gilt: Die AI denkt sich die Tests nicht selbst aus. Jede Überprüfung, jedes erwartete Ergebnis ist genau vorgegeben. Dadurch bleibt die Kontrolle beim Entwickler, und die Qualität des Codes wird nicht dem Zufall überlassen.

Vibe-Coding funktioniert anders. Hier konzentriert man sich auf die Ausarbeitung des Ergebnisses, auf das visuelle oder funktionale Endprodukt, ohne von Anfang an alles festzulegen. Das macht bei Prototypen Sinn, weil der Weg zum Ziel explorativ ist. In AI-gestützter Entwicklung hingegen geht es nicht nur um das Ziel, sondern um den gesamten Weg dahin. Jede Entscheidung, jede Vorgabe wird klar definiert und überprüft. Es geht um strukturierte, nachvollziehbare Softwareentwicklung, bei der die AI ein mächtiges Werkzeug ist, nicht ein magischer Generator von Lösungen.

Am Ende bedeutet Entwickeln mit AI also keineswegs, dass man nur „vibe-codet“. Es ist eine durchdachte, präzise und kontrollierte Arbeit, die Planung, klare Vorgaben und sorgfältige Umsetzung erfordert – nur eben deutlich schneller und effizienter, ohne dass man auf Qualität oder Kontrolle verzichten muss.

Legacy-Software: Warum PHP hier super ist

bbcode-image


Wenn es um Legacy-Software geht, denkt man oft zuerst an komplizierte Abhängigkeiten, schwer wartbaren Code und die Frage: Wie bekomme ich das Ganze wieder ans Laufen?
Doch in genau diesem Kontext spielt PHP seine Stärken aus.

Warum PHP bei Legacy-Systemen glänzt

Ein zentraler Vorteil ist, dass man den Code direkt vorliegen hat. Anders als bei kompilierten Sprachen muss nichts dekompiliert oder mühselig rekonstruiert werden. Stattdessen öffnet man einfach die Dateien und kann sofort mit der Arbeit beginnen.

Auch die Fehlersuche gestaltet sich unkompliziert. Fehlermeldungen lassen sich unmittelbar in den betroffenen Dateien nachvollziehen, ohne dass man komplizierte Werkzeuge bemühen oder Umwege gehen muss.

Kommt es einmal hart auf hart, sind Änderungen sogar direkt in den Dateien möglich. Man braucht keinen Deployment- oder Build-Prozess zu reparieren, um einen kleinen Fix einzubauen – was im Alltag oft enorm Zeit spart.

Darüber hinaus lässt sich ein altes Projekt einfach in ein Git-Repository überführen. Eine Kopie genügt, um anschließend Schritt für Schritt saubere Versionierung, Branching und moderne Workflows einzuführen.

Für die tägliche Arbeit stehen zudem starke Tools bereit. IDEs wie PhpStorm, VS Code oder Eclipse bieten eine breite Palette an Funktionen für Analyse, Refactoring und Debugging, sodass man auch bei großen Codebasen den Überblick behält.

Ein weiterer Pluspunkt: Selbst ältere PHP-Projekte setzen häufig auf bekannte Frameworks wie Symfony oder Laravel. Dadurch können Entwicklerinnen und Entwickler auf vertraute Strukturen zurückgreifen, statt sich durch völlig unbekannte Eigenkonstruktionen kämpfen zu müssen.

Nicht zu vergessen: Viele in der Branche haben ohnehin schon Erfahrung mit PHP. Diese Vertrautheit senkt die Einstiegshürden erheblich und beschleunigt die Einarbeitung.

Auch Updates der PHP-Umgebung sind meist kein Hexenwerk. Zwar braucht es manchmal kleinere Anpassungen, doch insgesamt bekommt man alte Anwendungen erstaunlich gut und zuverlässig wieder ans Laufen.

Schließlich spielt PHP auch bei der Infrastruktur seine Vorteile aus. Da es kaum komplexe Anforderungen mitbringt, lassen sich Test- und Entwicklungsumgebungen unkompliziert über Docker oder ähnliche Tools einrichten.

Fazit

Mit PHP kann man bei Legacy-Software-Projekten sofort loslegen: Fehler finden, Fixes einbauen, den Code in moderne Pipelines und Repositories überführen. Ohne dass man dem Quellcode mühsam hinterherlaufen muss, nur um einen winzigen Bug zu beheben.

Gerade in Zeiten, in denen viele Systeme noch lange weiterbetrieben werden müssen, zeigt sich: PHP ist für Legacy-Software oft genau die richtige Wahl gewesen.

AI: Next Generation Coding Monkey

bbcode-image


Es fühlt sich an, als wäre Vibe-Coding schon ewig da – zumindest, wenn man in KI-Jahren rechnet. Vor ein paar Monaten war es noch “Magie”, heute ist es Alltag: LLMs, die nicht nur Code schreiben, sondern auch Gedichte, Songtexte oder Blogposts polieren.
Ich lasse mir manchmal einfach spontan ein paar Zeilen Songtext ausspucken, schmeiße sie in Suno, und zack – habe ich genau den Soundtrack, den ich in diesem Moment beim Arbeiten brauche. Verrückt, oder?

Und dann gibt es da noch Nano-Banana. Wer’s kennt, weiß: Die Zensur ist dort so streng, dass man schon mal “die erwachsene Zauberin” schreiben muss, nur damit aus einem Pixelbild ein realistisches Artwork wird. Klingt witzig, ist aber auch ein bisschen absurd.

Zurück zum Coding
Doch das alles ist nur die bunte Spielwiese. Richtig spannend wird es bei Vibe-Coding:
Programme einfach in normaler Sprache beschreiben, und die KI baut sie für dich. Keine umständlichen Prompts, kein Copy & Paste – Tools wie Gemini-CLI oder JetBrains Junie holen sich die KI direkt in die IDE. Klingt nach Zukunft, oder?

Ja. Aber.

Die Wahrheit ist: Die AI denkt nicht für dich.
Wenn du selbst keine Idee hast, wie du ein Problem lösen würdest, bekommst du am Ende nur Quatsch zurück. Der Traum, einfach zwei Sätze zu tippen und die KI spuckt die perfekte App aus, ist genau das – ein Traum.
Oft muss man der KI sogar haarklein sagen, welche Libraries oder APIs sie nutzen soll. Sonst landet man bei Code, der zwar hübsch aussieht, aber in der Realität nie laufen wird.

Was wirklich zählt
Nach ein paar Monaten Experimentieren habe ich gemerkt: Das Geheimnis liegt nicht im Prompt selbst, sondern in der Vorbereitung.
Ich habe mir angewöhnt, eine saubere requirements.md zu schreiben – und zwar selbst. Nicht von der KI. Darin halte ich fest:

- Welche Lösung ich mir vorstellen würde
- Welche Datenstrukturen ich brauche
- Welche Beispiele wichtig sind
- Welche Use Cases abgedeckt sein müssen
- Welche Acceptance Criteria gelten

Klingt nach klassischem Ticket-Schreiben? Genau. Eigentlich nichts Neues – nur dass es hier den Unterschied zwischen gutem und miesem KI-Code macht. Wenn man diese “Definition of Ready” sauber aufsetzt, liefert die KI plötzlich großartige Lösungen. Sogar Sicherheitsaspekte, die ich selbst nicht auf dem Schirm hatte, tauchen auf.

Fazit
Die KI ist wie ein genialer Coding Monkey: schnell, fleißig und unermüdlich. Aber sie ersetzt nicht dein Denken. Sie schreibt deinen Code – sie entwirft aber nicht deine Lösung.

Shopware 6: Deployment-Pipeline

bbcode-image


Auch wenn Shopware 6.6 mittlerweile nicht mehr die aktuellste Version ist und ich derzeit nicht mehr überwiegend mit Shopware arbeite, habe ich meine GitLab-Pipeline für ein möglichst schlankes und robustes Shopware-6.6-Deployment so aufbereitet, dass ich sie hier veröffentlichen kann.
Die Pipeline lässt sich problemlos auch auf andere Shopware-6-Versionen anpassen (eine Variante für 6.4 lief bereits produktiv, bis sie per AI in GitHub Actions migriert wurde).

Für die lokale Entwicklung genügt ein sehr schlankes Projekt:


custom
apps
plugins
static-plugins
env_config
prod
stage
.env.local
auth.json
composer.json
composer.lock
docker-compose.yml


Die docker-compose.yml kann eine einfache Dockware-Variante sein, in der alle relevanten Ordner als Volumes gemountet werden, sodass man direkt darin entwickeln kann.

In env_config liegt alles, was für die jeweiligen Umgebungen spezifisch ist. Dateien aus diesem Verzeichnis werden übernommen und ergänzen oder überschreiben vorhandene Konfigurationen. Typischerweise findet man hier eine passende .env.local sowie verschiedene Konfigurationsdateien für den config-Ordner.

Die Einrichtung auf dem Server ist unkompliziert: Im gewünschten Verzeichnis wird folgendes Skript ausgeführt:


mkdir shopware
cd shopware
mkdir config
mkdir releases
mkdir shared
cd shared
mkdir media
mkdir thumbnail
mkdir bundles
mkdir files
mkdir log
mkdir sitemap
cd config
mkdir jwt
cd ../..
cd releases
mkdir _init
cd _init
mkdir public
cd public
echo "<html><body>INIT</body></html>" > index.php
cd ../../..
ln -sfn ./releases/_init/public current


Anschließend kann der VHost auf current/public/ zeigen – die INIT-Seite sollte dann bereits erreichbar sein.

Die eigentliche Pipeline besteht aus zwei Dateien: der .gitlab-ci und der rebuild.sh.
In der .gitlab-ci.yml werden sämtliche Dateien zusammengeführt, ohne dass Shopware-Code ausgeführt wird. Alles, was Shopware und eine Datenbank erfordert, wird in der rebuild.sh erledigt.

In der .gitlab-ci.yml sind folgende Variablen erforderlich:


* server
* server_folder
* ssh_user
* target_env
* SSH_PRIVATE_KEY



Falls zwischen Umgebungen unterschieden werden muss (z. B. unterschiedliche Server-Adressen für Stage und Prod), lassen sich hier Environments einsetzen. Die Bezeichnungen müssen exakt mit den Verzeichnisnamen in env_config übereinstimmen.

.gitlab-ci.yml

stages:
- deploy

.default_deploy:
image: kroniak/ssh-client:3.21
stage: deploy
script:
- echo "init start"
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
- echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config
- echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
- chmod 600 ~/.ssh/id_rsa
- echo "init finished"
- apk add --no-cache zip
- zip -rq custom_$CI_PIPELINE_IID.zip custom
- cd env_config/$target_env/ && zip -rq ../../env_config_$CI_PIPELINE_IID.zip . -x "../*" && cd ../..
- ssh ${ssh_user}@${server} "cd ${server_folder} && composer create-project shopware/production ./releases/$CI_BUILD_TOKEN "v6.6.10.3" --no-interaction --ignore-platform-reqs"
- scp custom_$CI_PIPELINE_IID.zip ${ssh_user}@${server}:${server_folder}
- scp env_config_$CI_PIPELINE_IID.zip ${ssh_user}@${server}:${server_folder}
- scp composer.json ${ssh_user}@${server}:${server_folder}/releases/$CI_PIPELINE_IID/composer.json
- scp composer.lock ${ssh_user}@${server}:${server_folder}/releases/$CI_PIPELINE_IID/composer.lock
- scp auth.json ${ssh_user}@${server}:${server_folder}/releases/$CI_PIPELINE_IID/auth.json
- scp rebuild.sh ${ssh_user}@${server}:${server_folder}/releases/$CI_PIPELINE_IID/rebuild.sh
- ssh ${ssh_user}@${server} "cd ${server_folder} && unzip -oq custom_$CI_PIPELINE_IID.zip -d ./releases/$CI_PIPELINE_IID/"
- ssh ${ssh_user}@${server} "cd ${server_folder} && unzip -oq env_config_$CI_PIPELINE_IID.zip -d ./releases/$CI_PIPELINE_IID/"
- ssh ${ssh_user}@${server} "cd ${server_folder} && rm custom_$CI_PIPELINE_IID.zip"
- ssh ${ssh_user}@${server} "cd ${server_folder} && rm env_config_$CI_PIPELINE_IID.zip"
- ssh ${ssh_user}@${server} "cd ${server_folder} && cd ./releases/$CI_PIPELINE_IID/public && rm -rf media && ln -sfn ../../../shared/media media"
- ssh ${ssh_user}@${server} "cd ${server_folder} && cd ./releases/$CI_PIPELINE_IID/public && rm -rf thumbnail && ln -sfn ../../../shared/thumbnail thumbnail"
- ssh ${ssh_user}@${server} "cd ${server_folder} && cd ./releases/$CI_PIPELINE_IID/public && rm -rf sitemap && ln -sfn ../../../shared/sitemap sitemap"
- ssh ${ssh_user}@${server} "cd ${server_folder} && cd ./releases/$CI_PIPELINE_IID/public && rm -rf bundles && ln -sfn ../../../shared/bundles bundles"
- ssh ${ssh_user}@${server} "cd ${server_folder} && cd ./releases/$CI_PIPELINE_IID/var && rm -rf log && ln -sfn ../../../shared/log log"
- ssh ${ssh_user}@${server} "cd ${server_folder} && cd ./releases/$CI_PIPELINE_IID/ && rm -rf files && ln -sfn ../../shared/files files"
- ssh ${ssh_user}@${server} "cd ${server_folder} && cd ./releases/$CI_PIPELINE_IID/ && ./rebuild.sh"
- ssh ${ssh_user}@${server} "cd ${server_folder} && ln -sfn ./releases/$CI_PIPELINE_IID current"
- ssh ${ssh_user}@${server} "cd ${server_folder} && cd releases && ls -dt */ | tail -n +4 | xargs rm -rf && cd .."
tags:
- runner-docker

deploy_stage:
extends: .default_deploy
environment: stage
only:
- /^staging_(.+)$/

deploy_prod:
extends: .default_deploy
environment: prod
only:
- /^prod_(.+)$/


Das Skript rebuild.sh kann projektspezifisch angepasst werden, z. B. wenn zusätzliche Node.js-Versionen oder Build-Schritte erforderlich sind.

rebuild.sh

bin/console system:update:prepare
composer composer install
bin/console system:update:finish
bin/console cache:clear
bin/console database:migrate --all
bin/console cache:clear
npm install --prefix ./vendor/shopware/administration/Resources/app/administration
bin/build-administration.sh
bin/build-storefront.sh
bin/console sw:theme:compile
bin/console cache:clear

bin/console cache:warmup
bin/console http:cache:warm:up


--

Zum Schluss noch ein Beispiel für eine docker-compose.yml, die sich bei Bedarf leicht anpassen lässt:


version: "3"

services:
# shopware_full:
# build: ./infrastructure
# ports:
# - "80:80"
# - "443:443"
# - "2222:22"
# volumes:
# - ./infrastructure/my_vhost.conf:/etc/apache2/sites-enabled/000-default.conf:rw
# - ./shopware:/var/www/html
# networks:
# - web

shopware:
image: dockware/dev:6.6.10.3
ports:
- "80:80"
- "443:443"
- "2222:22"
volumes:
- "./custom/apps:/var/www/html/custom/apps"
- "./custom/plugins:/var/www/html/custom/plugins"
- "./custom/static-plugins:/var/www/html/custom/static-plugins"
- "./vendor:/var/www/html/vendor"
- "./composer.json:/var/www/html/composer.json"
- "./composer.lock:/var/www/html/composer.lock"
- "./env_config/dev/.env:/var/www/html/.env"
- "./env_config/dev/.env.local:/var/www/html/.env.local"
- "lang_mysql:/var/lib/mysql"
- "./docker/volumes/media:/var/www/html/public/media"
- "./docker/volumes/theme:/var/www/html/public/theme"
- "./docker/volumes/bundles:/var/www/html/public/bundles"
- "./config/packages/cache.yaml:/var/www/html/config/packages/cache.yaml"

networks:
- web
environment:
# default = 0, recommended to be OFF for frontend devs
- XDEBUG_ENABLED=0
- XDEBUG_REMOTE_HOST=172.17.0.1
# default = latest PHP, optional = specific version
- PHP_VERSION=8.2
- NODE_VERSION=20

#config host:mail, port:1025, user:demo, password:demo
mail:
image: mailhog/mailhog
networks:
- web
ports:
- 8025:8025

# use init or bin/console system:install --force to init database
shopware_mysql:
image: bitnami/mysql:8.4.5
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: shopware
MYSQL_USER: app
MYSQL_PASSWORD: app
networks:
- web
volumes:
- ./docker/dumps/:/docker-entrypoint-initdb.d
- mysql_data:/var/lib/mysql

# shopware_mysql:
# image: bitnami/mariadb:10.6.20
# environment:
# MARIADB_ROOT_PASSWORD: root
# MARIADB_DATABASE: shopware
# MARIADB_USER: app
# MARIADB_PASSWORD: app
# networks:
# - web
# volumes:
# - ./infrastructure/dumps/:/docker-entrypoint-startdb.d
# - mysql_data:/bitnami/mariadb

shopware_redis:
image: redis:7
networks:
- web

shopware_elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.13.4
networks:
- web
environment:
- discovery.type=single-node
- xpack.security.enabled=false
volumes:
- elasticsearch_data:/usr/share/elasticsearch/data

volumes:
lang_mysql:
mysql_data:
elasticsearch_data:

networks:
web:
external: false

Tipp: Firebird 2.5.x mit Intellij verwenden

Eine Firebird 2.5.x Datenbank mag nicht mehr die neuste Datenbank sein. Sie skaliert nicht zeitgemäß, SQL dafür Schreiben macht wirklich keinen Spaß und auch sonst sollte man alles tun um zu vermeiden damit arbeiten zu müssen.. aber sie funktioniert noch und es gibt auch noch Docker-Container dafür.

In alten Projekten findet man also manchmal noch so eine alte Firebird Version und man kann sie super und schnell im Docker-Container starten und hat eine kontrollierbar Entwicklungsumgebung. DBeaver kann man gut damit verwenden.. also DBeaver spricht gut mit der Firebird DB, aber auch das Programm scheint aus einer Zeit zu stammen also Delphi noch der heiße geile Scheiß war.

Ich kam auf die abstruse Idee einfach meine Intellij IDE damit Verbindungen zu wollen. Es geht gut, wenn man erstmal weiß was man so einstellen muss.

1. Mut die fast älteste Version des Treibers wählen, die dabei ist

bbcode-image


2. Encoding einstellen... nicht erwarten das so etwas modernes wie UTF-8 funktioniert

bbcode-image


3. Die Software, die die Firebird 2.5.x nutzt so schnell wie möglich ablösen oder auf eine aktuelle MySQL/MariaDB Version bringen.

Sollte damals schon jemand voll modern gewesen ein und richtige JOIN Strukturen im FROM verwendet haben (das funktioniert da wirklich schon.. irgendwie), sollte so eine Migration gar nicht so schwer sein. ChatGPT hilft sicher gerne das DDL zu migrieren.

Tipp: Linux Oracle-Treiber installieren

Falls man unter Ubuntu 24.04 LTS diese Fehlermeldung erhält


(libaio.so.1: cannot open shared object file: No such file or directory)


hilft das hier


sudo apt install libaio1t64
sudo ln -s /usr/lib/x86_64-linux-gnu/libaio.so.1t64 /usr/lib/x86_64-linux-gnu/libaio.so.


So ein Oracle-DB Client ist erstaunlich umständlich zu installieren.

AI: Kraut-Ipsum mit ChatGPT wiederbelebt

bbcode-image


Lange Zeit war die erste Stelle für Platzhaltertexte immer Kraut-Ipsum. Doch dann war es weg. Nachdem nun GPT-5 rauskam brauchte ich ein paar Test-Ideen und irgendwie kam ich auf die Idee Kraut-Ipsum davon nochmal bauen zulassen.. ganz einfach und ganz simpler Prompt. Hab mich einmal bei der originalen Wort-Liste bedient aber man kann natürlich einfach sich da selber was zusammen basteln... so Firmen eigene Wort-Liste wäre auch möglich.

Demo:
https://krautipsum.annonyme.de/

Git:
https://gitlab.com/hpries/krautipsumresurrection

Agentur-Probleme: Verantwortung ist immer nach unten gerichtet

bbcode-image


Agenturen haben gerade viele Probleme, und es müssen neue Wege gefunden werden, wie man mit der sich verändernden Wirtschaft, Gesellschaft und Weltsituation umgeht. Ein riesiges Problem ist, dass bisher niemand einen funktionierenden Ansatz gefunden hat – oder auch nur eine Idee, wie man überhaupt einen solchen Ansatz entwickeln könnte. Ich habe es selbst versucht und musste mir eingestehen: Ich hatte nicht nur keine gute Idee, sondern nicht einmal eine schlechte.

Fehlende Kunden, KI, schlechte Wirtschaftslage – das alles lässt sich grob zusammenfassen mit: „Es fehlt das Geld, um etwas zu tun.“
Um als Agentur weiter existieren zu können, braucht man Kunden, die nicht nur den Status quo bezahlen, sondern auch genug Geld mitbringen, um Freiräume zu schaffen – um neue Wege zu gehen, Risiken einzugehen, auch mal zu scheitern. Aber genau dieses Geld ist bei den Kunden heute nicht mehr vorhanden. Gleichzeitig ist ihre Kompetenz gestiegen – sie stellen plötzlich unangenehme, weil sehr gezielte und berechtigte Fragen.

Vor fünf Jahren hat man eben 2000 € für etwas bezahlt, ohne dass ganz klar war, warum – weil es lief und das Geld da war.

Heute ist der Kampf um Kunden voll entbrannt. Jetzt muss man sich kompetent, professionell und innovativ präsentieren. Auch Verträge müssen so formuliert werden, dass nicht bei einem Problem gleich das ganze Projekt oder gar die Firma scheitert.

Das Sales-Department ist gefragt – und soll liefern. Was viele vergessen: In Agenturen spielen die Entwickler eine zentrale Rolle bei der Kundenakquise. Warum? Weil der Kunde mit seinem eigenen IT-Menschen kommt – und der zerlegt jeden Sales-Mitarbeiter argumentativ in der Luft. Sales können verkaufen, aber haben oft keine tiefere Ahnung vom Produkt oder der Dienstleistung. Deshalb sprechen am Ende meist Entwickler mit Entwicklern – oder mit Leuten, die selbst mal programmiert haben.

So kommt man auf einen gemeinsamen Stand, mit dem beide Seiten leben könnten.
Aber: Der Entwickler entscheidet nichts. Dann kommt wieder Sales – und es wird kompliziert. Die wollen mehr Geld, bessere Positionen. Wenn Entwickler verhandeln, kommt selten ein Deal dabei raus, der den Kunden „über den Tisch zieht“. Aber wenn mit ehrlicher Arbeit kein Geld mehr verdient wird, entsteht Druck – und dann sucht man eben doch Wege, irgendwie Geld rauszuholen. Auch wenn es nicht ganz sauber ist.

Sales gibt die Richtung vor. Das C-Level will: mehr Geld bei weniger Aufwand. Entwickler? Die kündigen womöglich, wenn der Druck zu groß wird. Verzögerungen? Möglich. Wenn schon die letzten Projekte nur „okay“ liefen, weil der Kunde über Probleme hinweggesehen hat und man Features nachliefern durfte, will man nicht an einen Kunden geraten, der vor Gericht um sein Geld kämpfen würde.

Also wird umformuliert, Zeiten angepasst. Nicht das Ergebnis soll bezahlt werden, sondern die Arbeit. Entwickler werden aus der Kommunikation genommen – weil sie zu ehrlich waren, sich zu gut mit dem Kunden verstanden haben.
Stattdessen schickt man eine Welle aus Inkompetenz los, um dem Kunden zu „zeigen“, dass er keine Ahnung hat und es für ihn besser sei, mehr für weniger zu zahlen. Aussagen wie: „Ein Pflichtenheft ist nur zum Nachteil des Kunden, weil er dann nicht mehr flexibel genug ist.“ – als ob es die letzten Male der Kunde gewesen wäre, der die Probleme verursacht hat.

Kommen wir zurück zum Punkt: Kunden sind kompetenter und stellen gute Fragen.
Sie wissen, dass nicht sie sich um die Agentur bemühen müssen, sondern umgekehrt – und das machen sie auch deutlich.

Plötzlich ist der Entwickler wieder gefragt, weil er sich ehrlich mit dem Kunden auseinandergesetzt hat. Wenn man in so einer Situation landet: Nicht mitmachen!
Es gibt einen Grund, warum plötzlich wieder Leute zurückgeholt werden, die schon raus waren oder nie dabei sein wollten. Es ist bereits zu spät – jetzt geht es nur noch darum, zu erklären, was der Kunde sehen will und warum er Recht hat.

Sales sagt nein.
Der Entwickler antwortet: „So geht es nicht weiter – genau deshalb sind wir jetzt in dieser Situation.“
Wenn man so weitermacht, verliert man den Kunden.

Und dann passiert etwas, das ich so noch nie erlebt habe:
Der Sales-Mitarbeiter sagt, es sei ihm egal – er würde für nichts, was er getan, gesagt oder noch sagen wird, die Verantwortung übernehmen.
Alle im Meeting nehmen das hin. Kein Widerspruch. Weiter im Text.

Was soll nun geschehen? Soll der Entwickler noch mal versuchen, den Kunden mit Argumenten zurückzuholen?
Auf keinen Fall!
Der Entwickler würde an allem Schuld sein.
Der Kunde würde das Problem allein bei ihm sehen – schließlich hat er auf das vertraut, was der Entwickler ihm vermittelt hat. Und das wurde nicht geliefert. Unter dem Subtext: „Eigentlich müsste man dich feuern.“

Wenn man wirklich, wirklich unfassbares Glück hat, merkt der Abteilungsleiter irgendwann, dass man nur das getan hat, was Sales gefordert hat – und er war dabei, als alle Bedenken vom Tisch gewischt wurden. Man hatte sich bewusst gegen die Wünsche des Kunden entschieden.

Und dann erkennt man:
Der Abteilungsleiter hat einfach nicht mitgespielt beim Spiel der Verantwortungsschieberei.
Die Verantwortung wandert immer nach unten.
Der Klassiker „Das war der Azubi“ ist bittere Realität – blöd nur, wenn man keinen Azubi hat. Dann bist du schuld.

Aber zum Glück ist das kein Problem, mit dem man leben muss – also nicht das Weiterreichen von Verantwortung an sich, sondern solche Abteilungsleiter. Wozu braucht man die überhaupt? Sie stehen nur im Weg, wenn das C-Level, das längst keinen Bezug mehr zum Alltag hat, seinen Frust direkt auf die Entwickler abregnen will. Und Entwickler? Die beschweren sich nicht – und wenn doch, sind sie zum Glück in einer Gehaltsklasse, auf die man nicht hören „muss“.

Am Ende bleibt die alte Erkenntnis:
Du HAST nicht Schuld.
Du BEKOMMST die Schuld.

Older posts:

Möchtest Du AdSense-Werbung erlauben und mir damit helfen die laufenden Kosten des Blogs tragen zu können?