Message Brokers/Event Streaming - wersjonowanie "api"

1

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?

2

Jeśli się da, to rozbijać zmiany na nieblokujące

3

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.

1

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

1

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.

@jarekr000000

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 ;)

0

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

1
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 ;)

2

@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)

4

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;)

2

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

1

Poczytaj o AVRO schema

0

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

1 użytkowników online, w tym zalogowanych: 0, gości: 1