Jak utrzymywać różne, dostosowane wersje tego samego oprogramowania dla wielu klientów

46

mamy wielu klientów o różnych potrzebach. Chociaż nasze oprogramowanie jest do pewnego stopnia zmodularyzowane, prawie pewne jest, że musimy dostosować logikę biznesową każdego modułu tu i tam trochę dla każdego klienta. Zmiany są prawdopodobnie zbyt małe, aby uzasadnić podział modułu na odrębny (fizyczny) moduł dla każdego klienta, obawiam się problemów z kompilacją, chaosu łączącego. Jednak zmiany te są zbyt duże i zbyt wiele, aby skonfigurować je za pomocą przełączników w pliku konfiguracyjnym, ponieważ prowadziłoby to do problemów podczas wdrażania i prawdopodobnie wielu problemów z obsługą, szczególnie w przypadku administratorów typu majsterkowiczów.

Chciałbym, aby system kompilacji tworzył wiele kompilacji, po jednej dla każdego klienta, gdzie zmiany są zawarte w specjalnej wersji pojedynczego modułu fizycznego, o którym mowa. Mam więc kilka pytań:

Czy doradziłbyś, aby system kompilacji tworzył wiele kompilacji? Jak powinienem przechowywać różne dostosowania w kontroli źródła, w szczególności svn?

Sokół
źródło
4
Czy #ifdefdziała dla ciebie?
ohho,
6
Dyrektywy preprocesora mogą szybko stać się bardzo skomplikowane i powodować, że kod jest trudniejszy do odczytania i trudniejszy do debugowania.
Falcon
1
Aby uzyskać lepsze odpowiedzi, należy podać szczegółowe informacje na temat faktycznej platformy i rodzaju aplikacji (komputer / sieć). Dostosowywanie w aplikacji komputerowej C ++ jest zupełnie inne niż aplikacja internetowa PHP.
GrandmasterB
2
@ Falcon: skoro wybrałeś w 2011 roku odpowiedź, na którą mam wiele wątpliwości, czy możesz nam powiedzieć, czy masz jakieś doświadczenie w korzystaniu z SVN w sugerowany sposób? Czy moje zastrzeżenia są bezzasadne?
Doc Brown
2
@Doc Brown: rozgałęzianie i łączenie było nużące i skomplikowane. Wówczas używaliśmy systemu wtyczek z wtyczkami specyficznymi dla klienta lub „łatkami”, które zmieniały zachowanie lub konfiguracje. Będziesz miał trochę narzutu, ale można nim zarządzać za pomocą Dependendy Injection.
Falcon

Odpowiedzi:

7

Potrzebna jest organizacja kodu przypominająca rozgałęzienie funkcji . W twoim konkretnym scenariuszu powinno to być nazywane rozgałęzieniem łącza specyficznym dla klienta, ponieważ prawdopodobnie będziesz używać rozgałęzienia funkcji również podczas opracowywania nowych rzeczy (lub rozwiązywania błędów).

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

Chodzi o połączenie pnia kodu z połączonymi gałęziami nowych funkcji. Mam na myśli wszystkie funkcje niespecyficzne dla klienta.

Następnie masz również oddziały specyficzne dla klienta, w których również scalasz oddziały tych samych funkcji, jeśli jest to wymagane (wybieranie wiśni, jeśli chcesz).

Te gałęzie klientów wyglądają podobnie do gałęzi obiektów, chociaż nie są tymczasowe ani krótkotrwałe. Są one utrzymywane przez dłuższy czas i są głównie połączone. Rozwój gałęzi funkcji specyficznych dla klienta powinien być jak najmniejszy.

Gałęzie specyficzne dla klienta są gałęziami równoległymi do pnia i są aktywne tak długo, jak sam pień, a nawet nie łączą się w całość.

            feature1
            ———————————.
                        \
trunk                    \
================================================== · · ·
      \ client1            \
       `========================================== · · ·
        \ client2            \
         `======================================== · · ·
              \ client2-specific feature   /
               `——————————————————————————´
Robert Koritnik
źródło
7
+1 za rozgałęzianie funkcji. Możesz także użyć oddziału dla każdego klienta. Chciałbym zasugerować tylko jedną rzecz: użyj rozproszonego VCS (hg, git, bzr) zamiast SVN / CVS, aby to osiągnąć;)
Herberth Amaral
43
-1. Nie do tego służą „gałęzie funkcji”; stoi w sprzeczności z ich definicją „tymczasowych gałęzi, które łączą się, gdy kończy się rozwój funkcji”.
P Shved
14
Nadal musisz utrzymywać wszystkie te delty pod kontrolą źródła i utrzymywać je w linii - na zawsze. W krótkim okresie może to zadziałać. W dłuższej perspektywie może to być okropne.
szybko_nie
4
-1, to nie jest problem z nazewnictwem - używanie różnych gałęzi dla różnych klientów jest po prostu inną formą duplikacji kodu. To jest IMHO anty-wzór, przykład jak tego nie robić.
Doc Brown
7
Robert, myślę, że całkiem dobrze zrozumiałem, co zasugerowałeś, nawet przed edycją, ale myślę, że to okropne podejście. Załóżmy, że masz N klientów, ilekroć dodasz nową funkcję podstawową do pnia, wydaje się, że SCM ułatwi propagację nowej funkcji do N gałęzi. Jednak użycie gałęzi w ten sposób zbyt łatwo uniknąć wyraźnego oddzielenia modyfikacji specyficznych dla klienta. W rezultacie masz teraz N szans na uzyskanie konfliktu scalenia dla każdej zmiany w linii głównej. Ponadto musisz teraz uruchomić N testów integracyjnych zamiast jednego.
Doc Brown
38

Nie rób tego z oddziałami SCM. Uczyń wspólny kod osobnym projektem, który tworzy bibliotekę lub artefakt szkielet projektu. Każdy projekt klienta jest osobnym projektem, który następnie zależy od wspólnego jako zależności.

Jest to najłatwiejsze, jeśli twoja wspólna aplikacja bazowa używa struktury wstrzykiwania zależności, takiej jak Spring, dzięki czemu możesz łatwo wstrzykiwać różne warianty zastępowania obiektów w każdym projekcie klienta, ponieważ wymagane są niestandardowe funkcje. Nawet jeśli nie masz już środowiska DI, dodanie go i zrobienie tego w ten sposób może być najmniej bolesnym wyborem.

Alba
źródło
Innym podejściem do uproszczenia jest użycie dziedziczenia (i pojedynczego projektu) zamiast zastrzyku zależności, poprzez zachowanie zarówno wspólnego kodu, jak i dostosowań w tym samym projekcie (przy użyciu dziedziczenia, klas częściowych lub zdarzeń). Przy takim projekcie odgałęzienia klienta działałyby dobrze (jak opisano w wybranej odpowiedzi) i zawsze można było zaktualizować rozgałęzienia klienta poprzez aktualizację wspólnego kodu.
drizin
Jeśli coś mi nie umknie, sugerujesz, że jeśli masz 100 klientów, utworzysz (100 x NumberOfChangedProjects ) i użyjesz DI do zarządzania nimi? Jeśli tak, zdecydowanie trzymam się z dala od tego rodzaju rozwiązań, ponieważ
łatwość
13

Przechowuj oprogramowanie dla wszystkich klientów w jednym oddziale. Nie ma potrzeby rozróżniania zmian wprowadzonych dla różnych klientów. Najprościej rzecz biorąc, będziesz chciał, aby twoje oprogramowanie było najlepszej jakości dla wszystkich klientów, a poprawka błędów w podstawowej infrastrukturze powinna wpływać na wszystkich bez niepotrzebnego nakładania się na siebie, co może również powodować więcej błędów.

Zmodularyzuj wspólny kod i zaimplementuj kod różniący się w różnych plikach lub strzeż go za pomocą różnych definicji. Spraw, aby system kompilacji miał określone cele dla każdego klienta, przy czym każdy cel kompiluje wersję zawierającą tylko kod związany z jednym klientem. Jeśli na przykład masz kod C, możesz chcieć zabezpieczyć funkcje dla różnych klientów za pomocą „ #ifdef” lub innego mechanizmu, który ma Twój język do zarządzania konfiguracją kompilacji i przygotować zestaw definicji odpowiadających ilości funkcji, za które klient zapłacił.

Jeśli nie lubisz ifdefs, użyj „interfejsów i implementacji”, „funktorów”, „plików obiektowych” lub innych narzędzi dostępnych w twoim języku do przechowywania różnych rzeczy w jednym miejscu.

Jeśli dystrybuujesz źródła do swoich klientów, najlepiej jest, aby skrypty kompilacji miały specjalne „docelowe źródła dystrybucji”. Gdy wywołasz taki cel, tworzy on specjalną wersję źródeł twojego oprogramowania, kopiuje je do osobnego folderu, abyś mógł je wysłać, i nie kompiluje ich.

P Shved
źródło
8

Jak wielu stwierdziło: poprawny kod i dostosuj go na podstawie wspólnego kodu - znacznie poprawi to łatwość konserwacji. Bez względu na to, czy używasz języka / systemu zorientowanego obiektowo, czy nie, jest to możliwe (chociaż w C jest to nieco trudniejsze niż w przypadku obiektów zorientowanych obiektowo). Jest to dokładnie ten rodzaj problemu, który pomaga rozwiązać dziedziczenie i hermetyzacja!

Michael Trausch
źródło
5

Bardzo ostrożnie

Rozgałęzianie funkcji jest opcją, ale uważam, że jest nieco ciężkie. Ułatwia także głębokie modyfikacje, które mogą prowadzić do rozwidlenia aplikacji, jeśli nie są pod kontrolą. Idealnie chcesz w jak największym stopniu ulepszyć dostosowania, starając się, aby Twój podstawowy kod był tak powszechny i ​​ogólny, jak to możliwe.

Oto jak bym to zrobił, chociaż nie wiem, czy ma on zastosowanie do twojej bazy kodu bez ciężkich modyfikacji i ponownego faktoryzacji. Miałem podobny projekt, w którym podstawowa funkcjonalność była taka sama, ale każdy klient wymagał bardzo określonego zestawu funkcji. Stworzyłem zestaw modułów i kontenerów, które następnie zestawiam przez konfigurację (à la IoC).

następnie dla każdego klienta stworzyłem projekt, który zasadniczo zawiera konfiguracje i skrypt kompilacji, aby utworzyć w pełni skonfigurowaną instalację dla ich witryny. Czasami umieszczam tam również komponenty wykonane na zamówienie dla tego klienta. Jest to jednak rzadkie i ilekroć jest to możliwe, staram się zrobić to w bardziej ogólnej formie i spycham w dół, aby inne projekty mogły z nich korzystać.

W rezultacie otrzymałem poziom dostosowywania, którego potrzebowałem, otrzymałem spersonalizowane skrypty instalacyjne, aby po przejściu na stronę klienta nie wyglądało na to, że cały czas aktualizuję system, a jako bardzo znaczącą premię otrzymuję aby móc tworzyć testy regresji zaczepione bezpośrednio na kompilacji. W ten sposób za każdym razem, gdy dostaję błąd, który jest specyficzny dla klienta, mogę napisać test, który zapewni, że system jest wdrażany, a zatem może wykonać TDD nawet na tym poziomie.

w skrócie:

  1. Mocno modułowy system o płaskiej strukturze projektu.
  2. Utwórz projekt dla każdego profilu konfiguracji (klienta, chociaż więcej niż jeden może współdzielić profil)
  3. Złóż wymagane zestawy funkcjonalne jako inny produkt i traktuj je jako takie.

Jeśli zostanie to wykonane poprawnie, zestaw produktu powinien zawierać wszystkie pliki konfiguracyjne oprócz kilku.

Po pewnym czasie korzystania z tego skończyłem, tworząc meta-pakiety, które składają najczęściej używane lub niezbędne systemy jako jednostkę podstawową i używają tego meta-pakietu do zestawów klientów. Po kilku latach otrzymałem duży zestaw narzędzi, który mogłem bardzo szybko zmontować, aby stworzyć rozwiązania dla klientów. Obecnie patrzę na Spring Roo i sprawdzam, czy nie mogę rozwinąć tego pomysłu trochę dalej, mając nadzieję, że pewnego dnia uda mi się stworzyć pierwszy szkic systemu bezpośrednio z klientem w naszym pierwszym wywiadzie ... Wydaje mi się, że można to nazwać kierowanym przez użytkownika Rozwój ;-).

Mam nadzieję, że to pomogło

Newtopian
źródło
3

Zmiany są prawdopodobnie zbyt małe, aby uzasadnić podział modułu na odrębny (fizyczny) moduł dla każdego klienta, obawiam się problemów z kompilacją, chaosu łączącego.

IMO, nie mogą być zbyt małe. Jeśli to możliwe, rozróżniam kod specyficzny dla klienta przy użyciu wzorca strategii praktycznie wszędzie. Zmniejszy to ilość kodu, który musi być rozgałęziony, i zmniejszy scalanie wymagane do synchronizacji ogólnego kodu dla wszystkich klientów. Uprości to również testowanie ... możesz przetestować ogólny kod przy użyciu domyślnych strategii i osobno przetestować klasy specyficzne dla klienta.

Jeśli Twoje moduły są zakodowane w taki sposób, że użycie zasady X1 w module A wymaga użycia zasady X2 w module B, pomyśl o refaktoryzacji, aby X1 i X2 można było połączyć w jedną klasę zasad.

Kevin Cline
źródło
1

Możesz użyć SCM do utrzymania oddziałów. Zachowaj nienaruszoną / czystą gałąź master od czystego kodu klienta. Dokonaj głównego rozwoju w tej branży. Dla każdej dostosowanej wersji aplikacji utrzymuj osobne gałęzie. Każde dobre narzędzie SCM poradzi sobie naprawdę dobrze z łączeniem gałęzi (przychodzi na myśl Git). Wszelkie aktualizacje w gałęzi głównej powinny zostać scalone z niestandardowymi gałęziami, ale kod specyficzny dla klienta może pozostać we własnym oddziale.


Chociaż, jeśli to w ogóle możliwe, spróbuj zaprojektować system w sposób modułowy i konfigurowalny. Minusem tych niestandardowych gałęzi może być to, że przesuwa się zbyt daleko od rdzenia / wzorca.

Htbaa
źródło
1

Jeśli piszesz zwykłym C, tutaj jest raczej brzydki sposób na zrobienie tego.

  • Wspólny kod (np. Jednostka „frangulator.c”)

  • kod specyficzny dla klienta, małe elementy, które są używane tylko dla każdego klienta.

  • w kodzie jednostki głównej użyj #ifdef i #include, aby zrobić coś takiego

#ifdef KLIENT = KLIENTA
#include „frangulator_client_a.c”
#endif

Używaj tego jako wzorca w kółko we wszystkich jednostkach kodu, które wymagają dostosowania do konkretnego klienta.

Jest to BARDZO brzydkie i prowadzi do innych problemów, ale jest również proste i dość łatwo można porównywać pliki specyficzne dla klienta.

Oznacza to również, że wszystkie elementy specyficzne dla klienta są przez cały czas wyraźnie widoczne (każdy we własnym pliku) i istnieje wyraźny związek między głównym plikiem kodu a częścią pliku specyficzną dla klienta.

Jeśli staniesz się naprawdę sprytny, możesz skonfigurować pliki makefile, aby utworzyć poprawną definicję klienta, a więc coś takiego:

zrobić klientę

zbuduje dla client_a, a „make clientb” zrobi dla client_b i tak dalej.

(i „make” bez podanego celu może wydać ostrzeżenie lub opis użytkowania).

Użyłem już podobnego pomysłu, konfiguracja zajmuje trochę czasu, ale może być bardzo skuteczna. W moim przypadku jedno drzewo źródłowe zbudowało około 120 różnych produktów.

szybko
źródło
0

W git chciałbym to zrobić, aby mieć gałąź master z całym wspólnym kodem i gałęzie dla każdego klienta. Za każdym razem, gdy wprowadzana jest zmiana w kodzie podstawowym, po prostu rozłóż wszystkie gałęzie specyficzne dla klienta na master, tak abyś miał zestaw ruchomych łat dla klientów, które są nakładane na bieżącą linię bazową.

Za każdym razem, gdy wprowadzasz zmiany dla klienta i zauważysz błąd, który powinien być zawarty w innych gałęziach, możesz wybrać go w jednym z nich lub w innych gałęziach, które wymagają poprawki (chociaż różne gałęzie klienta współużytkują kod , prawdopodobnie powinieneś mieć je obie rozgałęzione ze wspólnej gałęzi od master).

Cercerilla
źródło