Najlepsze rozwiązania dotyczące projektowania usługi obsługiwanej przez brokera
Postępuj zgodnie z ogólnymi wskazówkami i ograniczeniami udokumentowanymi dla interfejsów RPC dla usługi StreamJsonRpc.
Ponadto poniższe wytyczne dotyczą usług obsługiwanych przez brokera.
Wszystkie metody powinny przyjmować CancellationToken parametr jako ostatni parametr. Ten parametr zwykle nie powinien być opcjonalnym parametrem, dlatego osoby wywołujące są mniej prawdopodobne, aby przypadkowo pominąć argument. Nawet jeśli implementacja metody ma być trywialna, zapewniając CancellationToken klientowi możliwość anulowania własnego żądania przed przesłaniem go na serwer. Umożliwia również implementację serwera, aby przekształcić się w coś droższego bez konieczności aktualizowania metody w celu późniejszego dodania anulowania.
Rozważ uniknięcie wielu przeciążeń tej samej metody w interfejsie RPC. Mimo że rozpoznawanie przeciążeń zwykle działa (i testy powinny być zapisywane w celu sprawdzenia, czy tak się dzieje), opiera się na próbie deserializacji argumentów na podstawie typów parametrów każdego przeciążenia, co powoduje zgłaszanie wyjątków pierwszej szansy jako regularnej części wybierania przeciążenia. Ponieważ chcemy zminimalizować liczbę wyjątków z pierwszej szansy zgłoszonych w ścieżkach powodzenia, zaleca się po prostu mieć tylko jedną metodę o podanej nazwie.
Pamiętaj, że wszystkie argumenty i zwracane wartości wymieniane przez RPC to tylko dane. Wszystkie są serializowane i wysyłane przez drut. Wszystkie metody zdefiniowane na tych typach danych działają tylko na tej lokalnej kopii danych i nie mogą komunikować się z powrotem z usługą RPC, która je wyprodukowała. Jedynymi wyjątkami od tego zachowania serializacji są egzotyczne typy , dla których usługa StreamJsonRpc ma specjalną obsługę.
Rozważ użycie ValueTask<T>
Task<T>
metody typu zwrotnego, ponieważ ValueTask<T>
wiąże się z mniejszą liczbą alokacji.
W przypadku używania odmiany niegenerycznej (na przykład Task i ValueTask) jest ona mniej ważna, ale ValueTask nadal może być preferowana.
Należy pamiętać o ograniczeniach użycia opisanych ValueTask<T>
w tym interfejsie API. Ten wpis w blogu i wideo mogą być przydatne podczas podejmowania decyzji o typie, który ma być również używany.
Rozważ zdefiniowanie wszystkich typów danych, które mają być niezmienne, co pozwala na bezpieczniejsze udostępnianie danych w procesie bez kopiowania i pomaga wzmocnić ideę dla konsumentów, że nie mogą zmienić danych, które otrzymują w odpowiedzi na zapytanie bez umieszczania innego RPC.
Zdefiniuj typy danych, class
a nie struct
podczas korzystania z ServiceJsonRpcDescriptor.Formatters.UTF8programu , co pozwala uniknąć kosztów (potencjalnie powtarzających się) boksów podczas korzystania z pliku Newtonsoft.Json.
Boxing nie występuje w przypadku używania ServiceJsonRpcDescriptor.Formatters.MessagePack struktur, więc może być odpowiednią opcją, jeśli zostanie zatwierdzona do tego formatowania.
Rozważ zaimplementowanie IEquatable<T> GetHashCode() i zastąpienie typów danych oraz Equals(Object) metod, co umożliwia klientowi efektywne przechowywanie, porównywanie i ponowne używanie odebranych danych na podstawie tego, czy dane są odbierane w innym czasie.
Użyj elementu , DiscriminatedTypeJsonConverter<TBase> aby obsługiwać serializowanie typów polimorficznych przy użyciu formatu JSON.
Używaj interfejsów readonly kolekcji w sygnaturach metod RPC (na przykład IReadOnlyList<T>) zamiast konkretnych typów (na przykład List<T> lub T[]
), co umożliwia potencjalnie bardziej wydajną deserializacji.
Unikaj IEnumerable<T>.
Jego brak Count
właściwości prowadzi do nieefektywnego kodu i oznacza możliwe późne generowanie danych, które nie ma zastosowania w scenariuszu RPC.
Służy IReadOnlyCollection<T> do nieurządzanych kolekcji lub IReadOnlyList<T> dla uporządkowanych kolekcji.
Rozważmy .IAsyncEnumerable<T> Każdy inny typ kolekcji lub IEnumerable<T> spowoduje wysłanie całej kolekcji w jednej wiadomości. Użycie IAsyncEnumerable<T> umożliwia utworzenie małego komunikatu początkowego i zapewni odbiorcy metodę uzyskiwania tak samo wielu elementów z kolekcji, jak chcą, wyliczając je asynchronicznie. Dowiedz się więcej o tym nowym wzorcu.
Rozważ użycie wzorca projektowego obserwatora w interfejsie. Jest to prosty sposób, aby klient subskrybował dane bez wielu pułapek, które mają zastosowanie do tradycyjnego modelu zdarzeń opisanego w następnej sekcji.
Wzorzec obserwatora może być tak prosty, jak w następujący sposób:
Task<IDisposable> SubscribeAsync(IObserver<YourDataType> observer);
Używane IDisposable powyżej typy i IObserver<T> to dwa egzotyczne typy w usłudze StreamJsonRpc, więc są specjalnie marshalowane zachowanie, a nie serializowane jako zwykłe dane.
Zdarzenia mogą być problematyczne przez RPC z kilku powodów i zalecamy zamiast tego wzorzec obserwatora opisany powyżej.
Należy pamiętać, że usługa nie ma wglądu w liczbę procedur obsługi zdarzeń dołączonych przez klienta, gdy usługa i klient znajdują się w osobnych procesach. JsonRpc Zawsze będzie dołączać dokładnie jedną procedurę obsługi, która jest odpowiedzialna za propagowanie zdarzenia do klienta. Klient może mieć zero lub więcej programów obsługi dołączonych po skrajnej stronie.
Większość klientów RPC nie będzie mieć przewodowych programów obsługi zdarzeń po pierwszym połączeniu. Unikaj podnoszenia pierwszego zdarzenia, dopóki klient nie wywoła metody "Subskrybuj*" w interfejsie, aby wskazać zainteresowanie i gotowość do odbierania zdarzeń.
Jeśli zdarzenie wskazuje różnicę w stanie (na przykład nowy element dodany do kolekcji), rozważ podniesienie wszystkich przeszłych zdarzeń lub opisanie wszystkich bieżących danych tak, jakby były nowe w argumencie zdarzenia, gdy klient subskrybuje, aby pomóc im "zsynchronizować" z kodem obsługi zdarzeń.
Rozważ zaakceptowanie dodatkowych argumentów w metodzie "Subskrybuj*", o której wspomniano powyżej, jeśli klient może wyrazić zainteresowanie podzbiorem danych lub powiadomień, aby zmniejszyć ruch sieciowy i procesor CPU wymagany do przekazywania tych powiadomień.
Rozważ, aby nie oferować metody, która zwraca bieżącą wartość, jeśli również ujawniasz zdarzenie w celu otrzymywania powiadomień o zmianie lub aktywnie zniechęca klientów do korzystania z niej w połączeniu ze zdarzeniem. Klient, który subskrybuje zdarzenie dla danych i wywołuje metodę w celu uzyskania bieżącej wartości oznacza wyścig przed zmianami tej wartości i braku zdarzenia zmiany lub nie wiedząc, jak uzgodnić zdarzenie zmiany w jednym wątku z wartością uzyskaną w innym wątku. Ten problem jest ogólny w przypadku dowolnego interfejsu — nie tylko wtedy, gdy jest on za pośrednictwem RPC.
- Użyj sufiksu
Service
w interfejsach RPC i prostegoI
prefiksu. - Nie używaj sufiksu
Service
dla klas w zestawie SDK. Twoja biblioteka lub otoka RPC powinna używać nazwy, która opisuje dokładnie to, co robi, unikając terminu "usługa". - Unikaj terminu "remote" w nazwach interfejsu lub składowych. Pamiętaj, że usługi obsługiwane przez brokera najlepiej mają zastosowanie tak samo w scenariuszach lokalnych, jak w przypadku zdalnych.
Chcemy, aby każda usługa brokera uwidoczniona dla innych rozszerzeń lub uwidoczniona w usłudze Live Share mogła być zgodna z poprzednimi wersjami, co oznacza, że należy założyć, że klient może być starszy lub nowszy niż usługa i że funkcjonalność powinna być w przybliżeniu równa mniejszej z dwóch odpowiednich wersji.
Najpierw przejrzyjmy terminologię zmiany powodującej niezgodność:
Zmiana powodująca niezgodność binarną: zmiana interfejsu API, która spowodowałaby, że inny kod zarządzany skompilowany względem poprzedniej wersji zestawu nie może powiązać w czasie wykonywania z nowym. Oto kilka przykładów:
- Zmiana podpisu istniejącego członka publicznego.
- Zmiana nazwy członka publicznego.
- Usuwanie typu publicznego.
- Dodawanie abstrakcyjnego elementu członkowskiego do typu lub dowolnego elementu członkowskiego do interfejsu.
Ale następujące zmiany nie są zmianami powodujących niezgodność binarną:
- Dodawanie nie abstrakcyjnej składowej do klasy lub struktury.
- Dodanie pełnej (nie abstrakcyjnej) implementacji interfejsu do istniejącego typu.
Zmiana powodująca niezgodność protokołu: zmiana na serializowaną formę niektórych typów danych lub wywołanie metody RPC, tak aby strona zdalna nie może prawidłowo deserializować i przetwarzać. Oto kilka przykładów:
- Dodawanie wymaganych parametrów do metody RPC.
- Usunięcie elementu członkowskiego z typu danych, który wcześniej miał gwarancję, że nie ma wartości null.
- Dodanie wymagania, że należy umieścić wywołanie metody przed innymi istniejącymi operacjami.
- Dodawanie, usuwanie lub zmienianie atrybutu w polu lub właściwości kontrolującej serializowaną nazwę danych w tym elemencie członkowskim.
- (MessagePack): zmiana DataMemberAttribute.Order właściwości lub
KeyAttribute
liczby całkowitej istniejącego elementu członkowskiego.
Ale następujące zmiany nie są zmiany powodujące niezgodność protokołu:
- Dodawanie opcjonalnego elementu członkowskiego do typu danych.
- Dodawanie elementów członkowskich do interfejsów RPC.
- Dodawanie parametrów opcjonalnych do istniejących metod.
- Zmiana typu parametru reprezentującego liczbę całkowitą lub zmiennoprzecinkowa na jedną o większej długości lub precyzji (na przykład
int
dolong
lubfloat
dodouble
). - Zmiana nazwy parametru. Technicznie jest to istotne dla klientów korzystających z argumentów nazwanych JSON-RPC, ale klienci używający ServiceJsonRpcDescriptor argumentów pozycyjnych domyślnie nie będą miały wpływu na zmianę nazwy parametru. Nie ma to nic wspólnego z tym, czy kod źródłowy klienta używa nazwanej składni argumentu, do której zmiana nazwy parametru byłaby zmianą powodującą niezgodność źródła.
Zmiana powodująca niezgodność zachowania: zmiana implementacji usługi obsługiwanej przez brokera, która dodaje lub zmienia zachowanie, tak aby starsi klienci mogli działać nieprawidłowo. Oto kilka przykładów:
- Nie inicjując już elementu członkowskiego typu danych, który był wcześniej zawsze inicjowany.
- Zgłaszanie wyjątku w warunku, który wcześniej mógł zostać ukończony pomyślnie.
- Zwracanie błędu z innym kodem błędu niż został zwrócony wcześniej.
Ale następujące zmiany nie są zmianami powodującym niezgodność behawioralną:
- Zgłaszanie nowego typu wyjątku (ponieważ wszystkie wyjątki są mimo to opakowane RemoteInvocationException ).
W przypadku konieczności wprowadzenia zmian powodujących niezgodność można je bezpiecznie wprowadzić, rejestrując i proffering nowego monikera usługi. Ten pseudonim może mieć taką samą nazwę, ale z wyższym numerem wersji. Oryginalny interfejs RPC może być wielokrotnego użytku, jeśli nie ma zmian powodujących niezgodność binarną. W przeciwnym razie zdefiniuj nowy interfejs dla nowej wersji usługi. Unikaj przerywania starych klientów, kontynuuj rejestrowanie, proffer i obsługę starszej wersji.
Chcemy uniknąć wszystkich takich zmian powodujących niezgodność, z wyjątkiem dodawania elementów członkowskich do interfejsów RPC.
Nie dodawaj elementów członkowskich do interfejsu wywołania zwrotnego klienta RPC, ponieważ wielu klientów może zaimplementować ten interfejs i dodanie elementów członkowskich spowoduje zgłoszenie TypeLoadException clR w przypadku załadowania tych typów, ale nie implementują nowych elementów członkowskich interfejsu. Jeśli musisz dodać elementy członkowskie do wywołania w docelowym wywołaniu zwrotnym klienta RPC, zdefiniuj nowy interfejs (który może pochodzić z oryginalnego), a następnie postępuj zgodnie ze standardowym procesem profferingu usługi brokera z przyrostowym numerem wersji i zaoferuj deskryptor ze zaktualizowanym typem interfejsu klienta określonym.
Możesz dodać elementy członkowskie do interfejsów RPC, które definiują usługę brokera. Nie jest to zmiana powodująca niezgodność protokołu i jest tylko zmianą powodującą niezgodność binarną dla tych, którzy implementują usługę, ale prawdopodobnie będziesz aktualizować usługę w celu zaimplementowania nowego elementu członkowskiego. Ponieważ naszymi wskazówkami jest to, że nikt nie powinien implementować interfejsu RPC z wyjątkiem samej usługi obsługiwanej przez brokera (i testy powinny używać pozorowanych struktur), dodanie elementu członkowskiego do interfejsu RPC nie powinno przerywać nikogo.
Ci nowi członkowie powinni mieć komentarze dokumentu xml, które identyfikują, która wersja usługi po raz pierwszy dodała ten element członkowski. Jeśli nowszy klient wywołuje metodę w starszej usłudze, która nie implementuje metody, klient może przechwycić RemoteMethodNotFoundExceptionmetodę . Jednak ten klient może (i prawdopodobnie powinien) przewidzieć błąd i uniknąć wywołania w pierwszej kolejności. Najlepsze rozwiązania dotyczące dodawania członków do istniejących usług obejmują:
- Jeśli jest to pierwsza zmiana w wydaniu usługi: Bump the minor version on your service moniker when you add the member and declare the new descriptor .If is the first change in a release of your service: Bump the minor version on your service moniker when you add the member and declare the new descriptor .
- Zaktualizuj usługę, aby zarejestrować i wywnioskować nową wersję oprócz starej wersji.
- Jeśli masz klienta usługi brokera, zaktualizuj klienta, aby zażądał nowszej wersji, i wróć do żądania starszej wersji, jeśli nowsza zostanie przywrócona jako null.