Wie organisieren Sie hochgradig maßgeschneiderte Software?

28

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:

  1. 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.

  2. 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:

  1. Welche Erfahrungen haben Sie mit hochgradig kundenspezifischer Software gemacht, welchen Ansatz haben Sie gewählt und wie hat das bei Ihnen funktioniert?
  2. Welchen Ansatz empfehlen Sie und warum? Gibt es einen besseren Ansatz?
  3. Gibt es gute Bücher oder Artikel zu dem Thema, die Sie empfehlen können?
  4. 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!

aKzenT
quelle
Betrachten Sie die Behandlung von Datenbanktabellen als Code.
Wir tun dies bereits in dem Sinne, dass wir unsere Datenbankskripte in unserem Subversion-Repository haben, aber es löst die oben genannten Probleme nicht wirklich. Wir möchten keine Key-Value-Stiltabellen in unserem Datenbankmodell haben, da diese mit vielen Problemen verbunden sind. Wie können Sie Ihr Modell für einzelne Kunden erweitern und gleichzeitig ein gemeinsam genutztes Code-Repository für alle Kunden verwalten?
aKzenT

Antworten:

10

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 .

  1. Was ist der Kern / das Wesen des Systems, das immer von allen Systemen geteilt wird?
  2. Welche Erweiterungen / Abweichungen benötigt jeder Kunde?

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!

Steven A. Lowe
quelle
3
Jep. Architektur ist oft eher eine psychologische als eine technische Sache. Wenn Sie sich ganz auf das Produkt konzentrieren, erhalten Sie eine Aufschlüsselung der Arbeit, bei der die Komponenten nur für dieses Produkt nützlich sind. Wenn Sie sich andererseits darauf konzentrieren, eine Reihe von Bibliotheken zu erstellen, die allgemeiner nützlich sein werden, erstellen Sie eine Reihe von Funktionen, die in einer größeren Bandbreite von Situationen bereitgestellt werden können. Der Schlüssel ist natürlich, das richtige Gleichgewicht zwischen diesen beiden Extremen für Ihre spezielle Situation zu finden.
William Payne
Ich denke, dass es einen großen Anteil zwischen vielen unserer Filialen gibt (> 90%), aber die letzten 10% befinden sich immer an verschiedenen Orten, so dass es nur sehr wenige Komponenten gibt, bei denen ich mir einige kundenspezifische Änderungen nicht vorstellen kann, mit Ausnahme einiger Utility-Bibliotheken, die Enthält keine Geschäftslogik.
aKzenT
@aKzenT: hmmm ... stell dir nicht vor, messe stattdessen. Überprüfen Sie den Code und sehen Sie sich alle Stellen an, an denen Anpassungen vorgenommen wurden, erstellen Sie eine Liste der geänderten Komponenten, notieren Sie, wie oft jede Komponente geändert wurde, und überlegen Sie, welche Arten und Muster von Änderungen tatsächlich vorgenommen wurden. Sind sie kosmetisch oder algorithmisch? Fügen sie Basisfunktionen hinzu oder ändern sie sie? Was ist der Grund für jede Art von Änderung? Auch dies ist harte Arbeit , und Sie mögen möglicherweise die Auswirkungen dessen, was Sie entdecken, nicht.
Steven A. Lowe
1
Ich habe dies als Antwort akzeptiert, weil wir diese Entscheidung nicht treffen können, bevor wir mehr über unsere Architektur nachgedacht haben, Teile identifizieren, die gemeinsam sind oder die gemeinsam sein SOLLTEN, und dann sehen, ob wir mit einem einzigen Repository leben können oder ob Gabeln notwendig ist (z zumindest im Moment). Diese Antwort spiegelt diese beste IMO wider. Vielen Dank für all die anderen Beiträge!
aKzenT
11

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 ifMöglichkeiten gibt , das Programm für jeden Kunden anders zu verhalten.

EDIT: Eine Anekdote, um dies verständlicher zu machen:

Die Domäne dieses Unternehmens ist die Lagerverwaltung, und eine Aufgabe eines Lagerverwaltungssystems besteht darin, einen freien Lagerort für eingehende Waren zu finden. Klingt einfach, aber in der Praxis müssen viele Einschränkungen und Strategien beachtet werden.

Zu einem bestimmten Zeitpunkt bat das Management einen Programmierer, ein flexibles, parametrierbares Modul zum Auffinden von Speicherorten zu erstellen, das verschiedene Strategien implementierte und in allen nachfolgenden Projekten hätte verwendet werden sollen. Die große Anstrengung führte zu einem komplexen Modul, das sehr schwer zu verstehen und zu warten war. Im nächsten Projekt konnte der Projektleiter nicht herausfinden, wie es in diesem Warehouse funktioniert, und der Entwickler des Moduls war weg. Er ignorierte es schließlich und schrieb einen benutzerdefinierten Algorithmus für diese Aufgabe.

Einige Jahre später änderte sich das Layout des Lagers, in dem dieses Modul ursprünglich verwendet wurde, und das Modul mit all seiner Flexibilität entsprach nicht den neuen Anforderungen. deshalb habe ich es dort auch durch einen benutzerdefinierten Algorithmus ersetzt.

Ich weiß, dass LOC keine gute Messung ist, aber trotzdem: Die Größe des "flexiblen" Moduls betrug ~ 3000 LOC (PL / SQL), während ein benutzerdefiniertes Modul für dieselbe Aufgabe ~ 100..250 LOC benötigt. Durch den Versuch, flexibel zu sein, wurde die Codebasis extrem vergrößert, ohne die erhoffte Wiederverwendbarkeit zu erreichen.

user281377
quelle
Vielen Dank für Ihre Rückmeldung. Dies ist eine Erweiterung der zuerst beschriebenen Lösung, und wir haben auch darüber nachgedacht. Da die meisten unserer Bibliotheken jedoch einige Core-Entitäten verwenden und diese Core-Entitäten normalerweise für den einen oder anderen Kunden erweitert werden, können wir wahrscheinlich nur sehr wenige Bibliotheken in dieses Core-Repository aufnehmen. ZB haben wir eine "Kunden" -Entität definiert und ORM in einer Bibliothek zugeordnet, die von fast allen unseren anderen Bibliotheken und Programmen verwendet wird. Jeder Client verfügt jedoch über einige zusätzliche Felder, die er zu einem "Kunden" hinzufügen muss, sodass wir diese Bibliothek und damit alle Bibliotheken in Abhängigkeit davon aufteilen müssen.
AKZENT
3
Unsere Idee, unzählige "Wenns" zu vermeiden, ist es, die Abhängigkeitsinjektion umfassend zu nutzen und komplette Module für verschiedene Kunden auszutauschen. Ich bin mir nicht sicher, wie das funktionieren wird. Wie hat dieser Ansatz für Sie funktioniert?
aKzenT
+1, das entspricht im Wesentlichen meiner Erfahrung mit Projekten, die dies gut gemeistert haben. Wenn Sie den Ansatz "Wenn für verschiedene Kunden" verwenden möchten, 2 Punkte: 1. Tun Sie nicht wenn (Kunde1) ..., sondern wenn (KonfigurationOption1) ... und haben Sie Konfigurationsoptionen für jeden Kunden. 2. Versuchen Sie es nicht zu tun! In 1% der Fälle ist dies möglicherweise die bessere (verständlichere / leichter zu wartende) Option als konfigurationsspezifische Module.
Vaughandroid
@Baqueta: Nur zur Verdeutlichung: Sie empfehlen die Verwendung von kundenspezifischen Modulen anstelle von Konfigurationsoptionen (ifs), oder? Ich mag Ihre Idee, zwischen Merkmalen anstelle von Kunden zu unterscheiden. Die Kombination von beiden wäre also, verschiedene "Funktionsmodule" zu haben, die durch Konfigurationsoptionen gesteuert werden. Ein Kunde wird dann nur als eine Reihe unabhängiger Funktionsmodule dargestellt. Ich mag diesen Ansatz sehr, bin mir aber nicht sicher, wie ich das gestalten soll. DI löst das Problem des Ladens und Austauschs von Modulen, aber wie verwalten Sie unterschiedliche Datenmodelle zwischen Kunden?
aKzenT
Ja, Module pro Feature und dann Konfiguration / Auswahl der Features pro Kunde. DI wäre ideal. Leider musste ich Ihre Anforderungen an eine umfassende kundenspezifische Anpassung nie mit einer einzigen
Datenbibliothek
5

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:

  1. Viele, und das war eines der besten Teams, mit denen ich gearbeitet habe. Die Codebasis war zu dieser Zeit ungefähr 1 Million loc. Ich konnte mich nicht für den Ansatz entscheiden, aber es hat ziemlich gut geklappt. Auch im Nachhinein habe ich keine bessere Möglichkeit gesehen, mit Dingen umzugehen.
  2. Ich empfehle den zweiten Ansatz, den Sie mit den Nuancen vorgeschlagen haben, die ich in meiner Antwort erwähne.
  3. Keine Bücher, an die ich denken kann, aber ich würde als Einstieg in die Entwicklung mehrerer Plattformen forschen.
  4. Richten Sie eine starke Governance ein. Dies ist der Schlüssel, um sicherzustellen, dass Ihre Kodierungsstandards eingehalten werden. Das Befolgen dieser Standards ist die einzige Möglichkeit, die Dinge handhabbar und wartbar zu halten. Wir hatten unseren Anteil an leidenschaftlichen Bitten, das Modell zu brechen, dem wir folgten, aber keiner dieser Appelle hat jemals das gesamte Entwicklungsteam beeinflusst.

quelle
Vielen Dank für Ihre Erfahrung. Um es besser zu verstehen: Wie ist eine Plattform in Ihrem Fall definiert? Windows, Linux, X86 / x64? Oder etwas, das mehr mit verschiedenen Kunden / Umgebungen zu tun hat? Sie machen einen guten Punkt in 4) Ich denke, es ist eines der Probleme, die wir haben. Wir haben ein Team von vielen sehr klugen und qualifizierten Leuten, aber jeder hat etwas andere Ideen, wie man Dinge macht. Ohne jemanden, der eindeutig für Architektur zuständig ist, ist es schwierig, sich auf eine gemeinsame Strategie zu einigen, und Sie riskieren, sich in Diskussionen zu verlieren, ohne etwas zu ändern.
aKzenT
@aKzenT - ja, ich meinte physische Hardware und Betriebssystem als Plattformen. Wir hatten einen großen Funktionsumfang, von dem einige durch ein Lizenzierungsmodul ausgewählt werden konnten. Und wir haben eine breite Palette an Hardware unterstützt. Im Zusammenhang damit hatten wir ein gemeinsames Ebenenverzeichnis, das eine Reihe von Geräten mit eigenen Verzeichnissen in dieser Ebene für den Codebaum enthielt. Unsere Umstände waren also wirklich nicht allzu weit von Ihnen entfernt. Unsere Senior-Entwickler hätten heftige Diskussionen miteinander geführt, aber sobald eine kollektive Entscheidung gefallen war, waren alle einverstanden, sich an die Reihe zu setzen.
4

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.

Sam Goldberg
quelle
Danke für die Rückmeldung. Ich mag die Idee, zusätzliche Felder in einem Wörterbuch zu haben. Dies würde es uns ermöglichen, eine einzige Definition einer Entität zu haben und alle kundenspezifischen Inhalte in ein Wörterbuch aufzunehmen. Ich bin mir jedoch nicht sicher, ob es eine gute Möglichkeit gibt, es mit unserem ORM-Wrapper (Entity Framework) zum Laufen zu bringen. Und ich bin mir auch nicht sicher, ob es wirklich eine gute Idee ist, ein global geteiltes Datenmodell zu haben, anstatt für jedes Feature / Modul ein Modell zu haben.
aKzenT
2

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.

Dan Monego
quelle
Haben Sie zwischen Kernbibliotheken unterschieden, die für die Kunden gleich sind, oder haben Sie den gesamten Baum gegabelt? Verschmelzen Sie regelmäßig von der Hauptlinie zu den einzelnen Gabeln? Und wenn ja, wie viel Zeit hat die tägliche Zusammenführungsroutine gekostet und wie haben Sie vermieden, dass diese Prozedur auseinanderfällt, wenn der Zeitdruck beginnt (wie es immer an einem Punkt im Projekt der Fall ist)? Entschuldigung für so viele Fragen: p
aKzenT 10.08.12
Wir teilen den gesamten Baum auf, hauptsächlich, weil Clientanforderungen vor der Erstellung der Systemintegrität eingehen. Das Zusammenführen vom Hauptsystem erfolgt manuell mithilfe von mercurial und anderen Tools und ist in der Regel auf kritische Fehler oder große Feature-Updates beschränkt. Wir versuchen, Aktualisierungen für große, seltene Teile beizubehalten, sowohl aufgrund der Kosten für die Zusammenführung als auch, weil viele Clients ihre eigenen Systeme hosten und wir keine Installationskosten auf sie aufbringen möchten, ohne einen Mehrwert zu erzielen.
Dan Monego
Ich bin neugierig: IIRC mercurial ist ein DVCS ähnlich wie git. Haben Sie Vorteile beim Zusammenführen von Zweigen im Vergleich zu Subversion oder anderen herkömmlichen VCS festgestellt? Ich habe gerade einen sehr schmerzhaften Zusammenführungsprozess zwischen zwei völlig getrennten, entwickelten Zweigen mithilfe von Subversion abgeschlossen und überlegte, ob es einfacher gewesen wäre, wenn wir Git verwendet hätten.
aKzenT
Das Zusammenführen mit mercurial war sehr viel einfacher als das Zusammenführen mit unserem vorherigen Tool, dem Vault. Einer der Hauptvorteile ist, dass mercurial wirklich gut darin ist, die Änderungshistorie in den Vordergrund zu rücken und in den Mittelpunkt der Anwendung zu stellen, was es einfach macht, zu verfolgen, was wo getan wurde. Für uns war es am schwierigsten, die vorhandenen Zweige zu verschieben. Wenn Sie zwei Zweige zusammenführen, setzt mercurial voraus, dass beide die gleiche Wurzel haben. Daher ist für die Einrichtung eine letzte vollständig manuelle Zusammenführung erforderlich.
Dan Monego
2

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.

William Payne
quelle
2

Der zweite Ansatz scheint eleganter zu sein, aber wir haben viele ungelöste Probleme bei diesem Ansatz.

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.

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.

Es gibt einige Möglichkeiten, die ich in realen Systemen gesehen habe. Welche Sie wählen müssen, hängt von Ihrer Situation ab:

  • leben mit der Unordnung zu einem gewissen Grad
  • 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

  • Eine andere Alternative besteht darin, jeder Entitätstabelle ein großes Zeichenfolgenfeld zuzuweisen, in dem Sie eine einzelne XML-Zeichenfolge hinzufügen können.
  • Versuchen Sie, einige Konzepte zu verallgemeinern, damit sie für verschiedene Kunden leichter wiederverwendet werden können. Ich empfehle Martin Fowlers Buch " Analysis patterns ". Obwohl es in diesem Buch nicht um das Anpassen von Software geht, kann es auch für Sie hilfreich 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.

Doc Brown
quelle
Die Probleme beim Hinzufügen von CustomAttributes oder XML-Spalten zum Speichern benutzerdefinierter Eigenschaften bestehen darin, dass ihre Fähigkeiten sehr eingeschränkt sind. Das Sortieren, Gruppieren oder Filtern nach diesen Attributen ist beispielsweise eine große Herausforderung. Ich habe einmal an einem System gearbeitet, das eine zusätzliche Attributtabelle verwendet, und es wurde immer komplexer, diese benutzerdefinierten Attribute zu verwalten und zu handhaben. Aus diesem Grund habe ich mir überlegt, diese Attribute nicht als Spalten in eine zusätzliche Tabelle einzufügen, die dem Original 1: 1 zugeordnet ist. Das Problem, wie diese definiert, abgefragt und verwaltet werden, ist jedoch immer noch dasselbe.
aKzenT
@aKzenT: Die Anpassbarkeit ist nicht kostenlos, Sie müssen die Benutzerfreundlichkeit mit der Anpassbarkeit vergleichen. Mein genereller Vorschlag ist, dass Sie keine Abhängigkeiten einführen, bei denen der Kern des Systems in irgendeiner Weise von einem benutzerdefinierten Teil abhängt, sondern nur umgekehrt. Können Sie beispielsweise bei der Einführung dieser "zusätzlichen Tabellen" für Kunde 1 vermeiden, diese Tabellen und den zugehörigen Code für Kunde 2 bereitzustellen? Wenn die Antwort ja ist, ist die Lösung in Ordnung.
Doc Brown
0

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.

Michael Riley - AKA Gunny
quelle
-1

Wenn ich gebeten werde, die Entwicklung von B zu starten, das 80% der Funktionen mit A teilt, werde ich entweder:

  1. Klonen Sie A und ändern Sie es.
  2. Extrahieren Sie die Funktionen, die A und B gemeinsam nutzen, in C, die sie verwenden.
  3. Machen Sie A so konfigurierbar, dass es die Anforderungen von B und sich selbst erfüllt (daher ist B in A eingebettet).

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.

Moshe Revah
quelle
1
Klingt einfach, aber wie geht das in der Praxis? Wie können Sie Ihre Software so konfigurierbar machen, ohne sie mit "if (customer1)" zu überfrachten, was nach einiger Zeit nicht mehr verwaltbar ist?
AKZENT
@aKzenT Deshalb habe ich 2 und 3 für Sie zur Auswahl gelassen. Wenn die Art der erforderlichen Veränderungen Projekts machen customer1 Unterstützung customer2 Bedürfnisse durch Konfiguration wird , um den Code machen wartbaren dann nicht tun , 3.
Moshe Revah
Ich bevorzuge "if (option1)" anstelle von "if (customer1)". Auf diese Weise kann ich mit N Optionen viele mögliche Kunden verwalten. ZB mit N booleschen Optionen können Sie 2 ^ N Kunden verwalten, wobei nur N 'if' ... mit der Strategie "if (customer1)" unmöglich zu verwalten sind, was 2 ^ N 'if' erfordern würde.
Fil