Custom caching in Azure API Management (Aangepast opslaan in Azure API Management)

VAN TOEPASSING OP: Alle API Management-lagen

De Azure API Management-service biedt ingebouwde ondersteuning voor http-antwoordcaching met behulp van de resource-URL als sleutel. De sleutel kan worden gewijzigd door aanvraagheaders met behulp van de vary-by eigenschappen. Dit is handig voor het opslaan van volledige HTTP-antwoorden (ook wel weergaven genoemd), maar soms is het handig om een deel van een weergave in de cache op te cachen. Het cache-zoekwaarde - en cache-store-waardebeleid biedt de mogelijkheid om willekeurige stukjes gegevens op te slaan en op te halen uit beleidsdefinities. Deze mogelijkheid voegt ook waarde toe aan het beleid voor verzenden-aanvragen, omdat u reacties van externe services in de cache kunt opslaan.

Architectuur

API Management-service maakt gebruik van een gedeelde interne gegevenscache per tenant, zodat u, wanneer u omhoog schaalt naar meerdere eenheden, nog steeds toegang krijgt tot dezelfde gegevens in de cache. Bij het werken met een implementatie met meerdere regio's zijn er echter onafhankelijke caches binnen elk van de regio's. Het is belangrijk om de cache niet te behandelen als een gegevensarchief, waarbij het de enige bron van bepaalde informatie is. Als u dat hebt gedaan en later hebt besloten om te profiteren van de implementatie in meerdere regio's, hebben klanten met gebruikers die reizen mogelijk geen toegang meer tot die gegevens in de cache.

Notitie

De interne cache is niet beschikbaar in de verbruikslaag van Azure API Management. U kunt in plaats daarvan een externe Azure Cache voor Redis gebruiken. Een externe cache biedt meer cachebeheer en flexibiliteit voor API Management-exemplaren in alle lagen.

Fragmentcaching

Er zijn bepaalde gevallen waarin reacties die worden geretourneerd, een deel van de gegevens bevatten dat duur is om te bepalen en toch gedurende een redelijke tijd vers blijft. Denk bijvoorbeeld aan een service die is gebouwd door een luchtvaartmaatschappij die informatie biedt met betrekking tot vluchtreserveringen, vluchtstatus, enzovoort. Als de gebruiker lid is van het programma voor luchtvaartmaatschappijenpunten, zou hij ook informatie hebben met betrekking tot hun huidige status en geaccumuleerde kilometers. Deze gebruikersgerelateerde informatie kan worden opgeslagen in een ander systeem, maar het kan wenselijk zijn om deze op te nemen in antwoorden die worden geretourneerd over de vluchtstatus en reserveringen. Dit kan worden gedaan met behulp van een proces met de naam fragmentcaching. De primaire weergave kan worden geretourneerd van de oorspronkelijke server met behulp van een bepaald type token om aan te geven waar de gebruikersgerelateerde informatie moet worden ingevoegd.

Bekijk het volgende JSON-antwoord van een back-end-API.

{
  "airline" : "Air Canada",
  "flightno" : "871",
  "status" : "ontime",
  "gate" : "B40",
  "terminal" : "2A",
  "userprofile" : "$userprofile$"
}  

En secundaire resource /userprofile/{userid} ziet er als volgt uit:

{ "username" : "Bob Smith", "Status" : "Gold" }

Om de juiste gebruikersgegevens te bepalen die moeten worden opgenomen, moet API Management bepalen wie de eindgebruiker is. Dit mechanisme is afhankelijk van de implementatie. In het volgende voorbeeld wordt de Subject claim van een JWT token gebruikt.

<set-variable
  name="enduserid"
  value="@(context.Request.Headers.GetValueOrDefault("Authorization","").Split(' ')[1].AsJwt()?.Subject)" />

API Management slaat de enduserid waarde op in een contextvariabele voor later gebruik. De volgende stap is om te bepalen of een vorige aanvraag de gebruikersgegevens al heeft opgehaald en in de cache heeft opgeslagen. Hiervoor maakt API Management gebruik van het cache-lookup-value beleid.

<cache-lookup-value
key="@("userprofile-" + context.Variables["enduserid"])"
variable-name="userprofile" />

Als er geen vermelding in de cache is die overeenkomt met de sleutelwaarde, wordt er geen userprofile contextvariabele gemaakt. API Management controleert het succes van de zoekactie met behulp van het choose controlestroombeleid.

<choose>
    <when condition="@(!context.Variables.ContainsKey("userprofile"))">
        <!-- If the userprofile context variable doesn’t exist, make an HTTP request to retrieve it.  -->
    </when>
</choose>

Als de userprofile contextvariabele niet bestaat, moet API Management een HTTP-aanvraag indienen om deze op te halen.

<send-request
  mode="new"
  response-variable-name="userprofileresponse"
  timeout="10"
  ignore-error="true">

  <!-- Build a URL that points to the profile for the current end-user -->
  <set-url>@(new Uri(new Uri("https://apimairlineapi.azurewebsites.net/UserProfile/"),
      (string)context.Variables["enduserid"]).AbsoluteUri)
  </set-url>
  <set-method>GET</set-method>
</send-request>

API Management maakt gebruik van de enduserid URL voor de resource van het gebruikersprofiel. Zodra API Management het antwoord heeft, wordt de hoofdtekst uit het antwoord gehaald en weer opgeslagen in een contextvariabele.

<set-variable
    name="userprofile"
    value="@(((IResponse)context.Variables["userprofileresponse"]).Body.As<string>())" />

Als u wilt voorkomen dat API Management deze HTTP-aanvraag opnieuw maakt, kunt u opgeven dat het gebruikersprofiel in de cache moet worden opgeslagen wanneer dezelfde gebruiker een andere aanvraag indient.

<cache-store-value
    key="@("userprofile-" + context.Variables["enduserid"])"
    value="@((string)context.Variables["userprofile"])" duration="100000" />

API Management slaat de waarde op in de cache met dezelfde sleutel waarmee API Management oorspronkelijk heeft geprobeerd deze op te halen. De duur die API Management kiest om de waarde op te slaan, moet zijn gebaseerd op hoe vaak de informatie verandert en hoe tolerant gebruikers verouderde informatie hebben.

Het is belangrijk te beseffen dat het ophalen uit de cache nog steeds een niet-verwerkte netwerkaanvraag is en mogelijk tientallen milliseconden aan de aanvraag kan toevoegen. De voordelen zijn bij het bepalen van de gebruikersprofielgegevens langer dan dat omdat u databasequery's moet uitvoeren of gegevens van meerdere back-ends wilt aggregeren.

De laatste stap in het proces is het bijwerken van het geretourneerde antwoord met de gebruikersprofielgegevens.

<!-- Update response body with user profile-->
<find-and-replace
    from='"$userprofile$"'
    to="@((string)context.Variables["userprofile"])" />

U kunt ervoor kiezen om de aanhalingstekens op te nemen als onderdeel van het token, zodat zelfs wanneer de vervanging niet plaatsvindt, het antwoord nog steeds een geldige JSON is.

Zodra u deze stappen combineert, is het eindresultaat een beleid dat lijkt op de volgende.

<policies>
    <inbound>
        <!-- How you determine user identity is application dependent -->
        <set-variable
          name="enduserid"
          value="@(context.Request.Headers.GetValueOrDefault("Authorization","").Split(' ')[1].AsJwt()?.Subject)" />

        <!--Look for userprofile for this user in the cache -->
        <cache-lookup-value
          key="@("userprofile-" + context.Variables["enduserid"])"
          variable-name="userprofile" />

        <!-- If API Management doesn’t find it in the cache, make a request for it and store it -->
        <choose>
            <when condition="@(!context.Variables.ContainsKey("userprofile"))">
                <!-- Make HTTP request to get user profile -->
                <send-request
                  mode="new"
                  response-variable-name="userprofileresponse"
                  timeout="10"
                  ignore-error="true">

                   <!-- Build a URL that points to the profile for the current end-user -->
                    <set-url>@(new Uri(new Uri("https://apimairlineapi.azurewebsites.net/UserProfile/"),(string)context.Variables["enduserid"]).AbsoluteUri)</set-url>
                    <set-method>GET</set-method>
                </send-request>

                <!-- Store response body in context variable -->
                <set-variable
                  name="userprofile"
                  value="@(((IResponse)context.Variables["userprofileresponse"]).Body.As<string>())" />

                <!-- Store result in cache -->
                <cache-store-value
                  key="@("userprofile-" + context.Variables["enduserid"])"
                  value="@((string)context.Variables["userprofile"])"
                  duration="100000" />
            </when>
        </choose>
        <base />
    </inbound>
    <outbound>
        <!-- Update response body with user profile-->
        <find-and-replace
              from='"$userprofile$"'
              to="@((string)context.Variables["userprofile"])" />
        <base />
    </outbound>
</policies>

Deze cachingbenadering wordt voornamelijk gebruikt in websites waar HTML is samengesteld aan de serverzijde, zodat deze kan worden weergegeven als één pagina. Het kan ook handig zijn in API's waarbij clients http-caching aan clientzijde niet kunnen uitvoeren of het wenselijk is om die verantwoordelijkheid niet op de client te plaatsen.

Dit soort fragmentcaching kan ook worden uitgevoerd op de back-endwebservers met behulp van een Redis-cacheserver, maar het gebruik van de API Management-service om dit werk uit te voeren is handig wanneer de fragmenten in de cache afkomstig zijn van verschillende back-ends dan de primaire antwoorden.

Transparante versiebeheer

Het is gebruikelijk dat meerdere verschillende implementatieversies van een API op elk gewenst moment worden ondersteund. Bijvoorbeeld om verschillende omgevingen te ondersteunen (dev, test, productie, enzovoort) of om oudere versies van de API te ondersteunen om gebruikers van de API tijd te geven om te migreren naar nieuwere versies.

Een manier om dit te verwerken, in plaats van clientontwikkelaars te verplichten om de URL's /v1/customers/v2/customers te wijzigen, is door de profielgegevens van de consument op te slaan in welke versie van de API ze momenteel willen gebruiken en de juiste back-end-URL aanroepen. Als u de juiste back-end-URL wilt bepalen die moet worden aangeroepen voor een bepaalde client, moet u een query uitvoeren op bepaalde configuratiegegevens. Door deze configuratiegegevens in de cache op te nemen, kan API Management de prestatiestraf voor het uitvoeren van deze zoekopdracht minimaliseren.

De eerste stap is het bepalen van de id die wordt gebruikt om de gewenste versie te configureren. In dit voorbeeld heb ik ervoor gekozen om de versie te koppelen aan de productabonnementscode.

<set-variable name="clientid" value="@(context.Subscription.Key)" />

API Management voert vervolgens een cachezoekactie uit om te zien of deze al de gewenste clientversie heeft opgehaald.

<cache-lookup-value
key="@("clientversion-" + context.Variables["clientid"])"
variable-name="clientversion" />

Vervolgens controleert API Management of deze niet in de cache is gevonden.

<choose>
    <when condition="@(!context.Variables.ContainsKey("clientversion"))">

Als API Management het niet heeft gevonden, haalt API Management het op.

<send-request
    mode="new"
    response-variable-name="clientconfiguresponse"
    timeout="10"
    ignore-error="true">
            <set-url>@(new Uri(new Uri(context.Api.ServiceUrl.ToString() + "api/ClientConfig/"),(string)context.Variables["clientid"]).AbsoluteUri)</set-url>
            <set-method>GET</set-method>
</send-request>

Pak de hoofdtekst van het antwoord uit het antwoord.

<set-variable
      name="clientversion"
      value="@(((IResponse)context.Variables["clientconfiguresponse"]).Body.As<string>())" />

Sla deze weer op in de cache voor toekomstig gebruik.

<cache-store-value
      key="@("clientversion-" + context.Variables["clientid"])"
      value="@((string)context.Variables["clientversion"])"
      duration="100000" />

En ten slotte werkt u de back-end-URL bij om de versie van de service te selecteren die door de client is gewenst.

<set-backend-service
      base-url="@(context.Api.ServiceUrl.ToString() + "api/" + (string)context.Variables["clientversion"] + "/")" />

Het volledige beleid is als volgt:

<inbound>
    <base />
    <set-variable name="clientid" value="@(context.Subscription.Key)" />
    <cache-lookup-value key="@("clientversion-" + context.Variables["clientid"])" variable-name="clientversion" />

    <!-- If API Management doesn’t find it in the cache, make a request for it and store it -->
    <choose>
        <when condition="@(!context.Variables.ContainsKey("clientversion"))">
            <send-request mode="new" response-variable-name="clientconfiguresponse" timeout="10" ignore-error="true">
                <set-url>@(new Uri(new Uri(context.Api.ServiceUrl.ToString() + "api/ClientConfig/"),(string)context.Variables["clientid"]).AbsoluteUri)</set-url>
                <set-method>GET</set-method>
            </send-request>
            <!-- Store response body in context variable -->
            <set-variable name="clientversion" value="@(((IResponse)context.Variables["clientconfiguresponse"]).Body.As<string>())" />
            <!-- Store result in cache -->
            <cache-store-value key="@("clientversion-" + context.Variables["clientid"])" value="@((string)context.Variables["clientversion"])" duration="100000" />
        </when>
    </choose>
    <set-backend-service base-url="@(context.Api.ServiceUrl.ToString() + "api/" + (string)context.Variables["clientversion"] + "/")" />
</inbound>

Het inschakelen van API-consumenten om transparant te bepalen welke back-endversie wordt geopend door clients zonder dat clients hoeven bij te werken en opnieuw te implementeren, is een elegante oplossing waarmee veel problemen met API-versiebeheer worden opgelost.

Tenantisolatie

In grotere implementaties met meerdere tenants maken sommige bedrijven afzonderlijke groepen tenants op afzonderlijke implementaties van back-endhardware. Dit minimaliseert het aantal klanten dat wordt beïnvloed door een hardwareprobleem op de back-end. Ook kunnen nieuwe softwareversies in fasen worden geïmplementeerd. In het ideale geval moet deze back-endarchitectuur transparant zijn voor API-consumenten. Dit kan op een vergelijkbare manier worden bereikt als transparante versiebeheer, omdat deze is gebaseerd op dezelfde techniek voor het bewerken van de back-end-URL met behulp van de configuratiestatus per API-sleutel.

In plaats van een voorkeursversie van de API te retourneren voor elke abonnementssleutel, retourneert u een id die een tenant aan de toegewezen hardwaregroep relateert. Deze id kan worden gebruikt om de juiste back-end-URL te maken.

Samenvatting

De vrijheid om de Azure API Management-cache te gebruiken voor het opslaan van gegevens, maakt efficiënte toegang tot configuratiegegevens mogelijk die van invloed kunnen zijn op de manier waarop een binnenkomende aanvraag wordt verwerkt. Het kan ook worden gebruikt voor het opslaan van gegevensfragmenten die antwoorden kunnen uitbreiden, geretourneerd door een back-end-API.