Thoughts on Soft Delete
This document serves as a corrolary to this issue on aep.dev, including my thoughts to remove the soft delete pattern.
The background #
As of 2025-09-13, the aeps have a pattern in which one can “soft delete” a resource - where it not actually removed from a collection, but is actually flagged as deleted. This allows the resource to be recoverable later.
The soft delete pattern, I believe, exists to serve the purpose of situations where recovery is desired, such as most situations where persistent data is stored:
- A database, where recreating the resource does not restore any valuble data stored.
- A filesystem, where a file may need to be recovered after the fact.
The implementation of soft delete today #
The current implementation of AEPs’ soft delete boils down to the following:
- a new custom method,
undelete, on the resource. - an added field
show_deletedto theListstandard method, where resources are hidden unless specified. - an added field
show_deletedto theGetstandard method, where the resource will return 410 without it.
There are a few other details (such as the usage of expiry_date) that are additional, but do not force any additional fields on the standard methods.
The problems: soft delete is not compatible with declarative clients #
Declarative clients expect consistent resource-oriented operations on each resource - for simple CRUD on a resource, declarative clients are able to easily map each operation to the resource lifecycle.
graph LR
exists
not_exists
exists -- "update" --> exists
not_exists -- "create" --> exists
exists -- "delete" --> not_exists
For soft delete, where would that fit in? Effectively it creates another state - soft deleted.
graph LR
exists
soft_deleted
not_exists
not_exists -- "create" --> exists
exists -- "update" --> exists
soft_delete -- "undelete" --> exists
soft_delete -- "expiry hit" --> not_exists
But this may also result in weird cases like:
A resource “create” throwing an error because the resource is soft-deleted, and therefore exists. But a get would return a 404.
Proposed alternative #
Since the primary use case is about backing up and enabling restoration of data, the proposal would be to replace these augmentations on the primary resource, to adding a second resource for “backups” or “snapshots”.
The pattern would look something like:
/database/foo
/database-snapshots/foo/snapshots/latest
The database-snapshot could be support a custom method, restore, that would be
similar to undelete and bring an old resource back.
This would be similar to the resource revision pattern - where a design pattern is introduced by adding a new resource, not by augmenting standard methods.