Hey there!

At work recently, I ran into a bit of a challenge. I had to create an application using SQS and SNS to receive and send events, but I couldn’t use any cloud-based SQS queue. On top of that, I wanted to make sure that the setup was easy to work with so that any new team member could jump right in without any trouble.

After doing some research, I stumbled upon a solution that I thought could be helpful to share.

Localstack

Localstack is a fantastic open-source tool that lets you emulate most of AWS services like SNS, SQS, and S3 on your local machine. This is a great way to test your code without worrying about AWS costs.

The free plan offers core features like the ability to quickly spin up a mock environment. However, if you’re looking for more advanced features, Localstack’s pro plan has you covered. With the pro plan, you get a working Web UI that lets you easily monitor your mock environment. Unfortunately, this post’s example doesn’t showcase that feature as I’m using the free tier for development.

Overall, we highly recommend Localstack to anyone who wants to test their code locally against AWS services. Check out their website at https://localstack.cloud/.

Local setup

My solution for this was to spin up Localstack using Docker with docker-compose:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
version: "3.3"
services:
  localstack:
    image: localstack/localstack
    environment:
      - SERVICES=sns,sqs
      - DEBUG=1
      - PORT_WEB_UI=5000
      - HOSTNAME=localstack
      - EDGE_PORT=4566
    ports:
      - '4566-4597:4566-4597'
      - "8000:5000"

This starts a new container that includes Localstack and opens all required ports that I need. It also tells Localstack where to start my web UI (unfortunately, I don’t get a web UI due to using the free version) and which services I want to use. If I wanted to add S3 as a service, I would simply add S3 to - SERVICES=sns,sqs so it would be - SERVICES=sns,sqs,s3.

The next step was to create my queues. There are different approaches to this, but mine was to use the AWS CLI and create all the services I need using the CLI:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
awscli:
    image: garland/aws-cli-docker
    container_name: awscli
    depends_on:
      - localstack
    environment:
      - AWS_DEFAULT_REGION=eu-central-1
      - AWS_ACCESS_KEY_ID=xxx
      - AWS_SECRET_ACCESS_KEY=xxx
    command:
      - /bin/sh
      - -c
      - |
          sleep 5
          aws configure set aws_access_key_id "dummy" --profile test-profile
          aws configure set aws_secret_access_key "dummy" --profile test-profile
          aws configure set region "eu-central-1" --profile test-profile
          aws configure set output "table" --profile test-profile
          aws --endpoint-url=http://localstack:4566 sns create-topic --name dummy-topic --region eu-central-1 --profile test-profile --output table | cat
          aws --endpoint-url=http://localstack:4566 sqs create-queue --queue-name dummy-queue --profile test-profile --region eu-central-1 --output table | cat
          aws --endpoint-url=http://localstack:4566 sns subscribe --topic-arn arn:aws:sns:eu-central-1:000000000000:dummy-topic --profile test-profile --region eu-central-1 --protocol sqs --notification-endpoint arn:aws:sqs:eu-central-1:000000000000:dummy-queue --output table | cat

In this example, we create a new configuration with some dummy data:

1
2
3
4
 aws configure set aws_access_key_id "dummy" --profile test-profile
 aws configure set aws_secret_access_key "dummy" --profile test-profile
 aws configure set region "eu-central-1" --profile test-profile
 aws configure set output "table" --profile test-profile

Then, we create the SNS topic:

1
aws --endpoint-url=http://localstack:4566 sns create-topic --name dummy-topic --region eu-central-1 --profile test-profile --output table | cat

This outputs:

1
2
3
4
5
awscli                     | -------------------------------------------------------------------
awscli                     | |                           CreateTopic                           |
awscli                     | +----------+------------------------------------------------------+
awscli                     | |  TopicArn|  arn:aws:sns:eu-central-1:000000000000:dummy-topic   |
awscli                     | +----------+------------------------------------------------------

Next, I create my SQS queue:

1
aws --endpoint-url=http://localstack:4566 sqs create-queue --queue-name dummy-queue --profile test-profile --region eu-central-1 --output table | cat

This outputs:

1
2
3
4
5
awscli                     | -----------------------------------------------------------------
awscli                     | |                          CreateQueue                          |
awscli                     | +----------+----------------------------------------------------+
awscli                     | |  QueueUrl|  <http://localstack:4566/000000000000/dummy-queue>   |
awscli                     | +----------+----------------------------------------------------+

To connect these two services to work together, I later run the subscribe command:

1
aws --endpoint-url=http://localstack:4566 sns subscribe --topic-arn arn:aws:sns:eu-central-1:000000000000:dummy-topic --profile test-profile --region eu-central-1 --protocol sqs --notification-endpoint arn:aws:sqs:eu-central-1:000000000000:dummy-queue --output table | cat

This outputs:

1
2
3
4
5
awscli                     | ---------------------------------------------------------------------------------------------------------------
awscli                     | |                                                  Subscribe                                                  |
awscli                     | +-----------------+-------------------------------------------------------------------------------------------+
awscli                     | |  SubscriptionArn|  arn:aws:sns:eu-central-1:000000000000:dummy-topic:3858d0d7-8fad-45b0-bf0a-63c5ac5f3618   |
awscli                     | +-----------------+-------------------------------------------------------------------------------------------+

Setting up SQS & SNS Client with GO

Hey there! When we tell the AWS SDK that our queue is at localhost, it doesn’t automatically know which endpoint to use. By default, it will make calls to AWS cloud directly if we don’t specify the endpoint. But don’t worry, we can easily fix this by using an EndpointResolver:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
endpointResolver := func(service, region string, optFns ...func(*endpoints.Options)) (endpoints.ResolvedEndpoint, error) {
		if cfg.AWSEndpoint != "" {
			return endpoints.ResolvedEndpoint{
				URL:           cfg.AWSEndpoint,
				SigningRegion: cfg.AWSRegion,
			}, nil
		}

		return endpoints.DefaultResolver().EndpointFor(service, region, optFns...)
	}

sessionOptions := session.Options{
		Config: aws.Config{
			EndpointResolver: endpoints.ResolverFunc(endpointResolver),
		},
	}

If you want to learn more about using custom endpoints, you can check out the AWS documentation here: https://docs.aws.amazon.com/sdk-for-go/api/aws/endpoints/#hdr-Using_Custom_Endpoints

Also, if you’re interested, this API is also available in other languages:

Ending

Currently, I am populating SQS with messages by running a command in my GO application that creates and sends the message to SNS. The reason behind this is that I don’t want to restart my Docker containers every time I want to test my application. There are other ways of achieving this, such as running init-hooks or creating a bash file that posts messages to the SNS topic.

However, I will use init-hooks when developing integration tests for this application and running it inside a CI environment.

I hope this article has been helpful to you!