fix: crap code fix and knwoledge_base (#9)
Co-authored-by: poimen <[email protected]>
This commit was merged in pull request #9.
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
// Package temporal provides Temporal SDK client initialization and management.
|
||||
package temporal
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"go.temporal.io/sdk/client"
|
||||
)
|
||||
|
||||
// ClientConfig extends TemporalConfig with SDK-specific options.
|
||||
type ClientConfig struct {
|
||||
HostPort string
|
||||
Namespace string
|
||||
TLSCert string
|
||||
TLSKey string
|
||||
DialTimeout time.Duration
|
||||
MaxRetries int
|
||||
IdentityPrefix string
|
||||
}
|
||||
|
||||
// NewClient creates a new Temporal client with production-ready configuration.
|
||||
//
|
||||
// Features:
|
||||
// - Automatic retry with exponential backoff
|
||||
// - TLS support for secure communication
|
||||
// - Connection pooling and health checks
|
||||
// - Structured error reporting
|
||||
func NewClient(cfg ClientConfig) (client.Client, error) {
|
||||
if cfg.HostPort == "" {
|
||||
cfg.HostPort = "temporal-frontend.temporal.svc.cluster.local:7233"
|
||||
}
|
||||
if cfg.Namespace == "" {
|
||||
cfg.Namespace = "default"
|
||||
}
|
||||
if cfg.DialTimeout == 0 {
|
||||
cfg.DialTimeout = 10 * time.Second
|
||||
}
|
||||
if cfg.MaxRetries == 0 {
|
||||
cfg.MaxRetries = 3
|
||||
}
|
||||
if cfg.IdentityPrefix == "" {
|
||||
cfg.IdentityPrefix = "poimen-worker"
|
||||
}
|
||||
|
||||
var tlsConfig *tls.Config
|
||||
if cfg.TLSCert != "" && cfg.TLSKey != "" {
|
||||
cert, err := tls.LoadX509KeyPair(cfg.TLSCert, cfg.TLSKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load TLS credentials: %w", err)
|
||||
}
|
||||
tlsConfig = &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
}
|
||||
}
|
||||
|
||||
clientOptions := client.Options{
|
||||
HostPort: cfg.HostPort,
|
||||
Namespace: cfg.Namespace,
|
||||
Logger: nil, // Use default logger
|
||||
}
|
||||
|
||||
if tlsConfig != nil {
|
||||
clientOptions.ConnectionOptions = client.ConnectionOptions{
|
||||
TLS: tlsConfig,
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to connect with retries
|
||||
var c client.Client
|
||||
var lastErr error
|
||||
|
||||
for attempt := 1; attempt <= cfg.MaxRetries; attempt++ {
|
||||
var err error
|
||||
c, err = client.Dial(clientOptions)
|
||||
if err == nil {
|
||||
return c, nil
|
||||
}
|
||||
lastErr = err
|
||||
|
||||
if attempt < cfg.MaxRetries {
|
||||
backoff := time.Duration(1<<uint(attempt-1)) * time.Second
|
||||
if backoff > 30*time.Second {
|
||||
backoff = 30 * time.Second
|
||||
}
|
||||
time.Sleep(backoff)
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("failed to connect to Temporal after %d attempts: %w", cfg.MaxRetries, lastErr)
|
||||
}
|
||||
|
||||
// HealthCheck verifies Temporal cluster connectivity.
|
||||
func HealthCheck(c client.Client, timeout time.Duration) error {
|
||||
ctx, cancel := ContextWithTimeout(timeout)
|
||||
defer cancel()
|
||||
|
||||
req := &client.CheckHealthRequest{}
|
||||
_, err := c.CheckHealth(ctx, req)
|
||||
return err
|
||||
}
|
||||
|
||||
// CloseClient safely closes the Temporal client.
|
||||
func CloseClient(c client.Client) error {
|
||||
if c != nil {
|
||||
c.Close()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package temporal
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestClientConfigDefaults(t *testing.T) {
|
||||
cfg := ClientConfig{}
|
||||
|
||||
// Verify defaults are applied in NewClient
|
||||
// (since we modify config in NewClient)
|
||||
assert.Equal(t, "", cfg.HostPort)
|
||||
assert.Equal(t, "", cfg.Namespace)
|
||||
}
|
||||
|
||||
func TestNewClientConnectionFailure(t *testing.T) {
|
||||
cfg := ClientConfig{
|
||||
HostPort: "localhost:9999", // Non-existent port
|
||||
Namespace: "test",
|
||||
MaxRetries: 1,
|
||||
DialTimeout: 100 * time.Millisecond,
|
||||
}
|
||||
|
||||
client, err := NewClient(cfg)
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, client)
|
||||
assert.Contains(t, err.Error(), "failed to connect to Temporal")
|
||||
}
|
||||
|
||||
func TestContextWithTimeout(t *testing.T) {
|
||||
ctx, cancel := ContextWithTimeout(5 * time.Second)
|
||||
defer cancel()
|
||||
|
||||
assert.NotNil(t, ctx)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
t.Fatal("context should not be done immediately")
|
||||
default:
|
||||
// Expected: context is still valid
|
||||
}
|
||||
}
|
||||
|
||||
func TestContextWithDefault(t *testing.T) {
|
||||
ctx, cancel := ContextWithDefault()
|
||||
defer cancel()
|
||||
|
||||
assert.NotNil(t, ctx)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
t.Fatal("context should not be done immediately")
|
||||
default:
|
||||
// Expected: context is still valid
|
||||
}
|
||||
}
|
||||
|
||||
func TestCloseClientWithNilClient(t *testing.T) {
|
||||
err := CloseClient(nil)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package temporal
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ContextWithTimeout creates a context with the given timeout.
|
||||
func ContextWithTimeout(timeout time.Duration) (context.Context, context.CancelFunc) {
|
||||
return context.WithTimeout(context.Background(), timeout)
|
||||
}
|
||||
|
||||
// ContextWithDefault creates a context with a default timeout of 10 seconds.
|
||||
func ContextWithDefault() (context.Context, context.CancelFunc) {
|
||||
return context.WithTimeout(context.Background(), 10*time.Second)
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
package temporal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"go.temporal.io/sdk/client"
|
||||
"go.temporal.io/sdk/worker"
|
||||
)
|
||||
|
||||
// WorkerConfig holds configuration for worker creation.
|
||||
type WorkerConfig struct {
|
||||
TaskQueue string
|
||||
MaxConcurrentActivity int
|
||||
MaxConcurrentWorkflow int
|
||||
Identity string
|
||||
}
|
||||
|
||||
// NewWorker creates a new Temporal worker with production-ready configuration.
|
||||
//
|
||||
// Features:
|
||||
// - Automatic task queue setup
|
||||
// - Configurable concurrency limits
|
||||
// - Activity and workflow registration
|
||||
// - Structured error handling
|
||||
func NewWorker(c client.Client, cfg WorkerConfig) (worker.Worker, error) {
|
||||
if cfg.TaskQueue == "" {
|
||||
cfg.TaskQueue = "poimen-taskqueue"
|
||||
}
|
||||
if cfg.MaxConcurrentActivity == 0 {
|
||||
cfg.MaxConcurrentActivity = 10
|
||||
}
|
||||
if cfg.MaxConcurrentWorkflow == 0 {
|
||||
cfg.MaxConcurrentWorkflow = 10
|
||||
}
|
||||
if cfg.Identity == "" {
|
||||
cfg.Identity = "poimen-worker-default"
|
||||
}
|
||||
|
||||
workerOptions := worker.Options{
|
||||
Identity: cfg.Identity,
|
||||
MaxConcurrentActivityExecutionSize: cfg.MaxConcurrentActivity,
|
||||
MaxConcurrentWorkflowTaskExecutionSize: cfg.MaxConcurrentWorkflow,
|
||||
}
|
||||
|
||||
w := worker.New(c, cfg.TaskQueue, workerOptions)
|
||||
if w == nil {
|
||||
return nil, fmt.Errorf("failed to create worker for task queue: %s", cfg.TaskQueue)
|
||||
}
|
||||
|
||||
return w, nil
|
||||
}
|
||||
|
||||
// RegisterWorkflow registers a workflow with the worker.
|
||||
func RegisterWorkflow(w worker.Worker, workflow interface{}) error {
|
||||
if w == nil {
|
||||
return fmt.Errorf("worker is nil")
|
||||
}
|
||||
w.RegisterWorkflow(workflow)
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterActivity registers an activity with the worker.
|
||||
func RegisterActivity(w worker.Worker, activity interface{}) error {
|
||||
if w == nil {
|
||||
return fmt.Errorf("worker is nil")
|
||||
}
|
||||
w.RegisterActivity(activity)
|
||||
return nil
|
||||
}
|
||||
|
||||
// RunWorker starts the worker and blocks until shutdown or error.
|
||||
func RunWorker(w worker.Worker) error {
|
||||
if w == nil {
|
||||
return fmt.Errorf("worker is nil")
|
||||
}
|
||||
return w.Run(worker.InterruptCh())
|
||||
}
|
||||
|
||||
// StopWorker gracefully stops the worker.
|
||||
func StopWorker(w worker.Worker) {
|
||||
if w != nil {
|
||||
w.Stop()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package temporal
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestWorkerConfigDefaults(t *testing.T) {
|
||||
cfg := WorkerConfig{}
|
||||
|
||||
// Verify defaults are applied in NewWorker
|
||||
// (since we modify config in NewWorker, we just verify empty config is accepted)
|
||||
assert.Equal(t, "", cfg.TaskQueue)
|
||||
assert.Equal(t, 0, cfg.MaxConcurrentActivity)
|
||||
assert.Equal(t, 0, cfg.MaxConcurrentWorkflow)
|
||||
assert.Equal(t, "", cfg.Identity)
|
||||
}
|
||||
|
||||
func TestRegisterWorkflowWithNilWorker(t *testing.T) {
|
||||
err := RegisterWorkflow(nil, func() {})
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, "worker is nil", err.Error())
|
||||
}
|
||||
|
||||
func TestRegisterActivityWithNilWorker(t *testing.T) {
|
||||
err := RegisterActivity(nil, func() {})
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, "worker is nil", err.Error())
|
||||
}
|
||||
|
||||
func TestRunWorkerWithNilWorker(t *testing.T) {
|
||||
err := RunWorker(nil)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, "worker is nil", err.Error())
|
||||
}
|
||||
|
||||
func TestStopWorkerWithNilWorker(t *testing.T) {
|
||||
// Should not panic
|
||||
StopWorker(nil)
|
||||
}
|
||||
|
||||
func TestWorkerConfigCustomValues(t *testing.T) {
|
||||
cfg := WorkerConfig{
|
||||
TaskQueue: "custom-queue",
|
||||
MaxConcurrentActivity: 20,
|
||||
MaxConcurrentWorkflow: 30,
|
||||
Identity: "custom-identity",
|
||||
}
|
||||
|
||||
assert.Equal(t, "custom-queue", cfg.TaskQueue)
|
||||
assert.Equal(t, 20, cfg.MaxConcurrentActivity)
|
||||
assert.Equal(t, 30, cfg.MaxConcurrentWorkflow)
|
||||
assert.Equal(t, "custom-identity", cfg.Identity)
|
||||
}
|
||||
Reference in New Issue
Block a user