Go library cung cấp production-ready foundation cho Temporal workflow engine. Mục tiêu: project mới chỉ viết business logic, không phải boilerplate.
temporal-common/
├── client/ # Factory, config, health check, graceful shutdown
├── activity/ # Retry presets, error taxonomy, idempotency helpers
├── workflow/ # Saga manager, versioning, approval pattern, scheduler
├── observability/ # Prometheus metrics, OpenTelemetry tracing, logging
├── testing/ # Test environment, mock builder, assertions
├── examples/
│ ├── loan-disbursement/ # Full reference implementation
│ └── payment-saga/
└── references/ # Chi tiết từng pattern
├── saga-pattern.md
├── versioning.md
├── retry-presets.md
├── testing.md
└── observability.md
// ❌ KHÔNG viết thế này
retryPolicy := &temporal.RetryPolicy{MaxAttempts: 5, ...}
ctx = workflow.WithActivityOptions(ctx, workflow.ActivityOptions{RetryPolicy: retryPolicy})
// ✅ Chỉ cần
ctx = temporalcommon.WithFinancialAPIOptions(ctx)- Timeout phải luôn có — không để zero value
- Retry policy phải explicit — không dùng Temporal default ngầm
- Error classification phải tường minh — retryable vs non-retryable
- Saga manager tự động rollback theo LIFO khi có error
- Pivot point phải được đánh dấu tường minh
- Compensation step phải idempotent và retry-able vô hạn
- Mọi source of non-determinism phải dùng
workflow.SideEffect - Không dùng
time.Now()— dùngworkflow.Now(ctx) - Không dùng
randtrực tiếp — wrap qua SideEffect
// Retryable — infrastructure/transient failures
temporalcommon.NewRetryableError("message", cause)
// Non-retryable — business logic violations
temporalcommon.NewBusinessError("ErrorCode", "message", cause)
// ErrorCode: "InsufficientFund", "BorrowerBlacklisted", "LoanAlreadyDisbursed", "DuplicateTransaction"
// Compensation failure — cần human intervention
temporalcommon.NewCompensationError("message", sagaID, step, cause)Mỗi activity PHẢI có:
- Extract idempotency key:
temporalcommon.IdempotencyKey(activity.GetInfo(ctx)) - Heartbeat nếu operation > 10s:
activity.RecordHeartbeat(ctx, "...") - Check idempotency store trước khi execute
- Classify errors đúng type khi return
Mỗi workflow PHẢI có:
- Khởi tạo saga:
saga := temporalcommon.NewSaga(ctx); defer saga.Compensate() - Set search attributes cho observability
- Dùng preset options, không tự define ActivityOptions
saga.AddCompensation(...)ngay sau mỗi step thành côngsaga.SetPivot()đúng vị trísaga.Complete()khi thành công
Mọi feature PHẢI có 3 loại test:
- Unit test: logic thuần, không cần Temporal
- Workflow test: dùng TestEnvironment, cover happy/compensation/retry path
- Failure scenario test: activity fail, orchestrator crash, compensation fail, duplicate execution
Khi thay đổi workflow đã có running instances — LUÔN dùng GetVersion:
v := workflow.GetVersion(ctx, "descriptive-change-id", workflow.DefaultVersion, 1)
if v == 1 { /* new path */ }
// DefaultVersion: old pathXem references/versioning.md để biết naming convention và cleanup checklist.
// ❌ Tự define ActivityOptions trong business workflow
workflow.WithActivityOptions(ctx, workflow.ActivityOptions{StartToCloseTimeout: 30 * time.Second})
// ❌ Dùng time.Now() trong workflow (non-deterministic)
if time.Now().After(deadline) { ... }
// ❌ Compensation step có thể fail permanently (saga stuck forever)
func RefundFeeActivity(ctx context.Context, txID string) error {
return externalAPI.Refund(txID) // phải có retry + DLQ fallback
}
// ❌ Bỏ qua idempotency trong activity (double execution khi retry)
func TransferActivity(ctx context.Context, input TransferInput) error {
return bankAPI.Transfer(input.Amount) // phải check idempotency key trước
}import temporalcommon "github.com/yourorg/temporal-common"
// Bootstrap
engine, _ := temporalcommon.New(temporalcommon.Config{...})
// Retry presets
temporalcommon.WithFinancialAPIOptions(ctx) // external bank, payment GW
temporalcommon.WithNotificationOptions(ctx) // email, SMS, push — best effort
temporalcommon.WithInternalServiceOptions(ctx) // gRPC internal services
temporalcommon.WithLongRunningOptions(ctx) // jobs > 5 minutes
// Saga
saga := temporalcommon.NewSaga(ctx)
defer saga.Compensate()
saga.AddCompensation(RollbackActivity, payload)
saga.SetPivot()
saga.Complete()
// Versioning
v := workflow.GetVersion(ctx, "change-id", workflow.DefaultVersion, 1)
// Human approval
approval, err := temporalcommon.WaitForApproval(ctx, temporalcommon.ApprovalRequest{
ApprovalID: entityID,
Timeout: 48 * time.Hour,
})
// Idempotency
key := temporalcommon.IdempotencyKey(activity.GetInfo(ctx))
key := temporalcommon.IdempotencyKeyNoRetry(activity.GetInfo(ctx))