Going offline with golang, serverless and dynamodb
First of all, you do not need this! Really, Lambda (or other serverless platforms) is pretty cheap. Most of the time, development costs are covered with the free tier, and in the worst case, it will cost you less than one beer.
Anyway, there is a lot of noise and many tools related to offline development, so I was curious and tried to set up that stuff. The most common use case is an HTTP service backed by DynamoDB, so let's focus on that. Fortunately (or not?), there are tools that do all the hard work for you.
serverless-offline
serverless-offline is a serverless plugin which emulates AWS lambda and API gateway on your local machine.
Install the plugin:
$ npm install serverless-offline --save-dev
And the plugin to the plugins section in your serverless.yml
:
plugins:
- serverless-offline
That's it. After executing sls offline start
command, the service is available on http://localhost:3000/dev
.
Oh wait! Requests to one of the configured endpoints say my runtime is not supported.
offline: Failure: Unsupported runtime
Error: Unsupported runtime
Well, the Go runtime is supported only via the docker-runner, which is toggled with the --useDocker
flag.
So sls offline start --useDocker
spins the runtime in a Docker container, and I should be good now, shouldn't I? Let's move to the DynamoDB part.
note: at the time of writing I am using the plugin version v6.1.4
serverless-dynamodb-local
I am following the documentation and installing the plugin:
$ npm install --save [email protected]
Update the plugins section:
plugins:
- serverless-offline
- serverless-dynamodb-local
Install dynamodb local and start it:
$ sls plugin install -n serverless-dynamodb-local
$ sls dynamodb install
$ sls dynamodb start
Mocked DynamoDB will run on localhost and port 8000, so it's necessary to update the client configuration in the service code:
var db = dynamodb.New(session.New(&aws.Config{
Region: aws.String("localhost"),
Endpoint: aws.String("http://localhost:8000"),
}))
Note that in AWS, DynamoDB won't run on localhost; therefore, the DB must have different configuration in production. I use the IS_OFFLINE
environment variable to distinguish between offline and production deployment.
provider:
name: aws
runtime: go1.x
environment:
IS_OFFLINE: ${env:IS_OFFLINE}
var db *dynamodb.DynamoDB
func init() {
if os.Getenv("IS_OFFLINE") {
db = ...
} else {
db = ...
}
}
Start the application with sls offline start --useDocker
, do some requests, and tadaaa ... the application is not working because it's not able to connect to localhost:8000
, the port where DynamoDB is running.
$ netstat -ntlp | grep 8000
tcp6 0 0 :::8000 :::* LISTEN 172892/java
The reason is that the runtime is on a different network since the default network mode for Docker is bridge mode. After inspecting the runtime container or digging in the plugin's codebase, I found a host mapping for a docker0 bridge gateway IP which is bound to the host.docker.internal
hostname.
So let's fix the endpoint in the AWS config:
Endpoint: aws.String("http://host.docker.internal:8000"),
Restart the service and voila ... API Gateway with DynamoDB emulator is working now, and the local development setup is finally ready. Enjoy!