Petri-pilot generates Go code that leverages generics from go-pflow for type-safe event sourcing.
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()
}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
}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
}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
}| 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 |
// 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"])