nomadcode/services/core/internal/scheduler/river.go
toki ef4c2191ce feat(core): scheduler jobs 및 workflow 서비스 개선, 코드 리뷰 아카이브
- scheduler: roadmap_sync_jobs, river 개선 및 테스트 추가
- workflow: lifecycle, model, service 수정 및 테스트 보완
2026-06-15 22:16:11 +09:00

107 lines
3.1 KiB
Go

package scheduler
import (
"context"
"log/slog"
"time"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/riverqueue/river"
"github.com/riverqueue/river/riverdriver/riverpgxv5"
"github.com/riverqueue/river/rivermigrate"
"github.com/nomadcode/nomadcode-core/internal/agent"
"github.com/nomadcode/nomadcode-core/internal/model"
"github.com/nomadcode/nomadcode-core/internal/notification"
"github.com/nomadcode/nomadcode-core/internal/workflow"
)
type Client struct {
river *river.Client[pgx.Tx]
driver *riverpgxv5.Driver
logger *slog.Logger
}
// New builds the scheduler client. creationSync is the optional Plane-origin
// Milestone creation sync orchestrator: when non-nil its worker is registered so
// EnqueueRoadmapCreationSync has a runtime worker; when nil only the task worker
// is registered, preserving the default task scheduler behavior.
func New(pool *pgxpool.Pool, lifecycle TaskLifecycle, notifications *notification.Service, agentClient agent.Client, modelClient model.Client, creationSync CreationSyncRunner, finalizer TaskFinalizer, runTimeout time.Duration, logger *slog.Logger) (*Client, error) {
workers := river.NewWorkers()
river.AddWorker(workers, &TaskWorker{
Lifecycle: lifecycle,
Notifications: notifications,
Agent: agentClient,
Model: modelClient,
RunTimeout: runTimeout,
Logger: logger,
})
if creationSync != nil {
river.AddWorker(workers, &RoadmapCreationSyncWorker{
Sync: creationSync,
TaskFinalizer: finalizer,
Logger: logger,
})
}
driver := riverpgxv5.New(pool)
riverClient, err := river.NewClient[pgx.Tx](driver, &river.Config{
Logger: logger,
Queues: map[string]river.QueueConfig{
river.QueueDefault: {MaxWorkers: 2},
},
Workers: workers,
MaxAttempts: workflow.DefaultTaskMaxAttempts,
})
if err != nil {
return nil, err
}
return &Client{
river: riverClient,
driver: driver,
logger: logger,
}, nil
}
func (c *Client) Migrate(ctx context.Context) error {
migrator, err := rivermigrate.New[pgx.Tx](c.driver, &rivermigrate.Config{
Logger: c.logger,
})
if err != nil {
return err
}
result, err := migrator.Migrate(ctx, rivermigrate.DirectionUp, nil)
if err != nil {
return err
}
if c.logger != nil && len(result.Versions) > 0 {
c.logger.Info("river migrations applied", "count", len(result.Versions))
}
return nil
}
func (c *Client) Start(ctx context.Context) error {
return c.river.Start(ctx)
}
func (c *Client) Stop(ctx context.Context) error {
return c.river.Stop(ctx)
}
func (c *Client) EnqueueTask(ctx context.Context, taskID string) error {
_, err := c.river.Insert(ctx, TaskJobArgs{TaskID: taskID}, nil)
return err
}
// EnqueueRoadmapCreationSync enqueues a Plane-origin Milestone creation sync
// job. It is the internal seam the develop-scan handoff calls to hand a matched
// scan result to the orchestrator worker; it is intentionally separate from
// EnqueueTask and is not exposed through a public HTTP route.
func (c *Client) EnqueueRoadmapCreationSync(ctx context.Context, args RoadmapCreationSyncJobArgs) error {
_, err := c.river.Insert(ctx, args, nil)
return err
}