Návrh rozhraní API pro mikroslužby

Azure DevOps

Dobrý návrh rozhraní API je v architektuře mikroslužeb důležitý, 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 navrhují nezávisle pracující týmy, musí mít rozhraní API dobře definovaná sémantiku a schémata správy verzí, aby aktualizace nenarušily ostatní 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 prohlížečovými aplikacemi 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 PROTOKOL HTTP. U back-endových rozhraní API je ale potřeba 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ější 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 jsou obecně efektivnější než HTTP.

Požadavky

Tady je několik věcí, na které je potřeba myslet při volbě způsobu implementace rozhraní API.

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

  • Prostředky modelu REST, 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í možnostvolvability. Má dobře definovanou sémantiku z hlediska idempotenci, vedlejších účinků a kódů odpovědí. A vynucuje bezstavovou komunikaci, což zlepšuje škálovatelnost.

  • Rpc se více orientuje na operace nebo příkazy. Vzhledem k tomu, že rozhraní RPC vypadají jako volání místních metod, může vás to vést k návrhu příliš chatovacích rozhraní API. To ale neznamená, že rpc musí být chatrný. To jen znamená, že při návrhu rozhraní musíte věnovat pozornost.

Pro rozhraní RESTful je nejběžnější volbou 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 se používá k definování metod, parametrů a návratových hodnot rozhraní API. IDL lze použít ke generování kódu klienta, serializačního kódu a dokumentace k rozhraní API. Seznamy IDL můžou využívat také testovací nástroje rozhraní API, jako je Postman. Architektury jako gRPC, Avro a Thrift definují své vlastní specifikace IDL. REST přes HTTP nemá standardní formát IDL, ale běžnou volbou je OpenAPI (dříve Swagger). Rozhraní HTTP REST API můžete také vytvořit bez použití jazyka formální definice, ale pak ztratíte výhody generování a testování kódu.

Serializace. Jak jsou objekty serializovány 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 souboru definice schématu. V takovém případě budete muset tento krok začlenit do procesu sestavení.

Podpora architektury a jazyka. PROTOKOL HTTP je podporovaný téměř v každé architektuře a jazyce. gRPC, Avro a Thrift mají knihovny pro C++, C#, Javu 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. Tuto funkci může provádět brána . 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še základní doporučení je zvolit REST před protokolem HTTP, pokud nepotřebujete výhody binárního protokolu z výkonu. REST přes HTTP nevyžaduje žádné speciální knihovny. Vytvoří minimální spojení, protože volající ke komunikaci se službou nepotřebují zástupný kód klienta. Existují bohaté ekosystémy nástrojů pro podporu definic schémat, testování a monitorování koncových bodů HTTP RESTful. Protokol HTTP je 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řed protokolem HTTP, měli byste v rané fázi procesu vývoje provést testování výkonu a zatížení, 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 zdrojů informací. Tady jsou některé z nich, které vám můžou pomoct:

Tady je několik konkrétních aspektů, které byste měli mít na paměti.

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

  • Různé typy klientů, například mobilní aplikace a desktopový webový prohlížeč, můžou vyžadovat různé velikosti datových částí nebo vzorce interakce. Zvažte použití vzoru Back-endy pro front-endy 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 jejich idempotentní a implementaci jako metod PUT. To umožní bezpečné opakování a může zvýšit odolnost. Podrobnější informace o tomto problému najdete v článku Komunikace mezi službami .

  • Metody HTTP mohou mít asynchronní sémantiku, kdy metoda vrací 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 najdete v tématu Asynchronní Request-Reply vzor.

Mapování vzorů REST na DDD

Vzory, jako jsou entity, agregační a hodnotový objekt, jsou navržené tak, aby u objektů ve vašem doménovém modelu umisťovat určitá omezení. V mnoha diskuzích o DDD se vzory modelují pomocí jazykových konceptů orientovaných na objektově (OO), jako jsou konstruktory nebo metody získání vlastností a settery. 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 nastavení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 obzvláště důležité při vytváření tradičních monolitických aplikací. U velkého základu kódu může objekt používat Location mnoho subsystémů, takže je důležité, aby objekt vynutil správné chování.

Dalším příkladem je model Úložiště, který zajišťuje, že ostatní části aplikace nebudou přímo číst nebo zapisovat 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. Představte si případ, kdy služba Scheduler požaduje informace o dronu ze služby Drone. Služba Drone má svůj interní model dronu vyjádřený prostřednictvím kódu. Ale plánovač to nevidí. Místo toho získá zpět reprezentaci entity dronu – například objektu JSON v odpovědi HTTP.

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

Diagram služby dronů

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

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

Příklad:

  • Agregace se přirozeně mapují na prostředky v REST. Rozhraní API pro doručování by například zveřejnilo jako prostředek agregaci doručení.

  • Agregace jsou hranice konzistence. Operace s agregacemi by nikdy neměly nechat agregaci v nekonzistentním stavu. Proto byste se měli vyhnout vytváření rozhraní API, která umožňují klientovi manipulovat s interním stavem agregace. Místo toho upřednostněte hrubě odstupňovaná rozhraní API, která zveřejň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 pro klienta neprůsvědné.

  • Podřízené entity agregace jsou dostupné tak, že přejdete z kořenové entity. Pokud se řídíte principy HATEOAS , podřízené entity jsou 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 na objekty v kolekci, přidávat je nebo je odebírat a odebírat tak podrobnosti o podkladovém úložišti dat. V REST může být kolekce odlišným prostředkem s metodami pro dotazování kolekce nebo přidávání nových entit do kolekce.

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

Koncept DDD Ekvivalent REST Příklad
Agregace Prostředek { "1":1234, "status":"pending"... }
Identita 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í rozhraní API

Rozhraní API je kontrakt mezi službou a klienty nebo příjemci této služby. Pokud se rozhraní API změní, existuje riziko narušení klientů závislých na rozhraní API, ať už se jedná o externí klienty nebo jiné mikroslužby. Proto je vhodné minimalizovat počet změn rozhraní API, které provedete. 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 stávajícího rozhraní API.

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

Podpora správy verzí ve vašem kontraktu rozhraní API Pokud zavedete zásadní změnu rozhraní API, zavete novou verzi rozhraní API. Pokračujte v podpoře předchozí verze a nechte klienty vybrat, která verze se má volat. Můžete to udělat několika způsoby. Jedním z nich je jednoduše zveřejnění obou verzí 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" ukazuje, že klient verze 1 i klient v2 ukazují na jednu službu. Souběžné nasazení zobrazuje klienta v1 odkazujícího na službu v1 a klienta v2, který ukazuje na službu v2.

Podpora více verzí má určité náklady z hlediska času vývoje, testování a provozní režie. Proto je dobré co nejrychleji vyřazení starých verzí vyřazení. V případě interních rozhraní API může tým, který je vlastníkem rozhraní API, spolupracovat s jinými týmy, aby jim pomohl s migrací na novou verzi. To je v případě, že je užitečné mít proces zásad správného řízení mezi týmy. U externích (veřejných) rozhraní API může být obtížnější vyřadit verzi rozhraní API, zejména v případě, že rozhraní API využívají třetí strany nebo nativní klientské aplikace.

Když se implementace služby změní, je vhodné ji označit verzí. Tato verze poskytuje důležité informace při řešení chyb. Může být velmi užitečné, když analýza původní příčiny přesně zjistí, která verze služby byla volána. Zvažte použití sémantické správy verzí pro verze služby. Sémantická správa verzí používá hlavní. MENŠÍ. Formát PATCH . Klienti by ale měli vybrat rozhraní API pouze podle čísla hlavní verze nebo případně podverze, pokud mezi podverzemi dochází k významným (ale nezlomným) změnám. Jinými slovy je vhodné, aby klienti vybrali mezi verzí 1 a 2 rozhraní API, ale ne s verzí 2.1.3. Pokud povolíte tuto úroveň podrobností, riskujete, že budete muset podporovat rostoucí počet verzí.

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

Idempotentní operace

Operace je idempotentní , pokud ji lze volat vícekrát, aniž by po prvním volání došlo k dalším vedlejším účinkům. Idempotence může být užitečnou strategií odolnosti, protože umožňuje upstreamové službě bezpečně vyvolat operaci vícekrát. Informace o tomto bodu najdete v tématu Distribuované transakce.

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

Metoda požadavku je považována za idempotentní, pokud zamýšlený účinek na server více identických požadavků s danou 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. Ale význam identifikátoru URI je jiný.

  • V případě metody POST představuje identifikátor URI nadřazený prostředek nové entity, například kolekci. Pokud například chcete vytvořit nové doručení, identifikátor URI může být /api/deliveries. 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 Location odpovědi. Pokaždé, když klient odešle požadavek, server vytvoří novou entitu s novým identifikátorem URI.

  • U metody PUT identifikuje identifikátor URI entitu. Pokud entita s tímto identifikátorem URI již existuje, server nahradí existující entitu verzí v požadavku. Pokud s tímto identifikátorem URI neexistuje žádná entita, vytvoří ji server. Předpokládejme například, že klient odešle požadavek PUT na adresu api/deliveries/39660. Za předpokladu, že neexistuje doručení s tímto identifikátorem URI, server vytvoří nový. Pokud teď klient odešle stejný požadavek znovu, server nahradí existují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 zpracovává všechny výjimky duplicitních prostředků tím, že místo toho aktualizuje prostředek.

Další kroky

Přečtěte si o používání brány rozhraní API na hranici mezi klientskými aplikacemi a mikroslužbami.