Implementing Soft Delete Logic
1. Designing Soft Delete Schema
| Column | Detail |
|---|---|
| deleted_at | TIMESTAMPTZ NULL; NULL = active |
| deleted_by | FK to user |
| delete_reason | Optional audit text |
| Index | Partial: WHERE deleted_at IS NULL |
2. Implementing Soft Delete Filter
Example: Hibernate @SQLRestriction
@Entity
@SQLDelete(sql = "UPDATE orders SET deleted_at = now() WHERE id = ?")
@SQLRestriction("deleted_at IS NULL")
public class Order { ... }
3. Implementing Restore Logic
Example: Restore endpoint
@PostMapping("/orders/{id}/restore")
@PreAuthorize("hasRole('ADMIN')")
public Order restore(@PathVariable UUID id) {
int updated = jdbc.update(
"UPDATE orders SET deleted_at = NULL WHERE id = ? AND deleted_at IS NOT NULL", id);
if (updated == 0) throw new NotFoundException("order", id);
return orderRepo.findById(id).orElseThrow();
}
4. Handling Cascade Soft Deletes
| Approach | Detail |
|---|---|
| Application-level | Service traverses graph, marks each |
| Trigger | DB cascades soft-delete |
| Event-driven | Publish event; listeners cascade |
| Trade-off | Triggers can hide behavior |
5. Implementing Hard Delete Logic
| Trigger | Detail |
|---|---|
| Retention expiry | e.g., delete soft-deleted after 30 days |
| Compliance request | GDPR right-to-erasure |
| Admin action | Two-step confirmed |
| Cascade | FK ON DELETE handling |
6. Implementing Soft Delete Audit
| Field | Detail |
|---|---|
| actor | User who deleted |
| timestamp | UTC |
| reason | Compliance / cleanup / user request |
| restore events | Track restores too |
7. Handling Unique Constraints with Soft Delete
| Approach | Detail |
|---|---|
| Partial unique index | UNIQUE (email) WHERE deleted_at IS NULL |
| Append timestamp | On delete, suffix value to free key |
| Composite key | Include deleted_at in unique |
8. Implementing Soft Delete Queries
| Use | Query |
|---|---|
| Default reads | WHERE deleted_at IS NULL |
| Trash view | WHERE deleted_at IS NOT NULL |
| All | No filter (admin only) |
| Bypass | Native query when needed |
9. Implementing Permanent Delete After Period
Example: Cleanup job
@Scheduled(cron = "0 0 3 * * *")
public void purgeOldDeletes() {
jdbc.update("""
DELETE FROM orders
WHERE deleted_at IS NOT NULL
AND deleted_at < now() - interval '30 days'""");
}
10. Handling Soft Delete in Joins
| Concern | Detail |
|---|---|
| Apply per table | Filter each joined table |
| View | Define view that hides deleted |
| Watch | OUTER joins can leak deleted rows as NULLs |
11. Implementing Soft Delete Indicators
| UI Element | Detail |
|---|---|
| Badge "Deleted" | In admin trash view |
| Strikethrough | Visual cue |
| Restore button | For authorized users |
| Days-until-purge | Set expectations |