Memorizzazione nella cache personalizzata in Gestione API di AzureCustom caching in Azure API Management

Il servizio Gestione API di Azure prevede il supporto incorporato per la memorizzazione nella cache delle risposte HTTP usando l'URL della risorsa come chiave.Azure API Management service has built-in support for HTTP response caching using the resource URL as the key. La chiave può essere modificata dalle intestazioni della richiesta usando le proprietà vary-by.The key can be modified by request headers using the vary-by properties. Questo risulta utile per la memorizzazione nella cache di intere risposte HTTP (note anche come rappresentazioni), ma talvolta è utile memorizzare nella cache anche solo una parte di una rappresentazione.This is useful for caching entire HTTP responses (aka representations), but sometimes it is useful to just cache a portion of a representation. I nuovi criteri cache-lookup-value e cache-store-value consentono di archiviare e recuperare singoli dati arbitrari all'interno di definizioni dei criteri.The new cache-lookup-value and cache-store-value policies provide the ability to store and retrieve arbitrary pieces of data from within policy definitions. Questa possibilità migliora anche il criterio send-request introdotto in precedenza, dal momento che ora è possibile memorizzare nella cache le risposte provenienti da servizi esterni.This ability also adds value to the previously introduced send-request policy because you can now cache responses from external services.

ArchitectureArchitecture

Il servizio Gestione API usa una cache di dati condivisa per tenant. In questo modo, man mano che aumenta il numero di unità, è sempre possibile accedere agli stessi dati memorizzati nella cache.API Management service uses a shared per-tenant data cache so that, as you scale up to multiple units you still get access to the same cached data. Quando si adotta una distribuzione in più aree, tuttavia, sono presenti cache indipendenti all'interno di ogni area.However, when working with a multi-region deployment there are independent caches within each of the regions. È importante non considerare la cache come un archivio dati e l'unica fonte di alcune informazioni.It is important to not treat the cache as a data store, where it is the only source of some piece of information. Così facendo, se successivamente si decide di usare la distribuzione in più aree, i clienti con utenti che viaggiano posso perdere l'accesso ai dati memorizzati nella cache.If you did, and later decided to take advantage of the multi-region deployment, then customers with users that travel may lose access to that cached data.

Memorizzazione di frammentiFragment caching

Esistono casi in cui le risposte restituite contengono una parte di dati costosa da determinare ma che rimane aggiornata per un periodo di tempo ragionevole.There are certain cases where responses being returned contain some portion of data that is expensive to determine and yet remains fresh for a reasonable amount of time. Si consideri, ad esempio, un servizio creato da una compagnia aerea che fornisce informazioni sulla prenotazione dei voli, lo stato dei voli e così via. Se l'utente è membro del programma a punti della compagnia aerea, dovrà anche avere informazioni sul proprio stato e sulle miglia accumulate.As an example, consider a service built by an airline that provides information relating flight reservations, flight status, etc. If the user is a member of the airlines points program, they would also have information relating to their current status and accumulated mileage. Le informazioni relative agli utenti possono essere archiviate in un sistema diverso, ma potrebbe essere utile includerle nelle risposte restituite con le informazioni sulle prenotazioni e lo stato dei voli.This user-related information might be stored in a different system, but it may be desirable to include it in responses returned about flight status and reservations. Questa operazione può essere eseguita attraverso un processo denominato memorizzazione di frammenti.This can be done using a process called fragment caching. La rappresentazione primaria può essere restituita dal server di origine usando un token per indicare la posizione per l'inserimento delle informazioni relative agli utenti.The primary representation can be returned from the origin server using some kind of token to indicate where the user-related information is to be inserted.

Si consideri la seguente risposta JSON da un'API back-end.Consider the following JSON response from a backend API.

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

E la risorsa secondaria /userprofile/{userid} che presenta un aspetto simile al seguente.And secondary resource at /userprofile/{userid} that looks like,

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

Per determinare quali informazioni dell'utente includere, Gestione API deve identificare l'utente finale.To determine the appropriate user information to include, API Management needs to identify who the end user is. Questo meccanismo dipende dall'implementazione.This mechanism is implementation-dependent. Nell'esempio viene usata l'attestazione Subject di un token JWT.As an example, I am using the Subject claim of a JWT token.

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

Gestione API archivia il valore enduserid in una variabile di contesto per usarlo in seguito.API Management stores the enduserid value in a context variable for later use. Il passaggio successivo consiste nel determinare se una richiesta precedente ha già recuperato le informazioni utente e le ha archiviate nella cache.The next step is to determine if a previous request has already retrieved the user information and stored it in the cache. A tale scopo, Gestione API usa i criteri cache-lookup-value.For this, API Management uses the cache-lookup-value policy.

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

Se non sono presenti voci nella cache che corrispondono al valore della chiave, non viene creata alcuna variabile di contesto userprofile.If there is no entry in the cache that corresponds to the key value, then no userprofile context variable is created. Per verificare l'esito della ricerca, Gestione API usa il criterio del flusso di controllo choose.API Management checks the success of the lookup using the choose control flow policy.

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

Se la variabile di contesto userprofile non esiste, Gestione API dovrà eseguire una richiesta HTTP per recuperarla.If the userprofile context variable doesn’t exist, then API Management is going to have to make an HTTP request to retrieve it.

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

Per creare l'URL per la risorsa del profilo utente, Gestione API usa enduserid.API Management uses the enduserid to construct the URL to the user profile resource. Dopo aver ottenuto la risposta, Gestione API estrae il corpo della risposta dalla risposta e lo archivia nuovamente in una variabile di contesto.Once API Management has the response, it pulls the body text out of the response and stores it back into a context variable.

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

Per evitare che Gestione API esegua nuovamente la richiesta HTTP quando lo stesso utente esegue un'altra richiesta, è possibile specificare che si intende archiviare il profilo utente nella cache.To avoid API Management from making this HTTP request again, when the same user makes another request, you can specify to store the user profile in the cache.

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

Gestione API archivia il valore nella cache usando esattamente la stessa chiave con cui in origine ha cercato di recuperarlo.API Management stores the value in the cache using the exact same key that API Management originally attempted to retrieve it with. La durata scelta da Gestione API per l'archiviazione del valore deve essere basata sulla frequenza di modifica delle informazioni e sulla tolleranza degli utenti rispetto alle informazioni non aggiornate.The duration that API Management chooses to store the value should be based on how often the information changes and how tolerant users are to out-of-date information.

È importante tenere presente che il recupero dalla cache è ancora una richiesta di rete out-of-process, che può aggiungere decine di millisecondi della richiesta.It is important to realize that retrieving from the cache is still an out-of-process, network request and potentially can still add tens of milliseconds to the request. I vantaggi sono evidenti quando per determinare le informazioni relative al profilo utente è necessario più tempo dal momento che è necessario eseguire query di database o aggregare le informazioni da più sistemi back-end.The benefits come when determining the user profile information takes longer than that due to needing to do database queries or aggregate information from multiple back-ends.

Il passaggio finale del processo consiste nell'aggiornare la risposta restituita con le informazioni sul profilo utente.The final step in the process is to update the returned response with the user profile information.

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

È possibile scegliere di includere le virgolette come parte del token in modo che anche quando non si verifica la sostituzione, la risposta rimane un oggetto JSON valido.You can chose to include the quotation marks as part of the token so that even when the replace doesn’t occur, the response is still a valid JSON.

Quando si combinano insieme tutti questi passaggi, il risultato finale è un criterio simile a quello riportato di seguito.Once you combine all these steps together, the end result is a policy that looks like the following one.

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

Questo approccio alla memorizzazione nella cache viene usato principalmente in siti web in cui il codice HTML viene composto sul lato server in modo che sia possibile eseguirne il rendering come una singola pagina.This caching approach is primarily used in web sites where HTML is composed on the server side so that it can be rendered as a single page. Può risultare utile anche per le API in cui i client non possono eseguire la memorizzazione nella cache del codice HTTP sul lato client o non è consigliabile affidare tale compito al client.It can also be useful in APIs where clients cannot do client-side HTTP caching or it is desirable not to put that responsibility on the client.

Questo stesso tipo di memorizzazione di frammenti può essere eseguito anche nei server Web back-end usando un server di memorizzazione nella cache Redis. Tuttavia, usare il servizio Gestione API per eseguire questa operazione può risultare utile quando i frammenti memorizzati nella cache provengono da back-end diversi rispetto alle risposte primarie.This same kind of fragment caching can also be done on the backend web servers using a Redis caching server, however, using the API Management service to perform this work is useful when the cached fragments are coming from different back-ends than the primary responses.

Controllo delle versioni trasparenteTransparent versioning

Solitamente sono supportate più versioni diverse dell'implementazione di un'API in qualsiasi momento.It is common practice for multiple different implementation versions of an API to be supported at any one time. Questo allo scopo, ad esempio, di supportare ambienti diversi (sviluppo, test, produzione e così via), oppure di supportare versioni precedenti dell'API e consentire ai consumer dell'API di eseguire la migrazione a versioni più recenti.For example, to support different environments (dev, test, production, etc.) or to support older versions of the API to give time for API consumers to migrate to newer versions.

Invece di chiedere agli sviluppatori di client di modificare l'URL da /v1/customers in /v2/customers, un possibile approccio alla gestione di questa situazione consiste nell'archiviare tra i dati del profilo del consumer la versione dell'API da usare e chiamare l'URL del back-end appropriato.One approach to handling this, instead of requiring client developers to change the URLs from /v1/customers to /v2/customers is to store in the consumer’s profile data which version of the API they currently wish to use and call the appropriate backend URL. Per determinare l'URL del back-end corretto da chiamare per un determinato client, è necessario eseguire una query su alcuni dati di configurazione.To determine the correct backend URL to call for a particular client, it is necessary to query some configuration data. Con la memorizzazione nella cache di questi dati di configurazione, Gestione API può ridurre al minimo i problemi di prestazioni correlati alla ricerca.By caching this configuration data, API Management can minimize the performance penalty of doing this lookup.

Il primo passaggio consiste nel determinare l'identificatore usato per configurare la versione scelta.The first step is to determine the identifier used to configure the desired version. In questo esempio, si è scelto di associare la versione alla chiave della sottoscrizione del prodotto.In this example, I chose to associate the version to the product subscription key.

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

Gestione API esegue quindi una ricerca nella cache per verificare se è già stata recuperata la versione del client scelta.API Management then does a cache lookup to see whether it already retrieved the desired client version.

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

Verifica quindi se non è stata trovata nella cache.Then, API Management checks to see if it did not find it in the cache.

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

Se la versione non viene trovata, Gestione API la recupera.If API Management didn’t find it, API Management retrieves it.

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

Estrarre il corpo della risposta dalla risposta.Extract the response body text from the response.

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

Memorizzare nuovamente l'informazione nella cache per usarla in futuro.Store it back in the cache for future use.

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

Infine, aggiornare l'URL del back-end per selezionare la versione del servizio scelta dal client.And finally update the back-end URL to select the version of the service desired by the client.

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

Il criterio completo è il seguente:The complete policy is as follows:

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

Consentire ai consumer delle API il controllo trasparente della versione del back-end accessibile dai client senza dover aggiornare e ridistribuire i client è una soluzione elegante che risolve diversi problemi di controllo delle versioni delle API.Enabling API consumers to transparently control which backend version is being accessed by clients without having to update and redeploy clients is an elegant solution that addresses many API versioning concerns.

Isolamento dei tenantTenant Isolation

Nelle distribuzioni multi-tenant di grandi dimensioni alcune aziende creano gruppi di tenant separati in distribuzioni distinte dell'hardware back-end.In larger, multi-tenant deployments some companies create separate groups of tenants on distinct deployments of backend hardware. Questo approccio permette di ridurre al minimo il numero di clienti interessati da un problema hardware nel back-end.This minimizes the number of customers who are impacted by a hardware issue on the backend. Permette anche la distribuzione in più fasi delle nuove versioni dei software.It also enables new software versions to be rolled out in stages. È preferibile che questa architettura di back-end sia trasparente per i consumer dell'API.Ideally this backend architecture should be transparent to API consumers. Questo approccio può essere realizzato in modo analogo al controllo delle versioni trasparente perché si basa sulla stessa tecnica di manipolazione dell'URL del back-end usando lo stato di configurazione per chiave dell'API.This can be achieved in a similar way to transparent versioning because it is based on the same technique of manipulating the backend URL using configuration state per API key.

Invece di restituire una versione preferita dell'API per ogni chiave della sottoscrizione, viene restituito un identificatore che mette in relazione un tenant con il gruppo di componenti hardware assegnato.Instead of returning a preferred version of the API for each subscription key, you would return an identifier that relates a tenant to the assigned hardware group. L'identificatore può essere usato per creare l'URL del back-end appropriato.That identifier can be used to construct the appropriate backend URL.

RiepilogoSummary

La libertà di usare la cache di Gestione API di Azure per archiviare qualsiasi tipo di dati consente di accedere facilmente ai dati di configurazione che possono influenzare la modalità di elaborazione di una richiesta in ingresso.The freedom to use the Azure API management cache for storing any kind of data enables efficient access to configuration data that can affect the way an inbound request is processed. La cache può anche essere usata per memorizzare frammenti di dati che possono integrare le risposte restituite da un'API back-end.It can also be used to store data fragments that can augment responses, returned from a backend API.