Skip to main content

Autor: Johannes Schneider

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

Die Entscheidung Monorepo vs Polyrepo ist oft eine Glaubensfrage. Ein häufiges Argument gegen das Monorepo: Die Angst, dass es wie ein chaotischer Wühltisch endet. Aber ist das wirklich so? In Teil 1 unserer Serie haben wir bereits den Mythos der langen Build-Zeiten widerlegt. Heute klären wir die Frage: Verliert man wirklich den Überblick?

Polyrepo vs Monorepo

Woher kommt die Sorge?

Der Gedanke hinter diesem Mythos ist schnell erklärt: Wenn ALLES in einem einzigen Repository liegt, befürchten Entwickler im Vergleich Monorepo vs Polyrepo oft Nachteile:

  • Dateileichen und Chaos: Alte Dateien 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.
  • Verantwortlichkeit: „Wer macht was?“ ist oft die große Frage.

Klingt plausibel? Aber moderne Monorepos haben clevere Lösungen, die genau das verhindern.

Mythos entzaubert: Struktur im Monorepo behalten

Die Wahrheit ist: Monorepos bieten Tools, die helfen, den Überblick zu behalten – oft sogar besser als bei vielen Einzel-Repos (Polyrepos). Schauen wir uns an, wie das geht:

1. Klare Ordnerstruktur

Ein gut organisiertes Monorepo 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.

Der Vorteil gegenüber Polyrepos

Im Duell Monorepo vs Polyrepo punktet hier das Monorepo: Bei Polyrepos checkt jeder Entwickler die Projekte an unterschiedlichen Orten aus. Im Monorepo ist die Struktur zentral für alle gleich erzwungen. Das schafft Ordnung.

2. Code-Ownership und Verantwortlichkeit

Tools wie CODEOWNERS-Dateien ermöglichen es, klare Zuständigkeiten festzulegen. Jede Datei hat zugewiesene „Owner“, die Änderungen prüfen müssen. So ist die Verantwortlichkeit oft klarer geregelt als in verstreuten Repositories.

3. Smarter Code-Browser

IDEs wie VS Code oder IntelliJ bieten intelligente Filter. Damit zeigen Sie nur die Teile des Codes an, die Sie gerade brauchen. Alles andere ist ausgeblendet, aber nur einen Klick entfernt.

4. Isolation durch Module

Ein Monorepo heißt nicht, dass alles Spaghetti-Code ist. Mit Isolation durch Module stellen Sie sicher, dass Teams nur in ihrem Bereich arbeiten.

Wann kann es trotzdem unübersichtlich werden?

Natürlich gibt es Stolperfallen. Wenn die Ordnerstruktur wächst wie ein wilder Dschungel oder Dokumentation fehlt, wird es chaotisch. Das passiert aber bei Monorepo und Polyrepo gleichermaßen, wenn Disziplin fehlt.

Fazit: Monorepo vs Polyrepo – Übersicht ist Einstellungssache

Die Angst, im Monorepo den Überblick zu verlieren, ist meist unbegründet. Mit einer klaren Struktur und modernen Tools ist das Monorepo oft sogar übersichtlicher, da alles an einem zentralen Ort liegt.

Wir bei der Neckar IT nutzen diese Strukturen täglich in unserer Softwareentwicklung, um auch bei großen Projekten effizient zu bleiben.

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!

Die Wahl des richtigen Technologie Stack ist eine der wichtigsten Entscheidungen in der Softwareentwicklung. Wir haben im Laufe der Jahre „unseren“ Stack entdeckt und perfektioniert:

  • Frontend: TypeScript + React
  • Backend: Kotlin/Ktor oder Java/Spring Boot
  • Datenbank: MongoDB (seltener relationale Datenbanken), S3 für Object-Storage
Unser Technologie Stack: Fokus auf Stabilität und Expertise.

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

Wer alles im Technologie Stack hat, kann nix richtig!

Die Software-Welt wird immer komplexer. Jeden Tag erscheinen neue Tools. Da überall auf dem Laufenden zu bleiben, ist unmöglich. Wir beschränken unseren Technologie Stack daher auf wenige Komponenten und lernen diese richtig. Wir sind hier wahre Experten.

Erfahrung ist teuer – wir haben den Preis schon bezahlt

Wir haben schon viele Fehler gemacht – und daraus gelernt. Wir kennen die Stolpersteine.
Positiv formuliert: Wir haben viel investiert, um heute mit unserem Setup schnell und sicher entwickeln zu können.

„Wie schwer kann React schon sein“?

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

Der „beste“ Technologie Stack für den Job?

Lohnt es sich wirklich, immer das theoretisch „beste“ Tool zu suchen?
In fast allen Fällen gilt: Lieber ein „good enough“ Tool finden, welches vom Team bereits perfekt beherrscht wird.

Gibt es Projekte, in denen eine andere Datenbank als MongoDB geeigneter wäre? Sicher! Aber meistens sind die Vorteile marginal, während die Einarbeitungszeit in einen fremden Technologie Stack enorm ist.

Meistens kommt es sowieso anders

Früher oder später ändern sich die Anforderungen. Bei jeder Major Version neu zu evaluieren, um 5% Performance zu gewinnen, ist oft unwirtschaftlich.

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

Wir kennen unsere Technologien. Wir wissen sofort, ob unser Stack den Anforderungen gerecht wird. Es ist kein langwieriges Evaluieren notwendig. Wir legen sofort los – oder sagen ehrlich ab.

Ausnahmen und Grenzen

Manchmal setzen wir andere Datenbanken ein (z.B. SQL), wenn es zwingend nötig ist. Aber wir kennen unsere Grenzen. Wer uns engagiert, bucht 100%ige Experten für unseren Bereich.

Fazit: Teuer wird es am Ende

Architektur-Fehler zeigen sich oft erst spät. Deshalb gilt: Augen auf bei der Wahl der Experten und dem Technologie Stack. Niemand möchte Entwicklern eine teure Lektion in einer neuen Technologie finanzieren.

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