Files
orbit/examples/workflow-definition/main.go

133 lines
6.3 KiB
Go

// workflow-definition 演示研发定义与不可变版本发布;当前不启动流程实例。
package main
import (
"context"
"encoding/json"
"errors"
"fmt"
orbit "git.yuntuo.hk/k2safe/orbit/sdk/go"
"os"
"reflect"
"time"
)
func main() {
if err := run(); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
func run() error {
token, queue := os.Getenv("ORBIT_TOKEN"), os.Getenv("ORBIT_QUEUE_ID")
if path := os.Getenv("ORBIT_CREDENTIALS_FILE"); path != "" {
raw, err := os.ReadFile(path)
if err != nil {
return err
}
var credentials struct {
Token string `json:"token"`
QueueID string `json:"queue_id"`
}
if json.Unmarshal(raw, &credentials) != nil {
return errors.New("invalid private credential file")
}
if token == "" {
token = credentials.Token
}
if queue == "" {
queue = credentials.QueueID
}
}
client, err := orbit.NewClient(os.Getenv("ORBIT_URL"), orbit.Options{Token: token})
if err != nil {
return err
}
op := os.Getenv("ORBIT_OPERATION_ID")
if op == "" {
op, err = orbit.NewWorkerMessageID()
if err != nil {
return err
}
}
// 先输出稳定操作身份;响应丢失时用同一身份、同一版本和同一源码重新运行。
fmt.Printf("operation=%s\n", op)
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
created, err := client.CreateWorkflowDefinition(ctx, op+".create", orbit.CreateWorkflowDefinitionRequest{Name: "object-flow." + op, Description: "对象准备、并行生成结果与有界状态查询"})
if err != nil {
return err
}
id := created.Definition.ID
in := orbit.PublishWorkflowVersionRequest{Version: "v1", Reason: "initial developer release", Specification: specification(queue)}
published, err := client.PublishWorkflowVersion(ctx, id, op+".v1", in)
if err != nil {
return err
}
again, err := client.PublishWorkflowVersion(ctx, id, op+".v1", in)
if err != nil || !again.Deduplicated || !reflect.DeepEqual(again.Version, published.Version) {
return fmt.Errorf("original publication replay mismatch: %v", err)
}
same, err := client.PublishWorkflowVersion(ctx, id, op+".v1.same", in)
if err != nil || !same.Deduplicated || same.Version.ID != published.Version.ID {
return fmt.Errorf("same-version replay mismatch: %v", err)
}
in.Version = "v2"
in.Reason = "extend finite workflow deadline"
in.Specification.TimeoutMS = 90000
next, err := client.PublishWorkflowVersion(ctx, id, op+".v2", in)
if err != nil {
return err
}
original, err := client.GetWorkflowVersion(ctx, id, "v1")
if err != nil || !reflect.DeepEqual(original, published.Version) {
return fmt.Errorf("original immutable version changed: %v", err)
}
meta, err := client.GetWorkflowDefinition(ctx, id)
if err != nil {
return err
}
fmt.Printf("definition=%s revision=%d v1=%s v2=%s steps=%d max_concurrency=%d max_task_attempts=%d\n", id, meta.Revision, original.ID, next.Version.ID, original.Analysis.Steps, original.Analysis.MaxConcurrency, original.Analysis.MaxTaskAttempts)
fmt.Println("Both definitions are stored. No workflow instance or business task has been started.")
return nil
}
func specification(queue string) orbit.WorkflowSpecification {
str := orbit.WorkflowType{Kind: "STRING"}
boolean := orbit.WorkflowType{Kind: "BOOLEAN"}
input := orbit.WorkflowType{Kind: "OBJECT", Fields: map[string]orbit.WorkflowType{"object_ref": str, "operation": str, "approved": boolean}}
literal := func(raw string) orbit.WorkflowValue {
return orbit.WorkflowValue{Kind: "LITERAL", Literal: json.RawMessage(raw)}
}
block := func(value orbit.WorkflowValue) orbit.WorkflowBlock {
return orbit.WorkflowBlock{OutputType: str, Output: value}
}
task := func(handler string, value orbit.WorkflowValue) orbit.WorkflowTask {
return orbit.WorkflowTask{QueueID: queue, Handler: handler, HandlerVersion: "v1", PayloadVersion: "v1", InputType: str, Input: value, ResultType: str, TimeoutMS: 5000, Retry: orbit.RetryPolicy{MaxAttempts: 1, AttemptTimeoutMS: 3000}, RecoverySafety: orbit.RecoveryReconcileOnly}
}
prepare := task("object.prepare", orbit.WorkflowRef("input", "object_ref"))
branchDefault := block(literal(`"standard"`))
left, right := orbit.WorkflowRef("input", "approved"), literal(`true`)
branch := orbit.WorkflowBranch{Cases: []orbit.WorkflowBranchCase{{When: orbit.WorkflowPredicate{Op: "EQ", Left: &left, Right: &right}, Body: block(literal(`"approved"`))}}, Default: &branchDefault}
parallel := orbit.WorkflowParallel{}
for _, name := range []string{"preview", "manifest"} {
work := task("object."+name, orbit.WorkflowRef("prepare"))
body := block(orbit.WorkflowRef(name))
body.Steps = []orbit.WorkflowStep{{Key: name, Kind: "TASK", Task: &work}}
parallel.Branches = append(parallel.Branches, orbit.WorkflowParallelBranch{Name: name, Body: body})
}
status := orbit.WorkflowType{Kind: "OBJECT", Fields: map[string]orbit.WorkflowType{"status": {Kind: "STRING", Enum: []string{"PENDING", "DONE", "FAILED", "REVIEW"}}}}
query := task("job.query", orbit.WorkflowRef("input", "operation"))
query.ResultType = status
query.Retry = orbit.RetryPolicy{MaxAttempts: 2, AttemptTimeoutMS: 1000, InitialDelayMS: 100, MaxDelayMS: 200}
query.RecoverySafety = orbit.RecoveryIdempotent
poll := orbit.WorkflowPoll{Task: query, MaxRounds: 5, TimeoutMS: 20000, IntervalMS: 1000, StatusPath: []string{"status"}, Pending: []string{"PENDING"}, Succeeded: []string{"DONE"}, Failed: []string{"FAILED"}, Review: []string{"REVIEW"}, FinalSignal: &orbit.WorkflowSignal{Name: "job.final", PayloadVersion: "v1", Correlation: orbit.WorkflowRef("input", "operation"), PayloadType: status, TimeoutMS: 20000}}
return orbit.WorkflowSpecification{SchemaVersion: 1, InputType: input, TimeoutMS: 60000, FailurePolicy: "REVIEW", Body: orbit.WorkflowBlock{Steps: []orbit.WorkflowStep{
{Key: "prepare", Kind: "TASK", Task: &prepare},
{Key: "route", Kind: "BRANCH", Branch: &branch},
{Key: "outputs", Kind: "PARALLEL", Parallel: &parallel},
{Key: "approval", Kind: "SIGNAL", Signal: &orbit.WorkflowSignal{Name: "object.approval", PayloadVersion: "v1", Correlation: orbit.WorkflowRef("input", "operation"), PayloadType: boolean, TimeoutMS: 10000}},
{Key: "settle", Kind: "TIMER", Timer: &orbit.WorkflowTimer{DelayMS: 1000}},
{Key: "result", Kind: "POLL", Poll: &poll},
}, OutputType: status, Output: orbit.WorkflowRef("result")}}
}