Hinweis: Dieser Beitrag ist ein Working Draft, also noch weit entfernt von Fertig. Dennoch möchte ich die Informationen in diesem Status bereitstellen und das Feedback der Community einfließen lassen. Schon jetzt möchte ich vor allem für das Inhaltliche Feedback der AIT und Artiso bedanken. Wir planen, aus diesem Beitrag, wenn der entsprechende Reifegrad erreicht ist, ein Whitepaper zu erstellen.

Download als Dokument

Working Draft 0.1

Viele Teams haben die Notwendigkeit Frameworks oder andere Komponenten, welche von anderen Entwicklungsprojekten verwendet werden, unter Versionsverwaltung zu stellen. Hierbei stellt sich die Frage, wie Updates einer solchen Komponente dann diesen Projekten bereitgestellt werden? Je nach Projekt gibt es die unterschiedlichsten Anforderungen an diesen Prozess. Zum Beispiel: Werden die Komponenten nur als Binaries oder in Form von Source Code referenziert? Soll das Projekt direkt alle Änderungen an der Komponente oder nur stabilisierte Updates erhalten? Kann die Branch- Struktur des Zielprojektes eine Version der Komponente referenzieren oder muss jeder Branch die Möglichkeit haben eine spezifische Version zu referenzieren? Und da sind wir schon mitten im Thema, Branches. Team Foundation Server verwendet Branches, um Code-Versionen zu isolieren, was leider voraussetzt, dass man erst das Konzept der Branches etabliert haben muss, um mit deren Mechanismen Komponenten, die der Versionierung unterliegen, einem Projekt bereitzustellen. Es empfiehlt sich, aufbauend auf dem Visual Studio TFS Branching Guide 2010, das Thema Branches und Merges mit Team Foundation Server 2010 anzugehen.

clip_image002[8]

Bevor es dann losgeht, sei erwähnt, das dieses Dokument nicht den Anspruch auf Vollständigkeit der möglichen Szenarien hat, sondern vielmehr den schrittweisen Einstieg, der verschiedene Ansätze zeigt, um Einsteigern den ersten Schritt zu erleichtern. So fokussiere ich in den Szenarien den Ansatz mit Shared Code, wobei die Mechanismen natürlich für Shared Binaries ähnlich anwendbar und kombinierbar sind. Shared Binaries sind vor allem dann notwendig, wenn Komponenten anderer Hersteller Verwendung finden, für die kein Code vorliegt.

Szenario A) Shared Code mit First Class Branches:

Hierbei handelt es sich um ein sehr einfachen Ansatz, der in der Praxis allerdings nur begrenzt anwendbar ist. Warum wird er dennoch betrachtet? Weil er, wie ich finde, gut in die Thematik Einleitet und Limitationen aufzeigt, die Ihnen helfen sollen Ihr eigenen Anforderungen an das Shared Szenario zu definieren.

Im Team Project CommonLib wird die CommonDataAccessHelper Solution entwickelt, welche später dann von einem oder mehren anderen Projekten konsumiert werden soll, aber separat weiter entwickelt wird.
clip_image004[8]

Im nächsten Schritt konvertiere ich das Verzeichnis Main in einen Branch. Der Einfachheit halber soll Main auch der Branch sein, von der wir später in die entsprechen Consumer branchen. In komplexeren Projekten würde für diesen Zweck ein Release Branch von der Main erstellt werden, der eine stabilisierte Version des Shared Codes enthält.

clip_image006[8]

Jetzt erstelle ich ein Neues Team Project für den Consumer. In Main habe ich die PaymentSystem Solution hinzugefügt, welche den CommonDataAccessHelper verwenden soll:

clip_image008[7]

Ich füge nun ein Verzeichnis Common zu der ConsumerApp hinzu, das alle Branches von Shared Code enthalten soll. Optional könnte hier auch ein zusätzliches Verzeichnis für Shared Binaries angelegt werden.

clip_image010[7]

Im nächsten Schritt wird $CommonLib\Main nach $/ConsumerApp/Common/SharedDataAccessHelper „ge-branched“:

clip_image012[7]

Nun steht mir der Helper in der SharedDataAccessHelper Branch zur Verfügung und ich kann Ihn in der Payment Solution konsumieren. Unter $/ConsumerApp/Common/ würde nun für jeden Shared Code, wenn erforderlich, eine Branch erstellt werden, was letztendlich von den Anforderungen an die Granularität der Updates abhängt.

clip_image014[6]

Nachdem ich das CommonDataAccessHelper Projekt in die PaymentSystem Solution hinzugefügt und referenziert habe:

clip_image016[6]

kann ich die TXLogging Class verwenden:

clip_image018[6]

Das Team führt ein Update des Shared Codes in $CommonLib\Main durch:

clip_image020[6]

In der Historie kann man die Änderung im Chg99 sehen:

clip_image022[6]

Wenn man den Chg99 über die Branches verfolgt , sieht man, dass die $/ConsumerApp/Common/SharedDataAccessHelper Branch die Änderung noch nicht enhält und somit noch mit der vorhergehenden Version arbeitet:

clip_image024[6]

Durch einen Merge des Chg99 und Checkin wird das Update durchgeführt:

clip_image026[6]

Die Änderung ist jetzt in $/ConsumerApp/Common/SharedDataAccessHelper verfügbar und das Update wird von der PaymentSystem Solution verwendet:

clip_image028[6]

Für das häufige geforderte Szenario, dass unterschiedliche Helper Versionen in verschienden Branches z.B. DEV, MAIN, Release usw. referenziert werden sollen, ist dieser Ansatz NICHT geeignet, da unter Common eine Vielzahl von Branches für Shared Code entstehen würden, einzig um verschiede Versionen der Helper bereitzustellen. Daher das Szenario B, welches den Shared Code via Baseless Merge, also nicht als FirstClass Branch dem Konsumenten bereitstellt.

Szenario B) Shared Code mit Baseless Merge

Dieses Szenario greift den Ansatz von Szenario A auf, ohne $CommonLib\Main als eigene FirstClass Branch in die $ConsumerApp zu branchen. Daher kann das Verzeichnis $ConsumerApp\Common in den folgenden Screenshots einfach ignoriert werden. Vielmehr habe ich unter $ConsumerApp\Main das Verzeichnis CommonBin und CommonSrc angelegt. CommonBin würde alle Shared Binaries enhalten. CommonSrc den Shared Code, also die Solutions deren Projekte referenziert werden. Der Unterschied zu Szenario A ist, dass der Shared Code Teil jedes einzelnen Branches des Konsumers ist.

clip_image030[6]

Da eine FirstClass Branch, keine andere FristClass Branch enthalten kann, muss man hier einen anderen Mechanisums nutzen, der es erlaubt $CommonLib\Main nach $ConsumerApp\Main\CommonSrc zu Mergen. Hierzu wird einmalig eine Verknüpfung zwischen Quelle und Ziel mit einem Baseless Merge erstellt. Im Visual Studio Prompt und mit TF.exe kann diese Verknüpfung erstellt werden:

clip_image032[7]

Im Source Control Explorer wird der Baseless Merge wie folgt angezeigt:

clip_image034[6]

Jetzt noch den Check-In durchführen:

clip_image036[6]

Nun ist Grundlage für den Merge von $CommonLib\Main nach $ConsumerApp\Main\CommonSrc gegeben. $ConsumerApp\Main\CommonSrc steht als Target Branch zur Verfügung:

clip_image038[6]

clip_image040[6]

Check-in des Merges:

clip_image042[6]

Fertig. Im letzten Schritt noch analog zu Szenario A das CommonDataAccessHelper Projekt in die PaymentSystem Solution hinzufügen und referenzieren.

clip_image044[6]

Ein Update in $CommonLib\Main kann dann nach Bedarf nach $ConsumerApp\Main\CommonSrc ge-merged werden und steht dann zur Verfügung.

clip_image046[6]

In diesem Beispiel liegen die abhängigen Komponenten direkt im Branch des Konsumenten. Normalerweise gibt es aber mehrere Branches z.B. MAIN, DEV, Release:

clip_image048[6]

Updates würden, wenn möglich, immer über $ConsumerApp\Main laufen. Ein Update, welches von $CommonLib\Main nach $ConsumerApp\Main\CommonSrc durchgeführt wurde, würde dann innerhalb der FirstClass Branches via Forward Integration an die anderen Branches weiter gegeben werden. Dies ist das einfachste Szenario.

In der Praxis möchte man aber vielleicht nicht von $CommonLib\Main direkt nach $ConsumerApp\Main branchen, sondern erst in einer Development Branch den neuen Helper testen, zum Beispiel in $ConsumerApp\DEV . Hierzu müsste man wiederum einen Basless Merge erstellen und im Anschluss eine Reverse Integration nach $ConsumerApp\Main durchführen. Andere Branches unter $ConsumerApp erhalten das Update via Forward Integration.

clip_image050[6]

Das gleiche gilt, wenn man nicht von $CommonLib\Main branched, sondern von einem Release Branch im Helper zum Beispiel $CommonLib\Releases\R1, was durchaus üblich ist.

Hinweis: An dieser Stelle sein noch erwähnt, dass mit dem Tool tf.exe ein Merge und Branch auf Basis eines Labels möglich ist. Da Team Build einen Label bei jedem Build erzeugt, könnte man zum Beispiel von einen Build der $CommonLib\Main einen Branch erzeugen.

Wenn Änderungen am CommonDataAccessHelper innerhalb der ConsumerApp erlaubt sind, könnte man diese dann z.B. von $ConsumerApp\DEV > $ConsumerApp\Main > $CommonLib\Main mittels Reverse Integration überführen. Ich würde diesen Ansatz aber vermeiden, da er die Komplexität erhöht.

Ein Wort zur Visualisierung. Im Falle der Baseless-Merges werden Integrationen aus dem CommonLib-Projekt nicht mehr in der Branch-Hierarchie visualisiert. Hier sollte ein Konzept, z.B. auf Basis von Builds und der Historie helfen, nachvollziehen zu können, welche Bug-Fixes integriert wurden.

Letztendlich muss man sich ein klares Bild über den Fluss der Updates zwischen Helper und Konsumenten auf Basis des gewählten Branch Modells machen und die Mechanismen entsprechend anwenden. Weniger ist hier meistens mehr. Bei entsprechend komplexen Applikationen mit vielen Komponenten, gilt es einige dieser Mechanismen zu automatisieren.

Szenario C) Shared Code mit Workspace Mappings

Dieses Szenario führt den Shared Code nicht auf dem Server zusammen, sondern auf dem Client im Workspace.
ToDo :-)

Ist dieses Dokument fertig? Noch lange nicht. Backlog:

1) Einfaches Beispiel mit Shared Binaries?

2) Dependency Replication aus Build?

3) Workspace Mapping Konzepte

4) Multiple Solutions

5) Branch by Label

6) Rekursiver Build