Biorąc pod uwagę usługę A (CMS), która kontroluje model (Produkt, załóżmy, że jedyne pola, które ma, to identyfikator, tytuł, cena) oraz usługi B (Wysyłka) i C (E-mail), które muszą wyświetlać dany model, jakie powinno być podejście zsynchronizować informacje o danym modelu w tych usługach w podejściu do pozyskiwania zdarzeń? Załóżmy, że katalog produktów rzadko się zmienia (ale się zmienia) i że istnieją administratorzy, którzy bardzo często uzyskują dostęp do danych przesyłek i wiadomości e-mail (przykładowe funkcje to: B: display titles of products the order contained
i C display content of email about shipping that is going to be sent
:). Każda z usług ma własną bazę danych.
Rozwiązanie 1
Wyślij wszystkie wymagane informacje o produkcie w ramach wydarzenia - oznacza to następującą strukturę dla order_placed
:
{
order_id: [guid],
product: {
id: [guid],
title: 'Foo',
price: 1000
}
}
W serwisie B i C informacje o produkcie są przechowywane w product
atrybucie JSON w orders
tabeli
Jako taki, do wyświetlenia niezbędnych informacji wykorzystywane są tylko dane pobrane ze zdarzenia
Problemy : w zależności od tego, jakie inne informacje muszą być prezentowane w B i C, ilość danych w przypadku może wzrosnąć. B i C mogą nie wymagać tych samych informacji o Produkcie, ale wydarzenie będzie musiało zawierać oba (chyba że podzielimy zdarzenia na dwa). Jeśli podane dane nie są obecne w danym zdarzeniu, kod nie może ich użyć - jeśli dodamy opcję koloru do danego Produktu, dla istniejących zamówień w B i C, dany produkt będzie bezbarwny, chyba że zaktualizujemy zdarzenia, a następnie uruchomimy je ponownie .
Rozwiązanie 2
Wyślij tylko identyfikator produktu w ramach wydarzenia - oznacza to następującą strukturę dla order_placed
:
{
order_id: [guid],
product_id: [guid]
}
W usługach B i C informacje o produkcie są przechowywane w product_id
atrybucie w orders
tabeli
Informacje o produkcie są pobierane przez usługi B i C, gdy jest to wymagane przez wykonanie wywołania interfejsu API do A/product/[guid]
punktu końcowego
Problemy : powoduje to, że B i C zależą od A (przez cały czas). Jeśli schemat zmian produktu w A, należy wprowadzić zmiany we wszystkich usługach, które od nich zależą (nagle)
Rozwiązanie 3
Wyślij tylko identyfikator produktu w ramach zdarzenia - oznacza to następującą strukturę dla order_placed:
{
order_id: [guid],
product_id: [guid]
}
W usługach B i C informacje o produkcie są przechowywane w products
tabeli; wciąż jest product_id
na orders
stole, ale istnieje replikacja products
danych między A, B i C; B i C mogą zawierać inne informacje o produkcie niż A
Informacje o produkcie są inicjowane podczas tworzenia usług B i C i są aktualizowane za każdym razem, gdy informacje o Produktach ulegają zmianie przez wywołanie A/product
punktu końcowego (który wyświetla wymagane informacje o wszystkich produktach) lub przez bezpośredni dostęp DB do A i skopiowanie niezbędnych informacji o produkcie wymaganych dla danego produktu usługa.
Problemy : powoduje to, że B i C zależą od A (podczas wysiewu). Jeśli schemat zmian produktu w A, należy wprowadzić zmiany we wszystkich usługach, które od nich zależą (podczas inicjowania)
Z mojego zrozumienia, poprawnym podejściem byłoby iść z rozwiązaniem 1 i albo aktualizować historię zdarzeń zgodnie z pewną logiką (jeśli katalog produktów nie zmienił się i chcemy dodać wyświetlany kolor, możemy bezpiecznie zaktualizować historię, aby uzyskać aktualny stan Produktów i uzupełnij brakujące dane w ramach wydarzeń) lub uwzględnij nieistnienie danych (jeśli zmienił się katalog Produktów i chcemy dodać kolor do wyświetlenia, nie możemy być pewni, czy w danym momencie dany Produkt był w przeszłości miał kolor, czy nie - możemy założyć, że wszystkie Produkty z poprzedniego katalogu były czarne i uwzględniono je poprzez aktualizację zdarzeń lub kodu)
źródło
updating event history
- W przypadku pozyskiwania zdarzeń historia zdarzeń jest Twoim źródłem prawdy i nigdy nie powinna być zmieniana, a jedynie iść do przodu. Jeśli zdarzenia się zmienią, możesz użyć wersji zdarzeń lub podobnych rozwiązań, ale podczas odtwarzania zdarzeń do określonego momentu stan danych powinien być taki, jak w tym momencie.updating event history
Nie mam na myśli: przejrzyj wszystkie zdarzenia, kopiując je z jednego strumienia (v1) do innego strumienia (v2), aby zachować spójny schemat zdarzeń.display image at the point when purchase was made
) lub nie może (reprezentować zamiardisplay current image as it within catalog
)Odpowiedzi:
Rozwiązanie nr 3 jest naprawdę blisko właściwego pomysłu.
Sposób na przemyślenie tego: B i C przechowują w pamięci podręcznej „lokalne” kopie potrzebnych im danych. Wiadomości przetwarzane w B (i podobnie w C) wykorzystują lokalnie buforowane informacje. Podobnie raporty są tworzone przy użyciu informacji przechowywanych w pamięci podręcznej lokalnie.
Dane są replikowane ze źródła do pamięci podręcznej za pośrednictwem stabilnego interfejsu API. B i C nie muszą nawet używać tego samego API - używają dowolnego protokołu pobierania, który jest odpowiedni do ich potrzeb. W efekcie definiujemy umowę - protokół i schemat wiadomości - które ograniczają dostawcę i konsumenta. Wówczas każdy konsument dla tej umowy może być podłączony do dowolnego dostawcy. Niekompatybilne wstecz zmiany wymagają nowej umowy.
Usługi wybierają odpowiednią strategię unieważnienia pamięci podręcznej dla swoich potrzeb. Może to oznaczać pobieranie zmian ze źródła zgodnie z harmonogramem lub w odpowiedzi na powiadomienie, że coś mogło się zmienić, a nawet „na żądanie” - działając jak pamięć podręczna odczytu, wracając do przechowywanej kopii danych, gdy źródło nie jest dostępne.
Daje to „autonomię” w tym sensie, że B i C mogą nadal dostarczać wartość biznesową, gdy A jest czasowo niedostępne.
Zalecana lektura: Dane z zewnątrz, Dane z wnętrza , Pat Helland 2005.
źródło
W informatyce są dwie trudne rzeczy , a jedną z nich jest unieważnienie pamięci podręcznej.
Rozwiązanie 2 jest absolutnie moją domyślną pozycją i ogólnie powinieneś rozważyć wdrożenie buforowania tylko wtedy, gdy napotkasz jeden z następujących scenariuszy:
Problemy z wydajnością są tak naprawdę głównym czynnikiem. Istnieje wiele sposobów rozwiązania nr 2, które nie wymagają buforowania, na przykład zapewnienie wysokiej dostępności Usługi A.
Buforowanie powoduje znaczną złożoność systemu i może tworzyć przypadki brzegowe, które trudno uzasadnić, i błędy, które są bardzo trudne do odtworzenia. Musisz także zmniejszyć ryzyko dostarczenia nieaktualnych danych, gdy istnieją nowsze dane, co może być znacznie gorsze z perspektywy biznesowej niż (na przykład) wyświetlenie komunikatu „Usługa A nie działa - spróbuj ponownie później”.
Z tego doskonałego artykułu Udi Dahana:
Ponadto, jeśli potrzebujesz zapytania w czasie o dane produktu, powinno to być obsługiwane w sposób, w jaki dane są przechowywane w bazie danych produktu (np. Daty rozpoczęcia / zakończenia), powinny być wyraźnie widoczne w interfejsie API (data wejścia w życie musi być stanowić dane wejściowe dla wywołania API do zapytania danych).
źródło
Bardzo trudno jest po prostu powiedzieć, że jedno rozwiązanie jest lepsze od drugiego. Wybór jednego spośród Rozwiązania nr 2 i nr 3 zależy od innych czynników (czas trwania pamięci podręcznej, tolerancja spójności, ...)
Moje 2 centy:
Unieważnienie pamięci podręcznej może być trudne, ale w opisie problemu wspomniano, że katalog produktów zmienia się rzadko. Ten fakt sprawia, że dane produktu są dobrym kandydatem do buforowania
Rozwiązanie nr 1 (NOK)
Rozwiązanie nr 2 (OK)
Rozwiązanie nr 3 (złożone, ale preferowane)
źródło
Ogólnie rzecz biorąc, zdecydowanie odradzam opcję 2 ze względu na czasowe połączenie między tymi dwiema usługami (chyba że komunikacja między tymi usługami jest bardzo stabilna i niezbyt częsta). Sprzężenie czasowe jest tym, co opisujesz
this makes B and C dependant upon A (at all times)
i oznacza, że jeśli A jest w dół lub nieosiągalny z B lub C, B i C nie mogą spełniać swojej funkcji.Osobiście uważam, że obie opcje 1 i 3 mają sytuacje, w których są one ważnymi opcjami.
Jeśli komunikacja między A i B i C jest tak wysoka lub ilość danych potrzebnych do wejścia na wydarzenie jest wystarczająco duża, aby wzbudzić obawy, to opcja 3 jest najlepszą opcją, ponieważ obciążenie sieci jest znacznie mniejsze , a opóźnienie operacji zmniejszy się wraz ze zmniejszeniem rozmiaru wiadomości. Inne kwestie do rozważenia tutaj to:
Opcja 1 nie jest jednak czymś, co odrzucam. Sprzężenie jest takie samo, ale pod względem programistycznym powinno być łatwe do wykonania (nie trzeba specjalnych działań), a stabilność domeny powinna oznaczać, że nie będą się one często zmieniać (jak już wspomniałem).
Inną opcją, którą zasugerowałem, jest niewielka odmiana w stosunku do 3, która nie polega na uruchomieniu procesu podczas uruchamiania, ale zamiast tego obserwuj zdarzenie „ProductAdded i„ ProductDetailsChanged ”na B i C, przy czym istnieje zmiana w katalogu produktów w A. Dzięki temu wdrożenia będą szybsze (a więc łatwiej naprawić problem / błąd, jeśli go znajdziesz).
Edytuj 2020-03-03
Przy ustalaniu podejścia integracyjnego mam określoną kolejność priorytetów:
Jeśli koszt niespójności jest wysoki (w zasadzie dane produktu w A muszą być możliwie jak najszybciej spójne z produktem buforowanym w B i C), nie można uniknąć konieczności zaakceptowania niedostępności i złożenia żądania synchronicznego (jak w sieci / rest request) z B & C do A, aby pobrać dane. Być świadomym! To nadal nie oznacza zgodnej transakcyjnie, ale po prostu minimalizuje okna niespójności. Jeśli absolutnie, pozytywnie musisz być natychmiast konsekwentny, musisz zmienić swoje granice usług. Jednak bardzo mocno wierzę, że nie powinno to stanowić problemu. Z doświadczenia wynika, że tak naprawdę niezwykle rzadko firma nie jest w stanie zaakceptować kilku sekund niespójności, więc nie trzeba nawet wysyłać żądań synchronicznych.
Jeśli potrzebujesz zapytań w czasie (których nie zauważyłem w twoim pytaniu, a zatem nie uwzględniłem powyżej, być może błędnie), koszt utrzymania tego w usługach końcowych jest tak wysoki (musisz powielić wewnętrzna logika projekcji zdarzeń we wszystkich usługach niższego rzędu), która wyjaśnia decyzję: należy pozostawić własność na rzecz A i przesłać zapytanie A-hoc do żądania internetowego (lub podobnego), a A powinien użyć źródła zdarzeń, aby odzyskać wszystkie zdarzenia, o których wiedziałeś w tym czasie, aby wyświetlić stan i zwrócić go. Wydaje mi się, że może to być opcja 2 (jeśli dobrze zrozumiałem?), Ale koszty są takie, że chociaż połączenie czasowe jest lepsze niż koszty utrzymania zdublowanych zdarzeń i logiki projekcji.
Jeśli nie potrzebujesz punktu w czasie, a nie ma jednoznacznego, pojedynczego właściciela danych (który w mojej początkowej odpowiedzi założyłem na podstawie twojego pytania), bardzo rozsądnym wzorcem byłoby posiadanie reprezentacji produktu w każdej usłudze osobno. Gdy aktualizujesz dane dla produktów, aktualizujesz A, B i C równolegle, wysyłając równoległe żądania internetowe do każdego z nich, lub masz interfejs API poleceń, który wysyła wiele poleceń do każdego z A, B i C. B & C używa ich lokalna wersja danych do wykonania zadania, która może, ale nie musi być nieaktualna. Nie jest to żadna z powyższych opcji (chociaż można by uczynić ją zbliżoną do opcji 3), ponieważ dane w A, B i C mogą się różnić, a „całość” produktu może być kompozycją wszystkich trzech danych źródła.
Wiedza, czy źródłem prawdy jest stała umowa, jest przydatna, ponieważ można jej użyć do wykorzystania domeny / zdarzeń wewnętrznych (lub zdarzeń przechowywanych w źródle zdarzeń jako wzorzec przechowywania w A) do integracji w A i usługach B i C. Jeśli umowa jest stabilna, możesz zintegrować zdarzenia domeny. Masz jednak dodatkowe obawy w przypadku częstych zmian lub gdy umowa przesłania wiadomości jest wystarczająco duża, aby utrudnić transport.
Jeśli masz wyraźnego właściciela i antykoncepcję, która ma być stabilna, najlepszą opcją byłaby opcja 1; zamówienie zawierałoby wszystkie niezbędne informacje, a następnie B i C wykonywałyby swoją funkcję przy użyciu danych w zdarzeniu.
Jeśli zgodnie z opcją 3 istnieje możliwość zmiany lub zerwania umowy, to powrót do żądań internetowych w celu pobrania danych produktu jest w rzeczywistości lepszą opcją, ponieważ o wiele łatwiej jest utrzymać wiele wersji. Tak więc B złożyłby wniosek dotyczący v3 produktu.
źródło
ProductAdded
lubProductDetailsChanged
dodajemy złożoność śledzenia zmian katalogu produktów, musimy w pewien sposób synchronizować te dane między bazami danych, na wypadek, gdyby zdarzenia były odtwarzane i potrzebowaliśmy dostępu do danych katalogu z przeszłości.