APIs e Microsserviços

30 jul, 2015

A importância de serializar a saída da API

Publicidade

Eu tenho falado sobre a API Pain Points um zilhão de vezes ao longo do último ano. Apenas em 2015 eu falei sobre essa API em:

Uma área que parece ter um monte de comentários e perguntas é quando eu falo sobre a serialização, que eu chamo de “a inclusão de uma camada de apresentação para seus dados”.

O MSDN diz assim:

A serialização é o processo de converter um objeto em um fluxo de bytes para armazená-lo ou transmiti-lo para uma memória, um banco de dados ou um arquivo. Seu principal objetivo é salvar o estado de um objeto, a fim de ser capaz de recriá-lo quando necessário. O processo inverso é chamado desserialização. – Fonte: MSDN Conceitos de programação

Para os desenvolvedores PHP, que muitas vezes consideram serialização apenas o uso da função serialize (), digo que sim, essa é uma forma de serialização, mas não é a única. Outra abordagem de serialização comum é, naturalmente, usar a função json_encode (). Atualmente, as frameworks modernos irão converter automaticamente qualquer array retornada por um método controlador para JSON, ou seja, você não precisará nem mesmo chamar a função json_encode ().

Isso pode ser um atalho útil, mas se você está construindo uma API HTTP (AJAX/RESTful/Hipermedia), então é preciso ser um pouco mais específico com o que está retornando.

A classe mais comum é esta:

<?php
class PlaceController extends CoreController
{
    public function show($id)
    {
        return Place::find($id);
    }
}

Desculpe o pedaço drasticamente simplificado de código exposto aqui, mas o ponto é que nós estamos pegando um modelo (provavelmente usando um ORM) e retornando o resultado diretamente.

Isso parece bastante inocente, mas conduz a uma série de problemas.

Sem mais segredos

Cada campo que for adicionado ao seu armazenamento de dados vai acabar sendo a saída para o receptor da API. Se é uma API interna, então talvez isso seja o certo, mas se a sua informação vai para qualquer lugar a partir do navegador, ou para outro local como um dispositivo móvel, isso será um problema.

Os exemplos mais óbvios são senhas de usuários. Claro que elas estão criptografadas, mas você obviamente não quer entregar esses dados para estranhos.

As coisas são um pouco mais obscuras com tokens de redefinição de senha, que se forem vazados também podem levar problemas para seus usuários.

Mas as coisas podem ficar ainda mais obscuras. Neste exemplo, talvez uma exigência do negócio seja a adição de um “email de contato”, apenas para uso interno do sistema. Se você já passou meses recebendo mensagens desse sistema e tem construído alguns contatos exclusivos, pode não querer vazar todos aqueles e-mails para potenciais concorrentes.

Sim, muitas ORMs têm opções como verificar a sua lista de contatos em busca de propriedades como “oculto” ou “pessoal”, mas, com o tempo, as chances de manter todos os valores potencialmente perigosos escondidos se tornam cada vez mais improváveis, especialmente se você tiver um administrador júnior que não sabe que um desses campos é perigoso, ou um revisor de código cansado que deixa passar esse campo devido a um dia atarefado.

Um exemplo de Fractal – uma biblioteca de serialização PHP que construí para me ajudar a serializar minhas APIs – tem este exemplo simples:

<?php
use League\Fractal\Manager;
use League\Fractal\Resource\Collection;

// Create a top level instance somewhere
$fractal = new Manager();

// Ask the ORM for 10 books
$books = Book::take(10)->get();

// Turn this collection 
$resource = new Collection($books, function(array $book) {
    return [
        'id'      => (int) $book->id,
        'title'   => $book->title,
        'year'    => (int) $book->yr,
        'author'  => [
        	'name'  => $book->author_name,
        	'email' => $book->author_email,
        ],
        'links'   => [
            [
                'rel' => 'self',
                'uri' => '/books/'.$book->id,
            ]
        ]
    ];
});

Sim, mais uma vez é um script muito simplificado e usa callbacks em vez de classes para sua lógica, mas a ideia geral se mantém.

Há ferramentas específicas para cada linguagem existente. Eu trabalhei com ActiveModel Serializer, que é quase idêntica.

Independentemente da linguagem que você está usando atualmente, eu gostaria de explicar por que fazer isso é tão importante. Você pode aprender como mais tarde, mas este artigo é sobre por que fazer.

Atributo de tipos de dados

Muitas linguagens – PHP inclusive – são burras em relação a drivers de ligação de dados. Coisas como MySQL e PostgreSQL têm muitos tipos de dados: integer, float, boolean etc. Mas tudo o que é obtido através da área de usuário é apenas uma string.

Em vez de true e false, o que você vê é “1” e “0”, ou talvez até mesmo “t” e “f”. Pontos flutuantes saem como “-41,235” em vez de -41,235.

Para uma linguagem fracamente tipada, isso pode pode não parecer particularmente importantes, mas linguagens estritamente tipadas digitadas vão cair, se isso nuca mudar.

Algumas vezes, tenho visto uma mudança na sequência numérica de um inteiro devido a um pouco de matemática que está sendo usada em um acessor ORM, onde “1” + “2” = 3. Tal mudança poderia potencialmente driblar seus testes unitários se eles forem vagos o suficiente, mas ela vai quebrar todo o funcionamento do seu aplicativo iOS.

Os ActiveRecord no Rails irão acompanhar o que os campos de tipo de dados devem ser e como eles são adicionados ao esquema de migrações, mas, se eles mudarem – via acessors ou alterando tipo de esquema -, isso vai causar problemas.

Usando a serialização como o exemplo acima, você pode distribuir os papéis de seus dados para garantir que o resultado seja o tipo de dado esperado na saída, e que o tipo de dado só vai mudar se você alterá-lo no serializador.

Renomeando campos

A renomeação de campos no armazenamento de dados não deve quebrar a sua API. Se você acha que o fato de ter que atualizar todos os seus testes de integração é irritante, pense como será ainda mais irritante para os desenvolvedores de aplicativos móveis e outras equipes de frontend que têm que atualizar e implementar novos aplicativos. Talvez você nem se lembre de criar uma trava para o deploy. Se você não fizer isso, então vai ter quebrado aplicativos para usuários finais e, mesmo que você empurre uma atualização para o iOS, ela estará quebrada até a atualização do seu aplicativo.

Tudo o que você precisa para evitar esse inferno dos campos renomeados é uma camada de serialização, o que permitirá que você atualize a referência para o campo sem alterar a representação externa.

Múltiplos armazenamentos de dados

Muitas dessas soluções de serialização ORM têm um pressuposto importante: todos os seus dados vivem no mesmo grupo de armazenamento de dados. O que acontece quando alguns de seus dados SQL são enviados para o Redis ou similares?

Mesmo que você não esteja movendo dados parciais de SQL para o Redis, talvez seja necessário dividir a sua tabela em duas, ou então utilizar tabelas dinâmicas. A maioria dos serializadores ORM vai parar de funcionar se você tentar isso.

Em vez disso, talvez seja melhor usar o padrão de repositório que se tornou tão popular no Laravel, assim, você pode passar todos esses dados a partir de qualquer armazenamento que estiver na biblioteca de serialização, e o serializador irá manter uma saída consistente.

Versionamento de serializadores

No passado, usei serializadores versionados para as versões principais v1 e v2 do FooSerializer, já que ambos podem ser usados para testes diferentes e satisfazer o cliente da API perfeitamente para múltiplas necessidades.

Fomatos de serializador

Algo que o Fractal ainda não consegue, mas pretende corrigir nas próximas versões, são os recursos de múltiplos formatos de “adaptadores”. Isso tem sido muito usado na comunidade Rails, e você pode enviar cabeçalhos diferentes para obter formatos totalmente diferentes.

Dependendo do tipo de mime que você enviar, poderá dizer ao seu serializador para enviar de volta o resultado nesse formato, sem a necessidade de entupir toda a sua base de código com lógica potencialmente complexa.

Soluções

Eu provavelmente poderia argumentar com mais razões para usar serializadores, mas hoje meu voo pousou às 5h e as pessoas próximas a mim me mantiveram acordado a noite toda.

Eu cobri o básico sobre o “porquê” de usar a serialização, mas não o “como”. Para isso, dê uma olhada nas seguintes soluções:

Vi um excelente palestra na RailsConf 2015 ministrada por meu novo amigo João Moura chamada AMS, API e um desenvolvedor Rails, uma história de amor, que vai mostrar algumas das funcionalidades mais legais da serialização.

Qualquer que seja o sistema que você escolher, todos eles têm mais ou menos a mesma ideia.

Se você estiver fazendo qualquer tipo de API, por favor, faça isso.

Uma API não é apenas um proxy para comandos SQL, deve ser planejada, considerada e mantida com cuidado, e uma simples mudança para o armazenamento de dados não deve derrubar toda a rede de aplicações e serviços.

***

Phil Sturgeon faz parte do time de colunistas internacionais do iMasters. A tradução do artigo é feita pela redação iMasters, com autorização do autor, e você pode acompanhar o artigo em inglês no link: https://philsturgeon.uk/api/2015/05/30/serializing-api-output/