APIs e Microsserviços

19 dez, 2014

Exclusões RESTful, restaurações e revisões

Publicidade

Recentemente, eu escrevi o artigo URLs RESTful: ações não precisam ser aplicadas, que falava sobre como a única ação/verbo a aparecer na solicitação HTTP deve ser o próprio método HTTP (GET, POST, PUT, DELETE, HEAD etc.).

Isso foi descoberto por mim mesmo quando outras pessoas e eu estávamos trabalhando em casos de uso complexos, que não eram essas coisas de maçãs e peras de sempre que normalmente encontramos em material de design de APIs. APIs simples são simples, mas tentar descobrir “como faço para torná-la RESTful” com coisas mais complexas como, por exemplo, envio de mensagens em massa, pode travar seu cérebro.

Agora que sou “o cara do REST” para alguns na comunidade PHP, eu tenho recebido algumas perguntas sobre como abordar outros desafios.

As regras são:

  • Nenhum verbo na URL
  • Input deve parecer ao máximo com output
  • Não invente campos que só existem para input
  • Use cada método HTTP para a sua finalidade exata

Então, vamos lá!

Deletar, então …restaurar?

restful-1

Chris tinha um conjunto de rotas como este:

  • GET articles
  • POST articles
  • PATCH articles/{article}
  • DELETE articles/{article}

Bem simples até agora, certo? Pegue todos, crie um, atualize um já existente e exclua um.

O que acontecerá se você quiser restaurar um artigo excluído?

Obviamente, se você realmente o excluiu do banco de dados (hard-delete) então o caso é complicado, mas se o registro existe em algum lugar com um status alterado ou um campo deleted_at como em Laravel (soft-delete), aí você pode fazer isso com bastante facilidade.

As duas abordagens que eu vejo que seguem todas as regras são as seguintes:

Arquive depois Hard-Delete

Excluir deveria fazer o que diz: excluir o item. Se a excluir for uma ação destrutiva e irrecuperável, então você terá que inventar alguma outra condição para o artigo existir, o qual ainda está escondido da API, mas não inteiramente desaparecido do banco de dados.

PATCH /articles/foo HTTP/1.1  
Host: api.example.com  
Content-Type: application/json  

{ "status" : "archived" }

Isso o manteria disponível em todo a API, e você poderia obtê-lo se você quisesse, ou ignorá-lo com filtros. Por exemplo, a execução de um GET /articles?status=archived poderia bem facilmente fazer você ter acesso a itens arquivados, e executar GET /articles?status=published lhe daria acesso aos publicados.

Fazer uma solicitação GET para um item arquivado ainda funciona bem, mas você vai ver “status” : “archived” no corpo.

Se depois você quiser remover algo de fato do sistema, então poderia usar DELETE /articles/foo.

Soft-Delete e Restore

Nesta abordagem, usamos DELETE para apagar coisas e não brincar com um campo de status inicial.

Uma vez que você excluí-lo, ele terá ido de vez. Ele não vai aparecer em GET /articles, independentemente de qualquer filtro fornecido. Se você tentar e chamá-lo, você vai receber um 404 ou um 410.

Este artigo não está mais entre a população geral da API.

Para ganhar, o jogo precisamos, de alguma forma, ver o conteúdo “excluído” e restaurá-lo por isso, talvez, só talvez GET /articles/trashed.

A razão pela qual eu acho que isso funciona é porque os itens na lixeira são um sub-conjunto específico de artigos. Ele não usa simplesmente um filtro – porque esses artigos estão indo tão longe quanto é compreendido pela API – eles estão em um lugar totalmente diferente e, portanto, faz sentido ter um novo recurso com uma nova coleção.

Uma vez que encontrar o item, você pode fazer novamente um PATCH do status para “published” (ou qualquer outro):

PATCH /articles/trashed/foo HTTP/1.1  
Host: api.example.com  
Content-Type: application/json  

{ "status" : "published" }

Como alternativa, você pode usar algum tipo de campo booleano is_trash que mostra sempre quando você faz GET em alguma coisa, lixo ou não; em seguida, use PATCH com um corpo como “is_trash”: false para restaurá-lo. Independentemente da escolha entre um campo booleano ou um campo de status, aplicar um patch parece funcionar.

Qual usar?

Pessoalmente, acho que prefiro a primeira abordagem. Para mim, deletar significa deletar. É o mais semântico. Se o seu aplicativo não está realmente excluindo coisas, então chamar isso de deletar é enganoso.

Oferecer uma maneira para jogar no lixo, arquivar ou banir um recurso, alterando o estado dele, parece incrivelmente semântico.

Múltiplas revisões

Outra coisa que o Chris perguntou foi sobre como promover e despromover várias revisões que se relacionam com um artigo.

Ele tem estes caminhos:

  • GET articles/{article}/revisions
  • POST articles/{article}/revisions
  • PATCH articles/{article}/revisions/{article-revision}
  • DELETE articles/{article}/revisions/{article-revision}

Uma regra mencionada acima é:

  • Input deve parecer ao máximo com output.

Para mim, seria muito útil se todas as revisões tivessem um valor “is_current” para me informar se isso é a revisão atual. Você pode adicionar novos, e eles podem ser promovidos para o atual automaticamente, ou alguém pode querer reverter a revisão por meio de uma interface em algum lugar, mas aconteça o que acontecer a interface e o output precisarão destacar a revisão atual.

Nota: Isso não significa que você literalmente precisa de um campo no banco de dados chamado is_current. Talvez sua tabela de artigos tenha um campo revision_id apontando para a revisão atual em uso. Seus input e output JSON não devem ser um mapeamento direto ou seu esquema SQL, por isso não se sinta obrigado a fazer esse campo.

Por termos esse campo como output, ele não deve ser muito mais do que um trecho para assumir que podemos fazer um PATCH nele.

Se o artigo foo possui três revisões, deveria ser possível fazer revisão 2 da revisão atual:

PATCH /articles/foo/revisions/2 HTTP/1.1  
Host: api.example.com  
Content-Type: application/json  

{ "is_current" : true }

Nesse ponto, talvez a sua lógica interna atualize esse registro de revisão com um true e defina as outras revisões para esse artigo para ser false, ou talvez você vá e atualize a tabela de artigos e defina o revision_id para o ID fornecido. Tudo o que você fizer afetará o efeito que agora é a atual revisão promovida, e nós não tivemos que fazer outras coisas grosseiras como /articles/foo/revisions/promote/34 que vi sugerido antes.

Resumo

Como eu já disse antes, às vezes tentar tornar as coisas RESTful para o bem delas é mais uma escolha pessoal do que uma questão técnica. Em alguns casos, a maneira mais RESTful não é, inicialmente, a forma mais óbvia, mas só ao longo de uma década que nos foi ensinado que coisas como /articles/foo/revisions/promote/34 são úteis. Talvez para um site, mas a API RESTful é uma plataforma de possibilidades infinitas e não só uma coleção de funções com parâmetros personalizados, como XML-RPC/SOAP. Uma vez que você começar a entender como isso funciona, um monte de coisas que parecia inútil, mas RESTful, realmente começa a parecer muito mais claro e mais flexível.

***

Artigo traduzido pela Redação iMasters com autorização do autor. Publicado originalmente em https://philsturgeon.uk/rest/2014/05/25/restful-deletions-restorations-and-revisions/