Vinicius Quinafelex Alves

🌐English version

[Azure] Mocking com Azure API Management

Azure API Management

Azure API Management (APIM) é uma das ferramentas cloud disponibilizadas pela Azure.

Seu foco é em oferecer uma camada de API intermediária entre acessos externos e a infraestrutura interna, permitindo ativar e desativar recursos e políticas com baixo impacto no client ou no backend, as vezes não precisando de alteração alguma.

Em alguns casos, também possibilita que sejam feitas alterações na infraestrutura do backend sem impactar no client, por exemplo quando houver troca de URI no backend.

Alguns dos recursos oferecidos pelo APIM são monitoramento e metrificação, segurança e rate limiting, cacheamento, transformação de dados, autorização por chave de acesso, dentre outros. Neste artigo o foco principal será sobre mocking.

Mocking

Mocking se trata da prática de simular um comportamento sem realizar a operação real. O conceito é bastante utilizado no tópico de testes automatizados, mas a mesma ideia pode ser aplicada no API Management.

Por exemplo, é possível criar um endpoint no API Management cuja única função é devolver um JSON fixo. Isso permite que um time de frontend faça requisições e receba esse JSON como se fosse um resultado real, mesmo que o backend ainda não exista.

Quando o backend estiver pronto, esse mesmo endpoint pode ser alterado para que a requisição seja encaminhada para o backend e retorne o response do backend ao invés de retornar um JSON fixo, convertendo um mock em uma funcionalidade real sem afetar o frontend.

Reforçando conceitos do APIM

Antes de prosseguir no artigo, é recomendado que se tenha conhecimentos básicos sobre políticas do API Management. Os principais conceitos são:

Cada endpoint do APIM possui um bloco frontend aonde estão as configurações gerais, como o path do endpoint dentro do APIM, quais os parâmetros que podem ser enviados, quais tipos de content são aceitos, etc.

Além disso, toda requisição pode passar por 4 sessões. Cada sessão contém políticas configuradas através de tags XML:

- Inbound: Políticas, processos e transformações realizados antes da requisição ser encaminhada para o backend.

- Backend: Políticas e configurações para envio da requisição para um serviço de backend.

- Outbound: Políticas, processos e transformações realizados no response do backend.

- On-Error: Sessão opcional para tratamento de erros ocorridos durante o processo de requisição.

Mockando um response

Há diferentes estratégias pra criação de um mock, uma das mais simples é usar um <return-response> na sessão inbound.

Essa política faz com que o retorno aconteça antes mesmo de passar pelas sessões de backend e outbound.

<policies>
    <inbound>
        <return-response>
            <set-status code="200" />
            <set-header name="Content-Type" exists-action="override">
                <value>application/json</value>
            </set-header>
            <set-body>{ "data": "ok" }</set-body>
        </return-response>
    </inbound>
    <backend><!-- your policies --></backend>
    <outbound><!-- your policies --></outbound>
    <on-error><!-- your policies --></on-error>
</policies>

Salvar a política com um JSON puro no <set-body> pode emitir um aviso sobre potenciais problemas de sintaxe. Esse aviso não afeta o funcionamento da política.

O conteúdo e tipo podem ser alterados conforme necessidade. A Microsoft disponibiliza um artigo detalhando mais sobre a política de <set-body>.

Validando conteúdo do payload

O APIM disponibiliza validação de schemas nativo para formato JSON, XML e SOAP utilizando a política <validate-content>.

Para JSON, o schema é especificado pelo padrão JSON Schema. Existem plataformas que extraem schemas através de um exemplo do JSON, como o jsonschema.net.

Para a validação fazer efeito, o endpoint deve ter um Representation registrado, indicando o content-type e o schema esperados no request.

Os Representations podem ser configurados no bloco de Frontend -> Request -> Representations. O schema deve ser inserido no atributo Definitions.

Configurar o Representation é obrigatório, caso contrário o APIM retornará BadRequest com o corpo abaixo. O content type exibido varia de acordo com payload da requisição.

{
    "statusCode": 400,
    "message": "Unspecified content type application/json is not allowed."
}

Com o Representation e Definition registrados, a política pode ser configurada da seguinte forma:

<policies>
    <inbound>
        <validate-content
            unspecified-content-type-action="prevent"
            max-size="128"
            size-exceeded-action="ignore">

            <content 
                type="application/json" 
                validate-as="json" 
                action="prevent" />

        </validate-content>
    </inbound>
    <backend><!-- your policies --></backend>
    <outbound><!-- your policies --></outbound>
    <on-error><!-- your policies --></on-error>
</policies>

Via schema global

Outra opção de registrar um schema é o cadastro de schemas do API Management. Quando utilizar esse mecanismo, a tag <content> deve receber o atributo schema-id com o identificador do schema registrado.

Note que o schema-id não é necessário quando o schema foi registrado pelo Definitions do Representation.

<policies>
    <inbound>
        <validate-content
            unspecified-content-type-action="prevent"
            max-size="128"
            size-exceeded-action="ignore">

            <content 
                type="application/json" 
                validate-as="json" 
                action="prevent"
                schema-id="your-schema-id" />

        </validate-content>
    </inbound>
    <backend><!-- your policies --></backend>
    <outbound><!-- your policies --></outbound>
    <on-error><!-- your policies --></on-error>
</policies>

Diferentes responses e StatusCode

O APIM permite fluxos condicionais através da política de <choose>, e também disponibiliza acesso a dados do contexto de execução através da variável context.

Combinar essas duas funcionalidades permite configurar e disparar diferentes retornos. Por exemplo, podemos alterar o resultado baseado em flags na requisição, possibilitando responses previsíveis sem necessidade de diferentes endpoints.

O código abaixo demonstra isso com base em uma flag "mock" enviada pela query da URI.

<policies>
    <inbound>
        <choose>
            <when condition="@(context.Request.OriginalUrl.Query.GetValueOrDefault("mock") == "error")">
                <return-response>
                    <set-status code="500" />
                    <set-header name="Content-Type" exists-action="override">
                        <value>plain/text</value>
                    </set-header>
                    <set-body>Internal server error</set-body>
                </return-response>
            </when>
            <when condition="@(context.Request.OriginalUrl.Query.GetValueOrDefault("mock") == "no_access")">
                <return-response>
                    <set-status code="403" />
                </return-response>
            </when>
            <otherwise>
                <return-response>
                    <set-status code="200" />
                    <set-header name="Content-Type" exists-action="override">
                        <value>application/json</value>
                    </set-header>
                    <set-body>{ "result": "ok" }</set-body>
                </return-response>
            </otherwise>
        </choose>
    </inbound>
    <backend><!-- your policies --></backend>
    <outbound><!-- your policies --></outbound>
    <on-error><!-- your policies --></on-error>
</policies>

Utilizando a URI https://{apim_domain}/{api_path}/{endpoint_path}?mock={flag} e trocando o valor da {flag}, se obtem o resultado:

- mock=error: StatusCode 500 e mensagem de erro em texto

- mock=no_access: StatusCode 403 (Forbidden)

- Outro valor ou sem atributo: StatusCode 200 e resultado JSON

Em conclusão

APIM é uma ferramenta de trabalho com uma diversas funcionalidades que podem ser combinadas com bastante versatilidade, abstraindo detalhes de infraestrutura interna e diluindo responsabilidades que originalmente seriam atribuídas ao backend.

Acredita ter uma forma mais fácil de fazer mocks no APIM? Me mande uma mensagem no LinkedIn para faramos sobre!