@somekind:
Nie da się zastąpić wersjonowania feature togglem, bo nie da to konsumentowi wyboru ani nawet pewnej informacji odnośnie dostępnych funkcji.
Jeśli v1 i v2 mają być oddzielnymi fizycznie instancjami, to duplikacja będzie wręcz pełna, a jeśli wystarczy wersjonowanie w ramach jednego serwisu (np. po URLu, czy nagłówkach HTTP), to duplikacji może prawie nie być.
Moim zdaniem wersjonowanie i feature switch wymagają bardzo podobnej ilości duplikacji kodu (i nie ma znaczenia, czy mamy osobne instancje robione metodą Kopiego-Pejsta, czy wersjonujemy za pomocą nagłówków HTTP). Dla przykładu, feature switch w mikroserwisie X może określać z której wersji API mikroserwisu Y ma on korzystać. Z drugiej strony dużo zależy co ktoś rozumie przez wersję API. Gdzieś wyczytałem, że np zamiana pól na jakieś inne to już stworzenie nowego API, a nie zmiana starego. Niespecjalnie mnie taka terminologia przekonuje. Nie widzę przeszkód, by APIv2 wymagało nieco innych zapytań do bazy danych niż APIv1.
To jakiś bardzo dziwny flow. Feature branchem nie zastępuje się mastera, tylko się go z nim scala. To, czy zmiany z feature brancha powodują niekompatybilność API albo wymagają feature toggle wiadomo od momentu rozpoczęcia taska, więc należy ten problem rozwiązać podczas pracy nad taskiem.
Jeśli na masterze jest APIv1, a na branchu wywaliłem APIv1 i wstawiłem APIv2 to po zmerge'owaniu brancha na masterze APIv1 będzie zastąpione przez APIv2. Nie usuwasz starego kodu w swoich branchach?
@hauleth:
Opiszę przykład bardziej łopatologicznie. Programista X chce zmienić klasy A, B i C. Programista Y chce zmienić klasy B, C i D. Scenariusz działania wygląda tak:
- poniedziałek 9 programista X rozgrzebuje klasę A, programista Y rozgrzebuje klasę B,
- poniedziałek 17 programista X commituje zmiany w A, programista Y commituje zmiany w B,
- wtorek 9 programista X rozgrzebuje klasę B, programista Y rozgrzebuje klasę C,
- wtorek 17 programista X commituje zmiany w B, programista Y commituje zmiany w C,
- środa 9 programista X rozgrzebuje klasę C, programista Y rozgrzebuje klasę D,
- środa 17 programista X commituje zmiany w C, programista Y commituje zmiany w D,
Konfliktów nie ma, bo niby w którym momencie?
Teraz rozważmy schemat przy użyciu branchy. Programista X ma zmiany w A, B i C na swoim branchu, programista Y ma zmiany w B, C i D na swoim branchu. Oboje merge'ują się z masterem, ale ktoś musi być pierwszy. Ten drugi musi rozwiązać konflikty w klasach B i C.
Oczywiście powyższy scenariusz bardzo mocno faworyzuje brak branchy i rzeczywistość (bardzo często) jest zupełnie inna. Jednak próby podobnego szeregowania zmian w plikach się zdarzają. Konkretnie chodzi mi np o przypadek, w którym zarówno ja na swoim branchu jak i kolega chcemy zmodyfikować klasę S. Do tego jest też szereg innych zmian, ale przewidujemy, że w klasie S będzie najwięcej konfliktów. Kolega już rozgrzebał klasę S, więc mówi mi, żebym rozgrzebał inne klasy, on w międzyczasie zmerge'uje się do mastera, wtedy ja się zrebase'uję i będę mógł rozgrzebywać klasę S już z jego zmianami, unikając w ten sposób niepotrzebnych konfliktów. To miało szansę zadziałać, bo jego branch był już na ukończeniu, a mój dopiero rozgrzebywałem. Im mniejsze są zmiany na branchach tym częściej się takie manewry udają. Z drugiej strony - im mniejsze są zmiany na branchach, tym bliżej im do braku branchy.
Osobną sprawą niż wersjonowanie API, feature switche i rozmiary pull requestów jest ilość branchy. Branche pozwalają łatwo uniknąć wersjonowania API czy feature switchy - zamiast je implementować można deployować wersje z brancha na środowisko testowe i jak w końcu zadziałają poprawnie to można zastąpić nimi stare wersje. git-flow natomiast oznacza jakieś szalone żonglowanie branchami. master, develop, hotfix, release, feature, WTF? U nas efektywnie jest gałąź główna oraz feature branche i tyle. Więcej się nie przydaje, bo np nie prowadzimy wielu oficjalnych linii oprogramowania - na produkcji jest tylko jeden zestaw wersji. Faktycznie mamy dwie główne gałęzie - master i develop, bo kolega przekonywał, że takie coś się przyda. Jednak w praktyce się nie przydaje i master leży nieruszany odłogiem, a developa traktujemy jako główną gałąź. Hotfixów też nie mamy - zamiast tego jest rollback do poprzedniego release'a (co zdarza się nam bardzo rzadko). Reasumując schemat jest bardzo prosty - jedna główna gałąź i wiele feature branchy.
@leggo:
Wracając do problemu postawionego prze OPa, czyli użycie branchy do tworzenia ewolucji na bazie (czyli do skryptów SQLowych) to są one raczej problemem niż rozwiązaniem problemu. Narzędzia do ewolucji na bazkach czyli np https://flywaydb.org/ działają przyrostowo. Oczekują, że każdy kolejny skrypt będzie operował na stanie pozostawionym przez skrypt poprzedni. Jest to więc analogiczne do wrzucania commitów z SQLami bezpośrednio do mastera. Testowanie lokalnego brancha na pustej bazie to słaby test. Testowanie brancha na wspólnej bazie to prosta droga do rozsynchronizowania ewolucji na bazie. U nas ten problem jest pewnie w dużo mniejszej skali niż u was, bo rzadko kiedy dorzucamy kolejne ewolucje - możemy się więc dogadywać kiedy kto je wrzuca. Stąd zapewne nikt go u nas jakoś usilnie nie próbował rozwiązywać.
Jedyne co mi przychodzi do głowy jeśli chodzi o testowanie lokalnego brancha z ewolucjami na bazie to:
- klonowanie zawartości wspólnej bazy do świeżej lokalnej bazy (żeby klonowanie było szybkie to danych musi być względnie mało, przynajmniej jeśli chodzi o bazkę służącą do klonowania)
- odpalenie na niej zmian z lokalnego brancha i potestowanie
- jeśli testy przejdą to zmerge'owanie zmian do mastera
Powyższe oczywiście skomplikuje się jeśli będziesz chciał zaimplementować git-flow, bo git-flow wymaga wielu branchy (master, develop, release, hotfix, itd), a każdy branch wymaga osobnej instancji bazki, która musi być synchronizowana jednocześnie ze zmianami w gicie. Moim zdaniem takie żonglowanie nie będzie się opłacać, bo synchronizowanie zmian na bazie razem ze zmianami w gicie znacznie zwiększa już dużą złożoność git-flowa.