Große Visual Studio Solutions beherrschen - Aufteilen (4)
Die vorhergehenden Artikel haben Maßnahmen beschrieben, durch die die Arbeit in großen Solutions besser von der Hand geht – gewissermaßen als „Schmerzlinderung“. Zum Abschluss der Serie beschäftigen wir uns nun mit der Aufteilung von großen Visual Studio Solutions in kleinere, die von Haus aus einfacher handzuhaben und besser zu überblicken sind.
Sollbruchstellen suchen
Bei der Aufteilung der großen in mehrere kleinere Solutions muss das Ziel natürlich sein, möglichst unabhängige Einheiten zu schaffen, die losgelöst voneinander geändert werden können. Das Problem wäre andernfalls nur verlagert, wenn eine Änderung in einer Solution fast regelmäßig zu Änderungen in einer anderen führt. In diesem Fall steht man hinterher schlechter da als vorher, da nun zwei Solutions überblickt werden müssen anstatt einer. Insofern ist dieser Vorbereitungsschritt von großer Wichtigkeit.
Wie kann man also Bereiche identifizieren, die in eine eigene Solution ausgelagert werden können?
Das wichtigste Hilfsmittel ist hierbei der Überblick über die Solution, den Sie z.B. mit Code Maps bzw. dem Solution Dependency Viewer erhalten. Die folgenden Perspektiven bzw. Kriterien unterstützten dabei, geeignete Blöcke zu identifizieren:
- Bei welchen Projekten handelt es sich um Basiskomponenten?
- Werden diese u.U. noch in weiteren Solutions benötigt und dort per Binär-Referenz eingebunden?
- Welche Projekte gehören aus fachlicher Sicht zusammen?
- Welche Projekte werden oft gemeinsam geändert?
- Gibt es Projekte, die schon seit langer Zeit kaum noch geändert wurden?
- Auf welchen Technologien setzen die Projekte auf?
Natürlich ist es manchmal nicht gleich am Anfang klar, wo die Linien verlaufen. Das ist aber auch nicht unbedingt ein Problem; schließlich kann das Aufteilen auch ein iterativer Prozess sein. Insbesondere, weil die Aufteilung Auswirkungen auf die tägliche Arbeit und wichtige Prozesse wie z.B. den Build haben wird, ist eine schrittweise Vorgehensweise ohnehin zu empfehlen. So kann man aus dem Ergebnis lernen und bei der nächsten Aufteilung bereits erste Erkenntnisse einfließen lassen.
Auch wenn man einmal eine Trennung eingeführt hat, die sich als nicht zielführend erweist, so ist es normalerweise wesentlich einfacher, die Projekte wieder zusammenzuführen als die Aufteilung war.
Konsequenzen der Aufteilung
Solange die Projekte in einer Solution zusammengefasst waren, konnten diese per Projektreferenz auf andere Projekte verweisen. Wie im ersten Teil beschrieben, hat dies einige Vorteile, u.a. dass der Quelltext aller Projekte in der Solution vorliegt und die Projekte gemeinsam erstellt werden. Das folgende Schaubild zeigt eine Solution, die zwei Projekte enthält. Projekt A enthält Komponenten, die in Projekt B genutzt werden. Projekt B hält daher eine Projektreferenz auf A. Bei der Erstellung werden beide Projekte kompiliert und die Ausgaben liegen gemeinsam vor:
Nach der Aufteilung können nur noch Binärreferenzen verwendet werden, so dass bei der Kompilierung der abhängigen Solution die Ausgaben der anderen Solution vorhanden sein müssen. Wie in folgendem Schaubild leicht zu erkennen, sind nun mehrere Kompiliervorgänge notwendig, um die Ausgaben zu erzeugen. Zur Erstellung von Projekt B muss die binäre Ausgabe des Projekts A vorliegen:
Um nun die Effizienz, die man durch die Arbeit in kleineren Solutions gewinnt, nicht sofort wieder zu verlieren, liegt es auf der Hand, dass einerseits möglichst selten Änderungen an Projekt A notwendig sein sollten. Andererseits muss der Weg, auf dem Projekt B an die aktuellste Ausgabe von Projekt A kommt, möglichst aufwandsarm und stabil sein.
Hierauf haben folgende Faktoren Einfluss:
- Wie einfach können neue Versionen von Projekt A bereitgestellt werden?
- Wie einfach können neue Versionen von Projekt A in Projekt B integriert werden?
Holzwege
Bevor wir eine vorgeschlagene Lösung betrachten, wollen wir noch auf einige Ansätze eingehen, die das Problem zwar vordergründig lösen, aber schlussendlich gravierende Nachteile haben, dass von deren Einsatz abzuraten ist:
- Ausgaben von Projekt A im Dateisystem der Entwicklerrechner ablegen: dieser Ansatz ermöglicht es, neue Versionen von Projekt A sehr einfach in Projekt B zu verwenden. Allerdings ist es vorprogrammiert, dass die Versionen auf den Entwicklerrechnern auseinanderlaufen. Außerdem muss auf jedem Entwicklerrechner und insbesondere auf dem Build-Server immer die aktuellste Version abgelegt werden. Das endet eher früher als später im Chaos.
- Ausgaben von Projekt A in der Quelltextverwaltung ablegen: auf diesem Weg erhalten die Entwickler und der Build-Server die aktuellste Version durch einfaches Abholen des letzten Standes aus der Quelltextverwaltung. Auch so ist der Zugriff sehr einfach möglich; darüber hinaus ist die Verteilung stabiler, weil nur ein Ablageort gepflegt werden muss. Problematisch ist dieser Ansatz eher deshalb, weil das Veröffentlichen der neuen Version in der Quelltextverwaltung verhältnismäßig aufwändig ist. Außerdem kommen die meisten Quelltextverwaltungssysteme nicht besonders gut mit größeren Mengen an Binärdateien zurecht. Vor dem Hintergrund, dass die Binärdateien ohnehin auf Basis des Quelltextes erstellt werden, der in der Quelltextverwaltung hinterlegt ist, führt das Ablegen der Binärdateien ohnehin zu redundanten Informationen.
Insbesondere, wenn sich Projekt A häufig ändert, ist dieser Ansatz also eher ungeeignet.
Nuget und ein Package Feed
Seit vielen Jahren leistet Nuget als Paketmanager gute Dienste. Neben dem zentralen Feed auf Nuget.org, der öffentlich verfügbare Pakete enthält und bereitstellt, kann man auch einen eigenen, privaten Feed anlegen. Dieser kann im Visual Studio als Package Source konfiguriert werden, so dass auch über den privaten Feed Pakete bezogen werden können. Hierzu gibt es mehrere Möglichkeiten, z.B.: • Installieren eines eigenen Nuget-Servers. • Verwenden eines Netzwerk-Shares zum Ablegen der Pakete. • Einsatz der Package Management-Erweiterung für Visual Studio Team Services bzw. den Team Foundation Server.
Eine detaillierte Beschreibung der Erstellung von Nuget-Paketen und dem Aufsetzen eines Feed würde den Rahmen dieses Artikels sprengen, so dass wir an dieser Stelle zuerst einmal die prinzipielle Vorgehensweise darstellen. Später werden wir in einem separaten Artikel das Aufsetzen eines Build-Prozesses mit der Ausgabe von Nuget-Paketen und der Veröffentlichung in einem Feed auf Basis von Visual Studio Team Services beschreiben.
Der Einsatz von Nuget-Paketen und eines Feed hat folgende Vorteile: • Neben den Binärdateien können Nuget-Pakete weitere Inhalte enthalten, durch die beispielsweise Konfigurationseinstellungen o.ä. in einem Projekt gepflegt werden können. • Nuget-Pakete können einfach im Visual Studio aktualisiert werden. Sie sind mit einer Version gekennzeichnet, die in der Datei packages.config eines Projekts vermerkt wird, so dass alle anderen Entwickler und der Build-Server mit dem gleichen Stand arbeiten. • Die Pakete werden im Feed abgelegt und nicht in der Quelltextverwaltung. Somit wird diese nicht durch Binärdateien belastet, die sich häufig ändern. • Nuget-Pakete können außerdem als PreRelease-Version gekennzeichnet werden. Bei der Suche nach Paketen kann man sich entscheiden, ob man nur stabile Versionen abfragen will oder auch solche, die gerade erst in der Entwicklung sind. • Darüber hinaus kann das Erstellen von Paketen und deren Veröffentlichung während dem Build einfach automatisiert werden, so dass der Feed jedes Mal mit einer neuen Version aktualisiert wird, wenn der Build auf dem Build-Server läuft.
Die folgende Übersicht zeigt, wie das oben angeführte Beispiel auf Basis von Nuget-Paketen und einem Feed abgebildet wird:
Die Ausgabe von Projekt A wird als Nuget-Paket verpackt und auf einem privaten Feed abgelegt. In Projekt B wird das Nuget-Paket aus dem Feed geladen und referenziert. Nach einem Update können sich die Entwickler von Projekt B entscheiden, wann sie per Update auf die neue Version des Nuget-Pakets umsteigen.
Auch wenn das Schaubild größer ist als die beiden vorherigen, so ist der Ablauf durch die guten Automatisierungsmöglichkeiten und weitreichende Toolunterstützung trotzdem einfacher und stabiler in der täglichen Anwendung.
Falls Sie nun neugierig auf weitere Informationen zu Nuget und Feeds geworden sind, haben wir hier ein paar Links mit weiteren Informationen zusammengestellt:
- nuget.org
- Package Management Extension für Visual Studio und TFS
- Package Management in Visual Studio Team Services and TFS
- Nuget-Pakete im Visual Studio verwenden inkl. Konfiguration einer eigenen Package Source
Zusammenfassung
In diesem Beitrag haben wir dargestellt, wie eine große Solution in mehrere kleine aufgeteilt werden kann. Wichtig ist, geeignete Linien zu ziehen, so dass die entstehenden Solutions möglichst unabhängig voneinander bearbeitet werden können. Eine gute Möglichkeit zur Verwendung der Ausgaben einer Solution in Projekten in anderen Solutions ist die Erstellung eines Feed, auf dem Nuget-Pakete abgelegt werden. Nachdem wir dies in diesem Artikel von der prinzipiellen Herangehensweise her betrachtet haben, werden wir in einem späteren Post noch darauf zurückkommen, wie dies konkret mit den Visual Studio Team Services umgesetzt werden kann.