// Package services holds this service's AWS clients. Discovery reaches exactly // one AWS API — the SQS queue subscribed to the catalogue topic — so unlike // cms there is no S3 or MediaConvert here, and nothing database-related. package services import ( "context" "fmt" "github.com/aws/aws-sdk-go-v2/service/sqs" ) var SQSClient SQS // QueueMessage is one message taken off the queue. ReceiptHandle is what // identifies it for deletion — it belongs to this delivery, not to the // message, so it cannot be held across receives. type QueueMessage struct { Body string ReceiptHandle string } type SQS interface { ReceiveMessages(ctx context.Context) ([]QueueMessage, error) DeleteMessage(ctx context.Context, receiptHandle string) error } type SQSConcrete struct { SQSClient *sqs.Client QueueURL string } // receiveWaitSeconds turns every receive into a long poll: the call parks on // the server until a message arrives or this elapses, so an event is picked up // within milliseconds of being published while an idle consumer costs one // request every twenty seconds rather than spinning. 20 is the AWS maximum. const receiveWaitSeconds = 20 func (svc SQSConcrete) ReceiveMessages(ctx context.Context) ([]QueueMessage, error) { output, err := svc.SQSClient.ReceiveMessage(ctx, &sqs.ReceiveMessageInput{ QueueUrl: &svc.QueueURL, MaxNumberOfMessages: 10, WaitTimeSeconds: receiveWaitSeconds, }) if err != nil { return nil, err } messages := make([]QueueMessage, 0, len(output.Messages)) for _, message := range output.Messages { if message.Body == nil || message.ReceiptHandle == nil { continue } messages = append(messages, QueueMessage{ Body: *message.Body, ReceiptHandle: *message.ReceiptHandle, }) } return messages, nil } // DeleteMessage acknowledges a message. Until this is called the message is // merely invisible, and it returns to the queue when its visibility timeout // expires — which is how a handler that failed gets another attempt. func (svc SQSConcrete) DeleteMessage(ctx context.Context, receiptHandle string) error { _, err := svc.SQSClient.DeleteMessage(ctx, &sqs.DeleteMessageInput{ QueueUrl: &svc.QueueURL, ReceiptHandle: &receiptHandle, }) return err } // AssertSuccessfulConnection verifies the queue is reachable and readable with // the configured credentials. It panics on failure, since a service whose // consumer cannot start would serve a catalogue that silently stops growing. func (svc SQSConcrete) AssertSuccessfulConnection(ctx context.Context) { if _, err := svc.SQSClient.GetQueueAttributes(ctx, &sqs.GetQueueAttributesInput{ QueueUrl: &svc.QueueURL, }); err != nil { panic(fmt.Errorf("sqs: cannot connect to queue %q: %w", svc.QueueURL, err)) } }