Upravit

Sdílet prostřednictvím


Návrh rozhraní API pro mikroslužby

Azure DevOps

Dobrý návrh rozhraní API je důležitý v architektuře mikroslužeb, protože veškerá výměna dat mezi službami probíhá prostřednictvím zpráv nebo volání rozhraní API. Rozhraní API musí být efektivní, aby se zabránilo vytváření chatovacích vstupně-výstupních operací. Vzhledem k tomu, že služby jsou navržené týmy, které pracují nezávisle, musí mít rozhraní API dobře definovaná sémantika a schémata správy verzí, aby aktualizace neporušovaly jiné služby.

Návrh rozhraní API pro mikroslužby

Je důležité rozlišovat mezi dvěma typy rozhraní API:

  • Veřejná rozhraní API, která klientské aplikace volají.
  • Back-endová rozhraní API, která se používají pro komunikaci mezi službami.

Tyto dva případy použití mají poněkud odlišné požadavky. Veřejné rozhraní API musí být kompatibilní s klientskými aplikacemi, obvykle aplikacemi prohlížeče nebo nativními mobilními aplikacemi. Ve většině případů to znamená, že veřejné rozhraní API bude používat REST přes HTTP. Pro back-endová rozhraní API však musíte vzít v úvahu výkon sítě. V závislosti na členitosti vašich služeb může komunikace mezi službami vést k velkému množství síťového provozu. Služby se můžou rychle stát vstupně-výstupními operacemi. Z tohoto důvodu jsou důležité aspekty, jako je rychlost serializace a velikost datové části. Mezi oblíbené alternativy použití REST přes HTTP patří gRPC, Apache Avro a Apache Thrift. Tyto protokoly podporují binární serializaci a obecně jsou efektivnější než HTTP.

Důležité informace

Tady je několik věcí, které je potřeba zvážit při výběru způsobu implementace rozhraní API.

REST versus RPC. Zvažte kompromisy mezi používáním rozhraní REST a rozhraním ve stylu RPC.

  • Prostředky REST modeluje, což může být přirozený způsob vyjádření doménového modelu. Definuje jednotné rozhraní založené na příkazech HTTP, které podporují zvolitelnost. Má dobře definovanou sémantiku z hlediska idempotence, vedlejších účinků a kódů odpovědí. A vynucuje bezstavovou komunikaci, což zlepšuje škálovatelnost.

  • RPC je více orientované na operace nebo příkazy. Vzhledem k tomu, že rozhraní RPC vypadají jako volání místních metod, může vás vést k návrhu příliš chatovacích rozhraní API. To ale neznamená, že RPC musí být chatty. Jen to znamená, že při návrhu rozhraní potřebujete věnovat pozornost.

Nejběžnější volbou pro rozhraní RESTful je rest přes HTTP pomocí JSON. Pro rozhraní ve stylu RPC existuje několik oblíbených architektur, včetně gRPC, Apache Avro a Apache Thrift.

Efektivita. Zvažte efektivitu z hlediska rychlosti, paměti a velikosti datové části. Rozhraní založené na gRPC je obvykle rychlejší než rozhraní REST přes HTTP.

Jazyk IDL (Interface Definition Language) IDL slouží k definování metod, parametrů a návratových hodnot rozhraní API. IDL se dá použít ke generování kódu klienta, kódu serializace a dokumentace k rozhraní API. Seznamy IDLs můžou využívat také testovací nástroje rozhraní API. Architektury, jako jsou gRPC, Avro a Thrift, definují vlastní specifikace IDL. REST přes HTTP nemá standardní formát IDL, ale běžnou volbou je OpenAPI (dříve Swagger). Můžete také vytvořit rozhraní HTTP REST API bez použití formálního definičního jazyka, ale pak ztratíte výhody generování a testování kódu.

Serializace. Jak se objekty serializují přes drát? Mezi možnosti patří textové formáty (primárně JSON) a binární formáty, jako je vyrovnávací paměť protokolu. Binární formáty jsou obecně rychlejší než textové formáty. Json má ale výhody z hlediska interoperability, protože většina jazyků a architektur podporuje serializaci JSON. Některé formáty serializace vyžadují pevné schéma a některé vyžadují kompilaci definičního souboru schématu. V takovém případě budete muset tento krok začlenit do procesu sestavení.

Podpora architektury a jazyka Protokol HTTP se podporuje téměř ve všech architekturách a jazycích. GRPC, Avro a Thrift mají všechny knihovny pro C++, C#, Java a Python. Thrift a gRPC také podporují Go.

Kompatibilita a interoperabilita. Pokud zvolíte protokol, jako je gRPC, možná budete potřebovat vrstvu překladu protokolu mezi veřejným rozhraním API a back-endem. Brána může tuto funkci provést. Pokud používáte síť služeb, zvažte, které protokoly jsou kompatibilní se sítí služeb. Například Linkerd má integrovanou podporu pro HTTP, Thrift a gRPC.

Naším základním doporučením je zvolit REST přes HTTP, pokud nepotřebujete výhody výkonu binárního protokolu. ROZHRANÍ REST přes PROTOKOL HTTP nevyžaduje žádné speciální knihovny. Vytváří minimální párování, protože volající nepotřebují ke komunikaci se službou zástupný kód klienta. Existují bohaté ekosystémy nástrojů, které podporují definice schématu, testování a monitorování koncových bodů RESTful HTTP. Protokol HTTP je navíc kompatibilní s klienty prohlížeče, takže mezi klientem a back-endem nepotřebujete vrstvu překladu protokolu.

Pokud ale zvolíte REST přes PROTOKOL HTTP, měli byste v rané fázi procesu vývoje provést výkon a zátěžové testování, abyste ověřili, jestli funguje dostatečně dobře pro váš scénář.

Návrh rozhraní RESTful API

Pro návrh rozhraní RESTful API existuje mnoho prostředků. Tady je několik užitečných informací:

Tady je několik konkrétních aspektů, které je potřeba vzít v úvahu.

  • Dávejte pozor na rozhraní API, která nevracela podrobnosti interní implementace nebo jednoduše zrcadlí interní schéma databáze. Rozhraní API by mělo modelovat doménu. Jedná se o kontrakt mezi službami a v ideálním případě by se měl změnit pouze při přidání nových funkcí, nejen proto, že jste refaktorovali nějaký kód nebo normalizovali tabulku databáze.

  • Různé typy klientů, jako jsou mobilní aplikace a desktopový webový prohlížeč, můžou vyžadovat různé velikosti datové části nebo vzorce interakce. Zvažte použití back-endů pro vzor front-endů k vytvoření samostatných back-endů pro každého klienta, které zpřístupňují optimální rozhraní pro daného klienta.

  • U operací s vedlejšími účinky zvažte, jak je vytvořit idempotentní a implementovat je jako metody PUT. To umožní bezpečné opakování a může zlepšit odolnost. Podrobnější informace najdete v článku Komunikace mezi službami .

  • Metody HTTP mohou mít asynchronní sémantiku, kde metoda vrátí odpověď okamžitě, ale služba provádí operaci asynchronně. V takovém případě by metoda měla vrátit kód odpovědi HTTP 202 , který označuje, že požadavek byl přijat ke zpracování, ale zpracování ještě není dokončeno. Další informace naleznete v tématu Model asynchronní požadavek-odpověď.

Mapování rest na vzory DDD

Vzory, jako je entita, agregace a objekt hodnoty, jsou navržené tak, aby u objektů v modelu vaší domény umístily určitá omezení. V mnoha diskusích DDD se vzory modelují pomocí konceptů jazyka orientovaných na objekty (OO), jako jsou konstruktory nebo metody getters a setters. Například objekty hodnot mají být neměnné. V programovacím jazyce OO byste to vynutili přiřazením hodnot v konstruktoru a vytvořením vlastností jen pro čtení:

export class Location {
    readonly latitude: number;
    readonly longitude: number;

    constructor(latitude: number, longitude: number) {
        if (latitude < -90 || latitude > 90) {
            throw new RangeError('latitude must be between -90 and 90');
        }
        if (longitude < -180 || longitude > 180) {
            throw new RangeError('longitude must be between -180 and 180');
        }
        this.latitude = latitude;
        this.longitude = longitude;
    }
}

Tyto postupy kódování jsou zvlášť důležité při vytváření tradiční monolitické aplikace. U velkého základu Location kódu může objekt používat mnoho subsystémů, takže je důležité, aby objekt vynucil správné chování.

Dalším příkladem je model Úložiště, který zajišťuje, že ostatní části aplikace neprodá přímé čtení ani zápisy do úložiště dat:

Diagram úložiště dronů

V architektuře mikroslužeb ale služby nesdílely stejný základ kódu a nesdílely úložiště dat. Místo toho komunikují prostřednictvím rozhraní API. Vezměte v úvahu případ, kdy služba Scheduler požaduje informace o dronu ze služby Dron. Služba Dron má svůj interní model dronu vyjádřený kódem. Plánovač to ale nevidí. Místo toho získá zpět reprezentaci entity dronu – například objekt JSON v odpovědi HTTP.

Tento příklad je ideální pro letecký a letecký průmysl.

Diagram služby Drony

Služba Scheduler nemůže upravovat interní modely služby Dron ani zapisovat do úložiště dat služby Drone. To znamená, že kód, který implementuje službu Dron, má menší vystavenou plochu v porovnání s kódem v tradičním monolitu. Pokud služba Dron definuje třídu umístění, rozsah této třídy je omezený – žádná jiná služba třídu nebude přímo využívat.

Z těchto důvodů se tyto pokyny příliš nezaměřují na postupy kódování, protože se týkají taktických vzorů DDD. Ukázalo se ale, že prostřednictvím rozhraní REST API můžete také modelovat mnoho vzorů DDD.

Příklad:

  • Agreguje přirozeně prostředky v REST. Například agregace doručení by byla vystavena jako prostředek prostřednictvím rozhraní API pro doručování.

  • Agregace jsou hranice konzistence. Operace s agregacemi by nikdy neměly ponechat agregaci v nekonzistentním stavu. Proto byste se měli vyhnout vytváření rozhraní API, která klientovi umožňují manipulovat s interním stavem agregace. Místo toho upřednostněte hrubě odstupňovaná rozhraní API, která zpřístupňují agregace jako prostředky.

  • Entity mají jedinečné identity. V REST mají prostředky jedinečné identifikátory ve formě adres URL. Vytvořte adresy URL prostředků, které odpovídají identitě domény entity. Mapování z adresy URL na identitu domény může být neprůžné pro klienta.

  • Podřízené entity agregace lze dosáhnout přechodem z kořenové entity. Pokud dodržujete principy HATEOAS , můžou být podřízené entity dostupné prostřednictvím odkazů v reprezentaci nadřazené entity.

  • Vzhledem k tomu, že objekty hodnot jsou neměnné, aktualizace se provádějí nahrazením celého objektu hodnoty. V REST implementujte aktualizace prostřednictvím požadavků PUT nebo PATCH.

  • Úložiště umožňuje klientům dotazovat se, přidávat nebo odebírat objekty v kolekci a abstrahovat podrobnosti o podkladovém úložišti dat. V REST může být kolekce jedinečným prostředkem s metodami pro dotazování kolekce nebo přidáním nových entit do kolekce.

Při návrhu rozhraní API se zamyslete nad tím, jak vyjadřují doménový model, nejen data uvnitř modelu, ale také obchodní operace a omezení dat.

Koncept DDD Ekvivalent REST Příklad
Agregovat Prostředek { "1":1234, "status":"pending"... }
Identita Adresa URL https://delivery-service/deliveries/1
Podřízené entity Odkazy { "href": "/deliveries/1/confirmation" }
Aktualizace objektů hodnot PUT nebo PATCH PUT https://delivery-service/deliveries/1/dropoff
Repository Kolekce https://delivery-service/deliveries?status=pending

Správa verzí API

Rozhraní API je kontrakt mezi službou a klienty nebo uživateli této služby. Pokud se rozhraní API změní, hrozí riziko porušení klientů, kteří závisí na rozhraní API, ať už se jedná o externí klienty nebo jiné mikroslužby. Proto je vhodné minimalizovat počet provedených změn rozhraní API. Změny v podkladové implementaci často nevyžadují žádné změny rozhraní API. Realisticky ale v určitém okamžiku budete chtít přidat nové funkce nebo nové funkce, které vyžadují změnu existujícího rozhraní API.

Kdykoli je to možné, proveďte změny rozhraní API zpětně kompatibilní. Vyhněte se například odebrání pole z modelu, protože to může přerušit klienty, kteří očekávají, že pole tam bude. Přidání pole neporuší kompatibilitu, protože klienti by měli ignorovat všechna pole, kterým nerozumí v odpovědi. Služba ale musí zpracovat případ, kdy starší klient vynechá nové pole v požadavku.

Podpora správy verzí ve smlouvě rozhraní API Pokud zavádíte zásadní změnu rozhraní API, představte novou verzi rozhraní API. Pokračujte v podpoře předchozí verze a nechte klienty vybrat verzi, kterou chcete volat. Existuje několik způsobů, jak to udělat. Jedna z nich jednoduše zveřejňuje obě verze ve stejné službě. Další možností je souběžné spuštění dvou verzí služby a směrování požadavků na jednu nebo druhou verzi na základě pravidel směrování HTTP.

Diagram znázorňující dvě možnosti podpory správy verzí

Diagram má dvě části. "Služba podporuje dvě verze" zobrazuje klienta v1 a klienta v2, který ukazuje na jednu službu. "Souběžné nasazení" ukazuje klienta verze 1 odkazující na službu v1 a klient v2 odkazující na službu v2.

Podpora více verzí je nákladná z hlediska času vývojáře, testování a provozní režie. Proto je vhodné co nejrychleji zastarat staré verze. V případě interních rozhraní API může tým, který vlastní rozhraní API, spolupracovat s ostatními týmy, aby jim pomohl migrovat na novou verzi. To je užitečné, když máte proces zásad správného řízení napříč týmy. U externích (veřejných) rozhraní API může být obtížnější vyřadit verzi rozhraní API, zejména pokud rozhraní API využívá třetí strany nebo nativní klientské aplikace.

Když se implementace služby změní, je vhodné změnu označit verzí. Tato verze poskytuje důležité informace při řešení chyb. Může být velmi užitečné pro analýzu původní příčiny vědět přesně, jakou verzi služby se volala. Zvažte použití sémantické správy verzí pro verze služby. Sémantická správa verzí používá hlavní obor . MOLL. Formát PATCH . Klienti by ale měli vybrat rozhraní API pouze podle čísla hlavní verze nebo případně podverze, pokud mezi dílčími verzemi dojde k významným (ale neurušitelným) změnám. Jinými slovy, je vhodné, aby klienti vybrali mezi verzí 1 a 2 rozhraní API, ale nevybírejte verzi 2.1.3. Pokud tuto úroveň členitosti povolíte, riskujete, že budete muset podporovat šíření verzí.

Další informace o správě verzí rozhraní API najdete v tématu Správa verzí webového rozhraní RESTful API.

Idempotentní operace

Operace je idempotentní , pokud ji lze volat vícekrát, aniž by se po prvním volání vytvářely další vedlejší účinky. Idempotence může být užitečná strategie odolnosti, protože umožňuje upstreamové službě bezpečně vyvolat operaci několikrát. Diskuzi o tomto bodu najdete v tématu Distribuované transakce.

Specifikace HTTP uvádí, že metody GET, PUT a DELETE musí být idempotentní. Metody POST nejsou zaručeny jako idempotentní. Pokud metoda POST vytvoří nový prostředek, obecně neexistuje žádná záruka, že tato operace je idempotentní. Specifikace definuje idempotentní tímto způsobem:

Metoda požadavku se považuje za idempotentní, pokud zamýšlený účinek na server více identických požadavků s touto metodou je stejný jako účinek pro jeden takový požadavek. (RFC 7231)

Při vytváření nové entity je důležité pochopit rozdíl mezi sémantikou PUT a POST. V obou případech klient odešle reprezentaci entity v textu požadavku. Význam identifikátoru URI se ale liší.

  • U metody POST představuje identifikátor URI nadřazený prostředek nové entity, například kolekci. Například pro vytvoření nového doručení může být /api/deliveriesidentifikátor URI . Server vytvoří entitu a přiřadí jí nový identifikátor URI, například /api/deliveries/39660. Tento identifikátor URI se vrátí v hlavičce umístění odpovědi. Pokaždé, když klient odešle požadavek, server vytvoří novou entitu s novým identifikátorem URI.

  • U metody PUT identifikátor URI identifikuje entitu. Pokud již existuje entita s tímto identifikátorem URI, server nahradí existující entitu verzí v požadavku. Pokud s tímto identifikátorem URI neexistuje žádná entita, server ji vytvoří. Předpokládejme například, že klient odešle požadavek PUT do api/deliveries/39660. Za předpokladu, že neexistuje žádné doručení s tímto identifikátorem URI, vytvoří server nový. Pokud teď klient odešle stejný požadavek znovu, server nahradí stávající entitu.

Tady je implementace metody PUT ve službě Delivery Service.

[HttpPut("{id}")]
[ProducesResponseType(typeof(Delivery), 201)]
[ProducesResponseType(typeof(void), 204)]
public async Task<IActionResult> Put([FromBody]Delivery delivery, string id)
{
    logger.LogInformation("In Put action with delivery {Id}: {@DeliveryInfo}", id, delivery.ToLogInfo());
    try
    {
        var internalDelivery = delivery.ToInternal();

        // Create the new delivery entity.
        await deliveryRepository.CreateAsync(internalDelivery);

        // Create a delivery status event.
        var deliveryStatusEvent = new DeliveryStatusEvent { DeliveryId = delivery.Id, Stage = DeliveryEventType.Created };
        await deliveryStatusEventRepository.AddAsync(deliveryStatusEvent);

        // Return HTTP 201 (Created)
        return CreatedAtRoute("GetDelivery", new { id= delivery.Id }, delivery);
    }
    catch (DuplicateResourceException)
    {
        // This method is mainly used to create deliveries. If the delivery already exists then update it.
        logger.LogInformation("Updating resource with delivery id: {DeliveryId}", id);

        var internalDelivery = delivery.ToInternal();
        await deliveryRepository.UpdateAsync(id, internalDelivery);

        // Return HTTP 204 (No Content)
        return NoContent();
    }
}

Očekává se, že většina požadavků vytvoří novou entitu, takže metoda optimisticky volá CreateAsync objekt úložiště a pak zpracuje všechny výjimky duplicitních prostředků aktualizací prostředku.

Další kroky

Přečtěte si, jak používat bránu rozhraní API na hranici mezi klientskými aplikacemi a mikroslužbami.