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?
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/







