Implementowanie bram interfejsu API za pomocą rozwiązania Ocelot

Napiwek

Ta zawartość jest fragmentem książki eBook, architektury mikrousług platformy .NET dla konteneryzowanych aplikacji platformy .NET dostępnych na platformie .NET Docs lub jako bezpłatnego pliku PDF, który można odczytać w trybie offline.

.NET Microservices Architecture for Containerized .NET Applications eBook cover thumbnail.

Ważne

Aplikacja mikrousługi referencyjnej eShopOnContainers używa obecnie funkcji udostępnianych przez aplikację Envoy do implementowania bramy interfejsu API zamiast wcześniejszego przywoływanego rozwiązania Ocelot. Dokonaliśmy wyboru tego projektu z powodu wbudowanej obsługi protokołu WebSocket firmy Envoy, wymaganej przez nową komunikację między usługami gRPC zaimplementowaną w eShopOnContainers. Jednak ta sekcja została zachowana w przewodniku, aby można było rozważyć rozwiązanie Ocelot jako prostą, zdolną i uproszczoną bramę interfejsu API odpowiednią dla scenariuszy klasy produkcyjnej. Ponadto najnowsza wersja rozwiązania Ocelot zawiera zmianę powodującą niezgodność w schemacie JSON. Rozważ użycie rozwiązania Ocelot < w wersji 16.0.0 lub użyj kluczy Trasy zamiast usługi ReRoutes.

Tworzenie architektury i projektowanie bram interfejsu API

Poniższy diagram architektury przedstawia sposób implementacji bram interfejsu API za pomocą rozwiązania Ocelot w aplikacji eShopOnContainers.

Diagram showing the eShopOnContainers architecture.

Rysunek 6–28. Architektura eShopOnContainers z bramami interfejsu API

Na tym diagramie pokazano, jak cała aplikacja jest wdrażana na jednym hoście platformy Docker lub komputerze deweloperów z "Docker for Windows" lub "Docker for Mac". Jednak wdrożenie w dowolnym orkiestratorze byłoby podobne, ale każdy kontener na diagramie można skalować w poziomie w orkiestratorze.

Ponadto zasoby infrastruktury, takie jak bazy danych, pamięć podręczna i brokery komunikatów, powinny zostać odciążone z orkiestratora i wdrożone w systemach o wysokiej dostępności dla infrastruktury, takich jak Azure SQL Database, Azure Cosmos DB, Azure Redis, Azure Service Bus lub dowolne rozwiązanie klastrowania wysokiej dostępności w środowisku lokalnym.

Jak widać na diagramie, posiadanie kilku bram interfejsu API umożliwia wielu zespołom deweloperów autonomiczne (w tym przypadku funkcje marketingowe a funkcje zakupów) podczas opracowywania i wdrażania mikrousług oraz własnych powiązanych bram interfejsów API.

Jeśli masz pojedynczą monolityczną bramę interfejsu API, która oznaczałaby, że jeden punkt zostanie zaktualizowany przez kilka zespołów programistycznych, co może połączyć wszystkie mikrousługi z jedną częścią aplikacji.

Przejście znacznie dalej w projekcie, czasami szczegółowe bramy interfejsu API może być również ograniczone do jednej mikrousługi biznesowej w zależności od wybranej architektury. Posiadanie granic bramy interfejsu API dyktowanych przez firmę lub domenę pomoże Ci lepiej zaprojektować.

Na przykład szczegółowość w warstwie bramy interfejsu API może być szczególnie przydatna w przypadku bardziej zaawansowanych złożonych aplikacji interfejsu użytkownika opartych na mikrousługach, ponieważ koncepcja szczegółowej bramy interfejsu API jest podobna do usługi kompozycji interfejsu użytkownika.

Więcej szczegółów znajdziesz w poprzedniej sekcji Tworzenie złożonego interfejsu użytkownika na podstawie mikrousług.

Jako kluczowy wynos w przypadku wielu średnich i dużych aplikacji użycie niestandardowego produktu API Gateway jest zwykle dobrym podejściem, ale nie jednym monolitycznym agregatorem lub unikatową centralną niestandardową bramą interfejsu API Gateway, chyba że usługa API Gateway zezwala na wiele niezależnych obszarów konfiguracji dla kilku zespołów programistycznych tworzących autonomiczne mikrousługi.

Przykładowe mikrousługi/kontenery do przekierowania za pośrednictwem bram interfejsu API

Na przykład eShopOnContainers ma około sześciu wewnętrznych typów mikrousług, które muszą zostać opublikowane za pośrednictwem bram interfejsu API, jak pokazano na poniższej ilustracji.

Screenshot of the Services folder showing its subfolders.

Rysunek 6–29. Foldery mikrousług w rozwiązaniu eShopOnContainers w programie Visual Studio

Informacje o usłudze Identity, w projekcie nie ma routingu bramy interfejsu API, ponieważ jest to jedyny problem dotyczący krzyżowego w systemie, chociaż w przypadku rozwiązania Ocelot można go również uwzględnić w ramach list przekierowania.

Wszystkie te usługi są obecnie implementowane jako usługi internetowego interfejsu API platformy ASP.NET Core, co można określić w kodzie. Skoncentrujmy się na jednej z mikrousług, takich jak kod mikrousługi katalogu.

Screenshot of Solution Explorer showing Catalog.API project contents.

Rysunek 6–30. Przykładowa mikrousługa internetowego interfejsu API (mikrousługa wykazu)

Możesz zobaczyć, że mikrousługę wykazu jest typowym projektem internetowego interfejsu API podstawowego ASP.NET z kilkoma kontrolerami i metodami, takimi jak w poniższym kodzie.

[HttpGet]
[Route("items/{id:int}")]
[ProducesResponseType((int)HttpStatusCode.BadRequest)]
[ProducesResponseType((int)HttpStatusCode.NotFound)]
[ProducesResponseType(typeof(CatalogItem),(int)HttpStatusCode.OK)]
public async Task<IActionResult> GetItemById(int id)
{
    if (id <= 0)
    {
        return BadRequest();
    }
    var item = await _catalogContext.CatalogItems.
                                          SingleOrDefaultAsync(ci => ci.Id == id);
    //…

    if (item != null)
    {
        return Ok(item);
    }
    return NotFound();
}

Żądanie HTTP zakończy się uruchomieniem tego rodzaju kodu języka C#, który uzyskuje dostęp do bazy danych mikrousług i wszelkie dodatkowe wymagane działania.

Jeśli chodzi o adres URL mikrousługi, gdy kontenery są wdrażane na lokalnym komputerze deweloperskim (lokalnym hoście platformy Docker), każdy kontener mikrousługi zawsze ma port wewnętrzny (zazwyczaj port 80) określony w pliku dockerfile, jak w następującym pliku dockerfile:

FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
WORKDIR /app
EXPOSE 80

Port 80 pokazany w kodzie jest wewnętrzny na hoście platformy Docker, więc nie można go uzyskać przez aplikacje klienckie.

Aplikacje klienckie mogą uzyskiwać dostęp tylko do portów zewnętrznych (jeśli istnieją) opublikowanych podczas wdrażania za pomocą docker-composeprogramu .

Te porty zewnętrzne nie powinny być publikowane podczas wdrażania w środowisku produkcyjnym. Z tego konkretnego powodu, dlaczego chcesz użyć bramy interfejsu API, aby uniknąć bezpośredniej komunikacji między aplikacjami klienckimi a mikrousługami.

Jednak podczas opracowywania chcesz uzyskać bezpośredni dostęp do mikrousługi/kontenera i uruchomić go za pośrednictwem struktury Swagger. Dlatego w aplikacjach eShopOnContainers porty zewnętrzne są nadal określane nawet wtedy, gdy nie będą używane przez usługę API Gateway lub aplikacje klienckie.

Oto przykład docker-compose.override.yml pliku dla mikrousługi wykazu:

catalog-api:
  environment:
    - ASPNETCORE_ENVIRONMENT=Development
    - ASPNETCORE_URLS=http://0.0.0.0:80
    - ConnectionString=YOUR_VALUE
    - ... Other Environment Variables
  ports:
    - "5101:80"   # Important: In a production environment you should remove the external port (5101) kept here for microservice debugging purposes.
                  # The API Gateway redirects and access through the internal port (80).

W konfiguracji docker-compose.override.yml można zobaczyć, jak port wewnętrzny kontenera wykazu to port 80, ale port dla dostępu zewnętrznego to 5101. Jednak ten port nie powinien być używany przez aplikację podczas używania bramy interfejsu API tylko do debugowania, uruchamiania i testowania tylko mikrousługi wykazu.

Zwykle nie będziesz wdrażać przy użyciu narzędzia docker-compose w środowisku produkcyjnym, ponieważ odpowiednie środowisko wdrażania produkcyjnego dla mikrousług jest orkiestratorem, na przykład Kubernetes lub Service Fabric. Podczas wdrażania w tych środowiskach używasz różnych plików konfiguracji, w których nie będziesz publikować bezpośrednio żadnego portu zewnętrznego dla mikrousług, ale zawsze będziesz używać zwrotnego serwera proxy z bramy interfejsu API.

Uruchom mikrousługę wykazu na lokalnym hoście platformy Docker. Uruchom pełne rozwiązanie eShopOnContainers z programu Visual Studio (uruchamia wszystkie usługi w plikach docker-compose) lub uruchom mikrousługę katalogu za pomocą następującego polecenia docker-compose w cmD lub powerShell umieszczonym w folderze, w którym docker-compose.yml znajdują się i docker-compose.override.yml .

docker-compose run --service-ports catalog-api

To polecenie uruchamia tylko kontener usługi catalog-api oraz zależności określone w docker-compose.yml. W tym przypadku kontener programu SQL Server i kontener RabbitMQ.

Następnie możesz uzyskać bezpośredni dostęp do mikrousługi wykazu i wyświetlić jej metody za pośrednictwem interfejsu użytkownika struktury Swagger, który uzyskuje dostęp bezpośrednio za pośrednictwem tego portu "zewnętrznego", w tym przypadku http://host.docker.internal:5101/swagger:

Screenshot of Swagger UI showing the Catalog.API REST API.

Rysunek 6–31. Testowanie mikrousługi wykazu przy użyciu interfejsu użytkownika struktury Swagger

W tym momencie można ustawić punkt przerwania w kodzie języka C# w programie Visual Studio, przetestować mikrousługę przy użyciu metod uwidocznionych w interfejsie użytkownika struktury Swagger, a na koniec wyczyścić wszystko za docker-compose down pomocą polecenia .

Jednak bezpośredni dostęp do mikrousługi, w tym przypadku za pośrednictwem portu zewnętrznego 5101, jest dokładnie tym, czego chcesz uniknąć w aplikacji. Można tego uniknąć, ustawiając dodatkowy poziom pośredni bramy interfejsu API (Ocelot, w tym przypadku). Dzięki temu aplikacja kliencka nie będzie bezpośrednio uzyskiwać dostępu do mikrousługi.

Implementowanie bram interfejsu API za pomocą rozwiązania Ocelot

Ocelot to w zasadzie zestaw oprogramowania pośredniczącego, który można zastosować w określonej kolejności.

Rozwiązanie Ocelot jest przeznaczone tylko do pracy z platformą ASP.NET Core. Najnowsza wersja pakietu to 18.0, która jest przeznaczona dla platformy .NET 6, dlatego nie jest odpowiednia dla aplikacji .NET Framework.

Instalujesz rozwiązanie Ocelot i jego zależności w projekcie ASP.NET Core z pakietem NuGet rozwiązania Ocelot z poziomu programu Visual Studio.

Install-Package Ocelot

W aplikacji eShopOnContainers implementacja bramy interfejsu API jest prostym projektem ASP.NET Core WebHost, a oprogramowanie pośredniczące firmy Ocelot obsługuje wszystkie funkcje usługi API Gateway, jak pokazano na poniższej ilustracji:

Screenshot of Solution Explorer showing Ocelot API gateway project.

Rysunek 6–32. Projekt podstawowy OcelotApiGw w eShopOnContainers

Ten projekt ASP.NET Core WebHost jest kompilowany z dwoma prostymi plikami: Program.cs i Startup.cs.

Program.cs wystarczy utworzyć i skonfigurować typowe ASP.NET Core BuildWebHost.

namespace OcelotApiGw
{
    public class Program
    {
        public static void Main(string[] args)
        {
            BuildWebHost(args).Run();
        }

        public static IWebHost BuildWebHost(string[] args)
        {
            var builder = WebHost.CreateDefaultBuilder(args);

            builder.ConfigureServices(s => s.AddSingleton(builder))
                    .ConfigureAppConfiguration(
                          ic => ic.AddJsonFile(Path.Combine("configuration",
                                                            "configuration.json")))
                    .UseStartup<Startup>();
            var host = builder.Build();
            return host;
        }
    }
}

Ważnym punktem w przypadku rozwiązania Ocelot jest configuration.json plik, który należy podać konstruktorowi AddJsonFile() za pomocą metody . W configuration.json tym miejscu należy określić wszystkie przekierowania bramy interfejsu API, co oznacza, że zewnętrzne punkty końcowe z określonymi portami i skorelowane wewnętrzne punkty końcowe zwykle używają różnych portów.

{
    "ReRoutes": [],
    "GlobalConfiguration": {}
}

Konfiguracja obejmuje dwie sekcje. Tablica usług ReRoutes i GlobalConfiguration. ReRoutes to obiekty, które informują Ocelot, jak traktować żądanie nadrzędne. Konfiguracja globalna umożliwia przesłonięcia ustawień specyficznych dla usługi ReRoute. Jest to przydatne, jeśli nie chcesz zarządzać wieloma ustawieniami specyficznymi dla usługi ReRoute.

Oto uproszczony przykład pliku konfiguracji usługi ReRoute z jednej z bram interfejsu API z aplikacji eShopOnContainers.

{
  "ReRoutes": [
    {
      "DownstreamPathTemplate": "/api/{version}/{everything}",
      "DownstreamScheme": "http",
      "DownstreamHostAndPorts": [
        {
          "Host": "catalog-api",
          "Port": 80
        }
      ],
      "UpstreamPathTemplate": "/api/{version}/c/{everything}",
      "UpstreamHttpMethod": [ "POST", "PUT", "GET" ]
    },
    {
      "DownstreamPathTemplate": "/api/{version}/{everything}",
      "DownstreamScheme": "http",
      "DownstreamHostAndPorts": [
        {
          "Host": "basket-api",
          "Port": 80
        }
      ],
      "UpstreamPathTemplate": "/api/{version}/b/{everything}",
      "UpstreamHttpMethod": [ "POST", "PUT", "GET" ],
      "AuthenticationOptions": {
        "AuthenticationProviderKey": "IdentityApiKey",
        "AllowedScopes": []
      }
    }

  ],
    "GlobalConfiguration": {
      "RequestIdKey": "OcRequestId",
      "AdministrationPath": "/administration"
    }
  }

Główną funkcją bramy interfejsu API Ocelot jest odbieranie przychodzących żądań HTTP i przekazywanie ich do usługi podrzędnej, obecnie jako kolejne żądanie HTTP. Ocelot opisuje routing jednego żądania do innego jako reroute.

Na przykład skoncentrujmy się na jednej z tras w configuration.json powyżej konfiguracji mikrousługi Koszyk.

{
      "DownstreamPathTemplate": "/api/{version}/{everything}",
      "DownstreamScheme": "http",
      "DownstreamHostAndPorts": [
        {
          "Host": "basket-api",
          "Port": 80
        }
      ],
      "UpstreamPathTemplate": "/api/{version}/b/{everything}",
      "UpstreamHttpMethod": [ "POST", "PUT", "GET" ],
      "AuthenticationOptions": {
        "AuthenticationProviderKey": "IdentityApiKey",
        "AllowedScopes": []
      }
}

Wartości DownstreamPathTemplate, Scheme i DownstreamHostAndPorts tworzą wewnętrzny adres URL mikrousługi, do którego zostanie przekazane to żądanie.

Port jest portem wewnętrznym używanym przez usługę. W przypadku korzystania z kontenerów port określony w pliku dockerfile.

To Host nazwa usługi, która zależy od używanego rozpoznawania nazw usługi. W przypadku korzystania z narzędzia docker-compose nazwy usług są udostępniane przez hosta platformy Docker, który używa nazw usług podanych w plikach docker-compose. Jeśli używasz orkiestratora, takiego jak Kubernetes lub Service Fabric, ta nazwa powinna być rozpoznawana przez system DNS lub rozpoznawanie nazw udostępniane przez każdego orkiestratora.

DownstreamHostAndPorts to tablica zawierająca hosta i port wszystkich usług podrzędnych, do których chcesz przekazywać żądania. Zazwyczaj ta konfiguracja będzie zawierać tylko jeden wpis, ale czasami może być konieczne równoważenie obciążenia żądań do usług podrzędnych, a rozwiązanie Ocelot umożliwia dodanie więcej niż jednego wpisu, a następnie wybranie modułu równoważenia obciążenia. Jeśli jednak korzystasz z platformy Azure i dowolnego koordynatora, prawdopodobnie lepiej jest zrównoważyć obciążenie infrastrukturą chmury i orkiestratora.

UpstreamPathTemplate to adres URL, który będzie używany przez usługę Ocelot w celu zidentyfikowania elementu DownstreamPathTemplate do użycia dla danego żądania od klienta. Na koniec używany jest element UpstreamHttpMethod, dzięki czemu rozwiązanie Ocelot może rozróżniać różne żądania (GET, POST, PUT) na ten sam adres URL.

W tym momencie można mieć jedną bramę interfejsu API Ocelot (ASP.NET Core WebHost) przy użyciu jednego lub wielu scalonych plików configuration.json lub można również przechowywać konfigurację w magazynie KV consul.

Jednak zgodnie z opisem w sekcjach architektury i projektowania, jeśli naprawdę chcesz mieć autonomiczne mikrousługi, lepszym rozwiązaniem może być podzielenie tej pojedynczej monolitycznej bramy interfejsu API na wiele bram interfejsów API i/lub BFF (zaplecze frontonu). W tym celu zobaczmy, jak zaimplementować to podejście za pomocą kontenerów platformy Docker.

Używanie pojedynczego obrazu kontenera platformy Docker do uruchamiania wielu różnych typów kontenerów usługi API Gateway/BFF

W usłudze eShopOnContainers używamy pojedynczego obrazu kontenera platformy Docker z bramą interfejsu API Ocelot, ale w czasie wykonywania tworzymy różne usługi/kontenery dla każdego typu interfejsu API-Gateway/BFF, udostępniając inny plik configuration.json przy użyciu woluminu platformy Docker w celu uzyskania dostępu do innego folderu komputera dla każdej usługi.

Diagram of a single Ocelot gateway Docker image for all API gateways.

Rysunek 6–33. Ponowne tworzenie pojedynczego obrazu platformy Docker rozwiązania Ocelot w wielu typach bramy interfejsu API

W aplikacji eShopOnContainers zostanie utworzony "Ogólny obraz platformy Docker bramy interfejsu API Ocelot" z projektem o nazwie "OcelotApiGw" i nazwą obrazu "eshop/ocelotapigw", który jest określony w pliku docker-compose.yml. Następnie podczas wdrażania na platformie Docker będą istnieć cztery kontenery usługi API-Gateway utworzone na podstawie tego samego obrazu platformy Docker, jak pokazano w poniższym wyodrębnieniu z pliku docker-compose.yml.

  mobileshoppingapigw:
    image: eshop/ocelotapigw:${TAG:-latest}
    build:
      context: .
      dockerfile: src/ApiGateways/ApiGw-Base/Dockerfile

  mobilemarketingapigw:
    image: eshop/ocelotapigw:${TAG:-latest}
    build:
      context: .
      dockerfile: src/ApiGateways/ApiGw-Base/Dockerfile

  webshoppingapigw:
    image: eshop/ocelotapigw:${TAG:-latest}
    build:
      context: .
      dockerfile: src/ApiGateways/ApiGw-Base/Dockerfile

  webmarketingapigw:
    image: eshop/ocelotapigw:${TAG:-latest}
    build:
      context: .
      dockerfile: src/ApiGateways/ApiGw-Base/Dockerfile

Ponadto, jak widać w poniższym pliku docker-compose.override.yml, jedyną różnicą między tymi kontenerami usługi API Gateway jest plik konfiguracji rozwiązania Ocelot, który różni się dla każdego kontenera usługi i jest określony w czasie wykonywania za pośrednictwem woluminu platformy Docker.

mobileshoppingapigw:
  environment:
    - ASPNETCORE_ENVIRONMENT=Development
    - IdentityUrl=http://identity-api
  ports:
    - "5200:80"
  volumes:
    - ./src/ApiGateways/Mobile.Bff.Shopping/apigw:/app/configuration

mobilemarketingapigw:
  environment:
    - ASPNETCORE_ENVIRONMENT=Development
    - IdentityUrl=http://identity-api
  ports:
    - "5201:80"
  volumes:
    - ./src/ApiGateways/Mobile.Bff.Marketing/apigw:/app/configuration

webshoppingapigw:
  environment:
    - ASPNETCORE_ENVIRONMENT=Development
    - IdentityUrl=http://identity-api
  ports:
    - "5202:80"
  volumes:
    - ./src/ApiGateways/Web.Bff.Shopping/apigw:/app/configuration

webmarketingapigw:
  environment:
    - ASPNETCORE_ENVIRONMENT=Development
    - IdentityUrl=http://identity-api
  ports:
    - "5203:80"
  volumes:
    - ./src/ApiGateways/Web.Bff.Marketing/apigw:/app/configuration

Ze względu na ten poprzedni kod, jak pokazano w eksploratorze programu Visual Studio poniżej, jedynym plikiem wymaganym do zdefiniowania poszczególnych bram biznesowych/BFF API Gateway jest tylko plik configuration.json, ponieważ cztery bramy interfejsu API są oparte na tym samym obrazie platformy Docker.

Screenshot showing all API gateways with configuration.json files.

Rysunek 6–34. Jedynym plikiem wymaganym do zdefiniowania każdej bramy interfejsu API /BFF z rozwiązaniem Ocelot jest plik konfiguracji

Dzieląc bramę interfejsu API na wiele bram interfejsów API, różne zespoły programistyczne koncentrujące się na różnych podzestawach mikrousług mogą zarządzać własnymi bramami interfejsów API przy użyciu niezależnych plików konfiguracji rozwiązania Ocelot. Ponadto jednocześnie mogą ponownie używać tego samego obrazu platformy Ocelot Docker.

Teraz, jeśli uruchomisz aplikację eShopOnContainers z bramami interfejsu API (domyślnie dołączonymi do programu VS podczas otwierania rozwiązania eShopOnContainers-ServicesAndWebApps.sln lub w przypadku uruchomienia polecenia "docker-compose up"), zostaną wykonane następujące przykładowe trasy.

Na przykład podczas odwiedzania nadrzędnego adresu URL http://host.docker.internal:5202/api/v1/c/catalog/items/2/ obsługiwanego przez bramę interfejsu API Webshoppingapigw uzyskasz ten sam wynik z wewnętrznego adresu URL http://catalog-api/api/v1/2 podrzędnego na hoście platformy Docker, jak w poniższej przeglądarce.

Screenshot of a browser showing a response going through API gateway.

Rysunek 6–35. Uzyskiwanie dostępu do mikrousługi za pośrednictwem adresu URL dostarczonego przez bramę interfejsu API

Ze względu na testowanie lub debugowanie przyczyn, jeśli chcesz bezpośrednio uzyskać dostęp do kontenera Catalog Docker (tylko w środowisku deweloperskim) bez przechodzenia przez bramę interfejsu API, ponieważ "catalog-api" jest rozpoznawaniem dns wewnętrznym hosta platformy Docker (odnajdywanie usługi obsługiwane przez nazwy usługi docker-compose), jedynym sposobem bezpośredniego uzyskiwania dostępu do kontenera jest za pośrednictwem portu zewnętrznego opublikowanego w docker-compose.override.yml, który jest udostępniany tylko w przypadku testów programistycznych, takich jak http://host.docker.internal:5101/api/v1/Catalog/items/1 w poniższej przeglądarce.

Screenshot of a browser showing a direct response to the Catalog.api.

Rysunek 6–36. Bezpośredni dostęp do mikrousługi na potrzeby testowania

Jednak aplikacja jest skonfigurowana tak, aby uzyskiwała dostęp do wszystkich mikrousług za pośrednictwem bram interfejsu API, a nie za pośrednictwem bezpośredniego portu "skrótów".

Wzorzec agregacji bramy w eShopOnContainers

Jak wspomniano wcześniej, elastycznym sposobem implementowania agregacji żądań jest użycie usług niestandardowych według kodu. Możesz również zaimplementować agregację żądań za pomocą funkcji Agregacja żądań w rozwiązaniu Ocelot, ale może nie być tak elastyczna, jak potrzebujesz. W związku z tym wybrany sposób implementowania agregacji w aplikacji eShopOnContainers jest jawną usługą internetowego interfejsu API platformy ASP.NET Core dla każdego agregatora.

Zgodnie z tym podejściem diagram kompozycji bramy API Gateway jest w rzeczywistości nieco bardziej rozszerzony, biorąc pod uwagę usługi agregatora, które nie są wyświetlane na uproszczonym diagramie architektury globalnej pokazanym wcześniej.

Na poniższym diagramie można również zobaczyć, jak usługi agregatora współpracują z powiązanymi bramami interfejsu API.

Diagram of eShopOnContainers architecture showing aggregator services.

Rysunek 6–37. Architektura eShopOnContainers z usługami agregatora

Powiększ obszar biznesowy "Zakupy" na poniższej ilustracji widać, że czattiness między aplikacjami klienckimi a mikrousługami jest zmniejszany w przypadku korzystania z usług agregatora w bramach interfejsu API.

Diagram showing eShopOnContainers architecture zoom in.

Rysunek 6–38. Powiększanie wizji usług agregatora

Możesz zauważyć, że gdy diagram pokazuje możliwe żądania pochodzące z bram interfejsu API, które mogą uzyskać złożone. Z drugiej strony, gdy używasz wzorca agregatora, możesz zobaczyć, jak strzałki na niebiesko uprościłyby komunikację z perspektywy aplikacji klienckiej. Ten wzorzec nie tylko pomaga zmniejszyć pogawędność i opóźnienia w komunikacji, ale także znacznie poprawia środowisko użytkownika dla aplikacji zdalnych (aplikacji mobilnych i SPA).

W przypadku obszaru biznesowego "Marketing" i mikrousług jest to prosty przypadek użycia, więc nie było potrzeby używania agregatorów, ale może być również możliwe, jeśli to konieczne.

Uwierzytelnianie i autoryzacja w bramach interfejsu API rozwiązania Ocelot

W bramie interfejsu API rozwiązania Ocelot możesz siedzieć usługę uwierzytelniania, taką jak usługa internetowego interfejsu API podstawowego ASP.NET przy użyciu usługi IdentityServer , która udostępnia token uwierzytelniania , na out lub wewnątrz bramy interfejsu API.

Ponieważ usługa eShopOnContainers korzysta z wielu bram interfejsu API z granicami opartymi na usługach BFF i biznesowych, usługa Identity/Auth jest pominięta w bramach interfejsu API, jak wyróżniono na żółtym diagramie.

Diagram showing Identity microservice beneath the API gateway.

Rysunek 6–39. Pozycja usługi Identity w eShopOnContainers

Jednak rozwiązanie Ocelot obsługuje również obsługę mikrousługi Identity/Auth w granicach bramy interfejsu API, jak na tym innym diagramie.

Diagram showing authentication in an Ocelot API Gateway.

Rysunek 6–40. Uwierzytelnianie w rozwiązaniu Ocelot

Jak pokazano na poprzednim diagramie, gdy mikrousługa Identity znajduje się pod bramą interfejsu API (AG): 1) grupa dostępności żąda tokenu uwierzytelniania z mikrousługi tożsamości, 2) Mikrousługa tożsamości zwraca token do grupy dostępności, 3–4) żądania grupy dostępności z mikrousług przy użyciu tokenu uwierzytelniania. Ponieważ aplikacja eShopOnContainers podzieliła bramę interfejsu API na wiele bram BFF (backend for Frontend) i obszarów biznesowych bramy interfejsu API, kolejną opcją byłoby utworzenie dodatkowej bramy interfejsu API dla problemów obejmujących wiele cięć. Wybór ten byłby sprawiedliwy w bardziej złożonej architekturze opartej na mikrousługach z wieloma mikrousługami wielociągowymi. Ponieważ w eShopOnContainers istnieje tylko jeden problem krzyżowy, zdecydowano się po prostu obsłużyć usługę zabezpieczeń z obszaru bramy interfejsu API, dla uproszczenia.

W każdym razie, jeśli aplikacja jest zabezpieczona na poziomie bramy interfejsu API, moduł uwierzytelniania bramy interfejsu API Ocelot zostanie odwiedzony na początku podczas próby użycia dowolnej zabezpieczonej mikrousługi. To przekierowuje żądanie HTTP, aby odwiedzić mikrousługę Tożsamości lub uwierzytelniania w celu uzyskania tokenu dostępu, aby można było odwiedzić chronione usługi za pomocą access_token.

Sposób zabezpieczania za pomocą uwierzytelniania dowolnej usługi na poziomie bramy interfejsu API polega na ustawieniu wartości AuthenticationProviderKey w powiązanych ustawieniach w configuration.json.

    {
      "DownstreamPathTemplate": "/api/{version}/{everything}",
      "DownstreamScheme": "http",
      "DownstreamHostAndPorts": [
        {
          "Host": "basket-api",
          "Port": 80
        }
      ],
      "UpstreamPathTemplate": "/api/{version}/b/{everything}",
      "UpstreamHttpMethod": [],
      "AuthenticationOptions": {
        "AuthenticationProviderKey": "IdentityApiKey",
        "AllowedScopes": []
      }
    }

Po uruchomieniu rozwiązania Ocelot przyjrzy się elementowi AuthenticationOptions.AuthenticationProviderKey usługi ReRoutes i sprawdzi, czy istnieje dostawca uwierzytelniania zarejestrowany w danym kluczu. Jeśli tak nie jest, to Ocelot nie uruchomi się. Jeśli tak jest, usługa ReRoute będzie używać tego dostawcy podczas jego wykonywania.

Ponieważ host sieci Web Ocelot jest skonfigurowany przy authenticationProviderKey = "IdentityApiKey"użyciu elementu , który będzie wymagał uwierzytelniania za każdym razem, gdy usługa ma żądania bez tokenu uwierzytelniania.

namespace OcelotApiGw
{
    public class Startup
    {
        private readonly IConfiguration _cfg;

        public Startup(IConfiguration configuration) => _cfg = configuration;

        public void ConfigureServices(IServiceCollection services)
        {
            var identityUrl = _cfg.GetValue<string>("IdentityUrl");
            var authenticationProviderKey = "IdentityApiKey";
                         //…
            services.AddAuthentication()
                .AddJwtBearer(authenticationProviderKey, x =>
                {
                    x.Authority = identityUrl;
                    x.RequireHttpsMetadata = false;
                    x.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters()
                    {
                        ValidAudiences = new[] { "orders", "basket", "locations", "marketing", "mobileshoppingagg", "webshoppingagg" }
                    };
                });
            //...
        }
    }
}

Następnie należy również ustawić autoryzację za pomocą atrybutu [Authorize] dla dowolnego zasobu, aby uzyskać dostęp do takich mikrousług, jak w następującym kontrolerze mikrousług koszyka.

namespace Microsoft.eShopOnContainers.Services.Basket.API.Controllers
{
    [Route("api/v1/[controller]")]
    [Authorize]
    public class BasketController : Controller
    {
      //...
    }
}

Klasy ValidAudiences, takie jak "koszyk", są skorelowane z odbiorcami zdefiniowanymi w każdej mikrousłudze przy AddJwtBearer() użyciu klasy ConfigureServices(), takiej jak w poniższym kodzie.

// prevent from mapping "sub" claim to nameidentifier.
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();

var identityUrl = Configuration.GetValue<string>("IdentityUrl");

services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;

}).AddJwtBearer(options =>
{
    options.Authority = identityUrl;
    options.RequireHttpsMetadata = false;
    options.Audience = "basket";
});

Jeśli spróbujesz uzyskać dostęp do dowolnej zabezpieczonej mikrousługi, takiej jak mikrousługa Koszyk z adresem URL usługi ReRoute na podstawie bramy interfejsu API, na przykład http://host.docker.internal:5202/api/v1/b/basket/1, otrzymasz 401 Brak autoryzacji, chyba że podasz prawidłowy token. Z drugiej strony, jeśli adres URL usługi ReRoute jest uwierzytelniony, rozwiązanie Ocelot wywoła dowolny schemat podrzędny skojarzony z nim (wewnętrzny adres URL mikrousługi).

Autoryzacja w warstwie ReRoutes rozwiązania Ocelot. Rozwiązanie Ocelot obsługuje autoryzację opartą na oświadczeniach ocenianą po uwierzytelnieniu. Autoryzację można ustawić na poziomie trasy, dodając następujące wiersze do konfiguracji usługi ReRoute.

"RouteClaimsRequirement": {
    "UserType": "employee"
}

W tym przykładzie po wywołaniu oprogramowania pośredniczącego autoryzacji Ocelot znajdzie, czy użytkownik ma typ oświadczenia "UserType" w tokenie, a wartość tego oświadczenia to "employee". Jeśli tak nie jest, użytkownik nie będzie autoryzowany, a odpowiedź będzie 403 zabronione.

Korzystanie z ruchu przychodzącego kubernetes i bram interfejsu API rozwiązania Ocelot

W przypadku korzystania z platformy Kubernetes (na przykład w klastrze usługi Azure Kubernetes Service) zwykle ujednolicisz wszystkie żądania HTTP za pośrednictwem warstwy Ruchu przychodzącego Kubernetes na podstawie serwera Nginx.

Jeśli na platformie Kubernetes nie używasz żadnego podejścia przychodzącego, usługi i zasobniki mają adresy IP tylko do routingu przez sieć klastra.

Jeśli jednak używasz podejścia przychodzącego, będziesz mieć warstwę środkową między Internetem a usługami (w tym bramami interfejsu API), działając jako zwrotny serwer proxy.

Jako definicja ruch przychodzący jest kolekcją reguł, które umożliwiają nawiązywanie połączeń przychodzących do usług klastra. Ruch przychodzący jest skonfigurowany do dostarczania usług zewnętrznie osiągalnych adresów URL, równoważenia obciążenia ruchu, kończenia żądań SSL i nie tylko. Użytkownicy żądają ruchu przychodzącego przez poSTing zasobu ruchu przychodzącego do serwera interfejsu API.

W aplikacji eShopOnContainers podczas opracowywania lokalnego i używania tylko maszyny dewelopera jako hosta platformy Docker nie używasz żadnych ruchu przychodzącego, ale tylko wielu bram interfejsów API.

Jednak w przypadku określania wartości docelowej środowiska "produkcyjnego" na podstawie platformy Kubernetes usługa eShopOnContainers korzysta z ruchu przychodzącego przed bramami interfejsu API. W ten sposób klienci nadal nazywają ten sam podstawowy adres URL, ale żądania są kierowane do wielu bram interfejsu API lub BFF.

Bramy interfejsu API to frontony lub fasady, które są widoczne tylko dla usług, ale nie aplikacji internetowych, które zwykle są poza ich zakresem. Ponadto bramy interfejsu API mogą ukrywać pewne wewnętrzne mikrousługi.

Ruch przychodzący jest jednak tylko przekierowywaniem żądań HTTP, ale nie próbuje ukryć żadnej mikrousługi lub aplikacji internetowej.

Posiadanie warstwy Nginx ruchu przychodzącego na platformie Kubernetes przed aplikacjami internetowymi oraz kilku bram interfejsów API rozwiązania Ocelot / BFF jest idealną architekturą, jak pokazano na poniższym diagramie.

A diagram showing how an ingress tier fits into the AKS environment.

Rysunek 6–41. Warstwa ruchu przychodzącego w module eShopOnContainers podczas wdrażania w usłudze Kubernetes

Ruch przychodzący Kubernetes działa jako zwrotny serwer proxy dla całego ruchu do aplikacji, w tym aplikacji internetowych, które są poza zakresem bramy interfejsu API. Podczas wdrażania aplikacji eShopOnContainers na platformie Kubernetes uwidacznia ona tylko kilka usług lub punktów końcowych za pośrednictwem ruchu przychodzącego, zasadniczo następującą listę postfiksów na adresach URL:

  • / dla aplikacji internetowej SPA klienta
  • /webmvc dla aplikacji internetowej MVC klienta
  • /webstatus dla aplikacji internetowej klienta z wyświetlonym stanem/sprawdzaniem kondycji
  • /webshoppingapigw dla internetowych procesów biznesowych BFF i zakupów
  • /webmarketingapigw dla internetowych procesów biznesowych BFF i marketingu
  • /mobileshoppingapigw dla mobilnych procesów biznesowych BFF i zakupów
  • /mobilemarketingapigw dla mobilnych procesów biznesowych BFF i marketingu

Podczas wdrażania na platformie Kubernetes każda brama interfejsu API Ocelot używa innego pliku "configuration.json" dla każdego zasobnika z bramami interfejsu API. Te pliki "configuration.json" są dostarczane przez instalowanie (pierwotnie ze skryptem deploy.ps1) woluminu utworzonego na podstawie mapy konfiguracji Kubernetes o nazwie "ocelot". Każdy kontener instaluje powiązany plik konfiguracji w folderze kontenera o nazwie /app/configuration.

W plikach kodu źródłowego eShopOnContainers oryginalne pliki "configuration.json" można znaleźć w folderze k8s/ocelot/ . Istnieje jeden plik dla każdego elementu BFF/APIGateway.

Dodatkowe funkcje wycinania krzyżowego w bramie interfejsu API rozwiązania Ocelot

Istnieją inne ważne funkcje do badania i używania, w przypadku korzystania z bramy interfejsu API Ocelot opisane w poniższych linkach.