Skip to content

Instantly share code, notes, and snippets.

@stackdump
Last active January 26, 2026 19:18
Show Gist options
  • Select an option

  • Save stackdump/0986140ff3e15a90cb3580eed4eef415 to your computer and use it in GitHub Desktop.

Select an option

Save stackdump/0986140ff3e15a90cb3580eed4eef415 to your computer and use it in GitHub Desktop.
Petri-Pilot Generics Usage Examples

Generics in Petri-Pilot Generated Code

Petri-pilot generates Go code that leverages generics from go-pflow for type-safe event sourcing.

1. Type-Safe Aggregate State Machine

Every generated aggregate uses eventsource.StateMachine[State] for compile-time type safety:

// generated/erc20token/aggregate.go

type State struct {
    TotalSupply int64                       `json:"total_supply"`
    Balances    map[string]int64            `json:"balances"`
    Allowances  map[string]map[string]int64 `json:"allowances"`
}

type Aggregate struct {
    sm *eventsource.StateMachine[State]  // Generic state machine
}

func NewAggregate(id string) *Aggregate {
    sm := eventsource.NewStateMachine(id, NewState(), InitialPlaces())
    
    // Register typed handlers
    sm.RegisterHandler(EventTypeMint, func(state *State, event *eventsource.Event) error {
        return applyMint(state, event)
    })
    
    return &Aggregate{sm: sm}
}

// State() returns the typed state, not interface{}
func (a *Aggregate) State() *State {
    return a.sm.State()
}

2. Generic Event Unmarshaling Helper

Type-safe event data extraction without manual type assertions:

// generated/*/aggregate.go

func unmarshalEventData[T any](event *eventsource.Event) (*T, error) {
    var data T
    if err := json.Unmarshal(event.Data, &data); err != nil {
        return nil, err
    }
    return &data, nil
}

// Usage in event handlers:
func applyTransfer(state *State, event *eventsource.Event) error {
    data, err := unmarshalEventData[TransferData](event)  // Type-safe!
    if err != nil {
        return err
    }
    
    state.Balances[data.From] -= data.Amount
    state.Balances[data.To] += data.Amount
    return nil
}

3. go-pflow Generic Foundation

The eventsource.StateMachine[S] from go-pflow provides the generic infrastructure:

// github.com/pflow-xyz/go-pflow/eventsource/aggregate.go

type StateMachine[S any] struct {
    *Base[S]
    places map[string]int
}

type Base[S any] struct {
    id       string
    state    S
    version  int
    handlers map[string]func(*S, *Event) error
}

func NewStateMachine[S any](id string, initial S, places map[string]int) *StateMachine[S] {
    return &StateMachine[S]{
        Base:   NewBase(id, initial),
        places: places,
    }
}

// Type-safe state access
func (sm *StateMachine[S]) State() *S {
    return &sm.state
}

// Type-safe handler registration
func (sm *StateMachine[S]) RegisterHandler(eventType string, handler func(*S, *Event) error) {
    sm.handlers[eventType] = handler
}

4. Available Patterns (Future Use)

go-pflow provides additional generic patterns that could be leveraged:

// github.com/pflow-xyz/go-pflow/metamodel/generic.go

// Versioned data with optimistic concurrency
type DataState[T any] struct {
    Value   T
    Version int
}

func (d *DataState[T]) Update(value T) DataState[T] {
    return DataState[T]{Value: value, Version: d.Version + 1}
}

// github.com/pflow-xyz/go-pflow/metamodel/patterns.go

// Multi-step workflow orchestration
type Workflow[D any] struct {
    data  D
    steps []WorkflowStep[D]
}

// Token-based resource management
type ResourcePool[R any] struct {
    available []R
    inUse     map[string]R
}

5. Benefits of Generics in Generated Code

Aspect Without Generics With Generics
State access state.(State) with runtime panic risk sm.State() returns *State directly
Event data data := event.Data.(map[string]any) data, _ := unmarshalEventData[T](event)
Handler registration func(any, *Event) error func(*State, *Event) error
Compile-time safety Runtime type errors Compile-time type checking

6. Example: Full Event Handler Chain

// Type flows through the entire chain:

// 1. Aggregate with typed state
agg := NewAggregate("token-1")  // *Aggregate with StateMachine[State]

// 2. Fire returns typed event
event, _ := agg.Fire("transfer", TransferData{From: "alice", To: "bob", Amount: 100})

// 3. Apply with typed handler
agg.Apply(event)  // Calls func(*State, *Event) error

// 4. Access typed state
state := agg.State()  // *State, not any
fmt.Printf("Alice balance: %d\n", state.Balances["alice"])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment