Skip to content

Events

Dynorm provides lifecycle events for entities, allowing you to hook into create, update, delete, and other operations.

Overview

Implement event interfaces on your entities to receive callbacks:

type User struct {
    dynorm.Entity

    Email string
}

// Called before saving
func (u *User) Creating() error {
    // Validate, transform, or reject
    return nil
}

// Called after saving
func (u *User) Created() {
    // Post-creation logic
}

Event Interfaces

Create Events

Interface When Called Can Prevent
Creating Before first save Yes (return error)
Created After first save No
type Creating interface {
    Creating() error
}

type Created interface {
    Created()
}

Update Events

Interface When Called Can Prevent
Updating Before update Yes
Updated After update No
type Updating interface {
    Updating() error
}

type Updated interface {
    Updated()
}

Save Events

Called on both create and update:

Interface When Called Can Prevent
Saving Before any save Yes
Saved After any save No
type Saving interface {
    Saving() error
}

type Saved interface {
    Saved()
}

Delete Events

Interface When Called Can Prevent
Deleting Before delete Yes
Deleted After delete No
type Deleting interface {
    Deleting() error
}

type Deleted interface {
    Deleted()
}

Soft Delete Events

Interface When Called Can Prevent
Trashing Before soft delete Yes
Trashed After soft delete No
type Trashing interface {
    Trashing() error
}

type Trashed interface {
    Trashed()
}

Restore Events

Interface When Called Can Prevent
Restoring Before restore Yes
Restored After restore No
type Restoring interface {
    Restoring() error
}

type Restored interface {
    Restored()
}

Force Delete Events

Interface When Called Can Prevent
ForceDeleting Before permanent delete Yes
ForceDeleted After permanent delete No
type ForceDeleting interface {
    ForceDeleting() error
}

type ForceDeleted interface {
    ForceDeleted()
}

Retrieve Events

Interface When Called Can Prevent
Retrieved After loading from DB No
type Retrieved interface {
    Retrieved()
}

Replicate Events

Interface When Called Can Prevent
Replicating Before replication Yes
Replicated After replication No
type Replicating interface {
    Replicating() error
}

type Replicated interface {
    Replicated()
}

Event Order

On Create

sequenceDiagram
    participant App
    participant Dynorm
    participant Entity
    participant DynamoDB

    App->>Dynorm: Save(newEntity)
    Dynorm->>Entity: Saving()
    alt Saving returns error
        Entity-->>Dynorm: error
        Dynorm-->>App: error (aborted)
    end
    Dynorm->>Entity: Creating()
    alt Creating returns error
        Entity-->>Dynorm: error
        Dynorm-->>App: error (aborted)
    end
    Dynorm->>DynamoDB: PutItem
    DynamoDB-->>Dynorm: success
    Dynorm->>Entity: Created()
    Dynorm->>Entity: Saved()
    Dynorm-->>App: nil

On Update

sequenceDiagram
    participant App
    participant Dynorm
    participant Entity
    participant DynamoDB

    App->>Dynorm: Save(existingEntity)
    Dynorm->>Entity: Saving()
    Dynorm->>Entity: Updating()
    alt Returns error
        Entity-->>Dynorm: error
        Dynorm-->>App: error (aborted)
    end
    Dynorm->>DynamoDB: PutItem
    DynamoDB-->>Dynorm: success
    Dynorm->>Entity: Updated()
    Dynorm->>Entity: Saved()
    Dynorm-->>App: nil

Examples

Audit Logging

type User struct {
    dynorm.Entity

    Email     string
    UpdatedBy string
}

func (u *User) Saving() error {
    // Set audit field
    u.UpdatedBy = getCurrentUserID()
    return nil
}

func (u *User) Saved() {
    // Log the save
    log.Printf("User %s saved by %s", u.ID, u.UpdatedBy)
}

Data Transformation

type User struct {
    dynorm.Entity

    Email    string
    Username string
}

func (u *User) Creating() error {
    // Normalize email
    u.Email = strings.ToLower(strings.TrimSpace(u.Email))

    // Generate username if not provided
    if u.Username == "" {
        u.Username = strings.Split(u.Email, "@")[0]
    }

    return nil
}

Validation

type User struct {
    dynorm.Entity

    Email    string
    Username string
    Age      int
}

func (u *User) Creating() error {
    if u.Age < 13 {
        return errors.New("user must be at least 13 years old")
    }
    return nil
}

func (u *User) Updating() error {
    // Prevent email changes
    if u.IsDirty("Email") {
        return errors.New("email cannot be changed")
    }
    return nil
}

Cascade Operations

type User struct {
    dynorm.Entity

    Email string
}

func (u *User) Deleting() error {
    // Delete related entities
    posts, _ := PostRepo.Where("AuthorID", "=", u.ID).GetAll()
    for _, post := range posts.Items() {
        if err := PostRepo.Delete(post); err != nil {
            return err
        }
    }
    return nil
}

Prevent Deletion

type User struct {
    dynorm.Entity

    Email  string
    Status string
}

func (u *User) Deleting() error {
    if u.Status == "admin" {
        return errors.New("cannot delete admin users")
    }
    return nil
}

Send Notifications

type Order struct {
    dynorm.Entity

    CustomerEmail string
    Status        string
}

func (o *Order) Created() {
    // Send confirmation email
    go sendEmail(o.CustomerEmail, "Order Confirmed", o.ID)
}

func (o *Order) Updated() {
    if o.IsDirty("Status") {
        // Send status update
        go sendEmail(o.CustomerEmail, "Order Status Updated", o.Status)
    }
}

Cache Invalidation

type Product struct {
    dynorm.Entity

    Name  string
    Price float64
}

func (p *Product) Saved() {
    // Invalidate product cache
    cache.Delete("product:" + p.ID)
    cache.Delete("products:all")
}

func (p *Product) Deleted() {
    // Invalidate product cache
    cache.Delete("product:" + p.ID)
    cache.Delete("products:all")
}

Event Interface Reference

Interface Method Return Purpose
Creating Creating() error Before first save
Created Created() - After first save
Updating Updating() error Before update
Updated Updated() - After update
Saving Saving() error Before any save
Saved Saved() - After any save
Deleting Deleting() error Before delete
Deleted Deleted() - After delete
Trashing Trashing() error Before soft delete
Trashed Trashed() - After soft delete
Restoring Restoring() error Before restore
Restored Restored() - After restore
ForceDeleting ForceDeleting() error Before hard delete
ForceDeleted ForceDeleted() - After hard delete
Retrieved Retrieved() - After load from DB
Replicating Replicating() error Before replication
Replicated Replicated() - After replication

Best Practices

Do

  • Keep event handlers fast
  • Use async processing for slow operations
  • Return meaningful errors from "before" events
  • Clean up related data in delete events

Don't

  • Perform slow I/O in event handlers
  • Ignore errors from cascading operations
  • Create infinite loops between events
  • Rely solely on events for critical business logic

Next Steps