Ich arbeite an einem großen Softwareprojekt, das für verschiedene Kunden auf der ganzen Welt maßgeschneidert ist. Dies bedeutet, dass wir vielleicht 80% Code haben, der zwischen den verschiedenen Kunden gemeinsam ist, aber auch viel Code, der sich von einem Kunden zum anderen ändern muss. In der Vergangenheit haben wir unsere Entwicklung in separaten Repositorys (SVN) durchgeführt und zu Beginn eines neuen Projekts (wir haben nur wenige, aber große Kunden) ein weiteres Repository erstellt, das auf dem früheren Projekt basiert, das die beste Codebasis für unsere Anforderungen bietet. Dies hat in der Vergangenheit funktioniert, aber wir sind auf mehrere Probleme gestoßen:
- Fehler, die in einem Repository behoben wurden, werden in anderen Repositorys nicht behoben. Dies mag ein organisatorisches Problem sein, aber ich finde es schwierig, einen Fehler in 5 verschiedenen Repositorys zu beheben und zu beheben, wenn ich bedenke, dass sich das Team, das dieses Repository verwaltet, möglicherweise in einem anderen Teil der Welt befindet und wir keine Testumgebung haben Weder kennen sie ihren Zeitplan noch wissen sie, welche Anforderungen sie haben (ein "Bug" in einem Land könnte ein "Feature" in einem anderen sein).
- Für ein Projekt vorgenommene Funktionen und Verbesserungen, die auch für ein anderes Projekt nützlich sein können, gehen verloren oder verursachen bei der Verwendung in einem anderen Projekt häufig große Kopfschmerzen beim Zusammenführen dieser Funktionen von einer Codebasis zur anderen (da beide Zweige möglicherweise ein Jahr lang unabhängig voneinander entwickelt wurden) ).
- Refactorings und Code-Verbesserungen, die in einem Entwicklungszweig vorgenommen wurden, gehen entweder verloren oder verursachen mehr Schaden als Nutzen, wenn Sie alle diese Änderungen zwischen den Zweigen zusammenführen müssen.
Wir diskutieren jetzt, wie diese Probleme gelöst werden können, und haben bisher die folgenden Ideen zur Lösung dieser Probleme entwickelt:
Halten Sie die Entwicklung in getrennten Zweigen aufrecht, aber organisieren Sie sie besser, indem Sie ein zentrales Repository haben, in dem allgemeine Fehlerkorrekturen zusammengeführt werden, und alle Projekte regelmäßig (z. B. täglich) Änderungen aus diesem zentralen Repository in ihre eigenen übernehmen. Dies erfordert enorme Disziplin und viel Aufwand beim Zusammenführen der Filialen. Ich bin also nicht davon überzeugt, dass es funktionieren wird, und wir können diese Disziplin beibehalten, insbesondere wenn Zeitdruck aufkommt.
Verzichten Sie auf die separaten Entwicklungszweige und verfügen Sie über ein zentrales Code-Repository, in dem sich unser gesamter Code befindet, und passen Sie unsere Einstellungen an, indem Sie über steckbare Module und Konfigurationsoptionen verfügen. Wir verwenden bereits Abhängigkeitsinjektionscontainer, um Abhängigkeiten in unserem Code aufzulösen, und wir folgen dem MVVM-Muster in den meisten unserer Codes, um die Geschäftslogik sauber von unserer Benutzeroberfläche zu trennen.
Der zweite Ansatz scheint eleganter zu sein, aber wir haben viele ungelöste Probleme in diesem Ansatz. Zum Beispiel: Wie gehen Sie mit Änderungen / Ergänzungen in Ihrem Modell / Ihrer Datenbank um? Wir verwenden .NET mit Entity Framework, um stark typisierte Entitäten zu haben. Ich verstehe nicht, wie wir mit Eigenschaften umgehen können, die für einen Kunden erforderlich, für einen anderen Kunden jedoch nutzlos sind, ohne unser Datenmodell zu überfrachten. Wir denken darüber nach, dies in der Datenbank zu lösen, indem wir Satellitentabellen verwenden (mit separaten Tabellen, in denen unsere zusätzlichen Spalten für eine bestimmte Entität mit einer 1: 1-Zuordnung zur ursprünglichen Entität leben), aber dies ist nur die Datenbank. Wie gehst du damit im Code um? Unser Datenmodell befindet sich in einer zentralen Bibliothek, die wir mit diesem Ansatz nicht für jeden Kunden erweitern könnten.
Ich bin sicher, dass wir nicht das einzige Team sind, das mit diesem Problem zu kämpfen hat, und ich bin schockiert, so wenig Material zu diesem Thema zu finden.
Meine Fragen lauten also wie folgt:
- Welche Erfahrungen haben Sie mit hochgradig kundenspezifischer Software gemacht, welchen Ansatz haben Sie gewählt und wie hat das bei Ihnen funktioniert?
- Welchen Ansatz empfehlen Sie und warum? Gibt es einen besseren Ansatz?
- Gibt es gute Bücher oder Artikel zu dem Thema, die Sie empfehlen können?
- Haben Sie spezielle Empfehlungen für unsere technische Umgebung (.NET, Entity Framework, WPF, DI)?
Bearbeiten:
Danke für all die Vorschläge. Die meisten Ideen stimmen mit denen überein, die wir bereits in unserem Team hatten, aber es ist sehr hilfreich, Ihre Erfahrungen mit ihnen zu sehen und Tipps zu finden, um sie besser umzusetzen.
Ich bin mir immer noch nicht sicher, welchen Weg wir gehen werden und ich treffe die Entscheidung nicht (allein), aber ich werde dies in meinem Team weitergeben und ich bin sicher, dass es hilfreich sein wird.
Momentan scheint der Tenor ein einziges Repository mit verschiedenen kundenspezifischen Modulen zu sein. Ich bin mir nicht sicher, ob unsere Architektur diesen Anforderungen gerecht wird oder wie viel wir investieren müssen, um sie fit zu machen. Einige Dinge könnten für eine Weile in separaten Repositories gespeichert sein, aber ich denke, es ist die einzige langfristige Lösung, die funktionieren wird.
Nochmals vielen Dank für alle Antworten!
Antworten:
Es hört sich so an, als ob das grundlegende Problem nicht nur die Wartung des Code-Repositorys ist, sondern das Fehlen einer geeigneten Architektur .
Ein Framework oder eine Standardbibliothek umfasst das erstere, während das letztere als Add-Ons implementiert wird (Plugins, Unterklassen, DI, was auch immer für die Codestruktur sinnvoll ist).
Ein Quellcodeverwaltungssystem, das Zweigniederlassungen und verteilte Entwicklung verwaltet, würde wahrscheinlich ebenfalls helfen. Ich bin ein Fan von Mercurial, andere bevorzugen Git. Das Framework wäre der Hauptzweig, jedes kundenspezifische System wäre beispielsweise ein Unterzweig.
Die spezifischen Technologien zur Implementierung des Systems (.NET, WPF, was auch immer) sind weitgehend unwichtig.
Dies zu erreichen ist nicht einfach , aber entscheidend für die langfristige Rentabilität. Und je länger Sie warten, desto größer ist natürlich die technische Verschuldung.
Möglicherweise ist das Buch Softwarearchitektur: Organisationsprinzipien und Muster hilfreich.
Viel Glück!
quelle
Ein Unternehmen, für das ich gearbeitet habe, hatte das gleiche Problem, und der Ansatz, um das Problem anzugehen, war folgender: Es wurde ein gemeinsamer Rahmen für alle neuen Projekte geschaffen; Dies beinhaltet alles, was in jedem Projekt gleich sein muss. ZB Formularerstellungstools, Export nach Excel, Protokollierung. Es wurde versucht, sicherzustellen, dass dieses gemeinsame Framework nur verbessert wird (wenn ein neues Projekt neue Funktionen benötigt), aber nie geändert wird.
Basierend auf diesem Framework wurde kundenspezifischer Code in separaten Repositories verwaltet. Wenn es nützlich oder notwendig ist, werden Fehlerbehebungen und Verbesserungen zwischen Projekten kopiert (mit allen in der Frage beschriebenen Vorbehalten). Global nützliche Verbesserungen fließen jedoch in den gemeinsamen Rahmen ein.
Alles in einer gemeinsamen Codebasis für alle Kunden zu haben, hat einige Vorteile. Andererseits wird das Lesen des Codes schwierig, wenn es unzählige
if
Möglichkeiten gibt , das Programm für jeden Kunden anders zu verhalten.EDIT: Eine Anekdote, um dies verständlicher zu machen:
quelle
Eines der Projekte, an denen ich gearbeitet habe, unterstützte mehrere Plattformen (mehr als 5) für eine große Anzahl von Produktversionen. Viele der Herausforderungen, die Sie beschreiben, waren Dinge, mit denen wir konfrontiert waren, wenn auch auf etwas andere Weise. Wir hatten eine proprietäre Datenbank, daher hatten wir in dieser Arena nicht die gleichen Probleme.
Unsere Struktur war ähnlich wie Ihre, aber wir hatten ein einziges Repository für unseren Code. Plattformspezifischer Code wurde in eigene Projektordner innerhalb des Codebaums verschoben. Innerhalb des Baums lebte allgemeiner Code basierend auf der Ebene, zu der er gehörte.
Wir hatten eine bedingte Kompilierung, basierend auf der Plattform, die gebaut wurde. Das beizubehalten, war ein Problem, aber es musste nur getan werden, wenn neue Module auf der plattformspezifischen Ebene hinzugefügt wurden.
Wenn wir den gesamten Code in einem einzigen Repository haben, war es für uns einfach, Fehlerbehebungen auf mehreren Plattformen und Releases gleichzeitig durchzuführen. Wir hatten eine automatisierte Build-Umgebung für alle Plattformen, die als Backstop für den Fall diente, dass neuer Code eine vermeintlich nicht verwandte Plattform beschädigte.
Wir haben versucht, davon abzuraten, aber es gab Fälle, in denen eine Plattform eine Korrektur aufgrund eines plattformspezifischen Fehlers benötigte, der sich im sonst üblichen Code befand. Wenn wir die Kompilierung unter bestimmten Bedingungen überschreiben könnten, ohne das Modul flüchtig aussehen zu lassen, würden wir dies zuerst tun. Wenn nicht, würden wir das Modul aus dem gemeinsamen Gebiet entfernen und es plattformspezifisch verschieben.
Für die Datenbank hatten wir einige Tabellen mit plattformspezifischen Spalten / Änderungen. Wir würden sicherstellen, dass jede Plattformversion der Tabelle eine Basisfunktionalitätsebene erfüllt, sodass allgemeiner Code darauf verweisen kann, ohne sich um Plattformabhängigkeiten kümmern zu müssen. Plattformspezifische Abfragen / Manipulationen wurden in die Plattformprojektebenen verschoben.
So beantworten Sie Ihre Fragen:
quelle
Ich habe viele Jahre an einem Antrag der Rentenverwaltung mit ähnlichen Problemen gearbeitet. Die Vorsorgepläne sind von Unternehmen zu Unternehmen sehr unterschiedlich und erfordern hochspezialisiertes Wissen zur Implementierung von Berechnungslogik und Berichten sowie ein sehr unterschiedliches Datendesign. Ich kann nur einen Teil der Architektur kurz beschreiben, aber vielleicht wird es genug von der Idee geben.
Wir hatten 2 separate Teams: ein Kernentwicklungsteam, das für den Kernsystem - Code verantwortlich war (was Ihr 80% gemeinsamer Code oben sein würde), und eine Umsetzung Team, das in den Rentensystemen Domain Know - how hatte, und waren verantwortlich für das Lernen Client Anforderungen und Codierungsskripte und -berichte für den Kunden.
Wir hatten alle unsere Tabellen durch XML definiert (dies vor dem Zeitpunkt, als Entity-Frameworks zeitgetestet und allgemein waren). Das Implementierungsteam würde alle Tabellen in XML entwerfen und die Kernanwendung könnte aufgefordert werden, alle Tabellen in XML zu generieren. Zu jedem Client gehörten auch VB-Skriptdateien, Crystal Reports, Word-Dokumente usw. (Es wurde auch ein Vererbungsmodell in die XML integriert, um die Wiederverwendung anderer Implementierungen zu ermöglichen.)
Die Kernanwendung (eine Anwendung für alle Clients) zwischenspeichert alle clientspezifischen Daten, wenn eine Anforderung für diesen Client eingeht, und generiert ein gemeinsames Datenobjekt (eine Art Remote-ADO-Datensatz), das serialisiert und übergeben werden kann um.
Dieses Datenmodell ist weniger clever als Entity- / Domain-Objekte, aber es ist hochflexibel, universell und kann von einem Satz Kerncode verarbeitet werden. Vielleicht könnten Sie in Ihrem Fall Ihre Basisentitätsobjekte nur mit den gemeinsamen Feldern definieren und ein zusätzliches Dictionary für benutzerdefinierte Felder haben (fügen Sie Ihrem Entitätsobjekt eine Art von Datendeskriptoren hinzu, damit es Metadaten für die benutzerdefinierten Felder enthält. )
Wir hatten separate Quell-Repositorys für den Kernsystemcode und den Implementierungscode.
Unser Kernsystem verfügte über sehr wenig Geschäftslogik, abgesehen von einigen sehr gängigen Berechnungsmodulen. Das Kernsystem fungierte als: Bildschirmgenerator, Skriptläufer, Berichtsgenerator, Datenzugriff und Transportschicht.
Die Segmentierung der Kernlogik und der benutzerdefinierten Logik ist eine große Herausforderung. Wir waren jedoch immer der Meinung, dass es besser ist, ein Kernsystem mit mehreren Clients zu betreiben, als mehrere Kopien des Systems, die für jeden Client ausgeführt werden.
quelle
Ich habe an einem kleineren System (20 Kloc) gearbeitet und festgestellt, dass DI und Konfiguration großartige Möglichkeiten sind, um Unterschiede zwischen Clients zu verwalten, aber nicht genug, um das System nicht zu forken. Die Datenbank wird in einen anwendungsspezifischen Teil mit festem Schema und einen mandantenabhängigen Teil aufgeteilt, der durch ein benutzerdefiniertes XML-Konfigurationsdokument definiert wird.
Wir haben eine einzige Filiale in mercurial beibehalten, die so konfiguriert ist, als ob sie lieferbar wäre, aber für einen fiktiven Kunden als Marke und konfiguriert ist. Fehlerbehebungen werden in diesem Projekt behandelt, und die Neuentwicklung der Kernfunktionalität erfolgt nur dort. Releases an tatsächliche Kunden sind Verzweigungen davon, die in ihren eigenen Repositorys gespeichert sind. Wir verfolgen große Änderungen am Code durch manuell geschriebene Versionsnummern und verfolgen Fehlerbehebungen mithilfe von Festschreibungsnummern.
quelle
Ich fürchte, ich habe keine direkte Erfahrung mit dem von Ihnen beschriebenen Problem, aber ich habe einige Anmerkungen.
Die zweite Option, den Code in einem zentralen Repository zusammenzuführen (so weit wie möglich) und für Anpassungen zu konzipieren (so weit wie möglich), ist mit ziemlicher Sicherheit der langfristige Weg.
Das Problem ist, wie Sie vorhaben, dorthin zu gelangen, und wie lange es dauern wird.
In dieser Situation ist es wahrscheinlich in Ordnung, (vorübergehend) mehr als eine Kopie der Anwendung gleichzeitig im Repository zu haben.
Auf diese Weise können Sie schrittweise zu einer Architektur wechseln, die die Anpassung direkt unterstützt, ohne dies auf einen Schlag tun zu müssen.
quelle
Ich bin sicher, dass eines dieser Probleme nach dem anderen gelöst werden kann. Wenn Sie nicht weiterkommen, fragen Sie hier oder auf SO nach dem spezifischen Problem.
Wie bereits erwähnt, ist eine zentrale Codebasis / ein Repository die Option, die Sie bevorzugen sollten. Ich versuche Ihre Beispielfrage zu beantworten.
Es gibt einige Möglichkeiten, die ich in realen Systemen gesehen habe. Welche Sie wählen müssen, hängt von Ihrer Situation ab:
Führen Sie die Tabellen "CustomAttributes" (Beschreiben von Namen und Typen) und "CustomAttributeValues" (für die Werte, die beispielsweise als Zeichenfolgendarstellung gespeichert sind, auch wenn es sich um Zahlen handelt) ein. Auf diese Weise können solche Attribute zur Installations- oder Laufzeit mit individuellen Werten für jeden Kunden hinzugefügt werden. Bestehen Sie nicht darauf, dass jedes benutzerdefinierte Attribut "sichtbar" in Ihrem Datenmodell modelliert wird.
Jetzt sollte klar sein, wie man das im Code verwendet: Es muss nur allgemeiner Code für den Zugriff auf diese Tabellen und individueller Code (möglicherweise in einer separaten Plug-in-DLL, die Sie selbst bestimmen) für die korrekte Interpretation dieser Attribute vorhanden sein
Und für bestimmten Code: Sie können auch versuchen, eine Skriptsprache in Ihr Produkt einzuführen, insbesondere um kundenspezifische Skripts hinzuzufügen. Auf diese Weise stellen Sie nicht nur eine klare Linie zwischen Ihrem Code und dem kundenspezifischen Code her, sondern können Ihren Kunden auch die Möglichkeit geben, das System zu einem gewissen Grad selbst anzupassen.
quelle
Ich habe nur eine solche Anwendung erstellt. Ich würde sagen, dass 90% der verkauften Einheiten unverändert verkauft wurden, ohne Änderungen. Jeder Kunde hatte seinen eigenen angepassten Skin und wir haben das System innerhalb dieses Skins bedient. Als ein Mod dazu kam, der die Kernabschnitte betraf, haben wir versucht, IF-Verzweigungen zu verwenden . Als Mod Nr. 2 für denselben Abschnitt erschien, wechselten wir zur CASE-Logik, die eine zukünftige Erweiterung ermöglichte. Dies schien die meisten kleineren Anfragen zu bewältigen.
Alle weiteren geringfügigen benutzerdefinierten Anforderungen wurden von der Implementierung der Case-Logik verarbeitet.
Wenn die Mods zwei radikale waren, haben wir einen Klon (separates Include) erstellt und ein CASE um ihn gewickelt, um die verschiedenen Module einzuschließen.
Fehlerkorrekturen und Änderungen am Core betrafen alle Benutzer. Wir haben in der Entwicklung gründlich getestet, bevor wir in die Produktion gingen. Wir haben immer E-Mail-Benachrichtigungen verschickt, die mit Änderungen einhergingen, und NIE, NIE, NIE Produktionsänderungen freitags veröffentlicht ... NIE.
Unsere Umgebung war Classic ASP und SQL Server. Wir waren KEIN Spaghetti Code Shop ... Alles war modular aufgebaut mit Includes, Subroutinen und Funktionen.
quelle
Wenn ich gebeten werde, die Entwicklung von B zu starten, das 80% der Funktionen mit A teilt, werde ich entweder:
Sie haben 1 gewählt und es scheint nicht gut zu Ihrer Situation zu passen. Ihre Mission ist es, vorherzusagen, welche von 2 und 3 besser passt.
quelle