Deploy GoLambda With MongoDB and AWS SAM (Part 3)
By: Naeem Gitonga
Tags: Golang, AWS Lambda, Mongodb, Programming, AWS

Build the Lambda!

We can add the above to our import statement of our main.go file. A complete list of all the packages that I used can be found here in the repo. Let’s start looking at func main() :

func main() {…}

I like to keep things simple, so the first thing func main() is going to do is get our environment variables, check to see if there is already a Mongo client and that mongoURI isn’t assigned a value of an empty string. Once we do that we connect to our MongoDb instance, or start the lambda.

One of the first things that I noticed coming from JavaScript is that Go needs a little help when dealing with environment variables. You can make them and access them at a lower level using your system and the os package that Go gives you. But if you’re accustomed to using .env files for storing these variables rather than on your system you’ll need to import "github.com/joho/godotenv" . Even in Node you need the dotenv package so that the Node process will gather these env vars from your system and your .env file(s). And to access them all you need is proces.env.YOUR_VAR_NAME . But we’re not dealing with the simplicity of Node! Check out the getMongoURIEnvVar method:

func getMongoURIEnvVar() {...}

This function just takes a name of type string . First, we have to load the .env file then we can use the os package to retrieve the value assigned to that name. Notice how the Load method only returns an error — interesting assumption. If there wasn’t an error it must have worked, right?

Well now that we have all of that, let’s take a look at how we connect to our mongo instance:

Here you can see that I’m first checking to see if the client is not nil . If it isn’t the program will continue and use the cached instance that is available for connection pooling (best practice). If it’s nil , we create a connectionError variable since the short-hand declaration/assignment := will not interfere with us using client globally on line 7 . We also create a context in which our function will run and connect to the db (we have some basic logging for error handling in there too). In high-level terms this context says that if the program hasn’t connected in 15 seconds to stop and move on. Go has great documentation on how Google uses context.

If we pay attention to and pattern our usage of context after their usage, we can develop super-efficient APIs as well. I do a decent job here but would love for the more seasoned gophers to give me some pointers on how things can be optimized here — pun intended! You want to pass context around to ensure efficiency in your app and that there’s proper release of those resources once things have been executed within their context. Context gets deep, so please check out the two resources I listed in this paragraph for the lo’ on all the things of context.

After we’ve connected, or not, the next step in our Goroutine is to start our handler:

I try to keep it as simple as possible (KISS), right? Notice that I create another context. This is the context in which our CRUD operations run. I will pass this down to my functions and once they have returned this Goroutine will run the defer call to cancel() which will cancel the context, thus freeing up those resources. Next, I’m going to check that the client is nil . If it is, which it shouldn’t be, the program will send an error to the client alerting the end-user that a connection couldn’t be established — in so many words.

I use a simple switch statement to decide what happens next. I chose to do this because the project is small and I am only using one catchall route to handle my request. If the project were bigger, I probably wouldn’t, but seeing how this is an example of using Go, MongoDb and SAM, just know that everything here isn’t completely up to the RESTful spec — but we’re really close!

With that being said, let’s look at func getStories() .

First things first, I ping the client. I do this to make sure things are on the up and up. If they aren’t — the program throws an error. But let’s say that things are, then we execute a find on our collection, assess it for errors and use the cursor ‘s .Next() method to loop over the cursor and decode its elements into something that our Goroutine can use later on. We then append them to a stories list and return our call to func marshalJSONAndSend() passing it our list. The last thing done after that return executes is the deferred call to cursor.Close(ctx) .

But what about func marshalJSONAndSend() . Well, let’s take a peek:

This little guy is going to take our list of stories, make it into a data type — []byte — that can then be converted to a string, line 7 , and sent to the client via our API Gateway Response.

That’s that!

I’ve been using this func handleError() but haven’t shown it. There’s not much to it. Take a look for yourself.

func handleError(err error) (events.APIGatewayProxyResponse, error) {…}

We’re almost done. We have the ability to read from the db but let’s create a document. We do that with our func postStory() .

I follow the same pattern that’s used in the other methods, so no change there but I did need to do some type conversion so that it could properly be added to my db. Once I’ve done that, I used a variable, newDbResult , to create a map of the results according to their type. I did this to dry up some code that was used to handle the result and send the appropriate response. Before we jump to func handleResultSendResponse() let’s first take a look at the Story struct . Structs remind me of interfaces in TypeScript (remember I come from JavaScript land). They are used to enumerate the fields of a piece of data. They contain names as fields and the value type associated with said fields. They also contain metadata that is of critical importance to the proper functioning of our API. Check out the Story struct :

Without that metadata to the right of our field types, our struct would not map to our JSON/BSON schema. We also needed to capitalize the field names. See what this guy had to say about capitalizing field names of structs .

Now what about func handleResultSendResponse() ?

I commented the code so, there you have it. The important thing here is reflection. Golang uses reflect to allow us to use Go in a more dynamic fashion, i.e., examine types at runtime versus compile time. Reflection deserves its own post. So much that I found a good one here, in addition to the Go docs here and their blog here. Once we’ve properly reflected, we’re able to fmt.Sprintf a string so that we could send it back to our client, line 18 of func handleResultSendResponse() . Knowing reflection allowed me to DRY up some code for reuse in other places. You gotta love that!

The last thing we have is the update function. Let’s dive in!

First, let me say that I had to get used to all of the type conversion that is required to make this API run. If someone has a better way or optimizations please feel free to drop me a line or two in the comments.

First we declare a variable. Take the update that we have and convert it to []byte s. On line four I love the way Go allows us to do some shorthand assignment and the use of those variables in our if/else statements. Those are scoped to the if block of course, and its respective else should it have one. In fact, I liked it so much that I was willing to go against Go’s linter to use this syntax with an else block that did nothing but return a function call. I ended up refactoring the code.

When we look at our filter and upDate vars we see that the Go driver uses BSON. This was new to me and the double curlies made me think of Angular. Here’s a great tutorial on using the Go driver.

It was now time to deploy. To deploy follow the instructions in the README.md that SAM provides. It’s the simplest way to, declaratively speaking, to get you in the cloud. You can experience this API live here.

Although I must say, when I did go live, I still had a bug to work out. Originally I had placed a few variables in the global scope. One of which was being used in func getStories() ; it was the stories []Story list. Locally this was working fine but in the cloud where things were optimized, each time I made a get request the lambda would return one more of the same story — I only had one in the Db. It took me about 15 minutes to realize what was the potential problem. The best I could come up with was that the context of the lambda was outliving the actual request and each time it received a new request it appended a new document to the collection. The best course of action was to use the stories collection in the scope of func getStories() . Once I did that things were back to normal.

That’s all I have. I hope this benefits someone and shaves off a couple of hours of research. Let me know how I can improve, what you would have done differently, and what you liked!

Thanks for reading!