Desenvolvimento

10 nov, 2014

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

Publicidade

Há alguns meses, eu publiquei RESTful URLs: Actions Need Not Apply, que era sobre como as únicas ações/verbetes que aparecem numa requisição HTTP deveriam ser o próprio método HTTP (GET, POST, PUT, DELETE, HEAD etc.).

Isso foi descoberto quando os outros e eu estávamos trabalhando em casos complexos de uso que não são os materiais habituais de concepção na qual uma API é baseada. Simple API é simples, mas tentar descobrir como eu posso fazê-la RESTful é mais complexo.

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

As regras são:

  • Nenhum verbete na URL
  • Input deve se 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, e aí… reverter?

rest

Ei, @philsturgeon – qual é o caminho “mais RESTful” para restaurar/reverter um recurso?

Chris tinha um conjunto de caminhos como este:

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

Bem direto até agora, né? Pegue todos, crie um, atualize um já existente e exclua um. Legal.

O que acontecerá se você quiser restaurar um artigo deletado?

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

Duas abordagens que posso ver que seguem todas as regras são as seguintes:

Arquivar e depois fazer hard-delete

Excluir provavelmente deveria fazer o que se propõe a fazer, que é realmente excluir o item. Se a opção Excluir é uma ação destrutiva e irrecuperável, então você terá que inventar alguma outra condição para o artigo existir, que ainda está escondido da API, mas não totalmente 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 toda a API, e você poderia obtê-lo se quisesse, ou ignorá-lo com filtros. Por exemplo, executar um GET /articles?status=archived poderia facilmente te levar a itens arquivados, e executar GET /articles?status=published irá te levar aos itens publicados (mas vai rodar apenas uma vez).

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

Se depois quiser realmente remover algo do sistema, então você poderia usar DELETE /articles/foo para removê-lo totalmente.

Soft-delete e restauração

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

Uma vez que você o exclui, ele realmente se foi. Ele não vai aparecer em GET / articles, independentemente de qualquer filtro fornecido. Se você tentar chamá-lo, vai se deparar com 404 ou 410.

Nota: Desconfio de 410, já que não estou autorizado a retornar um body, o que significa que não há uma mensagem de erro amigável, código ou link de erro para a sua documentação. Se todos os outros erros contêm isso, fica estranho o conteúdo ausente ter apenas um body vazio. Você decide.

Update: Acontece que eu estava errado sobre 410 não ser autorizado a ter um body de resposta HTTP. RFC 2616 não diz nada do tipo. Isso soa como um daqueles boatos que alguém me disse em algum momento e que ficou grudado na minha cabeça sem que eu tenha feito uma verificação. Eu costumo tentar evitar esse tipo de coisa, sinto muito que tenha escapado.

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

Para ganhar o jogo, precisamos de uma maneira de ver conteúdo “excluído” e restaurá-lo, aí então, talvez … apenas talvez GET / articles / trashed.

A razão pela qual eu acho que isso funciona é porque os itens na lixeira formam um subconjunto específico de artigos. Só não usa um filtro porque esses artigos estão tão longe quanto o resto da API – eles estão em um lugar totalmente diferente, e, portanto, um novo recurso com uma nova coleção faz sentido.

Uma vez que você encontrar o item que poderia corrigir, novamente faça um PATCH para que o status seja “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 sempre mostra quando você consegue (GET) alguma coisa, lixo ou não, em seguida, use PATCH com um body como o “is_trash”: false para restaurá-lo. Independentemente da escolha de um campo booleano ou um campo de status, usar PATCH parece funcionar.

O que usar?

Eu acho que prefiro a primeira abordagem. Para mim, um delete significa excluir. É o mais semântico. Se o seu aplicativo não está realmente excluindo as coisas, então chamar qualquer um deles de delete é errado.

Oferecer uma maneira de “Apagar” ou “Arquivar” ou “Proibir” um recurso parece ser incrivelmente semântico também.

Revisões múltiplas

Outra coisa que o Chris perguntou foi sobre como promover e rebaixar várias revisões que dizem respeito a 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 se 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 foi a revisão atual. Você pode adicionar novas e elas podem ser promovidas para a atual automaticamente, ou alguém pode querer reverter a revisão através de uma interface em algum lugar, mas, independentemente de qualquer coisa que aconteça na interface e no output, será preciso 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 possua um campo revision_id apontando para a versão atual em uso. Seu input e seu output JSON não deveriam ser um mapeamento direto ou seu esquema SQL, por isso não se sinta obrigado a fazer este campo.

Por conta do campo de saída, ele não deve levar muito mais que um trecho de código para assumir que pode fazer um PATCH.

Se o artigo foo tem três revisões, ele deve ser capaz de executar a segunda revisão na 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 como false, ou talvez você pode atualizar a tabela de artigos e definir o revision_id para o ID fornecido. Tudo o que você faz vai afetar o que agora é a atual revisão promovida, e não tivemos que fazer qualquer coisa bruta como / artigos / foo / revisões / promoção / 34 que vi sugerido antes.

Resumo

Como eu falei antes, às vezes, tentar fazer as coisas RESTful para o bem delas é mais uma escolha religiosa do que uma técnica. Em alguns casos, a forma mais RESTful não é sempre a mais óbvia, mas tem só uma década que estamos aprendendo sobre coisas como /articles/foo/revisions/promote/34 são úteis. Talvez para um site, mas uma API RESTful é uma plataforma de possibilidades sem fim, e não apenas um conjunto de funções com parâmetros personalizados, como XML-RPC/SOAP. Uma vez que você começa a entender como isso funciona, um monte de coisas que pareciam inúteis, começam a parecer muito mais limpas e flexíveis com RESTful.

***

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