Files
orbit/sdk/go/batch.go

122 lines
4.6 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package orbit
import (
"context"
"errors"
"time"
)
// MaxBatchTasks 是单次批量受理的项数上限,总请求还受512 KiB限制。
const MaxBatchTasks = 32
type SubmitTasksRequest struct {
// Items 1至32项独立任务,总请求不超过512 KiB,不保证整批原子性。
Items []SubmitTasksItem `json:"items"`
}
type SubmitTasksItem struct {
// IdempotencyKey 调用方持久化的稳定键,与SubmitTask共用幂等身份;批内不得重复。
IdempotencyKey string `json:"idempotency_key"`
// Task 原始任务参数,重报必须保留原值。
Task SubmitTaskRequest `json:"task"`
}
type BatchItemStatus string
const (
BatchAccepted BatchItemStatus = "ACCEPTED"
BatchRejected BatchItemStatus = "REJECTED"
BatchUnknown BatchItemStatus = "UNKNOWN"
BatchNotAttempted BatchItemStatus = "NOT_ATTEMPTED"
)
type BatchTaskReceipt struct {
// TaskID 已受理任务标识,实际结果需要查询或订阅。
TaskID string `json:"task_id"`
// QueueID 任务所属队列。
QueueID string `json:"queue_id"`
// Revision 原始受理版本,JSON为十进制字符串,不代表当前版本。
Revision uint64 `json:"revision,string"`
// CreatedAt 原始受理时间,UTC微秒精度。
CreatedAt time.Time `json:"created_at"`
// Deduplicated 是否复用相同身份的已有回执。
Deduplicated bool `json:"deduplicated"`
}
type BatchItemError struct {
// Code 稳定错误码;UNKNOWN结果使用UNAVAILABLE,不证明回滚。
Code string `json:"code"`
// Message 不包含数据库错误或输入的对外说明。
Message string `json:"message"`
// RetryAfterMS 建议最小退避毫秒数,零为未指定,SDK不自动重试。
RetryAfterMS uint32 `json:"retry_after_ms,omitempty"`
}
type SubmitTasksItemResult struct {
// Index 本次请求内从0开始的索引,与Items输入同序。
Index int `json:"index"`
// Status 每项独立状态;HTTP成功不表示全部任务已受理。
Status BatchItemStatus `json:"status"`
// Receipt 仅ACCEPTED存在,省略大参数以保证回执大小有界。
Receipt *BatchTaskReceipt `json:"receipt,omitempty"`
// Error 仅非ACCEPTED存在,调用方需分别处理明确拒绝、未知及未尝试。
Error *BatchItemError `json:"error,omitempty"`
}
type SubmitTasksResult struct {
// Items 与输入数量和顺序一致的逐项结果。
Items []SubmitTasksItemResult `json:"items"`
}
// SubmitTasks 逐项提交独立任务。返回error时整批可能已部分或全部受理,必须保留原键及参数核对。
// error为nil时仍需检查每项Status。此方法不会自动重试、拆批、生成新键或启动并发请求。
func (c *Client) SubmitTasks(ctx context.Context, in SubmitTasksRequest) (SubmitTasksResult, error) {
if len(in.Items) < 1 || len(in.Items) > MaxBatchTasks {
return SubmitTasksResult{}, errors.New("items must contain 1 to 32 tasks")
}
keys := make(map[string]struct{}, len(in.Items))
for _, item := range in.Items {
if err := validateIdempotencyKey(item.IdempotencyKey); err != nil {
return SubmitTasksResult{}, err
}
if _, exists := keys[item.IdempotencyKey]; exists {
return SubmitTasksResult{}, errors.New("item idempotency keys must be distinct within a batch")
}
keys[item.IdempotencyKey] = struct{}{}
}
var out SubmitTasksResult
if err := c.callJSON(ctx, "POST", "/v1/tasks:batch", "", in, &out); err != nil {
return SubmitTasksResult{}, err
}
// 拒绝不完整或错误关联的回执,避免调用方将截断的成功响应误认为全部已受理。
if len(out.Items) != len(in.Items) {
return SubmitTasksResult{}, errors.New("invalid Orbit batch result count")
}
for i, item := range out.Items {
valid := item.Index == i
switch item.Status {
case BatchAccepted:
r := item.Receipt
valid = valid && item.Error == nil && r != nil && validID(r.TaskID) && r.QueueID == in.Items[i].Task.QueueID && validID(r.QueueID) && r.Revision > 0 && !r.CreatedAt.IsZero()
case BatchRejected, BatchUnknown, BatchNotAttempted:
e := item.Error
valid = valid && item.Receipt == nil && e != nil && len(e.Code) > 0 && len(e.Code) <= 64 && len(e.Message) > 0 && len(e.Message) <= 1024 && e.RetryAfterMS <= 3600000
if valid && item.Status == BatchUnknown {
valid = e.Code == "UNAVAILABLE"
}
if valid && item.Status == BatchNotAttempted {
valid = e.Code == "BATCH_STOPPED"
}
if valid && item.Status == BatchRejected {
valid = e.Code != "UNAVAILABLE" && e.Code != "BATCH_STOPPED"
}
default:
valid = false
}
if !valid {
return SubmitTasksResult{}, errors.New("invalid Orbit batch item result; retry using original identities")
}
}
return out, nil
}