Skip to main content

Autor: Johannes Schneider

Legenden zu Mono-Repos 2: „Man verliert komplett den Überblick!“

Die Vorstellung, dass ein Mono-Repo wie ein chaotischer Wühltisch aus Tausenden von Dateien ist, macht vielen Angst. Aber ist das wirklich so?

Woher kommt die Sorge?

Der Gedanke hinter diesem Mythos ist schnell erklärt: Wenn ALLES – wirklich ALLES – in einem einzigen Repository liegt, dann wird es unübersichtlich. Entwickler:innen befürchten:

  • Dateileichen und Chaos: Alte Dateien oder veraltete Projekte könnten den Code aufblähen.
  • Zu viel Rauschen: Es wird schwer, sich auf das Wesentliche zu konzentrieren, wenn jede Änderung Hunderte von Projekten betrifft.
  • „Wer macht was, wo und warum?“ – Das klassische Problem der Verantwortlichkeit.

Klingt plausibel, oder? Aber auch hier lohnt sich ein zweiter Blick, denn moderne Mono-Repos haben clevere Lösungen, die genau solche Probleme verhindern.

Mythos entzaubert: Überblick im Mono-Repo behalten? Easy!

Die Wahrheit ist: Mono-Repos bieten Tools und Strukturen, die dir helfen, den Überblick zu behalten – oft sogar besser als bei Poly-Repos. Schauen wir uns an, wie das geht:

1. Klare Ordnerstruktur

Ein gut organisiertes Mono-Repo beginnt mit einer sauberen Ordnerstruktur. Projekte, Tools und Shared Libraries werden logisch gruppiert.
Das hat den großen Vorteil, dass man mehrere Unterebenen einziehen kann. Viel Spaß bei der Pflege einer solchen Struktur in einem Poly-Repo-Umfeld…

Ordnerstruktur ist standardisiert

Wer je in einem Team mit Polyrepo gearbeitet hat, kennt das: Jede:r checkt die Repos an unterschiedlichen Ordnern nach persönlicher Präferenz aus.
Gar nicht so einfach, da (selbst) den Überblick zu behalten.

Wäre doch super, wenn diese Struktur zentral und für alle gleich gepflegt werden könnte…

2. Code-Ownership und Verantwortlichkeit

Tools wie CODEOWNERS-Dateien ermöglichen es, klare Zuständigkeiten festzulegen. Jede Datei oder jedes Verzeichnis hat zugewiesene „Owner“, die Änderungen prüfen und genehmigen müssen. So weiß jeder, wer wofür verantwortlich ist.

3. Smarter Code-Browser

Die meisten IDEs und Tools, wie VS Code oder JetBrains IntelliJ, bieten intelligente Such- und Filterfunktionen.

Damit ist es leicht möglich – je nach Bedarf – unterschiedliche Teile des Codes anzuzeigen und zu durchsuchen. Und alles andere ist nur einen Klick entfernt.

4. Isolation durch Module

Ein Mono-Repo heißt nicht, dass alles miteinander verbunden sein muss. Mit Isolation durch Module kannst du sicherstellen, dass Teams nur in ihrem Bereich arbeiten und nicht versehentlich in anderen Projekten herumpfuschen.

Wann kann es trotzdem unübersichtlich werden?

Natürlich hat jede Technologie ihre Stolperfallen. Hier sind ein paar Situationen, in denen Mono-Repos tatsächlich unübersichtlich werden können:

  • Wachstum ohne Struktur: Wenn die Ordnerstruktur wächst wie ein wilder Dschungel, wird es schwer, den Überblick zu behalten.
  • Fehlende Dokumentation: Mono-Repos sind mächtig, aber ohne klare Regeln und Guidelines weiß niemand, wie sie genutzt werden sollen.
  • Unkoordiniertes Arbeiten: Wenn Teams sich gegenseitig in die Quere kommen, kann es schnell chaotisch werden – unabhängig vom Repo-Typ.

Fazit: Mono-Repo und Übersichtlichkeit – kein Widerspruch!

Die Angst, in einem Mono-Repo den Überblick zu verlieren, ist verständlich, aber unbegründet – zumindest, wenn man es richtig angeht. Mit einer klaren Struktur, modernen Tools und etwas Disziplin können Mono-Repos sogar übersichtlicher sein als eine Sammlung von Poly-Repos.

Anstatt dich durch zig kleine Repos zu klicken, hast du alle Projekte an einem Ort – mit klarer Organisation, nachvollziehbaren Verantwortlichkeiten und den richtigen Hilfsmitteln. Der Schlüssel ist, das Chaos zu vermeiden, BEVOR es entsteht.

Legenden zu Mono-Repos 1: „Builds dauern ewig“

Wer schon einmal über die Vor- und Nachteile von Mono-Repos diskutiert hat, kennt ihn: den Satz, der in jeder Diskussion zuerst genannt wird: „Aber Builds in Mono-Repos dauern doch ewig!“

Klingt wie das perfekte Argument, um bei einem Poly-Repo zu bleiben, oder? Doch Moment mal – stimmt das wirklich? Zeit, diese Legende einmal gründlich auseinanderzunehmen.

Woher kommt der Mythos?

Mono-Repos, also das zentrale Zuhause für alle Projekte eines Teams oder Unternehmens, klingen erstmal nach einem Chaos-Magneten. „Du willst mir also sagen, dass ALLES in EINEM Repo liegt? Wie soll das gehen, ohne dass der CI/CD-Server in die Knie geht?“ Es ist verständlich, dass die Vorstellung abschreckt. Wenn ein Repo mit Hunderten von Projekten wächst, denkt man schnell an gigantische Builds, die Stunden oder gar Tage dauern.

Aber das ist eher ein Märchen aus der Vergangenheit als Realität. Warum? Schauen wir uns das mal genauer an.

Mythos entzaubert: Builds müssen NICHT ewig dauern

Die Wahrheit ist: Builds in Mono-Repos sind nicht automatisch langsamer. Im Gegenteil, moderne Tools und Techniken machen sie oft schneller und effizienter als Builds in einer Sammlung von Poly-Repos. Hier sind die wichtigsten Gründe:

1. Incremental Builds – Der Turbo für Mono-Repos

Mono-Repos haben einen großen Vorteil: Tools wie Bazel, Nx, oder Gradle können genau tracken, welche Teile des Codes wirklich geändert wurden. Statt alles neu zu bauen, wird nur das gebaut, was tatsächlich betroffen ist. Stell dir das vor wie ein intelligentes Navi: Es kennt immer den kürzesten Weg und spart dir Zeit.

2. Caching ist König

Viele Mono-Repo-Build-Systeme nutzen aggressive Caching-Strategien. Das bedeutet, wenn etwas schon einmal gebaut wurde und sich nicht geändert hat, wird es einfach wiederverwendet. So kannst du sicherstellen, dass Builds nicht nur schnell laufen, sondern auch konsistent sind.

3. Parallelisierung rettet den Tag

Die meisten modernen CI/CD-Pipelines können Build-Schritte parallel ausführen. Das ist besonders bei Mono-Repos ein Game-Changer. Anstatt stumpf alle Projekte nacheinander zu bauen, nutzt du die Power moderner Hardware. 1000 Projekte? Kein Problem – 100 gleichzeitig zu bauen spart ordentlich Zeit.

Was verursacht wirklich lange Builds?

Es gibt durchaus Fälle, in denen Builds in Mono-Repos lange dauern können – aber das liegt meist nicht am Mono-Repo selbst, sondern an anderen Faktoren:

  • Fehlendes Tooling: Ohne Tools wie Bazel oder Nx können Build-Prozesse ineffizient werden.
  • Zu große Abhängigkeiten: Wenn Projekte zu stark miteinander verknüpft sind, muss der gesamte Baum rekursiv gebaut werden.
  • Unzureichende Hardware: Ein Mono-Repo verlangt nach moderner Infrastruktur. Wenn deine CI/CD-Pipeline auf einem Server aus den 90ern läuft, hilft auch der beste Cache nichts.

Fazit: Keine Angst vor großen Repos

Die Legende, dass Builds in Mono-Repos ewig dauern, ist also genau das: ein Mythos. Mit dem richtigen Setup können Mono-Repos sogar schneller und zuverlässiger arbeiten als ihre poly-repo-gestützten Gegenstücke. Wichtig ist, dass du moderne Tools und Best Practices nutzt, um das Beste aus deinem Mono-Repo herauszuholen.

Also, beim nächsten Mal, wenn jemand mit „Builds dauern ewig“ um die Ecke kommt, kannst du entspannt sagen: „Klingt eher nach einem Tooling-Problem als nach einem Mono-Repo-Problem!“

Bleib gespannt auf Teil 2 der Serie – da geht es um den nächsten Mythos: „Man verliert komplett den Überblick!“ 👀

„Linke Socken in eine Schublade, rechte Socken in eine Schublade“ – Warum wir Daten nicht so speichern sollten

Stell dir vor, du würdest deine linken Socken in eine Schublade und deine rechten Socken in eine andere legen. Klingt unpraktisch, oder?

Genau so unpraktisch kann es sein, wenn wir Daten in der Software-Architektur speichern, „wie es sich gehört“, anstatt sie so zu organisieren, wie sie tatsächlich genutzt werden. In diesem Blog-Eintrag betrachten wir, warum es sinnvoller ist, Daten entsprechend ihrer Verwendung zu speichern, auch wenn das zunächst mehr Aufwand bedeutet.

Das Paradoxon der Sockenaufbewahrung

Im Alltag legen wir Socken paarweise in die Schublade, damit wir sie beim Anziehen schnell finden und nutzen können. Würden wir linke und rechte Socken getrennt aufbewahren, müssten wir jedes Mal ein Paar zusammensuchen – ein unnötiger Mehraufwand.

Übertragen auf die Software-Architektur

Ähnlich verhält es sich mit Daten: Wenn wir sie streng nach Kategorien oder logischen Strukturen speichern, die nicht ihrer tatsächlichen Nutzung entsprechen, erschweren wir den Zugriff und die Verarbeitung. Wir schaffen uns selbst Hindernisse, die den effizienten Betrieb unserer Anwendungen beeinträchtigen können.

Daten speichern, wie sie verwendet werden

Anstatt Daten so zu speichern, wie es auf den ersten Blick logisch erscheint, sollten wir uns darauf konzentrieren, wie die Daten tatsächlich genutzt werden. Dies kann bedeuten, Daten redundanter oder denormalisierter zu speichern, um den Zugriff zu beschleunigen und die Performance zu verbessern.

Vorteile dieses Ansatzes

  • Effizienter Datenzugriff: Daten liegen in der Form vor, in der sie benötigt werden, was die Verarbeitung beschleunigt.
  • Verbesserte Performance: Reduzierte Ladezeiten und schnellere Reaktionszeiten der Anwendungen.
  • Bessere Nutzererfahrung: Endbenutzer profitieren von schnelleren und zuverlässigeren Anwendungen.

Praktische Beispiele

Denormalisierung in Datenbanken

In relationalen Datenbanken streben wir oft nach einer hohen Normalisierungsstufe, um Redundanzen zu vermeiden. Doch in einigen Fällen kann Denormalisierung sinnvoll sein, um Lesezugriffe zu beschleunigen.

NoSQL-Datenbanken

NoSQL-Datenbanken wie MongoDB oder Cassandra speichern Daten oft in einem Format, das direkt den Anwendungsfällen entspricht. Dies ermöglicht schnelle Lese- und Schreibzugriffe, da die Datenstruktur auf die Nutzung ausgerichtet ist.

Caching und Materialized Views

Durch das Anlegen von Caches oder materialisierten Sichten können Daten in der benötigten Form bereitgestellt werden, ohne jedes Mal komplexe Abfragen ausführen zu müssen.

Herausforderungen und Lösungen

  • Datenkonsistenz: Redundante Speicherung kann zu Inkonsistenzen führen.
    • Lösung: Implementierung von Mechanismen zur Synchronisation und Konsistenzprüfung.
  • Erhöhter Speicherbedarf: Mehrfach gespeicherte Daten benötigen mehr Speicherplatz.
    • Lösung: Abwägung zwischen Speicherplatz und Performance; heute ist Speicher oft günstiger als Rechenzeit.
  • Komplexität bei Updates: Änderungen müssen an mehreren Stellen vorgenommen werden.
    • Lösung: Einsatz von Eventual Consistency und sorgfältige Planung der Datenflüsse.

Fazit

Der Socken-Beispiel dieses Beitrags soll verdeutlichen, dass es ineffizient ist, Daten entgegen ihrer Nutzung zu speichern – so wie es unpraktisch wäre, linke und rechte Socken getrennt aufzubewahren. In der Software-Architektur sollten wir uns von traditionellen Denkmustern lösen und Daten so organisieren, dass sie optimal genutzt werden können. Auch wenn dies anfänglich mehr Aufwand bedeutet, profitieren wir langfristig von effizienteren und leistungsfähigeren Systemen.

Die „Query-First“-Architektur: Optimierung für häufige Lesezugriffe

In der heutigen digitalen Welt, in der Daten in einem noch nie dagewesenen Tempo erzeugt und konsumiert werden, ist die Art und Weise, wie wir Daten speichern und darauf zugreifen, von entscheidender Bedeutung. Ein Ansatz, der in bestimmten Anwendungsfällen an Bedeutung gewinnt, ist die „Query-First“-Architektur. Dieser Ansatz konzentriert sich darauf, dass Daten typischerweise einmal geschrieben und sehr häufig gelesen werden.

Was ist die „Query-First“-Architektur?

Die „Query-First“-Architektur ist ein Designparadigma, bei dem der Schwerpunkt auf der Optimierung von Lesezugriffen liegt. Anstatt sich auf die Effizienz des Schreibens von Daten zu konzentrieren, werden Systeme so gestaltet, dass das Lesen schnell, effizient und skalierbar ist. Dies ist besonders nützlich in Szenarien, in denen Daten nach ihrer Erstellung selten geändert werden.

Daten werden viel öfters gelesen als geschrieben!

Vorteile

1. Verbesserte Leistung

Durch die Optimierung von Datenstrukturen für Lesezugriffe können Systeme schnellere Antwortzeiten bieten, was die Benutzerzufriedenheit erhöht.

2. Bessere Skalierbarkeit

Da Lesezugriffe oft weniger ressourcenintensiv sind als Schreibvorgänge, können Systeme leichter skaliert werden, um eine große Anzahl gleichzeitiger Benutzer zu unterstützen.

3. Vereinfachtes Caching

Mit seltenen Schreibvorgängen können aggressive Caching-Strategien implementiert werden, um die Last auf Backend-Systeme weiter zu reduzieren.

Herausforderungen

1. Komplexität beim Schreiben

Das einmalige Schreiben von Daten kann komplizierter werden, da Daten möglicherweise in mehreren Formaten oder an mehreren Orten gespeichert werden müssen, um Lesezugriffe zu optimieren.

2. Datenkonsistenz

Bei notwendigen Updates müssen alle Datenkopien konsistent gehalten werden, was zusätzliche Logik erfordert.

3. Speicherplatz

Die Optimierung für Lesezugriffe kann zu redundanter Datenspeicherung führen, was den Speicherbedarf erhöht.

Best Practices

  • Denormalisierung von Daten: Ermöglicht schnellere Abfragen auf Kosten von zusätzlichem Speicherplatz.
  • Verwendung von NoSQL-Datenbanken: Bieten Flexibilität und Leistung für leseintensive Anwendungen.
  • Eventual Consistency akzeptieren: In einigen Anwendungsfällen kann es akzeptabel sein, wenn Daten nicht sofort konsistent sind.

Fazit

Die „Query-First“-Architektur bietet einen wertvollen Ansatz für Systeme, in denen Daten einmal geschrieben und häufig gelesen werden. Durch die Fokussierung auf Lesezugriffe können Entwickler Anwendungen erstellen, die sowohl leistungsfähig als auch skalierbar sind. Wie bei jedem Architekturansatz ist es wichtig, die spezifischen Anforderungen und Einschränkungen des Projekts zu berücksichtigen, um die besten Ergebnisse zu erzielen.

Warum Startups wirklich scheitern (oder gar nicht erst gegründet werden)

Es gibt diesen berühmten Artikel mit 20 Gründen, warum Startups scheitern.

Aber: Ich bin überzeugt, dass die größeren Probleme ganz woanders zu suchen sind:

90% der Startups werden gar nicht erst gegründet oder sterben bereits in der Planungs-Phase!

Ich bin überzeugt, dass wir bereits eine ziemlich starke Suvivor-Bias finden. Und viele potenziell erfolgreiche Startups gar nicht erst gegründet werden.

Anforderungen an Gründer von Startups

Um ein Startup zu gründen, brauchen die Gründer eine ganze Menge an Eigenschaften, die sie gleichzeitig erfüllen müssen. Sobald auch nur eine davon fehlt, ist das Startup von Anfang an nur schwer überlebensfähig. Diese fehlenden Fähigkeiten müssen dann irgendwie kompensiert werden.

Fachliche Exzellenz + Idee

Die Gründer müssen fachlich exzellent sein. Wenn das fachliche Wissen fehlt, wird es sehr schnell, sehr schwierig.

Exzellent vernetzt

Ohne ein super Netzwerk wird es sehr, sehr schwer ein Startup voran zu bringen. Ohne entsprechendes Netzwerk werden viele Dinge sehr mühsam…

Kontakte werden benötigt zu Investoren, potentiellen Kunden, Multiplikatoren, …

Stark im Marketing und Vertrieb

Ohne exzellentes Marketing und Vertrieb ist jedes Startup zum scheitern verurteilt. Gerade am Anfang bleibt auch dabei ein Großteil der Aufgaben alleine aus Budgetgründen an den Gründern hängen.

Organisatorisch super

Erfolgreiche Startups müssen gut organisiert sein. Das muss man können, lernen und vor allem tun.

Teamfähig + Team-Leader

Die Teamfähigkeit von Gründern ist entscheidend für die Zusammenarbeit und den Aufbau eines kompetenten Teams. Ohne Team kein erfolgreiches Startup!

Menschenkenntnis

Irgendwie müssen die Gründe mit den richtigen Menschen zusammenarbeiten. Die richtigen Investoren finden und die richtigen Mitarbeiter.
Größtes Problem: Eine falsche Entscheidung in der frühen Phase eines Startups, kann den gesamten Erfolg gefährden.

„Bürokratisch“

Wir leben in Deutschland… Und es gibt sehr, sehr viel zu tun, um den Anforderungen der Bürokratie gerecht zu werden. Dafür braucht man definitiv ein Gen!

Risikobereit

Niemand gründet ein Startup, ohne eine ausgeprägte Risikobereitschaft. Die damit verbundene Ungewissheit ist nichts für schwache Nerven.

Flexibel

Kein Startup entwickelt sich geradlinig. Man benötigt eine ganze Menge an Flexibilität zur rechten Zeit, um auf sich änderne Dinge reagieren zu können.

„Reich“

Ein Startup bezahlt sich nicht von alleine – es ist einiges an Kapital notwendig. Ohne entsprechende Reserven und Investitionskapital wird es sehr, sehr schwer.

Deshalb werden die meisten Startups gar nicht erst gegründent!

Da so gut wie niemand auch nur einen Großteil dieser Anforderungen erfüllt, werden die meisten (möglichen) Startups gar nicht erst gegründet.

Alleine die Anforderungen an Kapital und Risikobereitschaft filtern bereits einen Großteil der idealen Gründer-Kandidaten aus.

Außerdem: Opportunitäts-Kosten

In Deutschland stehen sehr guten, motivierten und engagierten Menschen eine ganze Menge an sehr guten Karriere-Möglichkeiten offen. Deshalb entscheiden sich viele davon gegen das Risiko und die Anstrengungen ein Startup zu gründen.

Insbesondere gut vernetzte Personen finden leicht alternative weniger riskante Möglichkeiten (viel) Geld zu verdienen.

Alternative? „Startup as a Job“

Wir benötigen daher eine Möglichkeit in einem Startup mitzuarbeiten ohne das immense Risiko tragen zu müssen. Das kann nur erreicht werden, indem wir „Startups“ besser geplant gründen.

Besser ausgestattet mit Kapital von Risikokapitalgebern. Und ausgestattet mit Fachleuten, die die unterschiedlichen Anforderungen abdecken.

Frontend und Backend: Code sharing!

Zumindest für die Kommunikation per REST benötigen Frontend (TypeScript) und Backend (Kotlin/Java) die selben Klassen.

Es ist jetzt super nervig, wenn man den selben Code für die Requests und Responses für TypeScript und z.B. Kotlin schreiben muss.

Außerdem macht es das Leben sehr schwer – weil Änderungen immer an zwei Stellen gleichzeitig durchgeführt werden müssen.

Die Lösung: Code Generierung

Bei uns ist das Backend „führend“. D.h. wir erstellen Klassen in Kotlin und generieren daraus automatisch den Frontend-Code.
Damit ist sichergestellt, dass eine Änderung/Erweiterung an einer Stelle ausreichend ist.

Im Build wird automatisch neu generiert, so dass uns der TypeScript-Compiler auf Fehler/Probleme hinweist.

Technisch? Kotlin -> OpenApi -> Orval

Wir generieren aus unseren Kotlin-Klassen und Ktor-Endpoints vollautomatisch ein openapi.json-File.

Aus diesem openapi.json werden dann von Orval zur Compile-Time die TypeScript-Klassen generiert.

Zusammenfassung

  • Die Responses/Requests und alle zugehörigen Klassen werden nur einmal geschrieben (Kotlin).
  • die Endpoints werden im Code dokumentiert
  • automatisch wird daraus ein openapi.json erstellt
  • daraus wiederum wird TypeScript-Code generiert (inklusive des Boilerplate-Codes für die Kommunikation)
  • der TypeScript-Kompiler stellt sicher, dass wir inkompatible Änderungen auf Frontend-Seite entdecken

Warum wir (fast) nur unseren Tech-Stack verwenden. Oder: Wer alles kann, kann nix!

Wir haben im Laufe der Jahre „unseren“ Tech-Stack entdeckt/entwickelt:

Frontend: TypeScript + React

Backend: Kotlin/Ktor oder Java/Spring Boot

Datenbank: MongoDB (seltener relationale Datenbanken), S3 für Object-Storage

Von diesem Tech-Stack weichen wir nur sehr selten und möglichst wenig ab. Und dafür gibt es gute Gründe!

Wer alles macht, kann nix richtig!

Die Software-Welt wird immer komplexer und umfangreicher. Jeden Tag erscheinen eine ganze Menge an Tools und Libs.
Da überall auf dem Laufenden zu bleiben ist schon lange nicht mehr möglich.

Wir beschränken uns daher auf wenige Technologien und Libs und lernen diese richtig. Wir sind in diesen Technologien wahre Experten. Wir haben die meisten (oder zumindest viele) Probleme bereits gesehen und vor allem gelöst.

Erfahrungen ist teuer – gut, wenn der Preis dafür schon bezahlt ist

Wir haben schon viele Fehler gemacht – und daraus gelernt. Wir kennen deshalb die entscheidenden Stolpersteine.
Positiv formuliert: Wir haben schon sehr, sehr viel Zeit und Geld investiert um heute schnell den richtigen Weg verfolgen zu können.

„Wie schwer kann React schon sein“?

„Hello World“ in React ist tatsächlich sehr einfach. Aber eine wirklich komplexe Anwendung in React zu erstellen? Das ist definitiv eine Kunst! Jeder Angular-Entwickler wird im ersten Versuch daran scheitern.
Umgekehrt bilden wir uns auch nicht ein, komplexe Angular-Anwendungen schreiben zu können!

Das „beste“ Tool für den Job? Um jeden Preis?

Lohnt es sich wirklich das beste Tool für den Job zu finden?
In fast allen Fällen gilt: Lieber ein „good enough“ Tool finden, welches dafür vom Team bereits beherrscht wird.

Gibt es Projekte, in denen eine andere Datenbank als MongoDB geeigneter wäre? Mit Sicherheit!
Aber in den allermeisten Fällen, sind die Vorteile nur marginal. Und die Nachteile durch Evaluierung und Einarbeitung sind viel gravierender.

Meistens kommt es sowieso anders

Früher oder später ändern sich die Anforderungen und Umstände sowieso.
Bei jeder Major Version neu evaluieren um vielleicht 5% Performance zu gewinnen???

Während andere noch evaluieren wird bei der Neckar IT schon entwickelt!

Wir kennen unsere Technologien. Wir wissen sofort, ob unser Tech-Stack den Anforderungen gerecht wird. Es ist kein langwieriges (und teures) evaluieren mit unklarem Ausgang notwendig.
Wir legen entweder sofort los – oder kommunizieren offen und ehrlich, dass wir der falsche Partner für das Projekt sind.

Ausnahmen bestätigen die Regel + kenne deine Grenzen!

Manchmal, aber wirklich nur manchmal setzen wir eine andere Datenbank ein. Natürlich kennen und können wir auch relationale Datenbanken. Aber wir sind keine Experten in dem Bereich.
Wir können auch fortgeschrittene SQL-Queries schreiben. Und natürlich bestehende Systeme integrieren. Aber es gibt SQL-Experten da draußen, mit denen können und wollen wir uns nicht messen…

Wer uns engagiert, engagiert 100%ige Experten in unserem Gebiet!

Teuer wird es (sonst) am Ende!

Bei Software-Projekten ist es leider so, dass sich die großen, langfristigen Architektur-Entscheidungen erst nach geraumer Zeit bemerkbar machen. Und dann ist das Kind bereits in den Brunnen gefallen (und die Freelancer weitergezogen).
Deshalb gilt von Anfang an: Augen auf bei der Wahl der Experten und Technologien. Niemand möchte den Software-Entwicklern eine teure Lektion in einer neuen Technologie bezahlen.

Der Mythos vom „Fullstack-Entwickler“. Oder: Die Suche nach dem Einhorn!

Willkommen zu einem weiteren Abenteuer in der Welt der Softwareentwicklung! Heute tauchen wir in die mystische Sphäre des sagenumwobenen „Fullstack-Entwicklers“ ein. Ja, du hast richtig gehört – jener geheimnisvolle Zauberer, der gleichzeitig die Macht des Frontends und des Backends beherrscht. Aber gibt es ihn wirklich, oder ist er nur ein Mythos? Eine moderne Version des Einhorns, das wir alle jagen, aber niemals wirklich finden?

Was ist ein „Fullstack-Entwickler“?

Bevor wir uns auf die Suche nach dem Einhorn begeben, klären wir doch erst einmal, was mit „Fullstack-Entwickler“ überhaupt gemeint ist. Theoretisch handelt es sich um jemanden, der sowohl das Frontend – also das, was der Benutzer sieht und womit er interagiert – als auch das Backend – das, was im Hintergrund abläuft und die Daten verwaltet – beherrscht. Diese Entwickler kennen sich sowohl mit HTML, CSS und JavaScript aus als auch mit Server-Architekturen, Datenbanken und beherrscht mehrere Programmiersprachen Java und TypeScript.

Die Realität: Ein universelles Genie oder doch nur Wunschdenken?

Nun zur großen Frage: Gibt es den Fullstack-Entwickler wirklich? In der Theorie klingt das alles wunderbar. Ein Entwickler, der alles kann – das ist der Traum eines jeden Unternehmens! Wer würde nicht gerne jemanden einstellen, der sowohl die hübsche Benutzeroberfläche zaubern als auch die komplexe Serverlogik im Hintergrund orchestrieren kann?

Aber Moment mal! In der Realität stellt sich schnell heraus, dass die Anforderungen an einen Fullstack-Entwickler oftmals in den Bereich des Unmöglichen fallen. Die Technologien in beiden Bereichen entwickeln sich so rasant weiter, dass es schwer ist, in allen Feldern stets auf dem neuesten Stand zu bleiben. Selbst die besten Entwickler müssen sich spezialisieren, um in einem Bereich wirklich herausragend zu sein.

Der Mythos vom „Alleskönner“

Der Gedanke des „Fullstack-Entwicklers“ ist daher oft eher ein Marketing-Gag oder Wunschdenken als Realität. Klar, es gibt Entwickler, die sowohl ein solides Grundverständnis von Frontend- als auch Backend-Technologien haben. Doch ein tiefes, umfassendes Wissen in beiden Bereichen? Das ist schon schwieriger.

Was Unternehmen eigentlich suchen, sind Entwickler mit einer „Fullstack-Mentalität“: Menschen, die bereit sind, über den Tellerrand ihres Fachgebiets hinauszuschauen, die sich in neue Technologien einarbeiten und flexibel zwischen verschiedenen Aufgaben wechseln können. Aber das ist ein großer Unterschied zu dem, was oft erwartet wird.

Die Suche nach dem Einhorn

Hier sind wir also, auf der Suche nach dem Einhorn, dem „Fullstack-Entwickler“. Wir durchforsten Jobbörsen, schicken LinkedIn-Nachrichten, interviewen Kandidaten… und stoßen immer wieder auf dieselbe Erkenntnis: Ein echter „Fullstack-Entwickler“ ist so selten wie ein Einhorn. Die meisten Entwickler haben ihre Stärken und Leidenschaften entweder im Frontend oder im Backend, und das ist auch gut so!

Die Moral der Geschichte? Vielleicht sollten wir aufhören, nach Einhörnern zu suchen, und stattdessen lernen, die einzigartigen Fähigkeiten jedes Entwicklers zu schätzen. Teams sind dann am erfolgreichsten, wenn sie aus Spezialisten bestehen, die zusammenarbeiten und sich gegenseitig ergänzen.

Fazit: Ein Hoch auf die (teamfähigen!) Spezialisten

Statt den Mythos des Fullstack-Entwicklers weiter zu glorifizieren, sollten wir uns vielleicht auf das konzentrieren, was wirklich wichtig ist: Zusammenarbeit, kontinuierliches Lernen und die Bereitschaft, über den Tellerrand hinauszuschauen. Jeder Entwickler bringt seine eigenen Fähigkeiten und Erfahrungen mit ein – und genau das macht ein starkes Team aus.

Also, das nächste Mal, wenn du auf der Suche nach einem „Fullstack-Entwickler“ bist, frag dich lieber: Will ich wirklich ein Einhorn? Oder wäre ein Team aus talentierten, spezialisierten Entwicklern, die sich gegenseitig ergänzen, nicht viel wertvoller?

Das Einhorn mag zwar ein schöner Mythos sein, aber wahre Magie entsteht durch Zusammenarbeit und gemeinsame Leidenschaft. Und das ist kein Mythos.

Kotlin macht Spaß: Sealed Interfaces + when

Kotlin hat sich aus verschiedenen Programmiersprachen das Beste zusammengeklaut.
So erlaubt die Kombination von Sealed Interfaces und when sehr schönen und klaren Code. Der gleichzeitig auch noch sehr gut vom Compiler auf Korrektheit überprüft wird!

// Stub function to simulate fetching a response
private fun getResponse(): Response = TODO()

fun main() {
  // Call a method with a relaxed return type. 
  // This ensures the compiler treats 'response' as 'Response' rather than a specific subtype, avoiding compile-time warnings.
  val response: Response = getResponse()

  // Using 'when' with all concrete types. Skipping one type results in a compile error due to exhaustiveness checking.
  when (response) {
    is Response.Success.Success1 -> println("S1")
    is Response.Success.Success2 -> println("S2")
    is Response.Failure.Failure1 -> println("F1")
    is Response.Failure.Failure2 -> println("F2")
  }

  // Handling multiple types in one code block using commas.
  when (response) {
    is Response.Success.Success1,
    is Response.Success.Success2,
    is Response.Failure.Failure1,
    -> println("combined S1, S2, F1")  // Note the trailing comma for readability

    is Response.Failure.Failure2 -> println("F2")
  }

  // Handling all failure cases in one block by using the common interface 'Failure'
  when (response) {
    is Response.Success.Success1 -> println("S1")
    is Response.Success.Success2 -> println("S2")
    is Response.Failure -> println("Failure case") // This will match Failure1 and Failure2 both
  }

  // Handling Success and Failure cases together by their common interfaces
  when (response) {
    is Response.Success -> println("Success case")  // This will match Success1 and Success2
    is Response.Failure -> println("Failure case")  // This will match Failure1 and Failure2
  }
}

// Define a sealed interface 'Response' which can be either a Success or a Failure
sealed interface Response {
  
  // Define a nested sealed interface 'Success' under 'Response'
  sealed interface Success : Response {
    // Define concrete data classes for Success1 and Success2 which implement the Success interface
    data class Success1(val foo: String) : Success
    data class Success2(val bar: String) : Success
  }

  // Define a nested sealed interface 'Failure' under 'Response'
  sealed interface Failure : Response {
    // Define concrete data classes for Failure1 and Failure2 which implement the Failure interface
    data class Failure1(val foo: String) : Failure
    data class Failure2(val foo: String) : Failure
  }
}

Vorteile / Features

Sealed Interface-Hierarchie

Das Response-Interface ist sealed, was bedeutet, dass alle möglichen Unterklassen zur Compile-Time bekannt sind. Dadurch ist der when-Ausdruck exhaustiv – wir können nichts vergessen!

Unterschiedliche „Genaugigkeiten“ beim Machting

Durch die Typ-Hierarchie innerhalb der Sealed Interface können wir jetzt unterschiedliche Genauigkeiten matchen. Wir können z.B. alles Failure oder Success-Cases gemeinsam abhandeln.
Der Compiler übernimmt die Checks und fügt „Smart Casts“ hinzu.

Diese Beispiele zeigen, wie mächtig und flexibel sealed Interfaces und der when-Ausdruck in Kotlin sind, um komplexe Typ-Hierarchien zu handhaben und die Kompilierzeit-Sicherheit zu gewährleisten.

10 Gründe warum ein Scrum-Team aus Freelancern scheitern wird!

Ein Scrum-Team aus guten Freelancern zusammen stellen – was könnte da schon schief laufen?

Schließlich muss ein Team aus sehr guten Personen doch auch sehr gut performen, oder???

Sage mir, woran du mich misst, und ich sage dir, wie ich mich verhalten werde.

In der Regeln nach drei Monaten, spätestens nach sechs Monaten steht bei den meisten Freelancern die Vertragsverlängerung an.
Entsprechend passen Freelancer ihr Verhalten an diesen Zyklus an.

Welche Freelancer sind erfolgreich?

Wer wird als Freelancer beauftragt? Meiner Erfahrung nach sind das nicht unbedingt die besten Entwickler.
Oft genug sind es die guten Verkäufer (Selbstdarsteller?) und Menschen mit ausgesprochen gutem Netzwerk.

1. Nach der Vertragsverlängerung ist vor der Vertragsverlängerung

Jeder Freelancer lebt in einem 3/6-Monats-Zyklus von Verlängerung zu Verlängerung. Der Kunde muss immer zufrieden sein. Jeder Freelancer muss sich laufend beweisen, um die Verlängerung zu erhalten.

Kurzfristiges „beweisen“ ist daher Prio 1 für jeden Freelancer. Langfristige Entwicklungen und der „Dienst am Team“ müssen entsprechend hinten anstehen.

2. Niemand hat Zeit und Lust auf unsichtbare/undankbare Tätigkeiten

In jedem guten Scrum-Team gibt es die eher unscheinbaren Personen, die durch Fleiß und sorgfältige Arbeit das Team produktiv halten. Es gibt jede Menge unsichtbarer aber essenzieller Tätigkeiten, die unbedingt erledigt werden müssen.

Wer kümmert sich um die Dokumentation und testet den Code?
Wer führt sorgfältige und hilfreiche Code Reviews durch?
Wer kümmert sich um das Build Management und löst die Probleme?
Wer unterstützt die anderen Team-Mitglieder bei deren Tätigkeit und Problemen?
Wer hilft dabei die Kommunikation im Team zu optimieren?

Typischerweise wird keine dieser Tätigkeiten nach Außen hin deutlich sichtbar.

3. Alpha-Tiere unter sich

Der typische Freelancer tritt selbstbewusst auf und ist es gewohnt Dinge zu entscheiden und voran zu treiben. Warum sonst sollte das Risiko und die Herausforderungen der Selbständigkeit auf sich genommen werden?

Es ist leicht auszumalen, wie gut ein Team harmoniert, wenn es aus lauter (zumindest gefühlten) Alpha-Tieren besteht.

4. Freelancer haben wenig langfristige Erfahrungen

Die meisten Tätigkeiten von Freelancern sind kurzfristiger Natur. Entsprechend kennen viele Freelancer die langfristigen Folgen ihrer Entscheidungen nur aus der Ferne.

Erfahrung entsteht nicht durch das Wiederholen der selben Fehler und das Weiterziehen zum nächsten Projekt. Erfahrung entsteht durch das „Ausbaden“ von und Lösen von Fehlern, die sich erst im Nachhinein als Fehler herausstellen.

5. Freelancer verstehen „alte Projekte“ nicht

Durch das kurzfristige Engagement kennen Freelancer die Nöte und Schwierigkeiten von alten Projekten mit den ganzen Implikationen nicht. Freelancer sind oftmals gut darin neue Dinge zu entwickeln.
Wenn es aber um das Thema Migration alter Daten geht, hört die Erfahrung oftmals aus.

Das typische 80%/20%-Phänomen tritt auf. Die ersten 80% der Umsetzung verlaufen einwandfrei. Und bevor es dann ans Eingemachte geht, ist der Freelancer bereits beim nächsten Kunden aktiv…

6. Kontinuität fehlt

Ein Scrum-Team lebt zu sehr hohem Maße davon, dass das Team an sich funktioniert. Es geht dabei nicht nur um Know-How und Abläufe. Sondern auch um die Chemie und das Vertrauen im Team selbst.
Jeder Änderung am Team erfordert eine „Rekalibrierung“ des Teams.

Mit jeder Personaländerung im Team sinkt die Produktivität im Team für eine gewisse Zeit.

Je mehr Freelancer in einem Team sind, desto größer ist die Fluktuation im Team.

7. Keine gemeinsame Kultur

Jedes Unternehmen entwickelt – bewusst oder unbewusst – eine eigene Kultur. Die Menschen im Unternehmen teilen diese Kultur und passen sich dieser im Laufe der Zeit immer mehr an.

Jeder Freelancer bringt seine eigenen Vorstellungen und Werte bezüglich der Projekt-Kultur mit. Entsprechend dauert es länger bis sich das Team „findet“.

8. Unehrliche Retrospektive („alles super!“)

Um in der Retro (halbwegs) ehrlich miteinander sein zu können, ist ein sicheres Umfeld notwendig. Wenn die nächste Vertragsverhandlung direkt vor der Tür steht, fällt es schwer ehrlich zu sein.
Solche Retros sind daran zu erkennen, dass nur Positives geäußert wird. Höchstens über die Umstände und externe Dinge außerhalb des Einflusses des Teams wird negativ berichtet.

9. Wenig Identifikation mit dem Team bzw. dem Ziel des Teams

Jeder Freelancer ist per Definition Einzelkämpfer. Durch die Kurzfristigkeit der Tätigkeit ist eine Identifikation mit dem Team bzw. dem Ziel des Teams quasi unmöglich.
Ohne diese Identifikation mit dem gemeinsamen Ziel ist eine optimale Zusammenarbeit sehr schwer.

10. Wissenverlust

Die Fluktuation sorgt für beträchtlichen Wissensverlust. Dieser wird dadurch verstärkt, dass die Freelancer das langfristige Ziel des Projektes nicht teilen. Entsprechend steht der schnelle Erfolg und weniger die langfristige Qualität im Vordergrund.

Die Alternative?

Ein Team aus Freelancern wird nur in den seltensten Ausnahmefällen gut funktionieren. Im Prinzip bleiben nur zwei Alternativen:

  • Eigenes Team aufbauen
  • Komplettes Team engagieren

Komplettes Scrum-Team engagieren

Ein fertiges Team zu engagieren hat viele Vorteile:

  • Das Team ist bereits eingespielt und hat die unproduktive „Findungs-Phase“ bereits hinters ich
  • Das Team ist stabil und die Mitglieder haben ihre Rollen gefunden
  • Die Team-Mitglieder haben Sicherheit innerhalb des Teams und können entsprechend offen und ehrlich agieren
  • Das Team verfolgt gemeinsam ein Ziel und kann sich damit gemeinsam identifizieren
  • Das Team hat eine eigene Kultur entwickelt
  • Alle im Team haben ein persönliches Interesse am Erfolg des Gesamt-Projekts