patterns

package
v1.1.7 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 13, 2026 License: AGPL-3.0 Imports: 6 Imported by: 0

README

DynamORM Soft Delete Pattern

This package provides comprehensive soft delete functionality for DynamoDB models.

Features

Core Components
SoftDeletable Interface

Defines the contract for soft-deletable entities:

type SoftDeletable interface {
    SoftDelete()
    Restore()
    IsDeleted() bool
    GetDeletedAt() *time.Time
    SetDeletedAt(*time.Time)
    GetDeletedBy() string
    SetDeletedBy(string)
}
SoftDeleteModel

Embeddable struct that provides default soft delete functionality:

type MyModel struct {
    ID   string `dynamodbav:"pk"`
    Name string `dynamodbav:"name"`
    
    // Embed soft delete functionality
    patterns.SoftDeleteModel
}
SoftDeleteRepository

Repository with soft delete-aware operations:

repo := patterns.NewSoftDeleteRepository(client, "my-table", logger)

// Soft delete an item
err := repo.SoftDelete(ctx, model, "user123")

// Restore a soft-deleted item
err := repo.Restore(ctx, model)

// Hard delete (permanent)
err := repo.HardDelete(ctx, keys)
Query Modes
Default (Active Only)

By default, all queries exclude soft-deleted items:

// Only returns active items
items, err := repo.Query(ctx, queryInput)
Include Deleted Items

Use WithDeleted() to include soft-deleted items:

// Returns both active and soft-deleted items
withDeletedRepo := repo.WithDeleted()
items, err := withDeletedRepo.Query(ctx, queryInput)
Only Deleted Items

Use QueryOnlyDeleted() to return only soft-deleted items:

// Returns only soft-deleted items
deletedItems, err := repo.QueryOnlyDeleted(ctx, queryInput)

Usage Examples

Basic Model
type User struct {
    ID       string    `dynamodbav:"pk" json:"id"`
    Username string    `dynamodbav:"username" json:"username"`
    Email    string    `dynamodbav:"email" json:"email"`
    
    // Embed soft delete functionality
    patterns.SoftDeleteModel
    
    CreatedAt time.Time `dynamodbav:"created_at" json:"created_at"`
    UpdatedAt time.Time `dynamodbav:"updated_at" json:"updated_at"`
}

// Ensure User implements SoftDeletable
var _ patterns.SoftDeletable = (*User)(nil)
Repository Operations
// Initialize repository
repo := patterns.NewSoftDeleteRepository(dynamoClient, "users", logger)

// Create user
user := &User{
    ID:       "user123",
    Username: "john_doe",
    Email:    "john@example.com",
}

// Soft delete
err := repo.SoftDelete(ctx, user, "admin_user")
if err != nil {
    log.Fatal(err)
}

// Check if deleted
if user.IsDeleted() {
    fmt.Printf("User deleted at: %v by: %s\n", 
        user.GetDeletedAt(), user.GetDeletedBy())
}

// Restore
err = repo.Restore(ctx, user)
if err != nil {
    log.Fatal(err)
}
Cleanup Operations
Manual Cleanup

Remove items that have been soft-deleted for more than 30 days:

deleted, err := repo.CleanupOldDeletes(ctx, 30*24*time.Hour, 25)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Permanently deleted %d items\n", deleted)
Get Old Deleted Items

Query for items to be cleaned up:

oldItems, err := repo.GetDeletedItemsOlderThan(ctx, 30*24*time.Hour)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Found %d items ready for cleanup\n", len(oldItems))
Statistics

Get soft delete statistics:

stats, err := repo.GetSoftDeleteStats(ctx)
if err != nil {
    log.Fatal(err)
}

fmt.Printf("Total: %d, Active: %d, Deleted: %d (%.1f%%)\n",
    stats.TotalItems,
    stats.ActiveItems, 
    stats.DeletedItems,
    stats.GetDeletionPercentage())
Convenience Functions
// Check if item is deleted
if patterns.IsItemDeleted(user) {
    fmt.Println("User is soft deleted")
}

// Get deletion info
deletedAt, deletedBy, isDeleted := patterns.GetItemDeletionInfo(user)
if isDeleted {
    fmt.Printf("Deleted at %v by %s\n", deletedAt, deletedBy)
}

// Soft delete with user tracking
err := patterns.SoftDeleteByUser(ctx, repo, user, "admin123")

// Restore item
err := patterns.RestoreItem(ctx, repo, user)

Implementation Details

DynamoDB Fields

The SoftDeleteModel adds these fields to your table:

  • deleted_at (optional): Timestamp when item was soft deleted
  • deleted_by (optional): User ID who performed the soft delete
Query Filtering

The repository automatically adds DynamoDB filter expressions to exclude soft-deleted items:

// Active items only (default)
FilterExpression: attribute_not_exists(deleted_at)

// Deleted items only
FilterExpression: attribute_exists(deleted_at)
Batch Operations

Cleanup operations use DynamoDB batch operations with proper error handling:

  • Respects 25-item batch limits
  • Handles unprocessed items
  • Provides progress feedback
  • Logs cleanup statistics

Testing

Comprehensive tests cover:

  • Model lifecycle (soft delete, restore)
  • Repository operations (query, scan, get)
  • Filtering behavior
  • Cleanup operations
  • Statistics calculation
  • Error handling

Run tests:

go test ./pkg/storage/dynamorm/patterns/ -v

Best Practices

When to Use Soft Delete

Good candidates:

  • User accounts
  • Important business records
  • Data with audit requirements
  • Items referenced by other entities

Avoid for:

  • Temporary/cache data
  • Log entries
  • High-volume transactional data
Cleanup Strategy
  1. Regular cleanup: Schedule cleanup jobs to run weekly/monthly
  2. Retention policy: Define clear retention periods (30-90 days)
  3. Monitoring: Track soft delete statistics
  4. Backup: Consider backing up before permanent deletion
Performance Considerations
  • Soft deleted items still consume storage
  • Queries include additional filter expressions
  • Use GSIs for efficient soft-delete queries
  • Monitor table scan costs with high soft-delete ratios

Error Handling

The package provides comprehensive error handling:

// Soft delete
if err := repo.SoftDelete(ctx, model, userID); err != nil {
    if errors.Is(err, patterns.ErrAlreadyDeleted) {
        // Handle already deleted
    }
    // Handle other errors
}

// Restore
if err := repo.Restore(ctx, model); err != nil {
    if errors.Is(err, patterns.ErrNotDeleted) {
        // Handle not deleted
    }
    // Handle other errors
}

Documentation

Overview

Package patterns provides soft delete functionality and patterns for DynamORM model operations.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func GetItemDeletionInfo

func GetItemDeletionInfo(model SoftDeletable) (deletedAt *time.Time, deletedBy string, isDeleted bool)

GetItemDeletionInfo returns deletion information for an item

func IsItemDeleted

func IsItemDeleted(model SoftDeletable) bool

IsItemDeleted is a convenience function to check if an item is soft-deleted

func RestoreItem

func RestoreItem(ctx context.Context, repo *SoftDeleteRepository, model SoftDeletable) error

RestoreItem is a convenience function to restore a soft-deleted item

func SoftDeleteByUser

func SoftDeleteByUser(ctx context.Context, repo *SoftDeleteRepository, model SoftDeletable, userID string) error

SoftDeleteByUser is a convenience function to soft delete with user tracking

Types

type ExampleModel

type ExampleModel struct {
	PK    string `theorydb:"pk" json:"pk"` // Primary key for DynamORM
	SK    string `theorydb:"sk" json:"sk"` // Sort key for DynamORM
	ID    string `json:"id"`               // Business ID
	Name  string `json:"name"`
	Email string `json:"email"`

	// Embed soft delete functionality
	SoftDeleteModel

	CreatedAt time.Time `json:"created_at"`
	UpdatedAt time.Time `json:"updated_at"`
}

ExampleModel demonstrates usage of soft delete pattern with DynamORM

func NewExampleModel

func NewExampleModel(id, name, email string) *ExampleModel

NewExampleModel creates a new example model with proper DynamORM keys

type SoftDeletable

type SoftDeletable interface {
	SoftDelete()
	Restore()
	IsDeleted() bool
	GetDeletedAt() *time.Time
	SetDeletedAt(*time.Time)
	GetDeletedBy() string
	SetDeletedBy(string)
}

SoftDeletable interface defines methods for soft delete functionality

type SoftDeleteModel

type SoftDeleteModel struct {
	DeletedAt *time.Time `json:"deleted_at,omitempty"`
	DeletedBy string     `json:"deleted_by,omitempty"`
}

SoftDeleteModel provides default soft delete functionality

func (*SoftDeleteModel) GetDeletedAt

func (m *SoftDeleteModel) GetDeletedAt() *time.Time

GetDeletedAt returns the deletion timestamp

func (*SoftDeleteModel) GetDeletedBy

func (m *SoftDeleteModel) GetDeletedBy() string

GetDeletedBy returns the user who deleted this entity

func (*SoftDeleteModel) IsDeleted

func (m *SoftDeleteModel) IsDeleted() bool

IsDeleted returns true if the entity is soft deleted

func (*SoftDeleteModel) Restore

func (m *SoftDeleteModel) Restore()

Restore removes the soft delete marker

func (*SoftDeleteModel) SetDeletedAt

func (m *SoftDeleteModel) SetDeletedAt(deletedAt *time.Time)

SetDeletedAt sets the deletion timestamp

func (*SoftDeleteModel) SetDeletedBy

func (m *SoftDeleteModel) SetDeletedBy(deletedBy string)

SetDeletedBy sets the user who deleted this entity

func (*SoftDeleteModel) SoftDelete

func (m *SoftDeleteModel) SoftDelete()

SoftDelete marks the entity as deleted

func (*SoftDeleteModel) SoftDeleteBy

func (m *SoftDeleteModel) SoftDeleteBy(userID string)

SoftDeleteBy marks the entity as deleted by a specific user

type SoftDeleteRepository

type SoftDeleteRepository struct {
	// contains filtered or unexported fields
}

SoftDeleteRepository provides repository methods with soft delete support using DynamORM

func NewSoftDeleteRepository

func NewSoftDeleteRepository(db core.DB, logger *zap.Logger) *SoftDeleteRepository

NewSoftDeleteRepository creates a new soft delete repository using DynamORM

func (*SoftDeleteRepository) CleanupOldDeletes

func (r *SoftDeleteRepository) CleanupOldDeletes(ctx context.Context, model interface{}, olderThan time.Duration, batchSize int) (int, error)

CleanupOldDeletes permanently removes items that have been soft-deleted for longer than the specified duration

func (*SoftDeleteRepository) Get

func (r *SoftDeleteRepository) Get(ctx context.Context, model SoftDeletable, pk, sk interface{}) error

Get retrieves an item by primary key, optionally including soft-deleted items

func (*SoftDeleteRepository) GetDeletedItemsOlderThan

func (r *SoftDeleteRepository) GetDeletedItemsOlderThan(ctx context.Context, model interface{}, dest interface{}, olderThan time.Duration) error

GetDeletedItemsOlderThan returns items that have been soft-deleted for longer than the specified duration

func (*SoftDeleteRepository) GetSoftDeleteStats

func (r *SoftDeleteRepository) GetSoftDeleteStats(ctx context.Context, model interface{}) (SoftDeleteStats, error)

GetSoftDeleteStats returns statistics about soft-deleted items

func (*SoftDeleteRepository) HardDelete

func (r *SoftDeleteRepository) HardDelete(ctx context.Context, model interface{}) error

HardDelete permanently deletes an item from DynamoDB using DynamORM

func (*SoftDeleteRepository) OnlyDeleted

func (r *SoftDeleteRepository) OnlyDeleted() *SoftDeleteRepository

OnlyDeleted returns a new repository instance that only returns soft-deleted items

func (*SoftDeleteRepository) Query

func (r *SoftDeleteRepository) Query(ctx context.Context, model interface{}, _ interface{}) core.Query

Query performs a query with soft delete filtering using DynamORM

func (*SoftDeleteRepository) QueryOnlyDeleted

func (r *SoftDeleteRepository) QueryOnlyDeleted(ctx context.Context, model interface{}) core.Query

QueryOnlyDeleted queries only soft-deleted items using DynamORM

func (*SoftDeleteRepository) Restore

func (r *SoftDeleteRepository) Restore(ctx context.Context, model SoftDeletable) error

Restore restores a soft-deleted item

func (*SoftDeleteRepository) Scan

func (r *SoftDeleteRepository) Scan(ctx context.Context, model interface{}) core.Query

Scan performs a scan with soft delete filtering using DynamORM

func (*SoftDeleteRepository) ScanOnlyDeleted

func (r *SoftDeleteRepository) ScanOnlyDeleted(ctx context.Context, model interface{}) core.Query

ScanOnlyDeleted scans only soft-deleted items using DynamORM

func (*SoftDeleteRepository) SoftDelete

func (r *SoftDeleteRepository) SoftDelete(ctx context.Context, model SoftDeletable, userID string) error

SoftDelete performs a soft delete operation

func (*SoftDeleteRepository) WithDeleted

func (r *SoftDeleteRepository) WithDeleted() *SoftDeleteRepository

WithDeleted returns a new repository instance that includes soft-deleted items in queries

type SoftDeleteStats

type SoftDeleteStats struct {
	TotalItems   int `json:"total_items"`
	ActiveItems  int `json:"active_items"`
	DeletedItems int `json:"deleted_items"`
}

SoftDeleteStats contains statistics about soft-deleted items

func (SoftDeleteStats) GetDeletionPercentage

func (s SoftDeleteStats) GetDeletionPercentage() float64

GetDeletionPercentage returns the percentage of items that are soft-deleted

func (SoftDeleteStats) String

func (s SoftDeleteStats) String() string

String returns a string representation of soft delete stats

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL