Osvědčené postupy pro návrh zprostředkované služby
Postupujte podle obecných pokynů a omezení zdokumentovaných pro rozhraní RPC pro StreamJsonRpc.
Kromě toho platí následující pokyny pro zprostředkované služby.
Podpisy metod
Všechny metody by měly jako poslední parametr použít parametr CancellationToken. Tento parametr by obvykle neměl být volitelným parametrem, takže volající nebudou pravděpodobně argument omylem vynechat. I když se očekává, že implementace metody bude triviální, poskytnutí CancellationToken klientovi umožní zrušit vlastní požadavek předtím, než se přenese na server. Umožňuje také, aby se implementace serveru vyvinula do něčeho nákladnějšího, aniž by bylo nutné aktualizovat metodu, aby se přidalo zrušení jako možnost později.
Zvažte vyhýbat se většímu počtu přetížení stejné metody na rozhraní RPC. Zatímco rozlišení přetížení obvykle funguje (a měly by být napsány testy k ověření, že tomu tak skutečně je), spoléhá se na to, že se snaží deserializovat argumenty na základě typů parametrů jednotlivých přetížení, což vede k tomu, že jsou jako běžná součást výběru přetížení vyvolávány výjimky při prvním pokusu. Vzhledem k tomu, že chceme minimalizovat počet výjimek první šance vyvolaných v cestě úspěchu, je vhodnější mít pouze jednu metodu s daným názvem.
Parametry a návratové typy
Nezapomeňte, že všechny argumenty a návratové hodnoty přenášené pomocí RPC jsou pouze data. Všechny jsou serializovány a odesílány přes drát. Všechny metody, které na těchto datových typech definujete, pracují pouze s místní kopií dat a nemají žádný způsob, jak komunikovat zpět se službou RPC, která ji vytvořila. Jedinými výjimkami tohoto chování serializace jsou exotické typy, pro které má StreamJsonRpc zvláštní podporu.
Zvažte použití ValueTask<T>
místo Task<T>
jako návratového typu metod, protože ValueTask<T>
vyžaduje méně přidělení.
Při použití negenerické rozmanitosti (například Task a ValueTask) je méně důležité, ale ValueTask může být stále vhodnější.
Mějte na paměti omezení využití ValueTask<T>
, jak je uvedeno v tomto rozhraní API. Tento blogový příspěvek a video mohou být užitečné při rozhodování, jaký typ použít.
Vlastní datové typy
Zvažte definování všech datových typů jako neměnných, což umožňuje bezpečnější sdílení dat napříč procesem bez kopírování a pomáhá posílit myšlenku pro uživatele, že nemohou měnit data, která obdrží v reakci na dotaz, bez toho, aby provedli další vzdálené volání procedur (RPC).
Při použití ServiceJsonRpcDescriptor.Formatters.UTF8definujte datové typy jako class
místo struct
, aby se předešlo nákladům na (potenciálně opakované) boxování při použití Newtonsoft.Json.
Boxing při použití ServiceJsonRpcDescriptor.Formatters.MessagePack nedojde k, takže struktury mohou být vhodnou možností, pokud jste potvrzeni do daného formátovače.
Zvažte implementaci IEquatable<T> a přepsání GetHashCode() a Equals(Object) metod na datových typech, které klientovi umožňují efektivně ukládat, porovnávat a opakovaně používat přijatá data na základě toho, jestli se shodují s daty přijatými v jiném okamžiku.
Pomocí DiscriminatedTypeJsonConverter<TBase> můžete podporovat serializaci polymorfních typů pomocí FORMÁTU JSON.
Sbírky
Rozhraní pro čtení kolekcí používejte v podpisech metod RPC (například IReadOnlyList<T>) místo konkrétních typů (například List<T> nebo T[]
), což umožňuje potenciálně efektivnější deserializaci.
Vyhněte se IEnumerable<T>.
Nedostatek Count
vlastnosti vede k neefektivnímu kódu a implikuje možné pozdní generování dat, které se ve scénáři RPC nevztahuje.
Místo toho použijte IReadOnlyCollection<T> pro neuspořádané kolekce nebo IReadOnlyList<T> pro seřazené kolekce.
Zvažte IAsyncEnumerable<T>. Jakýkoli jiný typ kolekce nebo IEnumerable<T> způsobí odeslání celé kolekce v jedné zprávě. Použití IAsyncEnumerable<T> umožňuje malou počáteční zprávu a poskytuje příjemci prostředky k získání přesně tolika položek z kolekce, kolik chtějí, pomocí asynchronní enumerace. Další informace o tomto vzorci.
Vzor pozorovatele
Zvažte použití vzoru návrhu pozorovatele ve vašem rozhraní. To je jednoduchý způsob, jak se klient přihlásit k odběru dat bez mnoha nástrah, které platí pro tradiční model událostí popsaný v další části.
Vzor pozorovatele může být tak jednoduchý jako tento:
Task<IDisposable> SubscribeAsync(IObserver<YourDataType> observer);
Typy IDisposable a IObserver<T> použité výše jsou dva z exotických typů v StreamJsonRpc, a proto mají speciální způsob zpracování, namísto aby byly serializovány jako pouhá data.
Dění
Události můžou být problematické pro RPC z několika důvodů a místo toho doporučujeme vzor pozorovatele popsaný výše.
Mějte na paměti, že služba nemá žádný přehled o tom, kolik obslužných rutin událostí klient připojil, když služba a klient je v samostatných procesech. JsonRpc vždy přiřadí přesně jednu obslužnou rutinu, která je zodpovědná za předání události klientovi. Klient může mít na vzdálené straně připojené nula nebo více obslužných rutin.
Většina klientů RPC nebude mít obslužné rutiny událostí nastavené při prvním připojení. Vyhýbejte se vyvolání první události, dokud klient nevyvolá metodu "Subscribe*", která bude indikovat zájem a připravenost na příjem událostí.
Pokud vaše událost naznačuje změnu stavu (například přidání nové položky do kolekce), zvažte znovuspouštění všech minulých událostí nebo popis všech aktuálních dat jako nových při argumentaci události, když se klient přihlásí k odběru, aby se mohl "synchronizovat" čistě prostřednictvím kódu pro zpracování událostí.
Pokud by klient mohl chtít vyjádřit zájem o podmnožinu dat nebo oznámení, zvažte přijetí dalších argumentů metody "Subscribe*", aby se snížil síťový provoz a procesor potřebný k předávání těchto oznámení.
Zvažte možnost nenabídnout metodu, která vrací aktuální hodnotu, pokud také vystavujete událost pro příjem oznámení o změnách, nebo aktivně nedoporučujete klientům, aby ji používali v kombinaci s událostí. Klient, který se přihlásí k odběru události pro data a vyvolá metodu pro získání aktuální hodnoty, může čelit situaci, kdy změny této hodnoty probíhají tak rychle, že buď zmešká událost změny, nebo neví, jak sladit událost změny v jednom vlákně s hodnotou získanou v jiném vlákně. Tato záležitost je všeobecná pro jakékoli rozhraní, nejen když jde o RPC.
Konvence pojmenování
- V rozhraních RPC použijte příponu
Service
a jednoduchou předponuI
. - Nepoužívejte příponu
Service
pro třídy ve vašem SDK. Vaše knihovní rozhraní nebo RPC rozhraní by mělo mít název, který přesně popisuje svou funkci, a mělo by se vyhnout použití výrazu "služba". - Vyhněte se výrazu "remote" v názvech rozhraní nebo členů. Mějte na paměti, že zprostředkované služby by ideálně měly být použitelné jak v místních scénářích, tak i v těch vzdálených.
Problémy s kompatibilitou verzí
Chceme, aby každá zprostředkovaná služba, která je vystavena jiným rozšířením nebo byla zpřístupněna přes Live Share, dopředu a zpětně kompatibilní, což znamená, že bychom měli předpokládat, že klient může být starší nebo novější než služba a že funkce by se měla přibližně rovnat té menší ze dvou použitelných verzí.
Nejprve si projdeme terminologii zásadních změn:
binární zásadní změna: Změna rozhraní API, která by způsobila, že jiný spravovaný kód, zkompilovaný proti předchozí verzi sestavení, nebude schopen se za běhu programu svázat s novou verzí. Mezi příklady patří:
- Změna podpisu existujícího veřejného člena
- Přejmenování veřejného člena
- Odebrání veřejného typu
- Přidání abstraktního členu do typu nebo libovolného člena do rozhraní
Ale následující nejsou binární změny narušující kompatibilitu:
- Přidání ne abstraktového členu do třídy nebo struktury
- Přidání úplné (ne abstraktní) implementace rozhraní do existujícího typu
Změna protokolu způsobující chybu protokolu: Změna serializované formy některého datového typu nebo volání metody RPC tak, aby vzdálená strana nemohla správně deserializovat a zpracovat. Mezi příklady patří:
- Přidání požadovaných parametrů do metody RPC
- Odstranění člena z datového typu, u kterého bylo dříve zaručeno, že nemá null hodnotu.
- Přidání požadavku, že volání metody musí být umístěno před existujícími operacemi.
- Přidání, odebrání nebo změna atributu u pole nebo vlastnosti, která řídí serializovaný název dat v daném členu.
- (MessagePack): Změna vlastnosti DataMemberAttribute.Order nebo celého čísla
KeyAttribute
existujícího člena.
Následující ale nejsou změny způsobující chybu protokolu:
- Přidání volitelného člena do datového typu
- Přidání členů do rozhraní RPC
- Přidání volitelných parametrů do existujících metod
- Změna typu parametru, který představuje celé číslo nebo plovoucí na jedno s větší délkou nebo přesností (například
int
nalong
nebofloat
nadouble
). - Přejmenování parametru Technicky vzato, to ovlivňuje klienty, kteří používají pojmenované argumenty JSON-RPC, když klienti používající ServiceJsonRpcDescriptor ve výchozím nastavení využívají poziční argumenty, takže změna názvu parametru na ně nemá vliv. Toto nemá nic společného s tím, zda zdrojový kód klienta používá syntaxi pojmenovaných argumentů, pro kterou by přejmenování parametru znamenalo změnu způsobující porušení zdrojového kódu .
zlomová změna chování: Změna implementace zprostředkované služby, která přidává nebo mění chování takovým způsobem, že starší klienti mohou přestat správně fungovat. Mezi příklady patří:
- Už není inicializován člen datového typu, který byl dříve vždy inicializován.
- Vyvolání výjimky za podmínky, která se dříve mohla úspěšně splnit.
- Vrácení chyby s jiným kódem chyby, než byl vrácen dříve.
Následující ale nejsou zásadní změny chování:
- Vyvolání nového typu výjimky (protože všechny výjimky jsou stejně zabaleny do RemoteInvocationException).
Při vyžadování zásadních změn je možné je bezpečně provést registrací a vytvořením nového monikeru služby. Tento moniker může sdílet stejný název, ale s vyšším číslem verze. Původní rozhraní RPC může opakovaně použít, pokud nedojde k žádné binární zásadní změně. V opačném případě definujte nové rozhraní pro novou verzi služby. Vyhněte se způsobení problémů starým klientům pokračováním v podpoře, nabízením a podporem i starší verze.
Chceme se vyhnout všem takovým zásadním změnám, s výjimkou přidání členů do rozhraní RPC.
Přidání členů do rozhraní RPC
Nezadávejte přidávat členy do rozhraní zpětného volání klienta RPC, protože mnoho klientů může toto rozhraní implementovat a přidat členy by vedlo k vyvolání CLR TypeLoadException při načtení těchto typů, ale neimplementují nové členy rozhraní. Pokud je nutné přidat členy pro volání na cíli zpětného volání klienta RPC, definujte nové rozhraní (které může být odvozeno od původního) a pak postupujte podle standardního procesu nabídnutí vaší zprostředkované služby s navýšeným číslem verze a nabídněte deskriptor s aktualizovaným typem klientského rozhraní.
můžete přidávat členy do rozhraní RPC, která definují zprostředkovanou službu. Nejedná se o změnu porušující protokol, je to pouze binární změna pro ty, kteří implementují službu. Pravděpodobně byste však službu aktualizovali, aby implementovala i nového člena. Vzhledem k tomu, že naše pokyny je, že nikdo by neměl implementovat rozhraní RPC s výjimkou samotné zprostředkované služby (a testy by měly používat napodobování architektur), přidání člena do rozhraní RPC by nemělo narušit nikoho.
Tito noví členové by měli mít komentáře k dokumentu XML, které identifikují verzi služby, kterou tento člen poprvé přidal. Pokud novější klient volá metodu ve starší službě, která metodu neimplementuje, může tento klient zachytit RemoteMethodNotFoundException. Tento klient ale může (a pravděpodobně by měl) předpovědět selhání a vyhnout se volání v první řadě. Mezi osvědčené postupy pro přidávání členů do stávajících služeb patří:
- Pokud se jedná o první změnu v rámci vydání vaší služby: Zvyšte podverzi názvu služby, když přidáte člena a deklarujete nový popisovač.
- Aktualizujte službu tak, aby se zaregistrovala a nabídla nová verze kromě staré verze.
- Pokud máte klienta své zprostředkované služby, aktualizujte ho, aby požadoval novější verzi. Pokud je novější verze nedostupná (vrátí se jako null), přepněte zpět na požadování starší verze.