Jeśli mamy API restowe, to jedną z możliwości utrzymaniu ładu i skladu przy wielu klientach jest wersjowanie API. Dzięki temu jeśli mamy 10 klientów, to możemy np. usunąc jakies pole z JSONa w nowszej wersji nie obawiając się że wykrzaczą się dotychczasowi klienci. Ale jak to jest w przypadku korzystania z narzędzi typu RabbitMQ albo Apache Kafka? W większych projektach jak jakiś amazon takich klientów jest troszkę, co zrobić gdy chcemy modyfikować format wiadomości?
Jeśli się da, to rozbijać zmiany na nieblokujące
Nie modyfikujesz. Dodajesz nowe typy wiadomości. Hardkorowo to już mieć w nazwach "eventów" zaszyte wersje. UserMadePooV1.
Możesz nawet dodawać nowe topics
(ale tego raczej nigdy nie robiłem).
Po jakimś czasie można usunąć oczywiście - jak już wiadomo, że nie będzie obsługi starego formatu.
W naprawdę dużych systemach takich jak wymieniony przez Ciebie Amazon to faktycznie sprawa jest bardziej skomplikowana, i tam rzeczywiście należy albo unikać do maksimum "breaking changes" albo jak wspomniał Jarek dodawać nowe wersje w postaci całkowicie nowych typów.
W innych systemach można pokusić się o zwykle wersjonowanie, i liczyć się z tym że jak np. usunie się jakieś pole z wiadomości to klienci którzy się nie zaktualizowali będą dostawać domyślne wartości (bo raczej tak będzie skonfigurowana deserializacja).
Robić zmiany kompatybilne wstecz / wprzód, jeśli tylko się da
Takie "niełamiące" zmiany to może być np.:
- usunięcie atrybutu, który dotąd był opcjonalny
- dodanie opcjonalnego atrybutu
A jeśli musisz zrobić zmianę łamiącą kompatybilność i jako tako jesteś w stanie zapanować nad klientami (czyli nie na zasadzie - masz 100 klientów z czego 30 na pewno znajdzie ważny powód, by nigdy nie zupgradować swojej wersji klienta), to w miarę możliwości rozbić na etapy, np. chcąc usunąć/zastąpić atrybut, który był obowiązkowy ale właściwie na wyrost:
- robisz ten niechciany atrybut jako opcjonalny (w nowej wersji/nowym typie - jak wolisz) i wypuszczasz klienta, który sobie bez niego poradzi, oznaczasz to jako deprecated czy coś i rozgłaszasz nowinę
- dajesz temu trochę pożyć, żeby się otoczenie oswoiło i przeniosło (w końcu, kiedyś)
- kiedyś będziesz mógł to bezpiecznie usunąć. Chyba. Oby. Nadal będą kombinacje producera / consumera które są ze sobą niekompatybilne, ale przynajmniej będą
jakieś
kompatybilne i istnieje jakakolwiek gładka ścieżka.
Nie modyfikujesz. Dodajesz nowe typy wiadomości.
Nie będzie to nadal powodowało problemów, jeżeli stary odbiorca nie przewiduje
/ nie obsługuje
(nawet przez zignorowanie) nowych i nieznanych typów wiadomości? Jeśli w tej samej kolejce / topiku zacznie pojawiać się coś o nieznanym typie / schemacie to IMO tak czy owak może to łamać kompatybilność i stanowić breaking change
. Jak ta nieznaność
nie jest nijak obsłużona, to w sumie chyba mała różnica czy zmienił się typ, czy pojawił się nowy?
Tak czy siak moim zdaniem ważniejsze od konkretnego sposobu realizacji jest projektowanie z myślą o tym, by ewentualne zmiany były jak najmniej bolesne. Jeśli schemat jest sztywny i klient sztywno się go trzyma, to co do zasady ciężko będzie zrobić zmianę bez wysadzania czegoś w powietrze i/lub bez spędzenia kilku sprintów czekając, aż wszyscy łaskawie przyjmą do wiadomości zmianę.
Może trochę pomóc wpisanie do kodeksu rycerskiego, że w kwestii formatu wiadomości ostateczne słowo ma nadawca ;)
Nie będzie to nadal powodowało problemów, jeżeli stary odbiorca nie przewiduje / nie obsługuje (nawet przez zignorowanie) nowych i nieznanych typów wiadomości?
No ale o to chodzi.
Jest np. topic1 z serwisu A , na który są podpięte serwisy X, Y, Z. Serwis A chce coś zmydyfikowac, więc powstaje topic2, X i Y zostają na topic1 a Z ma topic2
Aleksander32 napisał(a):
No ale o to chodzi.
Jest np. topic1 z serwisu A , na który są podpięte serwisy X, Y, Z. Serwis A chce coś zmydyfikowac, więc powstaje topic2, X i Y zostają na topic1 a Z ma topic2
Słowa Jarka:
Możesz nawet dodawać nowe topics (ale tego raczej nigdy nie robiłem).
Zatem rozumiem, że idea dodawania nowych typów jest ortogonalna do dodawania nowych topików ;) nie mówiąc o tym, że w systemach rozproszonych mniej znaczy więcej, im mniej "odnóg" w postaci wysyłania eventów, requestów itd. tym lepiej bo mniej rzeczy może się popsuć.
Nie mówiąc o tym, że sam fakt dodania / zaprzestania używania topików
sam w sobie mógłby stanowić breaking change. Szczególnie, jeśli zaczniesz rozważać sytuacje, gdy w tych topicach persystujesz sobie eventy przez nieograniczony czas i potencjalnie możesz do nich kiedyś wrócić - ale wtedy to już jakikolwiek breaking change
, nawet rozbity na etapy, będzie dużym problemem gdy np. baaardzo nowy consumer zassie przedpotopowy event...
Ba, jak popuścić wodze fantazji jest jeszcze gorzej - co, jeśli oba topiki (stary i nowy) są spartycjonowane, kolejność eventów zachowana jest w obrębie partycji, a wprowadzona zmiana dotknie sposobu partycjonowania bo dotknie jakoś klucza? Może się okazać, że w zależności od topiku consumerzy mogą dostawać te same eventy w zamienionej kolejności - to już chyba lepiej walczyć z wieloma wersjami pod jednym topikiem ;)
@superdurszlak:
Nie będzie to nadal powodowało problemów, jeżeli stary odbiorca nie przewiduje / nie obsługuje (nawet przez zignorowanie) nowych i nieznanych typów wiadomości? Jeśli w tej samej kolejce / topiku zacznie pojawiać się coś o nieznanym typie / schemacie to IMO tak czy owak może to łamać kompatybilność i stanowić breaking change. Jak ta nieznaność nie jest nijak obsłużona, to w sumie chyba mała różnica czy zmienił się typ, czy pojawił się nowy?
Tak będą problemy - stąd te nowe topics
. Nie pracuje w żadnych amazonach więc u mnie tego typu problemy sa raczej proste:
a ) release - puty wszystkie instancje nie zostaną zupdatowane - (i tylko w dziwnych przypadkach to nie jest pare minut )- wtedy wystarczy zrobić najpierw release obsługujacy nowe wersje eventów, a jak już wszystko się zupdatuje to wrzucam taki, który też nowe eventy faktycznie wrzuca
b) inne serwisy, ale to są negocjowalne pojedyncze przypadki - można zawsze plan ułożyć (stare wersje eventów do starej kolejki/topiku, nowe do nowej)
mam projekt gdzie jest pure event sourcing, do utrzymania kontraktu korzystamy z avro i schema registry, dodatkowo mamy osobne repozytorium do wersjonowania schematow i poki co sie sprawdza;)
@Aleksander32: Nie napiszę nic odkrywczego, większość już padła w tym temacie.
Przede wszystkim trzeba zadbać, żeby dało się wprowadzać zmiany. Czyli jeżeli są jakieś nowe pola w strukturze, to muszą one zostać zachowane, nawet jeżeli obecny kod ich nie rozumie. Czyli parsowanie jsona na jakieś nasze DTO na 99% nie przejdzie, bo w naszej klasie nie będzie zmiennych na nowe pola, więc potem je stracimy. Nieznane pola dobrze jest sygnalizować w aplikacji czy logach, ale nie wolno ich tracić.
Po drugie, przy wprowadzaniu zmian robimy okres przejściowy, gdy kod rozpoznaje obie wersje wiadomości (starą i nową). Może to robić przez osobny topic, przez osobny typ, albo po prostu ifologią, podejść jest wiele, każde ma wady i zalety. Ważne jest, żeby w okresie przejściowym przemigrować wszystkie dane, czyli jeżeli trzeba, to idziemy do bazy danych i robimy update na każdym rekordzie, żeby był w nowym formacie czy co tam jest potrzebne. Możemy też robić to na bieżąco, ale wtedy rzadko dotykane rekordy prawdopodobnie zostaną w starym formacie na bardzo długo. Tu dobrze jest stosować podejście z krokowym wdrażaniem aplikacji, mamy setkę maszyn, to wrzucamy nową wersję aplikacji tylko na jedną maszynę i patrzymy, czy wszystko działa. Trzymamy to na produkcji przez chwilę i dopiero potem lecimy z podmianą dalej.
Po trzecie, hashmapa w evencie jest bardzo przydatna. Robimy zwykły słownik ze stringa na stringa i tam możemy wrzucać wszystkie rzeczy „na chwilę” lub w przypadku nieprzewidzianych sytuacji.
Dalej dochodzą mniejsze sztuczki, na przykład aliasy w enumach mapowane na tę samą wartość, jakiś sidecar tłumaczący wiadomości, scentralizowanie tworzenia wiadomości do jakiegoś serwisu, aby ten zajął się dbaniem o strukturę i tym podobne. Zależy od skali, konkretnego zastosowania i czasu.
Poczytaj o AVRO schema
@Charles_Ray: Dokładnie.
Sposobów na to jest wiele i raczej nie ma złotego graala, czasami poświęcasz latency, czasami flexibility, czasami coś innego. Warto zobaczyć jak to jest rozwiązane w popularnych protokołach - np. Protobuff, Avro, Thrift. Ogólnie jest to dość problematyczne, szczególnie jeżeli musisz zapewnić możliwość odtwarzania starych wiadomości (czasami nawet trzeba mieć sposób na powiązanie wersji kodu z wersją message z danego momentu).
Alternatywnie, w zależności od systemu, może da się wprowadzić 'negocjowanie' schema.
Oczywiście można stosować podejścia rodem z RESTa ale uciekałbym się do nich tylko w ostateczności.
Zarejestruj się i dołącz do największej społeczności programistów w Polsce.
Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.