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 metody

Všechny metody by měly jako poslední parametr použít CancellationToken parametr. Tento parametr by obvykle neměl být volitelným parametrem, takže volající pravděpodobně omylem vynechá argument. I když se očekává, že implementace metody bude triviální, poskytuje CancellationToken klientovi možnost 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 možnost vyhnout se více přetížení stejné metody v rozhraní RPC. Zatímco rozlišení přetížení obvykle funguje (a testy by měly být zapsány k ověření, že ano), spoléhá na pokus deserializovat argumenty na základě typů parametrů každého přetížení, což vede k vyvolání první náhody výjimky jako pravidelná součást výběru přetížení. 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 vyměněné přes 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é StreamJsonRpc má zvláštní podporu.

Zvažte použití ValueTask<T> jako Task<T> návratového typu metod, protože ValueTask<T> dochází k menšímu počtu přidělení. Při použití negenerické rozmanitosti (například Task a ValueTask) je méně důležité, ale ValueTask přesto může být vhodnější. Mějte na ValueTask<T> paměti omezení využití, jak je uvedeno v tomto rozhraní API. Tento blogový příspěvek a video mohou být užitečné při rozhodování, který typ se má použít.

Vlastní datové typy

Zvažte definování všech datovýchtypůchm datům, 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, aniž by umístili jiný RPC.

Při použití Newtonsoft.Json definujte datové typy jako class místo struct použití ServiceJsonRpcDescriptor.Formatters.UTF8. Tím se vyhnete nákladům na (potenciálně opakované) boxování. Při použití boxování nedojde při použitíServiceJsonRpcDescriptor.Formatters.MessagePack, 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) metody u datových typů, 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.

DiscriminatedTypeJsonConverter<TBase> Použijte k podpoře serializace polymorfních typů pomocí JSON.

Kolekce

Používejte rozhraní pro čtení kolekcí v podpisech metod RPC (například IReadOnlyList<T>) místo konkrétních typů (například List<T> ) T[]umožňujících potenciálně efektivnější deserializaci.

Nepoužívejte IEnumerable<T>. Count Nedostatek vlastnosti vede k neefektivnímu kódu a implikuje možné pozdní generování dat, které se nevztahuje ve scénáři RPC. Slouží 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> bude mít za následek odeslání celé kolekce v jedné zprávě. Použití IAsyncEnumerable<T> umožňuje malou počáteční zprávu a poskytne příjemci prostředky k získání přesně tolik položek z kolekce, kolik chcete, asynchronně ji vyčíslí. Přečtěte si další informace o tomto románu.

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

Výše IDisposable použité typy IObserver<T> jsou dvěma z exotického typu v StreamJsonRpc, takže se speciálně zařaďte chování místo serializace jako pouhá data.

Událost

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řipojí přesně jednu obslužnou rutinu, která je zodpovědná za šíření události do klienta. 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í připojené 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 událost označuje rozdíl ve stavu (například novou položku přidanou do kolekce), zvažte vyvolání všech minulých událostí nebo popis všech aktuálních dat, jako by byla nová v argumentu události, když se klient přihlásí k odběru, aby se "synchronizoval" s ničím, ale s kódem 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 volá metodu, která získá aktuální hodnotu, stojí za rasou proti změnám této hodnoty a buď chybí událost změny nebo neví, jak odsouhlasit událost změny v jednom vlákně s hodnotou získanou v jiném vlákně. Tento problém je obecný pro jakékoli rozhraní , nejen když je přes RPC.

Zásady vytváření názvů

  • Použijte příponu Service v rozhraních RPC a jednoduchou I předponu.
  • Nepoužívejte příponu Service pro třídy v sadě SDK. Obálka knihovny nebo RPC by měla používat název, který přesně popisuje, co dělá, a vyhnout se 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 ideálně platí tolik v místních scénářích jako vzdálené služby.

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í změna způsobující chybu: Změna rozhraní API, která by způsobila jiné spravované kódy zkompilované proti předchozí verzi sestavení, se nepodařilo vytvořit vazbu za běhu na nový. Příkladem může být:

    • 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í

    Následující změny ale nejsou binární zásadní změny:

    • 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 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. Příkladem může být:

    • Přidání požadovaných parametrů do metody RPC
    • Odebrání člena z datového typu, u kterého bylo dříve zaručeno, že není null.
    • Přidání požadavku, že volání metody musí být umístěné před jinými 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 DataMemberAttribute.Order vlastnosti nebo KeyAttribute celého čísla existujícího členu.

    Následující změny ale nejsou zásadními protokoly:

    • 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 do long nebo float do double).
    • Přejmenování parametru Technicky je to pro klienty, kteří používají pojmenované argumenty JSON-RPC, ale klienti používající ServiceJsonRpcDescriptor poziční argumenty ve výchozím nastavení nemají vliv na změnu názvu parametru. To nemá nic společného s tím, jestli zdrojový kód klienta používá syntaxi pojmenovaných argumentů, na kterou by přejmenování parametru bylo zásadní změnou zdroje.
  • Změna způsobující chybu chování: Změna implementace zprostředkované služby, která přidává nebo mění chování tak, aby starší klienti mohli chybně fungovat. Příkladem může být:

    • 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ě dokončit.
    • Vrácení chyby s jiným kódem chyby, než bylo vráceno dříve.

    Následující ale nejsou změny způsobující chybu chování:

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 být opakovaně použitelné, pokud nedojde k žádné binární zásadní změně. V opačném případě definujte nové rozhraní pro novou verzi služby. Vyhýbejte se přerušení starých klientů tím, že se budete dál registrovat, řadit a podporovat i starší verzi.

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

Nepřidávejte členy do rozhraní zpětného volání klienta RPC, protože mnoho klientů může implementovat toto rozhraní a přidání členů by vedlo k vyvolání TypeLoadException CLR při načtení těchto typů, ale neimplementuje nové členy rozhraní. Pokud je nutné přidat členy, které se mají vyvolat v 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 pro zpracování zprostředkované služby s číslem přírůstkové verze a nabídněte popisovač s aktualizovaným typem klientského rozhraní zadaným typem.

Členy můžete přidat do rozhraní RPC, která definují zprostředkovanou službu. Nejedná se o změnu způsobující chybu protokolu a jedná se pouze o binární zásadní změnu pro ty, které implementují službu, ale pravděpodobně byste službu aktualizovali tak, 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 napodobované architektury), 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ě) předpovědět selhání a vyhnout se volání na prvním místě. 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 ve vydané verzi služby: Při přidání člena a deklarování nového popisovače překryjete podverzi na moniker služby.
  • Aktualizujte službu tak, aby se kromě staré verze zaregistrovala a nabídla i novou verzi.
  • Pokud máte klienta zprostředkované služby, aktualizujte klienta tak, aby požadoval novější verzi, a náhradním požadavkem na starší verzi, pokud se novější verze vrátí jako null.