Versionamento

Uma boa API é versionada: Mudanças e novos recursos são implementados em novas versões da API em vez de alterar continuamente apenas uma versão. Diferente de aplicações Web, com a qual você tem total controle do código de ambos os lados cliente e servidor, APIs são destinadas a ser utilizadas por clientes além de seu controle. Por esta razão, a compatibilidade (BC) entre as APIs deve ser mantida sempre que possível. Se uma mudança que pode quebrar esta compatibilidade é necessária, você deve introduzi-la em uma nova versão de API e subir o número da versão. Os clientes existentes podem continuar a usar a versão antiga da API; e os clientes novos ou atualizados podem obter a nova funcionalidade na nova versão da API.

Dica: Consulte o artigo Semantic Versioning para obter mais informações sobre como projetar números de versão da API.

Uma maneira comum de implementar versionamento de API é incorporar o número da versão nas URLs da API. Por exemplo, http://example.com/v1/users representa o terminal /users da API versão 1.

Outro método de versionamento de API, que tem sido muito utilizado recentemente, é colocar o número da versão nos cabeçalhos das requisições HTTP. Isto é tipicamente feito através do cabeçalho Accept:

// Através de um parâmetro
Accept: application/json; version=v1
// através de um vendor content type
Accept: application/vnd.company.myapp-v1+json

Ambos os métodos tem seus prós e contras, e há uma série de debates sobre cada abordagem. A seguir, você verá uma estratégia prática para o controle de versão de API que é uma mistura dos dois métodos:

  • Coloque cada versão principal de implementação da API em um módulo separado cuja identificação é o número de versão principal (ex. v1, v2). Naturalmente, as URLs da API irão conter os números da versão principal.
  • Dentro de cada versão principal (e, assim, dentro do módulo correspondente), utilize o cabeçalho Accept da requisição HTTP para determinar o número de versão secundária e escrever código condicional para responder às versões menores em conformidade.

Para cada módulo destinado a uma versão principal, deve incluir o recurso e a classe de controller destinados a esta versão específica. Para melhor separar a responsabilidade do código, você pode manter um conjunto comum de classes base de recursos e de controller e criar subclasses delas para cada versão individual do módulo. Dentro das subclasses, implementar o código concreto, tais como Model::fields().

Seu código pode ser organizado da seguinte maneira:

api/
   common/
       controllers/
           UserController.php
           PostController.php
       models/
           User.php
           Post.php
   modules/
       v1/
           controllers/
               UserController.php
               PostController.php
           models/
               User.php
               Post.php
           Module.php
       v2/
           controllers/
               UserController.php
               PostController.php
           models/
               User.php
               Post.php
           Module.php

A configuração da sua aplicação seria algo como:

return [
   'modules' => [
       'v1' => [
           'class' => 'app\modules\v1\Module',
       ],
       'v2' => [
           'class' => 'app\modules\v2\Module',
       ],
   ],
   'components' => [
       'urlManager' => [
           'enablePrettyUrl' => true,
           'enableStrictParsing' => true,
           'showScriptName' => false,
           'rules' => [
               ['class' => 'yii\rest\UrlRule', 'controller' => ['v1/user', 'v1/post']],
               ['class' => 'yii\rest\UrlRule', 'controller' => ['v2/user', 'v2/post']],
           ],
       ],
   ],
];

Como resultado do código acima, http://example.com/v1/users retornará a lista de usuários na versão 1, enquanto http://example.com/v2/users retornará a lista de usuários na versão 2.

Graças aos módulos, o código para diferentes versões principais pode ser bem isolado. Entretanto esta abordagem torna possível a reutilização de código entre os módulos através de classes bases comuns e outros recursos partilhados.

Para lidar com números de subversões, você pode tirar proveito da negociação de conteúdo oferecida pelo behavior contentNegotiator. O behavior contentNegotiator irá configurar a propriedade yii\web\Response::$acceptParams que determinará qual versão é suportada.

Por exemplo, se uma requisição é enviada com o cabeçalho HTTP Accept: application/json; version=v1, Após a negociação de conteúdo, yii\web\Response::$acceptParams terá o valor ['version' => 'v1'].

Com base na informação da versão em acceptParams, você pode escrever um código condicional em lugares tais como ações, classes de recursos, serializadores, etc. para fornecer a funcionalidade apropriada.

Uma vez que subversões por definição devem manter compatibilidades entre si, esperamos que não haja muitas verificações de versão em seu código. De outra forma, você pode precisar criar uma nova versão principal.