Dev & EngARTIGO

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

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

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↳PHP44 conteúdosPadronizando seu código com PHP CS FixerDev (Back & Front) · mai 2019Docker de imagem. Para rodar projetos PHP em menos de 30 segundosDev (Back & Front) · jan 2026PHP 7.3: conheça as novidades desta versãoDev (Back & Front) · abr 2019Ver tudo em Dev (Back & Front) →, 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↳Banco de dados134 conteúdosSQL ou NoSQL: eis a questão!!Data · mar 2020Banco de dados: como organizar e dar segurança para milhões de dados de loteriasData · mai 20215 serviços gratuitos na cloud para bancos de dados PostgresData · fev 2025Ver tudo em Data → (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/

Programa em PHP desde os 12 anos. Gosta de explorar novas tecnologias, testar novas metodologias e construir coisas maiores e melhores. Atualmente, trabalha na Ride, uma empresa de transporte solidário.

Mais de Phil Sturgeon
Ver perfil →