September 22, 2019 in Programming6 minutes
I am doing some prototyping for a project and part of this includes becoming more familiar with the NATS project, including its Go client (since all of the components in my project that will be talking to NATS are written in Go). In short, I have a bunch of little services that need to talk to each other, and a message broker like NATS fits the bill.
One thing that drew me to NATS specifically is that it is unapologetically - nay, proudly - simple. In every online talk I’ve seen, this was made very clear as an intentional contrast to some of the very robust and capable (but at times heavy-handed) messaging systems like Kafka or RabbitMQ.
So, part of my prototyping is aimed at deciding if the tradeoffs that the NATS project is making to achieve this simplicity align with my goals. Regardless though - in the meantime, I figure I can get some good blog posts out of it. In general, if you’re new to the world of publish/subscribe messaging, I think NATS is a good place to start because of its simplicity.
It took me very little time to get up and running with NATS. The examples on their README for the Go library are really great, and running a nats server for my prototyping was a very simple docker one-liner:
The objective is to send a native Go type from our publisher, to the subscriber. The subscriber will connect to NATS, and wait for messages. The publisher will then connect to NATS and send messages, which will then be received by the subscriber.
Note that with the main NATS server, there’s no message persistence - so any messages we send to NATS before any subscribers are connected are lost forever. There are some optional systems we can plug into NATS to provide this functionality, and I’ll cover this in a future post, but for now, messages are either immediately received by a subscriber, or they’re dropped.
I created two simple examples for this post:
These are the links to the full source files - throughout this post we’ll be highlighting specific sections, but please refer to these for the full context.
In the main()
function for both of these programs, connecting to NATS is identical:
At this point, we can use ec
to send or receive messages, which we’ll get to shortly.
We’ll put together a very basic type for sending through NATS:
IMPORTANT NOTE - since we’re using a JSON encoder with our connection, our custom type and its fields must be exported (meaning start with a capital letter), otherwise you’ll get strange issues like default/zero values. This is an expected behavior of the standard JSON library in Go and it has bit me more times than I care to admit.
Now - as outlined in the Godoc for NATS, there are a number of
ways to publish messages to NATS. I am hugely in favor of creating a channel for sending these native types
and then using the BindSendChan()
function of our encoded connection to bind this channel and use it for
sending to NATS.
request_subject
is the name for the subject we intend to send messages to. In NATS, a “subject” is roughly equivalent to a Kafka “topic”, or a RabbitMQ “queue”. Our subscriber must subscribe to the same subject name to receive these messages.
Now, all we have to do to send messages into NATS is use this channel. For the sake of demonstration, I’ve done this in a loop with a one-second pause per iteration:
Running the publisher is simple, and we see the expected log messages.
Note again that in this basic NATS setup, all messages sent while there are no subscribers to receive them are lost - so let’s get a subscriber up and running.
With respect to setting up an encoded connection to NATS, the code for the subscriber is exactly the same.
Even the code that establishes a channel and binds it to the NATS encoded connection is similar, albeit with a different function name that indicates we intend to receive messages, instead of send them:
Again, we now have native Go constructs to work with, and listening for channels is a blocking operation, meaning we can do this in a loop with no pause, and messages will be processed (in this case, logged to screen) as soon as they’re received:
Here’s a quick demo of this process in action:
It’s still early, but I love what I’ve seen thus far. I especially like the ability to tie the client to native Go constructs, so that it doesn’t even feel like I’m sending data to NATS - just sending native Go types into a channel.
I have a lot more prototyping to do, and I expect this will result in more blog posts on NATS. Let me know in the comments below if you have ideas for specific things you want me to cover, either within NATS, Go, or pub/sub messaging in general.
In the meantime, the below resources are useful to have bookmarked as you get started with the subjects in this post.