Jak organizujesz wysoce spersonalizowane oprogramowanie?

28

Pracuję nad dużym projektem oprogramowania, który jest wysoce dostosowany do potrzeb różnych klientów na całym świecie. Oznacza to, że mamy może 80% kodu, który jest wspólny dla różnych klientów, ale także dużo kodu, który musi się zmieniać z jednego klienta na drugiego. W przeszłości zajmowaliśmy się tworzeniem oddzielnych repozytoriów (SVN), a kiedy rozpoczął się nowy projekt (mamy niewielu, ale dużych klientów), utworzyliśmy kolejne repozytorium w oparciu o wcześniejszy projekt, który ma najlepszą bazę kodu dla naszych potrzeb. To działało w przeszłości, ale mieliśmy kilka problemów:

  • Błędy naprawione w jednym repozytorium nie są załatane w innych repozytoriach. Może to być problem z organizacją, ale trudno mi naprawić i załatać błąd w 5 różnych repozytoriach, pamiętając, że zespół utrzymujący to repozytorium może znajdować się w innej części świata i nie mamy ich środowiska testowego , nie znają harmonogramu ani wymagań, jakie mają („błąd” w jednym kraju może być „cechą” w innym).
  • Funkcje i ulepszenia wprowadzone dla jednego projektu, które mogą być również przydatne w innym projekcie, są tracone lub jeśli są używane w innym projekcie, często powodują duże problemy z łączeniem ich z jednej bazy kodu do drugiej (ponieważ obie gałęzie mogły być rozwijane niezależnie przez rok ).
  • Refaktoryzacje i ulepszenia kodu dokonane w jednej gałęzi programistycznej zostają utracone lub powodują więcej szkody niż pożytku, jeśli trzeba scalić wszystkie te zmiany między gałęziami.

Dyskutujemy teraz, jak rozwiązać te problemy i do tej pory wymyśliliśmy następujące pomysły, jak to rozwiązać:

  1. Utrzymuj rozwój w oddzielnych gałęziach, ale lepiej organizuj się, mając centralne repozytorium, w którym ogólne poprawki błędów są łączone, a wszystkie projekty scalają zmiany z tego centralnego repozytorium do swoich własnych regularnie (np. Codziennie). Wymaga to dużej dyscypliny i dużego wysiłku w celu połączenia oddziałów. Nie jestem więc przekonany, że to zadziała i możemy zachować tę dyscyplinę, zwłaszcza gdy pojawia się presja czasu.

  2. Porzuć oddzielne gałęzie programistyczne i posiadaj centralne repozytorium kodu, w którym żyje cały nasz kod, i dostosowuj go, mając moduły wtykowe i opcje konfiguracji. Używamy już kontenerów wstrzykiwania zależności do rozwiązywania zależności w naszym kodzie i postępujemy zgodnie ze wzorcem MVVM w większości naszego kodu, aby w czysty sposób oddzielić logikę biznesową od naszego interfejsu użytkownika.

Drugie podejście wydaje się być bardziej eleganckie, ale mamy wiele nierozwiązanych problemów w tym podejściu. Na przykład: jak obsługiwać zmiany / uzupełnienia w modelu / bazie danych. Używamy .NET z Entity Framework, aby mieć silnie typowane podmioty. Nie rozumiem, w jaki sposób możemy obsłużyć właściwości wymagane dla jednego klienta, ale bezużyteczne dla innego klienta bez zaśmiecania naszego modelu danych. Myślimy o rozwiązaniu tego w bazie danych za pomocą tabel satelitarnych (posiadających osobne tabele, w których nasze dodatkowe kolumny dla konkretnego obiektu żyją z odwzorowaniem 1: 1 na oryginalny obiekt), ale jest to tylko baza danych. Jak sobie z tym poradzić w kodzie? Nasz model danych znajduje się w centralnej bibliotece, której nie bylibyśmy w stanie rozszerzyć dla każdego klienta stosującego to podejście.

Jestem pewien, że nie jesteśmy jedynym zespołem zmagającym się z tym problemem i jestem zszokowany, gdy znalazłem tak mało materiału na ten temat.

Więc moje pytania są następujące:

  1. Jakie masz doświadczenie z wysoce spersonalizowanym oprogramowaniem, jakie podejście wybrałeś i jak to zadziałało?
  2. Jakie podejście polecasz i dlaczego? Czy istnieje lepsze podejście?
  3. Czy są jakieś dobre książki lub artykuły na ten temat, które możesz polecić?
  4. Czy masz konkretne zalecenia dla naszego środowiska technicznego (.NET, Entity Framework, WPF, DI)?

Edytować:

Dziękuję za wszystkie sugestie. Większość pomysłów pasuje do tych, które już mieliśmy w naszym zespole, ale naprawdę pomocne jest zapoznanie się z ich doświadczeniem i wskazówkami, jak je lepiej wdrożyć.

Nadal nie jestem pewien, w którą stronę pójdziemy i nie podejmuję decyzji (sam), ale przekażę to zespołowi i jestem pewien, że będzie to pomocne.

W tej chwili tenor wydaje się być pojedynczym repozytorium wykorzystującym różne moduły specyficzne dla klienta. Nie jestem pewien, czy nasza architektura jest gotowa na to, ani ile musimy zainwestować, aby ją dopasować, więc niektóre rzeczy mogą przez jakiś czas żyć w osobnych repozytoriach, ale myślę, że to jedyne długoterminowe rozwiązanie, które zadziała.

Dziękuję raz jeszcze za wszystkie odpowiedzi!

aKzenT
źródło
Rozważ traktowanie tabel bazy danych jako kodu.
Robimy to już w tym sensie, że mamy nasze skrypty bazy danych w naszym repozytorium subversion, ale tak naprawdę to nie rozwiązuje wyżej wymienionych problemów. Nie chcemy mieć tabel w stylu Klucz-Wartość w naszym modelu bazy danych, ponieważ mają one wiele problemów. Jak więc dopuścić dodatki do swojego modelu dla indywidualnych klientów, jednocześnie zachowując wspólne repozytorium kodów dla wszystkich z nich?
aKzenT,

Odpowiedzi:

10

Wygląda na to, że podstawowym problemem jest nie tylko utrzymanie repozytorium kodu, ale brak odpowiedniej architektury .

  1. Jaka jest podstawa / esencja systemu, która zawsze będzie współdzielona przez wszystkie systemy?
  2. Jakie ulepszenia / odchylenia są wymagane przez każdego klienta?

Frameworki lub standardowa biblioteka obejmuje te pierwsze, podczas gdy te drugie zostałyby zaimplementowane jako dodatki (wtyczki, podklasy, DI, cokolwiek ma sens dla struktury kodu).

Pomógłby także system kontroli źródła, który zarządza oddziałami i rozproszonym rozwojem; Jestem fanem Mercurial, inni wolą Git. Ramy byłyby główną gałęzią, każdy dostosowany system byłby na przykład gałęziami podrzędnymi.

Konkretne technologie zastosowane do wdrożenia systemu (.NET, WPF, cokolwiek) są w dużej mierze nieistotne.

Właściwe rozwiązanie tego problemu nie jest łatwe , ale ma kluczowe znaczenie dla długoterminowej rentowności. Oczywiście im dłużej będziesz czekać, tym większy będzie dług techniczny, z którym będziesz musiał sobie poradzić.

Przyda ci się książka Architektura oprogramowania: zasady i wzorce organizacyjne .

Powodzenia!

Steven A. Lowe
źródło
3
Tak. Architektura jest jednak często bardziej rzeczą psychologiczną niż techniczną. Jeśli skupisz się całkowicie na produkcie, skończysz z podziałem pracy, w którym komponenty są przydatne tylko dla tego produktu. Jeśli natomiast skupiasz się na budowaniu zestawu bibliotek, które będą bardziej ogólnie przydatne, budujesz zestaw możliwości, które można wdrożyć w szerszym zakresie sytuacji. Kluczem jest oczywiście znalezienie właściwej równowagi między tymi dwoma skrajnościami dla konkretnej sytuacji.
William Payne,
Myślę, że istnieje duża wspólna część między wieloma naszymi oddziałami (> 90%), ale ostatnie 10% jest zawsze w różnych miejscach, więc jest bardzo niewiele komponentów, w których nie wyobrażam sobie pewnych zmian specyficznych dla klienta, z wyjątkiem niektórych bibliotek narzędziowych, które nie zawierają żadnej logiki biznesowej.
aKzenT
@ aKzenT: hmmm ... nie wyobrażaj sobie, zamiast tego zmierz. Sprawdź kod i spójrz na wszystkie miejsca, w których nastąpiło dostosowanie, utwórz listę zmodyfikowanych komponentów, zwróć uwagę na to, jak często każdy komponent był modyfikowany, i pomyśl o rodzajach i wzorcach dokonanych modyfikacji. Czy są kosmetyczne czy algorytmiczne? Czy dodają lub zmieniają podstawową funkcjonalność? Jaki jest powód każdego rodzaju zmiany? Ponownie jest to ciężka praca i możesz nie lubić konsekwencji tego, co odkryjesz.
Steven A. Lowe,
1
Zaakceptowałem to jako odpowiedź, ponieważ nie możemy podjąć tej decyzji, zanim nie zastanowimy się więcej nad naszą architekturą, zidentyfikujemy części, które są wspólne lub POWINNY być wspólne, a następnie sprawdzić, czy możemy żyć z jednym repozytorium lub czy konieczne jest rozwidlenie (dla przynajmniej moment). Ta odpowiedź odzwierciedla to najlepsze IMO. Dziękuję za wszystkie pozostałe posty!
aKzenT
11

Jedna firma, w której pracowałem, miała ten sam problem, a podejście do rozwiązania tego problemu było następujące: Stworzono wspólne ramy dla wszystkich nowych projektów; obejmuje to wszystkie rzeczy, które muszą być takie same w każdym projekcie. Np. Narzędzia do generowania formularzy, eksport do Excela, logowanie. Podjęto wysiłki, aby upewnić się, że ta wspólna struktura jest ulepszona tylko (gdy nowy projekt wymaga nowych funkcji), ale nigdy się nie rozwidla.

W oparciu o te ramy kod specyficzny dla klienta był utrzymywany w osobnych repozytoriach. Gdy jest to przydatne lub konieczne, poprawki błędów i ulepszenia są kopiowane między projektami (ze wszystkimi zastrzeżeniami opisanymi w pytaniu). Jednak przydatne na całym świecie ulepszenia wchodzą we wspólne ramy.

Posiadanie wszystkiego we wspólnej bazie kodu dla wszystkich klientów ma pewne zalety, ale z drugiej strony, czytanie kodu staje się trudne, gdy istnieje niezliczona liczba ifs, aby program zachowywał się inaczej dla każdego klienta.

EDYCJA: Jedna anegdota, aby uczynić to bardziej zrozumiałym:

Domeną tej firmy jest zarządzanie magazynem, a jednym z zadań systemu zarządzania magazynem jest znalezienie bezpłatnej lokalizacji magazynowej dla przychodzących towarów. Brzmi łatwo, ale w praktyce należy przestrzegać wielu ograniczeń i strategii.

W pewnym momencie kierownictwo poprosiło programistę o stworzenie elastycznego, parametryzowalnego modułu w celu znalezienia lokalizacji do przechowywania, który wdrożył kilka różnych strategii i powinien był być zastosowany we wszystkich kolejnych projektach. Szlachetny wysiłek zaowocował złożonym modułem, który był bardzo trudny do zrozumienia i utrzymania. W kolejnym projekcie kierownik projektu nie był w stanie wymyślić, jak sprawić, by działał w tym magazynie, a programisty wspomnianego modułu już nie było, więc w końcu go zignorował i napisał niestandardowy algorytm dla tego zadania.

Kilka lat później zmienił się układ magazynu, w którym ten moduł był pierwotnie używany, a moduł z całą swoją elastycznością nie spełniał nowych wymagań; więc zastąpiłem go również niestandardowym algorytmem.

Wiem, że LOC nie jest dobrym pomiarem, ale i tak: rozmiar „elastycznego” modułu wynosił ~ 3000 LOC (PL / SQL), podczas gdy moduł niestandardowy dla tego samego zadania zajmuje ~ 100..250 LOC. Dlatego starając się być elastycznym, niezwykle zwiększyliśmy rozmiar podstawy kodu, bez uzyskania możliwości ponownego użycia, na którą liczyliśmy.

użytkownik 281377
źródło
Dziękuję za twój komentarz. Jest to rozszerzenie pierwszego opisanego rozwiązania i pomyśleliśmy również o tym. Ponieważ jednak większość naszych bibliotek korzysta z niektórych encji podstawowych i te encje podstawowe są zwykle rozszerzone dla jednego klienta lub innego, myślę, że moglibyśmy umieścić w tym repozytorium tylko bardzo niewiele bibliotek. Np. Mamy zdefiniowaną encję „Klient” i mapę ORM w jednej bibliotece, z której korzystają prawie wszystkie nasze inne biblioteki i programy. Ale każdy klient ma kilka dodatkowych pól, które musi dodać do „klienta”, więc musielibyśmy rozwidlić tę bibliotekę, a tym samym wszystkie biblioteki w zależności od niej.
aKzenT,
3
Naszym pomysłem, aby uniknąć niezliczonych „ifs”, jest szerokie wykorzystanie zastrzyku zależności i wymiany kompletnych modułów dla różnych klientów. Nie jestem jednak pewien, jak to się ułoży. Jak to podejście działało dla Ciebie?
aKzenT,
+1, to w zasadzie odpowiada mojemu doświadczeniu z projektami, które dobrze sobie z tym poradziły. Jeśli zamierzasz zastosować podejście „ifs dla różnych klientów”, 2 punkty: 1. Nie rób if (klient1) ..., zamiast tego rób if (configurationOption1) ... i masz opcje konfiguracji dla poszczególnych klientów. 2. Staraj się tego nie robić! Może 1% czasu będzie lepszą (bardziej zrozumiałą / łatwą w utrzymaniu) opcją niż moduły specyficzne dla konfiguracji.
vaughandroid
@Baqueta: Tylko dla wyjaśnienia: zaleca się stosowanie modułów dla poszczególnych klientów zamiast opcji konfiguracji (ifs), prawda? Podoba mi się twój pomysł rozróżnienia funkcji zamiast klientów. Tak więc połączenie obu byłoby mieć różne „moduły funkcji”, które są kontrolowane przez opcje konfiguracji. Klient jest wówczas reprezentowany tylko jako zestaw niezależnych modułów funkcji. Bardzo podoba mi się to podejście, ale nie jestem pewien, jak to zaprojektować. DI rozwiązuje problem ładowania i wymiany modułów, ale jak zarządzasz różnymi modelami danych między klientami?
aKzenT,
Tak, moduły według funkcji, a następnie konfiguracja / wybór funkcji według klienta. DI byłby idealny. Niestety nigdy nie musiałem łączyć wymagań dotyczących znacznego dostosowania do potrzeb klienta z pojedynczą biblioteką danych, więc nie jestem pewien, czy mogę w tym pomóc…
vaughandroid
5

Jeden z projektów, nad którymi pracowałem, obsługiwał wiele platform (więcej niż 5) w wielu wydaniach produktów. Wiele wyzwań, które opisujesz, to rzeczy, z którymi się borykaliśmy, choć w nieco inny sposób. Mieliśmy zastrzeżoną bazę danych, więc nie mieliśmy takich samych problemów na tym polu.

Nasza struktura była podobna do twojej, ale mieliśmy jedno repozytorium dla naszego kodu. Kod specyficzny dla platformy wszedł do własnych folderów projektu w drzewie kodu. Wspólny kod mieszkał w drzewie na podstawie warstwy, do której należał.

Mieliśmy kompilację warunkową, opartą na budowanej platformie. Utrzymanie tego było dość uciążliwe, ale trzeba było to zrobić tylko wtedy, gdy dodano nowe moduły w warstwie specyficznej dla platformy.

Posiadanie całego kodu w jednym repozytorium ułatwiło nam usuwanie błędów na wielu platformach i wydaniach jednocześnie. Mieliśmy zautomatyzowane środowisko kompilacji dla wszystkich platform, które mogą służyć jako zabezpieczenie na wypadek, gdyby nowy kod złamał przypuszczalnie niepowiązaną platformę.

Próbowaliśmy go zniechęcić, ale zdarzałyby się przypadki, gdy platforma potrzebowała naprawy opartej na specyficznym dla platformy błędzie, który byłby w innym typowym kodzie. Jeśli moglibyśmy warunkowo zastąpić kompilację, nie powodując, że moduł wyglądałby na niezręcznie, zrobilibyśmy to najpierw. Jeśli nie, przenieślibyśmy moduł ze wspólnego terytorium i przenieśliśmy go na platformę.

W przypadku bazy danych mieliśmy kilka tabel, które miały kolumny / modyfikacje specyficzne dla platformy. Upewnilibyśmy się, że każda wersja platformy tabeli spełniała podstawowy poziom funkcjonalności, więc wspólny kod mógłby się do niej odwoływać bez obawy o zależności platformy. Zapytania / manipulacje specyficzne dla platformy zostały wepchnięte do warstw projektu platformy.

Aby odpowiedzieć na twoje pytania:

  1. Wiele, i to był jeden z najlepszych zespołów, z którymi pracowałem. Codebase w tym czasie znajdowało się około 1 mln loc. Nie mogłem wybrać podejścia, ale zadziałało całkiem nieźle. Nawet z perspektywy czasu nie widziałem lepszego sposobu postępowania.
  2. Polecam drugie podejście, które zasugerowałeś, z niuansami, o których wspomniałem w mojej odpowiedzi.
  3. Żadnych książek, o których nie mogę myśleć, ale na początek badałbym rozwój wielu platform.
  4. Ustanowienie silnego zarządzania. Jest to klucz do zapewnienia przestrzegania standardów kodowania. Przestrzeganie tych standardów jest jedynym sposobem na utrzymanie zarządzania i utrzymania. Mieliśmy udział w pełnych entuzjazmu próśb o przełamanie przyjętego przez nas modelu, ale żadna z tych apelacji nie zachwiała całym zespołem ds. Rozwoju wyższego szczebla.

źródło
Dziękujemy za podzielenie się wrażeniami. Żeby lepiej zrozumieć: jak zdefiniowano platformę w twoim przypadku. Windows, Linux, X86 / x64? A może coś bardziej związanego z różnymi klientami / środowiskami? Robisz dobry punkt w 4) Myślę, że to jeden z problemów, które mamy. Mamy zespół bardzo inteligentnych i wykwalifikowanych ludzi, ale każdy ma nieco inne pomysły na to, jak to zrobić. Bez kogoś, kto jest wyraźnie odpowiedzialny za architekturę, trudno jest uzgodnić wspólną strategię i ryzykujesz zatracenie się w dyskusjach bez zmiany niczego.
aKzenT
@ aKzenT - tak, miałem na myśli fizyczny sprzęt i system operacyjny jako platformy. Mieliśmy duży zestaw funkcji, z których niektóre były wybierane przez moduł licencjonowania. I wspieraliśmy szeroki wachlarz sprzętu. W związku z tym mieliśmy wspólny katalog warstw, który miał kilka urządzeń z własnymi katalogami w tej warstwie dla drzewa kodu. Więc nasze okoliczności naprawdę nie były tak dalekie od tego, gdzie jesteś. Nasi starsi deweloperzy rozmawiali ze sobą ożywione dyskusje, ale kiedy zapadła wspólna decyzja, wszyscy zgodzili się podnieść linię.
4

Przez wiele lat pracowałem nad aplikacją do administracji emerytalnej, która miała podobne problemy. Plany emerytalne różnią się znacznie między firmami i wymagają wysoce specjalistycznej wiedzy do wdrożenia logiki obliczeniowej i raportów, a także bardzo różnych projektów danych. Mogę tylko krótko opisać część architektury, ale może da to dość pomysłu.

Mieliśmy 2 oddzielne zespoły: główny zespół programistów , który był odpowiedzialny za kod systemu podstawowego (który byłby twoim 80% kodem współdzielonym powyżej) oraz zespół wdrożeniowy , który posiadał specjalistyczną wiedzę w zakresie systemów emerytalnych i był odpowiedzialny za uczenie się klienta wymagania i skrypty i raporty dla klienta.

Mieliśmy wszystkie nasze tabele zdefiniowane przez Xml (to przed czasem, gdy ramy encji były testowane pod względem czasu i wspólne). Zespół wdrażający zaprojektowałby wszystkie tabele w Xml, a podstawowa aplikacja mogłaby zostać poproszona o wygenerowanie wszystkich tabel w Xml. Dla każdego klienta były także powiązane pliki skryptów VB, Crystal Reports, Word Word itp. (W Xml był również wbudowany model dziedziczenia, aby umożliwić ponowne użycie innych implementacji).

Aplikacja podstawowa (jedna aplikacja dla wszystkich klientów) buforowałaby wszystkie rzeczy specyficzne dla klienta, gdy nadejdzie żądanie dla tego klienta, i wygenerowała wspólny obiekt danych (coś w rodzaju zdalnego zestawu rekordów ADO), który można serializować i przekazywać na około.

Ten model danych jest mniej elastyczny niż obiekty encji / domeny, ale jest bardzo elastyczny, uniwersalny i może być przetwarzany przez jeden zestaw podstawowego kodu. Być może w twoim przypadku możesz zdefiniować obiekty encji bazowej przy użyciu tylko wspólnych pól i mieć dodatkowy Słownik dla pól niestandardowych (dodaj do swojego obiektu encji pewien zestaw deskryptorów danych, aby zawierał metadane dla pól niestandardowych. )

Mieliśmy osobne repozytoria źródłowe dla kodu systemu podstawowego i kodu implementacji.

Nasz system podstawowy miał bardzo małą logikę biznesową, poza kilkoma bardzo standardowymi wspólnymi modułami obliczeniowymi. Podstawowy system działał jako: generator ekranu, skrypt uruchamiający, generator raportów, dostęp do danych i warstwa transportowa.

Segmentacja logiki podstawowej i logiki niestandardowej jest trudnym wyzwaniem. Zawsze jednak uważaliśmy, że lepiej jest mieć jeden podstawowy system z wieloma klientami, niż wiele kopii systemu dla każdego klienta.

Sam Goldberg
źródło
Dzięki za opinie. Podoba mi się pomysł posiadania dodatkowych pól w słowniku. To pozwoliłoby nam mieć jedną definicję bytu i umieścić wszystkie rzeczy specyficzne dla klienta w słowniku. Nie jestem jednak pewien, czy istnieje dobry sposób na to, aby działał z naszym opakowaniem ORM (Entity Framework). Nie jestem też pewien, czy naprawdę dobrym pomysłem jest posiadanie globalnie współdzielonego modelu danych zamiast modelu dla każdej funkcji / modułu.
aKzenT
2

Pracowałem na mniejszym systemie (20 kloc) i odkryłem, że DI i konfiguracja to świetne sposoby zarządzania różnicami między klientami, ale niewystarczające, aby uniknąć rozwidlenia systemu. Baza danych jest podzielona między część specyficzną dla aplikacji, która ma ustalony schemat, a część zależną od klienta, która jest zdefiniowana w niestandardowym dokumencie konfiguracyjnym XML.

Utrzymaliśmy jeden oddział w Merkurialu, który jest skonfigurowany tak, jakby był dostarczalny, ale oznakowany i skonfigurowany dla fikcyjnego klienta. Poprawki błędów są zawarte w tym projekcie, a nowy rozwój podstawowej funkcjonalności ma miejsce tylko tam. Wydania dla rzeczywistych klientów są od tego wyłączone, przechowywane we własnych repozytoriach. Śledzimy duże zmiany w kodzie poprzez ręcznie napisane numery wersji i śledzimy poprawki błędów za pomocą numerów zatwierdzeń.

Dan Monego
źródło
czy rozróżniasz biblioteki podstawowe, które są takie same między klientami, czy czy rozwidlasz całe drzewo? Czy regularnie łączysz z głównej linii do poszczególnych widelców? A jeśli tak, ile czasu kosztowało codzienne rutynowe łączenie i jak uniknąłeś tego, że procedura ta rozpada się, gdy zaczyna się presja czasu (tak jak zawsze w jednym punkcie projektu)? Przepraszamy za tak wiele pytań: p
aKzenT
Rozwidlamy całe drzewo, głównie dlatego, że żądania klientów są poprzedzane integralnością systemu. Scalanie z głównego systemu odbywa się ręcznie przy użyciu narzędzi rtęciowych i innych i zwykle ogranicza się do krytycznych błędów lub dużych aktualizacji funkcji. Staramy się aktualizować duże, rzadkie fragmenty, zarówno z powodu kosztu scalenia, jak i dlatego, że wielu klientów hostuje własne systemy i nie chcemy obciążać ich kosztami instalacji bez zapewnienia wartości.
Dan Monego,
Jestem ciekawy: IIRC mercurial to DVCS podobny do git. Czy zauważyłeś jakieś korzyści wynikające z tych połączeń między oddziałami w porównaniu do Subversion lub innych tradycyjnych VCS? Właśnie skończyłem bardzo bolesny proces scalania między 2 całkowicie odrębnymi rozwiniętymi gałęziami za pomocą subversion i myślałem, czy byłoby łatwiej, gdybyśmy użyli git.
aKzenT
Scalanie z merkurialem było o wiele, wiele łatwiejsze niż scalanie z naszym poprzednim narzędziem, którym był Vault. Jedną z głównych zalet jest to, że mercurial jest naprawdę dobry w umieszczaniu historii zmian na pierwszym planie w centrum aplikacji, co ułatwia śledzenie tego, co zostało zrobione. Najtrudniejsze dla nas było przeniesienie istniejących gałęzi - jeśli łączysz dwie gałęzie, mercurial wymaga, aby oba miały ten sam katalog główny, więc uzyskanie tej konfiguracji wymagało ostatniego całkowicie ręcznego scalenia.
Dan Monego,
2

Obawiam się, że nie mam bezpośredniego doświadczenia z problemem, który opisujesz, ale mam kilka uwag.

Druga opcja, polegająca na zebraniu kodu w centralnym repozytorium (w miarę możliwości) i architekturze w celu dostosowania (ponownie, w miarę możliwości) to prawie na pewno długa droga.

Problem polega na tym, jak planujesz się tam dostać i jak długo to zajmie.

W tej sytuacji prawdopodobnie (tymczasowo) posiadanie więcej niż jednej kopii aplikacji jednocześnie w repozytorium jest OK.

Umożliwi to stopniowe przejście do architektury, która bezpośrednio obsługuje dostosowywanie bez konieczności robienia tego za jednym zamachem.

William Payne
źródło
2

Drugie podejście wydaje się być bardziej eleganckie, ale mamy wiele nierozwiązanych problemów w tym podejściu.

Jestem pewien, że każdy z tych problemów można rozwiązać jeden po drugim. Jeśli utkniesz, zapytaj tutaj lub na SO o konkretny problem.

Jak zauważyli inni, posiadanie jednej centralnej bazy kodów / jednego repozytorium jest opcją, którą powinieneś preferować. Próbuję odpowiedzieć na twoje przykładowe pytanie.

Na przykład: jak obsługiwać zmiany / uzupełnienia w modelu / bazie danych. Używamy .NET z Entity Framework, aby mieć silnie typowane podmioty. Nie rozumiem, w jaki sposób możemy obsłużyć właściwości wymagane dla jednego klienta, ale bezużyteczne dla innego klienta, bez zaśmiecania naszego modelu danych.

Istnieje kilka możliwości, wszystkie widziałem w systemach rzeczywistych. Który wybrać zależy od twojej sytuacji:

  • żyć z bałaganem do pewnego stopnia
  • wprowadzić tabele „CustomAttributes” (opisujące nazwy i typy) oraz „CustomAttributeValues” (dla wartości, na przykład przechowywanych jako ciąg znaków, nawet jeśli są liczbami). Pozwoli to dodać takie atrybuty w czasie instalacji lub w czasie wykonywania, mając indywidualne wartości dla każdego klienta. Nie nalegaj, aby każdy niestandardowy atrybut był modelowany „widocznie” w modelu danych.

  • teraz powinno być jasne, jak używać tego w kodzie: mieć tylko ogólny kod dostępu do tych tabel oraz indywidualny kod (być może w osobnej wtyczce DLL, która zależy od ciebie) dla poprawnej interpretacji tych atrybutów

  • inną alternatywą jest nadanie każdej tabeli encji dużego pola ciągu, w którym można dodać pojedynczy ciąg XML.
  • spróbuj uogólnić niektóre koncepcje, aby można je było łatwiej wykorzystać ponownie u różnych klientów. Polecam książkę Martina Fowlera „ Wzorce analizy ”. Chociaż w tej książce nie chodzi o dostosowywanie oprogramowania, może być również pomocne.

A w przypadku konkretnego kodu: możesz także spróbować wprowadzić język skryptowy do swojego produktu, szczególnie w celu dodania skryptów specyficznych dla klienta. W ten sposób nie tylko tworzysz wyraźną linię między kodem a kodem specyficznym dla klienta, możesz również pozwolić swoim klientom na samodzielne dostosowanie systemu do pewnego stopnia.

Doktor Brown
źródło
Problemy z dodawaniem niestandardowych atrybutów lub kolumn XML do przechowywania niestandardowych właściwości polegają na tym, że ich możliwości są bardzo ograniczone. Na przykład sortowanie, grupowanie lub filtrowanie w oparciu o te atrybuty jest bardzo trudne. Kiedyś pracowałem nad systemem, który używał dodatkowej tabeli atrybutów, a utrzymanie i obsługa tych niestandardowych atrybutów staje się coraz bardziej skomplikowana. Z tego powodu myślałem zamiast umieszczać te atrybuty jako kolumny w dodatkowej tabeli, która jest odwzorowana w stosunku 1: 1 do oryginału. Problem z definiowaniem, sprawdzaniem i zarządzaniem nimi jest wciąż ten sam.
aKzenT
@ aKzenT: Dostosowywanie nie jest dostępne za darmo, trzeba będzie wymienić łatwość użytkowania w porównaniu z dostosowaniem. Moja ogólna sugestia jest taka, że ​​nie wprowadza się zależności, w których podstawowa część systemu zależy w jakikolwiek sposób od dowolnej części niestandardowej, a jedynie na odwrót. Na przykład, wprowadzając te „dodatkowe tabele” dla klienta 1, czy można uniknąć wdrożenia tych tabel i powiązanego kodu dla klienta 2? Jeśli odpowiedź brzmi tak, to rozwiązanie jest w porządku.
Doc Brown
0

Zbudowałem tylko jedną taką aplikację. Powiedziałbym, że 90% sprzedanych jednostek zostało sprzedanych bez zmian. Każdy klient miał własną, dostosowaną skórkę, a my obsłużyliśmy system w obrębie tej skórki. Kiedy pojawił się mod, który wpłynął na główne sekcje, próbowaliśmy użyć rozgałęzienia IF . Kiedy mod # 2 pojawił się w tej samej sekcji, przełączyliśmy się na logikę CASE, która pozwoliła na dalszą rozbudowę. Wydawało się, że poradzi sobie z większością drobnych próśb.

Wszelkie dalsze drobne niestandardowe żądania zostały obsłużone przez wdrożenie logiki Case.

Jeśli mody były dwa radykalne, zbudowaliśmy klon (osobne dołączenie) i otoczyliśmy CASE, aby uwzględnić inny moduł.

Poprawki i modyfikacje rdzenia wpłynęły na wszystkich użytkowników. Przed rozpoczęciem produkcji dokładnie przetestowaliśmy rozwój. Zawsze wysyłaliśmy powiadomienia e-mail, które towarzyszyły każdej zmianie i NIGDY, NIGDY, NIGDY nie publikowaliśmy zmian produkcyjnych w piątki ... NIGDY.

Nasze środowisko było klasyczne ASP i SQL Server. NIE byliśmy sklepem z kodami do spaghetti ... Wszystko było modułowe przy użyciu Obejmuje, podprogramów i funkcji.

Michael Riley - AKA Gunny
źródło
-1

Kiedy poproszę o rozpoczęcie rozwoju B, który udostępnia 80% funkcjonalności A, będę albo:

  1. Sklonuj A i zmodyfikuj go.
  2. Wyodrębnij funkcjonalność, z której korzystają zarówno A, jak i B, do C, którego będą używać.
  3. Spraw, aby A był wystarczająco konfigurowalny, aby zaspokoić potrzeby zarówno B, jak i siebie (dlatego B jest osadzony w A).

Wybrałeś 1 i wydaje się, że nie pasuje to do twojej sytuacji. Twoim zadaniem jest przewidzieć, który z 2 i 3 lepiej pasuje.

Moshe Revah
źródło
1
Brzmi łatwo, ale jak to zrobić w praktyce? Jak sprawić, by twoje oprogramowanie było tak konfigurowalne, bez zaśmiecania go słowem „if (klient1)”, które po pewnym czasie staje się niemożliwe do utrzymania.
aKzenT
@ aKzenT Właśnie dlatego zostawiłem 2 i 3 do wyboru. Jeśli zmiany potrzebne do wsparcia projektu klienta 1 potrzebami klienta 2 poprzez konfigurację spowodują, że kod nie będzie możliwy do utrzymania, to nie rób tego 3.
Moshe Revah
Wolę robić „if (opcja1)” zamiast „if (klient1)”. W ten sposób dzięki opcjom N mogę zarządzać wieloma potencjalnymi klientami. Np. Z opcjami logicznymi N możesz zarządzać 2 ^ N klientami, mając tylko N 'if' ... niemożliwym do zarządzania za pomocą strategii "if (customer1)", która wymagałaby 2 ^ N 'if'.
Fil