PostProcessing as a Success Boundary
In the previous guides, we used tags to apply behavior and to select semantics based on capability.
In this guide, we introduce Tagex’s most powerful abstraction: PostProcessing as a guaranteed success path.
The Missing Abstraction in Most Code
In typical Go code, success is implicit. You know something worked because nothing failed.
This leads to patterns like:
if err := normalize(row); err != nil { return err }
if err := applyLimits(row); err != nil { return err }
if err := persist(row); err != nil { return err }
The problem is not correctness — it is structure:
- Success is inferred, not explicit
- Logic is scattered
- Boilerplate grows linearly
- Data and consequence are disconnected
Tagex’s Guarantee
Tagex introduces a strong invariant:
If Success() runs, all tagged semantics have succeeded.
This is not a convention. It is enforced by the execution model.
Declaring the Success Path
Consider an import pipeline where data must be:
- Normalized
- Capability-adjusted
- Only then persisted
type ImportRow struct {
SKU string `norm:"upper"`
Price string `norm:"currency, locale=nl_NL"`
Stock int `cap:"nonnegative"`
repo *Repository
}
The struct declares all required semantics. No action is taken yet.
Attaching the Consequence
func (r *ImportRow) Success() error {
return r.repo.UpsertItem(
r.SKU,
r.Price,
r.Stock,
)
}
This method expresses a single idea:
Persist this row only if everything succeeded.
No flags. No checks. No duplicated logic.
Execution Flow
When ProcessStruct is called:
- All tagged directives are executed
- Any error aborts processing
Success()runs only if no errors occurred
The success boundary is explicit and reliable.
Why This Is Powerful
- The struct owns its success path
- Data and consequence are colocated
- No caller needs to “remember” what to do next
- Boilerplate error handling disappears
This turns a plain data structure into a semantic transaction.
Reusability and Composition
Because semantics and consequences are attached to the struct:
- The same struct can be used in batch jobs
- In streaming pipelines
- In tests
- In one-off tools
The caller does not need to know what “success” means — the struct defines it.
PreProcessing (Briefly)
While this guide focuses on PostProcessing, it is worth noting the complementary role of PreProcessing.
Before() allows a struct to:
- Prepare context
- Initialize resources
- Fail fast before any semantics run
Together, Pre- and PostProcessing form a clean execution envelope.
What This Is (and Is Not)
PostProcessing is not a callback. It is not an event hook. It is not a side-channel.
It is a commit phase that only exists if all declared semantics succeeded.
Looking Ahead
In the next guide, we will step back and look at validation as one specific use of this model — evaluation without mutation or side effects.
Validation turns out to be a very small special case.