Modelling Go Structs and Functionality for SQS Messages
Recently we built a background processing service, which uses AWS SQS as its queue. I needed to model our data and the SQS functionality in Go, and I’d like to share my experience and final solution.
In this post I want to cover following imaginary scenario:
- a system needs the ability to execute a large amount of webhooks asynchronously in the background
- there are many targets for the webhooks
- payloads should be pretty generic in the first iteration
- we decide to create a service which processes jobs from a SQS queue and executes those webhooks
All code mentioned in this post is just exemplary and could certainly be improved a lot. Please don’t treat this post as “the answer”, but rather as a starting point and collection of ideas.
Data
SQS messages are pretty simple: put some data in, get some data out. Simple, but also restrictive:
A message can include only XML, JSON, and unformatted text. The following Unicode characters are allowed. For more information, see the W3C specification for characters.
#x9
|#xA
|#xD
|#x20
to#xD7FF
|#xE000
to#xFFFD
|#x10000
to#x10FFFF
Amazon SQS does not throw an exception or completely reject the message if it contains invalid characters. Instead, it replaces those invalid characters with
U+FFFD
before storing the message in the queue, as long as the message body contains at least one valid character.Source: API Reference
Let’s create a simple struct for our data and prepare it for JSON:
|
|
This struct represents the data towards our application and we have a definition of how the data should be (un)marshaled towards SQS. But it also only represents a message body.
Functionality
AWS SQS provides the option to attach attributes to a message. Such attributes are a nice way to differentiate between data and metadata. A good example could be a tracing ID for an open tracing integration.
So let’s use message attributes as an example functionality and create a new entity for attributes with basic functionality:
|
|
Great, Attributes
encapsulates functionality around message attributes and can now be embedded into WebhookJob
:
|
|
With json:"-"
we also make sure that Attributes
will not be marshaled.
Using the things we created up until now is straightforward:
|
|
Surely we could have put all functionality straight into WebhookJob
, but using this approach of embedding promotes code reuse across different message types that we may have.
Receiving Messages
Fetching from SQS via Client.ReceiveMessage
gives us *ReceiveMessageOutput
, which contains a list of messages []types.Message
. It’s always a slice as we have the option to receive up to 10 messages from the queue.
A Message
contains the messages Body
and MessageAttributes
. The quick solution for us is to create a constructor for our WebhookJob
type, so we can easily ingest the raw message from SQS:
|
|
Sending Messages
When sending messages with the SDK, we need to pass a *SendMessageInput
to the Client.SendMessage
method, where the message body is stored as *string
.
What can be helpful here is to implement fmt.Stringer
on WebhookJob
to easily retrieve marshaled JSON, like this:
|
|
Now we can quickly build the necessary *SendMessageInput
:
|
|
What’s Next?
- there are a lot more things we could add, like passing the messages
ReceiptHandle
for later use - depending on how you process your messages, you may need to add thread safety with mutexes
- sending messages in batches needs different input types
- utilizing generics for different kinds of messages
- utilizing options pattern for nicer constructor functions
- error handling, logging, default return values