Files
gis/internal/messaging/rabbitmq/landscape_consumer.go

83 lines
2.6 KiB
Go

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}
}