feat(landscape): run planning/execution service with fingerprint reuse; RabbitMQ job
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
82
internal/messaging/rabbitmq/landscape_consumer.go
Normal file
82
internal/messaging/rabbitmq/landscape_consumer.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package rabbitmq
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
|
||||
"github.com/google/uuid"
|
||||
amqp "github.com/rabbitmq/amqp091-go"
|
||||
)
|
||||
|
||||
const (
|
||||
// LandscapeRunRoutingKey routes Landscape Program run jobs.
|
||||
LandscapeRunRoutingKey = "landscape.run"
|
||||
// LandscapeRunQueue receives Landscape Program run jobs.
|
||||
LandscapeRunQueue = "gis.landscape.run"
|
||||
)
|
||||
|
||||
// LandscapeJob is the message body for a Landscape Program run job.
|
||||
type LandscapeJob struct {
|
||||
RunID uuid.UUID `json:"run_id"`
|
||||
}
|
||||
|
||||
// LandscapeJobPublisher publishes Landscape Program run jobs; it implements
|
||||
// service.LandscapeJobEnqueuer.
|
||||
type LandscapeJobPublisher struct {
|
||||
pub *Publisher
|
||||
}
|
||||
|
||||
// NewLandscapeJobPublisher returns a LandscapeJobPublisher.
|
||||
func NewLandscapeJobPublisher(pub *Publisher) *LandscapeJobPublisher {
|
||||
return &LandscapeJobPublisher{pub: pub}
|
||||
}
|
||||
|
||||
// EnqueueRun publishes an execution job for the given run.
|
||||
func (p *LandscapeJobPublisher) EnqueueRun(ctx context.Context, runID uuid.UUID) error {
|
||||
body, err := json.Marshal(LandscapeJob{RunID: runID})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return p.pub.Publish(ctx, LandscapeRunRoutingKey, body)
|
||||
}
|
||||
|
||||
// LandscapeProcessor executes Landscape Program runs; implemented by the service.
|
||||
type LandscapeProcessor interface {
|
||||
Execute(ctx context.Context, runID uuid.UUID) error
|
||||
// MarkRunFailed records a terminal failure on the run so it does not remain
|
||||
// stuck in an in-progress status.
|
||||
MarkRunFailed(ctx context.Context, runID uuid.UUID, reason string) error
|
||||
}
|
||||
|
||||
// landscapeHandler executes one Landscape Program run per delivery.
|
||||
type landscapeHandler struct {
|
||||
proc LandscapeProcessor
|
||||
log *slog.Logger
|
||||
}
|
||||
|
||||
func (h landscapeHandler) Handle(ctx context.Context, d amqp.Delivery) error {
|
||||
var job LandscapeJob
|
||||
if err := json.Unmarshal(d.Body, &job); err != nil {
|
||||
return fmt.Errorf("decode landscape run job: %w", err)
|
||||
}
|
||||
h.log.Info("processing landscape run", "run_id", job.RunID)
|
||||
|
||||
err := h.proc.Execute(ctx, job.RunID)
|
||||
if err != nil {
|
||||
// The delivery is nacked without requeue (see Consumer.dispatch), so the
|
||||
// run will not be retried. Record the failure on the run itself;
|
||||
// otherwise it stays "running" forever. Best-effort: log if marking
|
||||
// fails, but still return the original error so the delivery is nacked.
|
||||
if markErr := h.proc.MarkRunFailed(ctx, job.RunID, err.Error()); markErr != nil {
|
||||
h.log.Error("mark landscape run failed", "run_id", job.RunID, "error", markErr)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// NewLandscapeRunHandler returns a handler that executes Landscape Program runs.
|
||||
func NewLandscapeRunHandler(p LandscapeProcessor, log *slog.Logger) Handler {
|
||||
return landscapeHandler{proc: p, log: log}
|
||||
}
|
||||
Reference in New Issue
Block a user