feat: publish immutable workflow definitions with typed SDK
This commit is contained in:
132
sdk/go/examples/workflow-definition/main.go
Normal file
132
sdk/go/examples/workflow-definition/main.go
Normal file
@@ -0,0 +1,132 @@
|
||||
// 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: ¶llel},
|
||||
{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")}}
|
||||
}
|
||||
Reference in New Issue
Block a user