Lifecycle Hooks in Practice

Tagex lets a struct define optional lifecycle hooks that run around directive execution. This guide shows how to use Before, Success, and Failure together to model real, production workflows.

The Problem: Success Is Implicit

In most Go code, success is inferred by the absence of errors. That makes it hard to centralize side effects, commit work, or emit events only when all tagged behavior has succeeded.

Lifecycle hooks make the success path explicit and structured.

The Hooks

  • Before() error runs before any directives execute
  • Success() runs only if all directives succeed
  • Failure(err) runs if any directive fails

Tagex does not interpret these hooks. They are owned by the struct and can do anything: initialize resources, write logs, persist data, or roll back.

Example: Importing a Row with a Commit on Success


type ImportRow struct {
    SKU   string `norm:"upper"`
    Price string `norm:"currency, locale=nl_NL"`
    Notes string `audit:"log, event=import"`
}

type ImportJob struct {
    Row   ImportRow
    LogID string
}

func (j *ImportJob) Before() error {
    logID, err := startAuditLog()
    if err != nil {
        return err
    }
    j.LogID = logID
    return nil
}

func (j *ImportJob) Success() {
    persistRow(j.Row)
    finalizeAuditLog(j.LogID, "success")
}

func (j *ImportJob) Failure(err error) {
    finalizeAuditLog(j.LogID, "failure")
    recordFailure(err)
}

The directives handle normalization and audit tagging. The hooks handle orchestration and side effects.

Execution Flow

  1. Before() runs
  2. Tagged directives execute in order
  3. If all succeed, Success() runs
  4. If any fail, Failure(err) runs

This makes the success path explicit and reliable. If Success() runs, you know all directives completed.

Lifecycle flow with Before running first, directives executed inside ProcessStruct, then Success or Failure based on error
Lifecycle sequence: Before() gates execution, directives run inside ProcessStruct, resulting in either Success() or Failure(err).

When to Use Each Hook

  • Before(): allocate resources, start timers, load context
  • Success(): commit work, emit events, persist outcomes
  • Failure(err): log errors, roll back, update metrics

Hooks are a boundary between semantic behavior (directives) and orchestration (side effects).

Common Pitfalls

  • Doing heavy work in directives that belongs in Success()
  • Duplicating error handling in directives and Failure(err)
  • Using Before() for semantic validation instead of setup and gating

Keep directives focused on field-level semantics. Use hooks to coordinate the wider operation. If Before() returns an error, execution halts and the struct is not processed, making it a clean place to enforce preconditions.