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")}}
|
||||
}
|
||||
55
sdk/go/workflow_definitions.go
Normal file
55
sdk/go/workflow_definitions.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package orbit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
)
|
||||
|
||||
// WorkflowRef 构造输入或已完成步骤的字段引用;合法作用域及类型在发布时由服务端校验。
|
||||
func WorkflowRef(source string, path ...string) WorkflowValue {
|
||||
return WorkflowValue{Kind: "REFERENCE", Reference: &WorkflowReference{Source: source, Path: append([]string(nil), path...)}}
|
||||
}
|
||||
|
||||
// CreateWorkflowDefinition 幂等创建稳定定义身份,不自动发布或启动流程。
|
||||
func (c *Client) CreateWorkflowDefinition(ctx context.Context, key string, in CreateWorkflowDefinitionRequest) (WorkflowDefinitionResult, error) {
|
||||
var out WorkflowDefinitionResult
|
||||
err := c.call(ctx, "POST", "/v1/workflow-definitions", key, in, &out)
|
||||
return out, err
|
||||
}
|
||||
func (c *Client) GetWorkflowDefinition(ctx context.Context, id string) (WorkflowDefinition, error) {
|
||||
var out WorkflowDefinition
|
||||
if !validID(id) {
|
||||
return out, errors.New("invalid definition id")
|
||||
}
|
||||
err := c.call(ctx, "GET", "/v1/workflow-definitions/"+id, "", nil, &out)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// PublishWorkflowVersion 冻结规则;网络结果不明时保留原定义ID、版本、内容、原因和幂等键重报。
|
||||
func (c *Client) PublishWorkflowVersion(ctx context.Context, id, key string, in PublishWorkflowVersionRequest) (WorkflowVersionResult, error) {
|
||||
var out WorkflowVersionResult
|
||||
if !validID(id) {
|
||||
return out, errors.New("invalid definition id")
|
||||
}
|
||||
err := c.call(ctx, "POST", "/v1/workflow-definitions/"+id+"/versions", key, in, &out)
|
||||
return out, err
|
||||
}
|
||||
func (c *Client) GetWorkflowVersion(ctx context.Context, id, version string) (WorkflowVersion, error) {
|
||||
var out WorkflowVersion
|
||||
if !validID(id) || !workflowVersionName(version) {
|
||||
return out, errors.New("invalid definition id or version")
|
||||
}
|
||||
err := c.call(ctx, "GET", "/v1/workflow-definitions/"+id+"/versions/"+version, "", nil, &out)
|
||||
return out, err
|
||||
}
|
||||
func workflowVersionName(s string) bool {
|
||||
if len(s) == 0 || len(s) > 64 {
|
||||
return false
|
||||
}
|
||||
for i, r := range s {
|
||||
if !(r >= 'a' && r <= 'z' || r >= 'A' && r <= 'Z' || r >= '0' && r <= '9' || i > 0 && (r == '_' || r == '.' || r == '-')) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
109
sdk/go/workflow_definitions_integration_test.go
Normal file
109
sdk/go/workflow_definitions_integration_test.go
Normal file
@@ -0,0 +1,109 @@
|
||||
package orbit
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestIntegrationWorkflowDefinitionSDK(t *testing.T) {
|
||||
api, queue, _ := integrationWorkerClient(t)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
key := workerTestID(t)
|
||||
created, err := api.CreateWorkflowDefinition(ctx, key, CreateWorkflowDefinitionRequest{Name: "workflow." + key, Description: "精确数字发布回归"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
id := created.Definition.ID
|
||||
typ := WorkflowType{Kind: "OBJECT", Fields: map[string]WorkflowType{"n": {Kind: "NUMBER"}}}
|
||||
task := WorkflowTask{QueueID: queue, Handler: "object.prepare", HandlerVersion: "v1", PayloadVersion: "v1", InputType: typ, Input: WorkflowValue{Kind: "LITERAL", Literal: json.RawMessage(`{"n":9007199254740993}`)}, ResultType: typ, TimeoutMS: 5000, Retry: RetryPolicy{MaxAttempts: 1, AttemptTimeoutMS: 3000}, RecoverySafety: RecoveryReconcileOnly}
|
||||
in := PublishWorkflowVersionRequest{Version: "v1", Reason: "SDK publication", Specification: WorkflowSpecification{SchemaVersion: 1, TimeoutMS: 30000, FailurePolicy: "REVIEW", InputType: typ, Body: WorkflowBlock{Steps: []WorkflowStep{{Key: "prepare", Kind: "TASK", Task: &task}, {Key: "wait", Kind: "TIMER", Timer: &WorkflowTimer{DelayMS: 1000}}}, OutputType: typ, Output: WorkflowRef("prepare")}}}
|
||||
// 真服务确认发布提交后,代理关闭客户端连接;SDK不得自动换键或默默重试。
|
||||
target, _ := url.Parse(api.baseURL)
|
||||
proxy := httputil.NewSingleHostReverseProxy(target)
|
||||
var dropped atomic.Bool
|
||||
var calls atomic.Int32
|
||||
proxy.ModifyResponse = func(r *http.Response) error {
|
||||
calls.Add(1)
|
||||
if dropped.CompareAndSwap(false, true) {
|
||||
raw, err := io.ReadAll(r.Body)
|
||||
r.Body.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if r.StatusCode != 201 || !strings.Contains(string(raw), `"deduplicated":false`) {
|
||||
t.Error("publication was not committed before response loss")
|
||||
}
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
return nil
|
||||
}
|
||||
proxy.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err error) {
|
||||
conn, _, e := w.(http.Hijacker).Hijack()
|
||||
if e == nil {
|
||||
conn.Close()
|
||||
}
|
||||
}
|
||||
gateway := httptest.NewServer(proxy)
|
||||
defer gateway.Close()
|
||||
client, err := NewClient(gateway.URL, Options{Token: api.token})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := client.PublishWorkflowVersion(ctx, id, key, in); err == nil || calls.Load() != 1 || !dropped.Load() {
|
||||
t.Fatal("response loss not surfaced", err)
|
||||
}
|
||||
published, err := client.PublishWorkflowVersion(ctx, id, key, in)
|
||||
if err != nil || !published.Deduplicated {
|
||||
t.Fatal("lost publication not recovered", err)
|
||||
}
|
||||
original := published.Version
|
||||
if string(original.Specification.Body.Steps[0].Task.Input.Literal) != `{"n":9007199254740993}` || len(original.ContentDigest) != 64 || original.Analysis.Steps != 2 || original.Analysis.MaxTaskAttempts != 1 {
|
||||
t.Fatal("SDK result changed precision or analysis")
|
||||
}
|
||||
same, err := api.PublishWorkflowVersion(ctx, id, workerTestID(t), in)
|
||||
if err != nil || !same.Deduplicated || !reflect.DeepEqual(same.Version, original) {
|
||||
t.Fatal("same version", err)
|
||||
}
|
||||
in.Specification.Body.Steps[0].Task.Input.Literal = json.RawMessage(`{"n":9007199254740992}`)
|
||||
_, err = api.PublishWorkflowVersion(ctx, id, workerTestID(t), in)
|
||||
var apiErr *APIError
|
||||
if !errors.As(err, &apiErr) || apiErr.Code != "STATE_CONFLICT" {
|
||||
t.Fatal("overwrite accepted", err)
|
||||
}
|
||||
in.Version = "v2"
|
||||
next, err := api.PublishWorkflowVersion(ctx, id, workerTestID(t), in)
|
||||
if err != nil || next.Version.ContentDigest == original.ContentDigest {
|
||||
t.Fatal("v2", err)
|
||||
}
|
||||
stored, err := api.GetWorkflowVersion(ctx, id, "v1")
|
||||
if err != nil || !reflect.DeepEqual(stored, original) {
|
||||
t.Fatal("immutable v1", err)
|
||||
}
|
||||
metadata, err := api.GetWorkflowDefinition(ctx, id)
|
||||
if err != nil || metadata.Revision != 3 {
|
||||
t.Fatal("unexpected version count", err)
|
||||
}
|
||||
// 类型的空块输出支持显式null,不放宽普通任务的null参数约束。
|
||||
in.Version = "v3"
|
||||
in.Specification.Body = WorkflowBlock{OutputType: WorkflowType{Kind: "NULL"}, Output: WorkflowValue{Kind: "LITERAL", Literal: json.RawMessage(`null`)}}
|
||||
v3, err := api.PublishWorkflowVersion(ctx, id, workerTestID(t), in)
|
||||
if err != nil || v3.Version.Analysis.Queues == nil || len(v3.Version.Analysis.Queues) != 0 {
|
||||
t.Fatal("null block", err)
|
||||
}
|
||||
if _, err := api.GetWorkflowVersion(ctx, id, "../other"); err == nil {
|
||||
t.Fatal("unsafe path accepted")
|
||||
}
|
||||
t.Log("real HTTP publication response dropped after commit; original identity restored precise immutable version, no second revision; all SDK definition routes exercised")
|
||||
}
|
||||
115
sdk/go/workflow_types.go
Normal file
115
sdk/go/workflow_types.go
Normal file
@@ -0,0 +1,115 @@
|
||||
package orbit
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
// WorkflowSpecification 是结构版本1的完整定义。版本发布后禁止原地修改。
|
||||
type WorkflowSpecification struct {
|
||||
SchemaVersion uint32 `json:"schema_version"` // SchemaVersion 当前必须为1,和业务发布版本分开。
|
||||
InputType WorkflowType `json:"input_type"` // InputType 实例输入契约,OBJECT字段全部必填且不允许额外字段。
|
||||
TimeoutMS uint64 `json:"timeout_ms"` // TimeoutMS 从实例受理起计的有限总时限,1毫秒至30天,暂停不续期。
|
||||
FailurePolicy string `json:"failure_policy"` // FailurePolicy REVIEW或FAIL_AFTER_SETTLED;未知效果始终先核查。
|
||||
Body WorkflowBlock `json:"body"` // Body 顶层顺序块;组合节点内部同样使用有限顺序块。
|
||||
}
|
||||
|
||||
// WorkflowType 是有限的类型描述;JSON表示只能整体传递的任意JSON,不能据此访问未知字段。
|
||||
type WorkflowType struct {
|
||||
Kind string `json:"kind"` // Kind STRING、NUMBER、BOOLEAN、NULL、OBJECT、ARRAY或JSON。
|
||||
Fields map[string]WorkflowType `json:"fields,omitempty"` // Fields 仅OBJECT使用,最多64项;字段名称为ASCII标识。
|
||||
Items *WorkflowType `json:"items,omitempty"` // Items 仅ARRAY使用,数组元素采用同一契约。
|
||||
Enum []string `json:"enum,omitempty"` // Enum 仅STRING使用,可选的1至32个互异值,按精确文本比较。
|
||||
}
|
||||
|
||||
// WorkflowValue 明确区分常量、引用和构造对象/数组,禁止字符串插值和任意脚本。
|
||||
type WorkflowValue struct {
|
||||
Kind string `json:"kind"` // Kind LITERAL、REFERENCE、OBJECT或ARRAY。
|
||||
Literal json.RawMessage `json:"literal,omitempty"` // Literal 仅LITERAL使用,保留精确数字,可以显式为null。
|
||||
Reference *WorkflowReference `json:"reference,omitempty"` // WorkflowReference 仅REFERENCE使用,必须指向当前路径可用的输入或结果。
|
||||
Fields map[string]WorkflowValue `json:"fields,omitempty"` // Fields 仅OBJECT使用,字段值递归解析,最多64项。
|
||||
Items []WorkflowValue `json:"items,omitempty"` // Items 仅ARRAY使用,最多256项;业务大数组可整体引用。
|
||||
}
|
||||
|
||||
type WorkflowReference struct {
|
||||
Source string `json:"source"` // Source input、workflow或在当前词法作用域已完成的步骤key。
|
||||
Path []string `json:"path,omitempty"` // Path 最多8层已声明OBJECT字段;空路径引用整个值,不支持动态路径或数组下标。
|
||||
}
|
||||
|
||||
// WorkflowBlock 的输出是组合节点向外暴露的唯一数据出口;内部步骤不能被外部直接引用。
|
||||
type WorkflowBlock struct {
|
||||
Steps []WorkflowStep `json:"steps,omitempty"` // Steps 按顺序执行;空块可直接映射输入,所有分支仍需显式输出。
|
||||
OutputType WorkflowType `json:"output_type"` // OutputType 本块输出契约。
|
||||
Output WorkflowValue `json:"output"` // Output 全部步骤完成后解析的输出表达式。
|
||||
}
|
||||
|
||||
type WorkflowStep struct {
|
||||
Key string `json:"key"` // Key 全定义唯一ASCII标识,1至64字符,不能为input或workflow。
|
||||
Kind string `json:"kind"` // Kind TASK、BRANCH、PARALLEL、SIGNAL、TIMER或POLL;必须只提供对应配置。
|
||||
Task *WorkflowTask `json:"task,omitempty"` // WorkflowTask 普通业务工作,交由独立Worker处理。
|
||||
Branch *WorkflowBranch `json:"branch,omitempty"` // WorkflowBranch 按顺序选择第一个匹配路径,必须有默认路径。
|
||||
Parallel *WorkflowParallel `json:"parallel,omitempty"` // WorkflowParallel 静态有限分支,全部成功后汇合。
|
||||
Signal *WorkflowSignal `json:"signal,omitempty"` // WorkflowSignal 等待一次按步骤和业务操作匹配的外部输入。
|
||||
Timer *WorkflowTimer `json:"timer,omitempty"` // WorkflowTimer 持久化时间等待,不占业务Worker槽位。
|
||||
Poll *WorkflowPoll `json:"poll,omitempty"` // WorkflowPoll 有界查询,轮次预算与每轮Pulse尝试预算独立。
|
||||
}
|
||||
|
||||
type WorkflowTask struct {
|
||||
QueueID string `json:"queue_id"` // QueueID 发布及启动时都需按命名空间和当前权限校验。
|
||||
Handler string `json:"handler"` // Handler 已命名的业务处理器;定义不携带可执行源码。
|
||||
HandlerVersion string `json:"handler_version"` // HandlerVersion 固定的处理器版本。
|
||||
PayloadVersion string `json:"payload_version"` // PayloadVersion 固定的输入契约版本。
|
||||
InputType WorkflowType `json:"input_type"` // InputType 输入映射必须满足的处理器参数契约。
|
||||
Input WorkflowValue `json:"input"` // Input 在激活时根据已保存数据解析,不使用当前时间或随机数。
|
||||
ResultType WorkflowType `json:"result_type"` // ResultType 权威成功结果契约,不匹配时进入核查而不推进。
|
||||
TimeoutMS uint64 `json:"timeout_ms"` // TimeoutMS 单任务总时限,正值且不超过流程时限,运行时还受实例剩余时限限制。
|
||||
Retry RetryPolicy `json:"retry"` // Retry 显式冻结有限策略,包含首次尝试,最多100次。
|
||||
RecoverySafety RecoverySafety `json:"recovery_safety"` // RecoverySafety 显式RECONCILE_ONLY或IDEMPOTENT。
|
||||
Priority int16 `json:"priority,omitempty"` // Priority 冻结优先级0至9。
|
||||
BusinessKey *WorkflowValue `json:"business_key,omitempty"` // BusinessKey 可选字符串映射,只用于业务追踪,不代表幂等。
|
||||
ConcurrencyKey *WorkflowValue `json:"concurrency_key,omitempty"` // ConcurrencyKey 可选字符串映射,采用Pulse共享资源许可规则。
|
||||
}
|
||||
|
||||
type WorkflowPredicate struct {
|
||||
Op string `json:"op"` // Op EQ/NE/LT/LE/GT/GE或AND/OR/NOT;只比较已声明的标量。
|
||||
Left *WorkflowValue `json:"left,omitempty"` // Left 比较操作左值,逻辑操作不得携带。
|
||||
Right *WorkflowValue `json:"right,omitempty"` // Right 比较操作右值,必须与左值同种标量。
|
||||
Arguments []WorkflowPredicate `json:"arguments,omitempty"` // Arguments AND/OR为2至16项,NOT为1项,深度最多8层。
|
||||
}
|
||||
|
||||
type WorkflowBranch struct {
|
||||
Cases []WorkflowBranchCase `json:"cases"` // Cases 1至16个按顺序匹配的条件路径。
|
||||
Default *WorkflowBlock `json:"default"` // Default 必填,穷尽未匹配情况,不能静默跳过。
|
||||
}
|
||||
type WorkflowBranchCase struct {
|
||||
When WorkflowPredicate `json:"when"` // When 在分支激活时根据前置持久化数据计算。
|
||||
Body WorkflowBlock `json:"body"` // Body 选中路径;所有路径输出类型必须一致。
|
||||
}
|
||||
type WorkflowParallel struct {
|
||||
Branches []WorkflowParallelBranch `json:"branches"` // Branches 2至8个静态分支;结果为各分支名称对应输出的OBJECT。
|
||||
}
|
||||
type WorkflowParallelBranch struct {
|
||||
Name string `json:"name"` // Name 本并行节点内唯一ASCII字段标识。
|
||||
Body WorkflowBlock `json:"body"` // Body 分支内部独立顺序块;不能读取兄弟分支尚未汇合的数据。
|
||||
}
|
||||
|
||||
type WorkflowSignal struct {
|
||||
Name string `json:"name"` // Name 外部信号类型,需同时匹配实例、步骤激活和业务关联。
|
||||
PayloadVersion string `json:"payload_version"` // PayloadVersion 外部输入契约版本。
|
||||
Correlation WorkflowValue `json:"correlation"` // Correlation 必须解析为非空业务操作字符串,运行时冻结在等待记录。
|
||||
PayloadType WorkflowType `json:"payload_type"` // PayloadType 信号内容及该节点成功输出的类型。
|
||||
TimeoutMS uint64 `json:"timeout_ms"` // TimeoutMS 激活起的有限等待时限,不超过流程时限;暂停不续期。
|
||||
}
|
||||
type WorkflowTimer struct {
|
||||
DelayMS uint64 `json:"delay_ms"` // DelayMS 激活起的正等待时长,不超过流程时限;输出为NULL。
|
||||
}
|
||||
|
||||
type WorkflowPoll struct {
|
||||
Task WorkflowTask `json:"task"` // WorkflowTask 同一查询处理器及固定输入;查询不得再次调用原业务提交接口。
|
||||
MaxRounds uint32 `json:"max_rounds"` // MaxRounds 1至1000,轮次含首次;每轮一个任务,单轮内部重试不新增轮次。
|
||||
TimeoutMS uint64 `json:"timeout_ms"` // TimeoutMS 激活起的总查询时限,不超过流程时限。
|
||||
IntervalMS uint64 `json:"interval_ms"` // IntervalMS 从上一轮处理中结果受理后等待,至少1000毫秒且小于查询总时限;不补发错过的轮次。
|
||||
StatusPath []string `json:"status_path,omitempty"` // StatusPath 成功查询结果内的STRING枚举字段路径,空表示整个结果就是枚举。
|
||||
Pending []string `json:"pending"` // Pending 表示仍在处理的枚举值,至少1项。
|
||||
Succeeded []string `json:"succeeded"` // Succeeded 表示业务确认成功的枚举值,至少1项。
|
||||
Failed []string `json:"failed,omitempty"` // Failed 表示业务确认失败的枚举值。
|
||||
Review []string `json:"review,omitempty"` // Review 表示需要核查的枚举值;四组必须无交集并覆盖整个状态枚举。
|
||||
FinalSignal *WorkflowSignal `json:"final_signal,omitempty"` // FinalSignal 可选最终回执,其内容类型与查询结果一致,只接受非Pending状态。
|
||||
}
|
||||
56
sdk/go/workflow_views.go
Normal file
56
sdk/go/workflow_views.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package orbit
|
||||
|
||||
import "time"
|
||||
|
||||
// CreateWorkflowDefinitionRequest 创建命名空间内的稳定定义身份,不发布版本或启动实例。
|
||||
type CreateWorkflowDefinitionRequest struct {
|
||||
Name string `json:"name"` // Name 命名空间内唯一ASCII名称,最长128字符。
|
||||
Description string `json:"description"` // Description 用途说明,最多1024个Unicode字符。
|
||||
}
|
||||
|
||||
// PublishWorkflowVersionRequest 发布不可变版本;同版本内容冲突不能通过更换幂等键覆盖。
|
||||
type PublishWorkflowVersionRequest struct {
|
||||
Version string `json:"version"` // Version 研发指定的ASCII版本名,最长64字符,不包含路径分隔符。
|
||||
Specification WorkflowSpecification `json:"specification"` // WorkflowSpecification 完整且显式的冻结规则。
|
||||
Reason string `json:"reason"` // Reason 非空发布原因,最多1024个字符,写入审计。
|
||||
}
|
||||
|
||||
// WorkflowDefinition 仅含命名空间级元数据;读取完整版本还须授权所有引用队列。
|
||||
type WorkflowDefinition struct {
|
||||
ID string `json:"id"` // ID 定义稳定标识。
|
||||
NamespaceID string `json:"namespace_id"` // NamespaceID 定义隔离范围。
|
||||
Name string `json:"name"` // Name 创建时冻结的唯一名称。
|
||||
Description string `json:"description"` // Description 创建时冻结的用途说明。
|
||||
Revision uint64 `json:"revision,string"` // Revision 从1开始,每个新发布版本增加1,不能替代业务版本名。
|
||||
CreatedAt time.Time `json:"created_at"` // CreatedAt UTC数据库创建时间。
|
||||
UpdatedAt time.Time `json:"updated_at"` // UpdatedAt 最近新版本发布的UTC数据库时间。
|
||||
}
|
||||
|
||||
// WorkflowAnalysis 是静态预算上界;不表示业务任务已执行或已分配配额。
|
||||
type WorkflowAnalysis struct {
|
||||
Steps uint32 `json:"steps"` // Steps 全定义静态步骤数。
|
||||
MaxConcurrency uint32 `json:"max_concurrency"` // MaxConcurrency 并行业务任务上界。
|
||||
MaxTaskAttempts uint32 `json:"max_task_attempts"` // MaxTaskAttempts 所有路径的保守尝试预算总和。
|
||||
Queues []string `json:"queues"` // Queues 排序去重队列,始终为数组。
|
||||
}
|
||||
|
||||
// WorkflowVersion 返回原始发布内容和身份,不使用可变的latest别名。
|
||||
type WorkflowVersion struct {
|
||||
ID string `json:"id"` // ID 不可变版本记录标识。
|
||||
NamespaceID string `json:"namespace_id"` // NamespaceID 隔离范围。
|
||||
DefinitionID string `json:"definition_id"` // DefinitionID 父定义标识,由应用事务校验。
|
||||
Version string `json:"version"` // Version 发布版本名。
|
||||
Specification WorkflowSpecification `json:"specification"` // WorkflowSpecification 已冻结规则,数字精度不经float64转换。
|
||||
ContentDigest string `json:"content_digest"` // ContentDigest 带域分隔的SHA-256,64位小写十六进制。
|
||||
PublishedBy string `json:"published_by"` // PublishedBy 原发布凭据标识,不包含凭据秘密。
|
||||
PublishedAt time.Time `json:"published_at"` // PublishedAt 原发布时间,重复发布不更新。
|
||||
Analysis WorkflowAnalysis `json:"analysis"` // WorkflowAnalysis 根据结构版本1规则计算的静态检查结果。
|
||||
}
|
||||
type WorkflowDefinitionResult struct {
|
||||
Definition WorkflowDefinition `json:"definition"` // Definition 创建事务的原始快照。
|
||||
Deduplicated bool `json:"deduplicated"` // Deduplicated 是否返回原回执。
|
||||
}
|
||||
type WorkflowVersionResult struct {
|
||||
Version WorkflowVersion `json:"version"` // Version 原版本快照,不包含当前父定义状态。
|
||||
Deduplicated bool `json:"deduplicated"` // Deduplicated 原键或同版本同内容重复请求。
|
||||
}
|
||||
Reference in New Issue
Block a user