feat: add bounded polling and evidence-based workflow recovery examples
This commit is contained in:
119
sdk/go/examples/internal/flowdemo/runtime.go
Normal file
119
sdk/go/examples/internal/flowdemo/runtime.go
Normal file
@@ -0,0 +1,119 @@
|
||||
// Package flowdemo 仅为可运行流程示例提供初始化和有界等待,不属于公开SDK或业务恢复策略。
|
||||
package flowdemo
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
orbit "git.yuntuo.hk/k2safe/orbit/sdk/go"
|
||||
)
|
||||
|
||||
func Client() (*orbit.Client, string, string, error) {
|
||||
raw, err := os.ReadFile(os.Getenv("ORBIT_CREDENTIALS_FILE"))
|
||||
if err != nil {
|
||||
return nil, "", "", err
|
||||
}
|
||||
var credentials struct {
|
||||
Token string `json:"token"`
|
||||
QueueID string `json:"queue_id"`
|
||||
}
|
||||
if json.Unmarshal(raw, &credentials) != nil || credentials.Token == "" || credentials.QueueID == "" {
|
||||
return nil, "", "", errors.New("invalid private credentials file")
|
||||
}
|
||||
client, err := orbit.NewClient(os.Getenv("ORBIT_URL"), orbit.Options{Token: credentials.Token})
|
||||
if err != nil {
|
||||
return nil, "", "", err
|
||||
}
|
||||
operation, err := orbit.NewWorkerMessageID()
|
||||
return client, credentials.QueueID, operation, err
|
||||
}
|
||||
|
||||
// PublishStart 每次示例运行创建独立身份;不将重新运行命令当作未知响应的原键恢复。
|
||||
func PublishStart(ctx context.Context, api *orbit.Client, operation, name string, spec orbit.WorkflowSpecification, input json.RawMessage) (orbit.WorkflowInstance, error) {
|
||||
var empty orbit.WorkflowInstance
|
||||
fmt.Printf("scenario=%s operation=%s\n", name, operation)
|
||||
created, err := api.CreateWorkflowDefinition(ctx, operation, orbit.CreateWorkflowDefinitionRequest{Name: name + "." + operation, Description: "controlled standalone workflow example"})
|
||||
if err != nil {
|
||||
return empty, err
|
||||
}
|
||||
if _, err = api.PublishWorkflowVersion(ctx, created.Definition.ID, operation, orbit.PublishWorkflowVersionRequest{Version: "v1", Specification: spec, Reason: "publish controlled SDK example"}); err != nil {
|
||||
return empty, err
|
||||
}
|
||||
result, err := api.StartWorkflow(ctx, operation, orbit.StartWorkflowRequest{DefinitionID: created.Definition.ID, Version: "v1", BusinessKey: operation, Input: input})
|
||||
if err != nil {
|
||||
return empty, err
|
||||
}
|
||||
fmt.Printf("workflow=%s version=%s\n", result.Instance.ID, result.Instance.Version)
|
||||
return result.Instance, nil
|
||||
}
|
||||
|
||||
// Runner 持有一个真实Worker及其结束通知,等待时同时检测Worker异常退出。
|
||||
type Runner struct {
|
||||
Worker *orbit.Worker
|
||||
stop context.CancelFunc
|
||||
done chan struct{}
|
||||
err error // err 仅在done关闭后读取,关闭通道建立同步关系。
|
||||
}
|
||||
|
||||
func StartWorker(ctx context.Context, api *orbit.Client, queue string, handlers ...orbit.WorkerHandler) (*Runner, error) {
|
||||
w, err := orbit.NewWorker(api, orbit.WorkerOptions{QueueID: queue, Capacity: 1, ShutdownTimeout: 5 * time.Second, Handlers: handlers})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
workerCtx, stop := context.WithCancel(ctx)
|
||||
r := &Runner{Worker: w, stop: stop, done: make(chan struct{})}
|
||||
go func() { r.err = w.Run(workerCtx); close(r.done) }()
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (r *Runner) Close() error {
|
||||
r.stop()
|
||||
<-r.done
|
||||
return r.err
|
||||
}
|
||||
|
||||
func (r *Runner) Wait(ctx context.Context, check func() (bool, error)) error {
|
||||
for {
|
||||
ready, err := check()
|
||||
if err != nil || ready {
|
||||
return err
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-r.done:
|
||||
return fmt.Errorf("example Worker stopped before expected state: %v", r.err)
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *Runner) Step(ctx context.Context, api *orbit.Client, workflow, key string, predicate func(orbit.WorkflowStepDetail) bool) (orbit.WorkflowStepDetail, error) {
|
||||
var out orbit.WorkflowStepDetail
|
||||
err := r.Wait(ctx, func() (bool, error) {
|
||||
var err error
|
||||
out, err = api.GetWorkflowStep(ctx, workflow, key)
|
||||
return err == nil && predicate(out), err
|
||||
})
|
||||
return out, err
|
||||
}
|
||||
|
||||
func (r *Runner) Workflow(ctx context.Context, api *orbit.Client, workflow, state string) (orbit.WorkflowInstance, error) {
|
||||
var out orbit.WorkflowInstance
|
||||
err := r.Wait(ctx, func() (bool, error) {
|
||||
var err error
|
||||
out, err = api.GetWorkflow(ctx, workflow)
|
||||
if err != nil || out.State == state {
|
||||
return err == nil, err
|
||||
}
|
||||
if out.State == "NEEDS_REVIEW" || out.State == "FAILED" || out.State == "CANCELLED" || out.State == "SUCCEEDED" {
|
||||
return false, fmt.Errorf("unexpected workflow state %s; wanted %s", out.State, state)
|
||||
}
|
||||
return false, nil
|
||||
})
|
||||
return out, err
|
||||
}
|
||||
Reference in New Issue
Block a user