So verwalten Sie verschiedene, angepasste Versionen derselben Software für mehrere Clients


46

Wir haben mehrere Kunden mit unterschiedlichen Bedürfnissen. Obwohl unsere Software bis zu einem gewissen Grad modularisiert ist, ist es fast sicher, dass wir die Geschäftslogik jedes Moduls hier und da ein wenig für jeden Kunden anpassen müssen. Die Änderungen sind wahrscheinlich zu klein, um das Aufteilen des Moduls in ein bestimmtes (physisches) Modul für jeden Client zu rechtfertigen. Ich fürchte, Probleme mit dem Build, ein Chaos bei der Verknüpfung. Diese Änderungen sind jedoch zu umfangreich und zu umfangreich, um sie in einigen Konfigurationsdateien über Switches zu konfigurieren, da dies zu Problemen bei der Bereitstellung und möglicherweise zu zahlreichen Supportproblemen führen würde, insbesondere bei Admins vom Typ "Basteln".

Ich möchte, dass das Build-System mehrere Builds erstellt, einen für jeden Client, in denen Änderungen in einer speziellen Version des betreffenden einzelnen physischen Moduls enthalten sind. Ich habe also einige Fragen:

Würden Sie empfehlen, das Build-System mehrere Builds erstellen zu lassen? Wie soll ich die verschiedenen Anpassungen in der Quellcodeverwaltung speichern, insbesondere SVN?


4
Arbeitet #ifdeffür Sie?
Ohho

6
Präprozessor-Direktiven können schnell sehr kompliziert werden und das Lesen und Debuggen des Codes erschweren.
Falcon

1
Sie sollten Details zur tatsächlichen Plattform und Art der Anwendung (Desktop / Web) angeben, um bessere Antworten zu erhalten. Das Anpassen in einer Desktop-C ++ - App unterscheidet sich grundlegend von einer PHP-Web-App.
GroßmeisterB

2
@ Falcon: Da Sie 2011 eine Antwort ausgewählt haben, über die ich viele Zweifel habe, können Sie uns mitteilen, ob Sie Erfahrung mit der Verwendung von SVN in der vorgeschlagenen Weise zwischenzeitlich haben? Sind meine Einwände unbegründet?
Doc Brown

2
@ Doc Brown: Das Verzweigen und Zusammenführen war mühsam und kompliziert. Damals verwendeten wir ein Pluginsystem mit kundenspezifischen Plugins oder "Patches", die das Verhalten oder die Konfigurationen veränderten. Der Aufwand ist gering, er kann jedoch mit Dependendy Injection verwaltet werden.
Falcon

Antworten:


7

Was Sie brauchen , ist Feature Verzweigung -ähnliche Code Organisation. In Ihrem speziellen Szenario sollte dies als clientspezifische Trunk-Verzweigung bezeichnet werden, da Sie wahrscheinlich auch die Feature-Verzweigung verwenden, während Sie neue Inhalte entwickeln (oder Fehler beheben).

http://svnbook.red-bean.com/de/1.5/svn.branchmerge.commonpatterns.html

Die Idee ist, einen Code-Trunk mit den Zweigen der neuen Features zusammenzuführen. Ich meine alle nicht-kundenspezifischen Funktionen.

Dann haben Sie auch Ihre kundenspezifischen Filialen, in denen Sie auch die Filialen der gleichen Features nach Bedarf zusammenführen (Kirschernte, wenn Sie so wollen).

Diese Client-Zweige ähneln Feature-Zweigen, obwohl sie nicht vorübergehend oder kurzlebig sind. Sie bleiben über einen längeren Zeitraum erhalten und werden überwiegend nur zusammengeführt. Es sollte so wenig wie möglich kundenspezifische Zweigentwicklungen geben.

Kundenspezifische Zweige sind parallele Zweige zum Trunk und sind so lange aktiv wie der Trunk selbst und werden nirgendwo als Ganzes zusammengeführt.

            feature1
            ———————————.
                        \
trunk                    \
================================================== · · ·
      \ client1            \
       `========================================== · · ·
        \ client2            \
         `======================================== · · ·
              \ client2-specific feature   /
               `——————————————————————————´

7
+1 für Feature-Verzweigung. Sie können auch für jeden Mandanten eine Verzweigung verwenden. Ich möchte nur eines vorschlagen: Verwenden Sie ein verteiltes VCS (hg, git, bzr) anstelle von SVN / CVS, um dies zu erreichen;)
Herberth Amaral

42
-1. Dafür gibt es keine "Feature-Zweige". es widerspricht ihrer Definition als "temporäre Zweige, die zusammengeführt werden, wenn die Entwicklung eines Features abgeschlossen ist".
P Shved

13
Sie müssen immer noch alle diese Deltas in der Quellcodeverwaltung beibehalten und in einer Reihe halten - für immer. Kurzfristig könnte dies funktionieren. Langfristig könnte es schrecklich sein.
quick_now

4
-1, dies ist kein Namensproblem - die Verwendung verschiedener Zweige für verschiedene Clients ist nur eine andere Form der Codeduplizierung. Dies ist meiner Meinung nach ein Anti-Pattern, ein Beispiel, wie man es nicht macht.
Doc Brown

6
Robert, ich glaube, ich habe schon vor der Bearbeitung ziemlich gut verstanden, was Sie vorgeschlagen haben, aber ich denke, das ist ein schrecklicher Ansatz. Angenommen, Sie haben N Clients, und wann immer Sie dem Trunk ein neues Kernfeature hinzufügen, scheint es, dass der SCM die Weitergabe des neuen Features an die N Zweige vereinfacht. Die Verwendung von Verzweigungen auf diese Weise macht es jedoch zu einfach, eine klare Trennung der kundenspezifischen Änderungen zu vermeiden. Infolgedessen haben Sie jetzt N Chancen, einen Zusammenführungskonflikt für jede Änderung im Trunk zu erhalten. Außerdem müssen Sie jetzt N Integrationstests anstelle von einem ausführen.
Doc Brown

37

Tun Sie dies nicht mit SCM-Zweigen. Machen Sie den allgemeinen Code zu einem separaten Projekt, das ein Bibliothek- oder Projektskelett-Artefakt erzeugt. Jedes Kundenprojekt ist ein separates Projekt, das dann von dem gemeinsamen als Abhängigkeit abhängt.

Dies ist am einfachsten, wenn Ihre allgemeine Basisanwendung ein Framework für die Abhängigkeitsinjektion wie Spring verwendet, sodass Sie problemlos verschiedene Ersatzvarianten von Objekten in jedes Kundenprojekt einfügen können, wenn benutzerdefinierte Features erforderlich sind. Auch wenn Sie noch kein DI-Framework haben, ist das Hinzufügen eines Frameworks und das Ausführen auf diese Weise möglicherweise die schmerzloseste Wahl.


Ein weiterer Ansatz, um die Dinge einfach zu halten, ist die Verwendung der Vererbung (und eines einzelnen Projekts) anstelle der Abhängigkeitsinjektion, indem sowohl der allgemeine Code als auch die Anpassungen im selben Projekt beibehalten werden (entweder mit Vererbung, Teilklassen oder Ereignissen). Mit diesem Design würden Client-Zweige gut funktionieren (wie in der ausgewählten Antwort beschrieben), und man könnte die Client-Zweige immer aktualisieren, indem man den allgemeinen Code aktualisiert.
Drizin

Wenn ich etwas nicht verpasse, schlagen Sie vor, dass Sie bei 100 Kunden (100 x NumberOfChangedProjects ) erstellen und DI verwenden, um diese zu verwalten? Wenn dem so ist, würde ich mich definitiv von dieser Art von Lösung fernhalten, da die Wartbarkeit schrecklich wäre.
Sotn

12

Speichern Sie Software für alle Kunden in einer einzigen Filiale. Es ist nicht erforderlich, die für verschiedene Kunden vorgenommenen Änderungen zu ändern. Im Endeffekt möchten Sie, dass Ihre Software für alle Clients von bester Qualität ist, und die Fehlerbehebung in Ihrer Kerninfrastruktur sollte alle Benutzer betreffen, ohne unnötigen Aufwand beim Zusammenführen zu verursachen, der auch weitere Fehler verursachen kann.

Modularisieren Sie den allgemeinen Code und implementieren Sie den Code, der sich in verschiedenen Dateien unterscheidet, oder schützen Sie ihn mit verschiedenen Definitionen. Stellen Sie sicher, dass Ihr Build-System für jeden Client spezifische Ziele hat, wobei jedes Ziel eine Version kompiliert, die nur den Code für einen Client enthält. Wenn Ihr Code beispielsweise C ist, möchten Sie möglicherweise Funktionen für verschiedene Clients mit einem " #ifdef" oder einem anderen in Ihrer Sprache verfügbaren Mechanismus für das Build-Konfigurationsmanagement schützen und eine Reihe von Definitionen vorbereiten, die der Anzahl der Funktionen entsprechen, für die ein Client bezahlt hat.

Wenn Sie ifdefs nicht mögen , verwenden Sie "Schnittstellen und Implementierungen", "Funktoren", "Objektdateien" oder welche Tools Ihre Sprache zum Speichern verschiedener Dinge an einem Ort bereitstellt.

Wenn Sie Quellen an Ihre Kunden verteilen, sollten Sie dafür sorgen, dass Ihre Erstellungsskripten spezielle "Quellenverteilungsziele" haben. Sobald Sie ein solches Ziel aufrufen, erstellt es eine spezielle Version der Quellen Ihrer Software, kopiert sie in einen separaten Ordner, damit Sie sie versenden können, und kompiliert sie nicht.


8

Wie so viele gesagt haben: Berechnen Sie Ihren Code richtig und passen Sie ihn basierend auf dem allgemeinen Code an - dies wird die Wartbarkeit erheblich verbessern. Unabhängig davon, ob Sie eine objektorientierte Sprache / ein objektorientiertes System verwenden oder nicht, ist dies möglich (obwohl es in C etwas schwieriger ist als etwas, das wirklich objektorientiert ist). Dies ist genau die Art von Problem, bei deren Lösung Vererbung und Kapselung helfen!


5

Sehr vorsichtig

Feature Branching ist eine Option, aber ich finde es etwas schwer. Darüber hinaus werden tiefgreifende Änderungen erleichtert, die dazu führen können, dass Ihre Anwendung nicht mehr funktioniert, wenn Sie nicht die Kontrolle behalten. Im Idealfall möchten Sie die Anpassungen so weit wie möglich vorantreiben, um Ihre Kerncodebasis so allgemein und allgemein wie möglich zu halten.

Hier ist, wie ich es tun würde, obwohl ich nicht weiß, ob es auf Ihre Codebasis ohne große Modifikationen und Umformulierungen anwendbar ist. Ich hatte ein ähnliches Projekt, bei dem die Grundfunktionalität identisch war, aber jeder Kunde einen sehr spezifischen Satz von Funktionen benötigte. Ich habe eine Reihe von Modulen und Containern erstellt, die ich dann durch Konfiguration (à la IoC) zusammenstelle.

dann habe ich für jeden Kunden ein Projekt erstellt, das im Wesentlichen die Konfigurationen und das Build-Skript enthält, um eine vollständig konfigurierte Installation für seine Site zu erstellen. Gelegentlich platziere ich dort auch einige für diesen Kunden maßgeschneiderte Komponenten. Dies ist jedoch selten und wenn immer möglich, versuche ich, es in einer allgemeineren Form zu erstellen und nach unten zu verschieben, damit andere Projekte sie verwenden können.

Das Endergebnis ist, dass ich die Anpassungsstufe habe, die ich benötige. Ich habe angepasste Installationsskripte, so dass ich beim Aufrufen des Kundenstandorts nicht die ganze Zeit über das System pfeife und als zusätzlichen SEHR signifikanten Bonus bekomme Regressionstests erstellen zu können, die direkt mit dem Build verknüpft sind. Auf diese Weise kann ich immer dann, wenn ich einen kundenspezifischen Fehler erhalte, einen Test schreiben, der das System während der Bereitstellung bestätigt und somit TDD auch auf dieser Ebene durchführen kann.

Also in Kürze:

  1. Stark modularisiertes System mit flacher Projektstruktur.
  2. Erstellen Sie ein Projekt für jedes Konfigurationsprofil (Kunde, obwohl mehrere ein Profil gemeinsam nutzen können)
  3. Stellen Sie die erforderlichen Funktionssätze als ein anderes Produkt zusammen und behandeln Sie sie als solches.

Bei ordnungsgemäßer Ausführung sollte Ihre Produktassembly bis auf einige Konfigurationsdateien alle enthalten.

Nachdem ich dies für eine Weile verwendet hatte, erstellte ich Metapakete, die die am häufigsten verwendeten oder wesentlichen Systeme als eine Kerneinheit zusammenfassten, und verwendete dieses Metapaket für Kundenassemblys. Nach ein paar Jahren hatte ich einen großen Werkzeugkasten, den ich sehr schnell zusammenbauen konnte, um Kundenlösungen zu erstellen. Ich bin gerade dabei, Spring Roo zu untersuchen und zu sehen, ob ich die Idee nicht ein bisschen weiter vorantreiben kann, in der Hoffnung, dass ich eines Tages mit dem Kunden in unserem ersten Interview einen ersten Entwurf des Systems erstellen kann Entwicklung ;-).

Hoffe das hat geholfen


3

Die Änderungen sind wahrscheinlich zu klein, um das Aufteilen des Moduls in ein bestimmtes (physisches) Modul für jeden Client zu rechtfertigen. Ich fürchte, Probleme mit dem Build, ein Chaos bei der Verknüpfung.

IMO, sie können nicht zu klein sein. Wenn möglich, würde ich den kundenspezifischen Code mithilfe des Strategiemusters so ziemlich überall herausrechnen. Dies reduziert die Menge des Codes, der verzweigt werden muss, und verringert die Zusammenführung, die erforderlich ist, um den allgemeinen Code für alle Clients synchron zu halten. Es vereinfacht auch das Testen ... Sie können den allgemeinen Code mit Standardstrategien testen und die clientspezifischen Klassen separat testen.

Wenn Ihre Module so codiert sind, dass für die Verwendung von Richtlinie X1 in Modul A die Verwendung von Richtlinie X2 in Modul B erforderlich ist, sollten Sie über ein Refactoring nachdenken, damit X1 und X2 zu einer einzigen Richtlinienklasse kombiniert werden können.


1

Sie können Ihr SCM verwenden, um Zweigniederlassungen zu verwalten. Halten Sie den Master-Zweig von benutzerdefiniertem Clientcode sauber. Hauptentwicklung auf diesem Zweig tun. Pflegen Sie für jede angepasste Version der Anwendung separate Zweige. Jedes gute SCM-Tool eignet sich hervorragend zum Zusammenführen von Zweigen (Git fällt mir ein). Aktualisierungen im Hauptzweig sollten in den benutzerdefinierten Zweigen zusammengeführt werden, aber der kundenspezifische Code kann im eigenen Zweig bleiben.


Versuchen Sie jedoch, das System möglichst modular und konfigurierbar zu gestalten. Der Nachteil dieser benutzerdefinierten Zweige kann sein, dass sie sich zu weit vom Core / Master entfernen.


1

Wenn Sie in C schreiben, ist dies ein ziemlich hässlicher Weg.

  • Common Code (zB Einheit "frangulator.c")

  • Kundenspezifischer Code, kleine Teile, die nur für jeden Kunden verwendet werden.

  • Verwenden Sie im Code der Haupteinheit #ifdef und #include, um so etwas wie zu tun

#ifdef CLIENT = CLIENTA
#include "frangulator_client_a.c"
#endif

Verwenden Sie dies immer wieder als Muster in allen Codeeinheiten, die eine kundenspezifische Anpassung erfordern.

Dies ist SEHR hässlich und führt zu einigen anderen Problemen, ist aber auch einfach, und Sie können clientspezifische Dateien relativ einfach miteinander vergleichen.

Dies bedeutet auch, dass alle kundenspezifischen Teile jederzeit deutlich sichtbar sind (jeweils in einer eigenen Datei), und dass eine eindeutige Beziehung zwischen der Hauptcodedatei und dem kundenspezifischen Teil der Datei besteht.

Wenn Sie wirklich schlau sind, können Sie Makefiles einrichten, um die richtige Client-Definition zu erstellen.

clienta machen

erstellt für client_a und "make clientb" erstellt für client_b und so weiter.

(und "make" ohne angegebenes Ziel kann eine Warnung oder eine Verwendungsbeschreibung ausgeben.)

Ich habe schon einmal eine ähnliche Idee verwendet, die Einrichtung dauert eine Weile, kann aber sehr effektiv sein. In meinem Fall hat ein Quellbaum ungefähr 120 verschiedene Produkte erstellt.


0

In git würde ich es so machen, dass ich einen Hauptzweig mit dem gesamten gemeinsamen Code und Zweigen für jeden Client habe. Wenn eine Änderung am Kerncode vorgenommen wird, setzen Sie einfach alle clientspezifischen Zweige auf dem Master neu auf, sodass Sie eine Reihe von sich bewegenden Patches für die Clients haben, die auf der aktuellen Baseline angewendet werden.

Wann immer Sie eine Änderung für einen Client vornehmen und einen Fehler bemerken, der in anderen Zweigen enthalten sein sollte, können Sie diesen Fehler entweder in den Master-Zweig oder in den anderen Zweigen auswählen, die den Fix benötigen (obwohl verschiedene Client-Zweige Code gemeinsam nutzen) Sie sollten wahrscheinlich beide einen gemeinsamen Zweig vom Master abzweigen lassen.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.