Skip to content

Ferdinand Agyei-Yeboah

Writing Go Applications

January 20, 2023

How to create a Go project

  1. Create a new folder. Ex: my-project.
  2. Run go mod init to initialize a go module (project).
    • It will create a go.mod file which is a package management file (similar to package.json or pom.xml)
  3. Create a main.go file
    • This can really be named anything.
    • Boilerplate looks like this.
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
  • Brief explanation.
    • Package keyword
      • Two types, executable and reusable packages.
      • Executable packages are normal application code that is meant to be run. The main file can be called anything, commonly main.go but it MUST declare package main to be an executable. Once you declare package main, then you must define a func main() which will be automatically run.
        • Summary: Create file.go, declare package main and a main function that will be called automatically. Package and function name has to be main, cannot be anything else to be executable.
      • Reusuable packages (libraries) simply need to have a package name other than main. Ex: package logging.
    • Import keyword
      • Pull in other libraries or your own code from other folders.
  1. Run go run main.go to build and run the file.
  2. Add any needed libraries to go.mod and run go mod tidy to sync (install them). Can also use go get to install packages.

My Folder Structure Preferences

There is no strict folder structure in Go but below is what I prefer.

  • src
    • Contains entrypoint (main.go) and all the other application code (controller, services, etc..). You can use folders to separate code like normal, ex: src/controllers, src/services.
  • test or e2e
    • Contains end to end tests. Not unit tests. Unit tests in go are generally in the same folder as the code file (so in src).
  • bin
    • Folder to store final native platform binary. This folder is optional really, you could just put the binary output in the root of project or anywhere at all. But it’s good to know where the binary file is supposed to be. Also makes sense to have a separate folder if you are distributing the binaries.

Options For Creating Go Lambdas

In addition basic Go setup above, lambda functions need to adhere to certain rules and be easily deployable into AWS. Here are the options for creating Go lambdas easily.

  • Use AWS Toolkit for VSCode and Go extension (Preferred)
    • Preferred since allows you to debug within vscode with breakpoints.
    • Uses AWS SAM so can deploy to AWS easily.
    • Video 1 / Video 2
  • Use AWS SAM CLI on its own
    • Initialize new lambda using command line with sam init.
    • Use sam with existing lambda code by manually creating a template.yml that defines resources.
    • Can use any editor you want and can test via command line with sam local invoke or aws cli lambda commands but can’t debug with breakpoints.
  • Write code, zip and upload to lambda console.
    • Worst option. Requires most effort and offers fewest debugging options (can’t test locally, have to deploy and test).

Writing Go Lambdas

When writing Golang to be placed in an AWS lambda, the code has to be written to the lambda interface and in the context of how it will be executed within AWS (api gateway, alb, direct invocation, sqs, etc..).

This means that if you plan to deploy your lambda behind an API gateway, then it must have input and output that is compatible with what API gateway will send and receive. In other words, since api gateway will be calling your lambda, your lambda must define an interface that matches the json API gateway will be sending and also match the output interface that API gateway will expect (status code and such). Same goes for load balancer invocation, direct invocation and such, the request/response needs to be tailored for its context. This can also mean that your lambda will be programmed for one context at a time (support api gateway, but not load balancer or direct) unless you manually determine the context and deserialize/serialize accordingly.

This means you have to check the AWS documentation for the expected request/response model to use in your context and then create a Go struct (with json tags) that is identical.

The request/response models for different services are from here: AWS Working With Other Services

To streamline things, there are already models in the aws sdk for Go that provide the structs for these common contexts. These models can be imported from "github.com/aws/aws-lambda-go/events".
For example, if you are using API gateway use events.APIGatewayProxyRequest and events.APIGatewayProxyResponse. Here are all event types and readme code showing sample implementations.

Go lambdas must follow certain handler signatures (for returning errors and such) read more about it here: Valid Lambda Handler Signatures

Writing Go APIs

Gin can be used as the REST layer to write a REST API in Go.

Using gin to write a Go api looks like so.

package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "pong",
})
})
r.Run() // listen and serve on 0.0.0.0:8080 (for windows "localhost:8080")
}

Misc - Everything Under This Section Is Random Tidbits

Commands

go mod init: Initializes Go project (creates a go.mod file).
go run: Compiles and executes program.
go build: Compiles code into executable binary.
go mod tidy: Syncs dependencies in go.mod file (downloads missing dependencies, removes unneeded dependencies).
go test: Executes tests.

Application Tips

  • Context
    • Description: Context is used to pass around information.
    • Use: Can create singletons at start of program, store them into context, and then have access to them everywhere (global logger, db connection, etc…). Convention is every function takes context as its first parameter.
    • Resources: 1
  • Defer
    • Description: Defers execution of a statement until the current function returns.
    • Use: Primary use is to clean up resources (open files, db connections, etc..). Think of it as a finally block in Java.
    • Resources: 1
  • Errors
    • Error handling in Golang - 1
  • Panic
    • Description: Panics are uncaught and unforeseen errors that lead a program to terminate. These are not caught be the developer. Think runtime exceptions like array out of bounds and null pointer exceptions in Java. Panics can be caught with recover.
  • Pointer vs not
    • If you want to modify the parameter (as opposed to a copy) of if you want to represent null (as opposed to default zero value, ex: 0 for int and "" fot string), then use a pointer.

Other

  • Running Go lambda locally in custom docker container
    • A bootstrapped sam Go lambda uses a go runtime docker image by default. But if you want to run in your own custom docker images (Ex: want to add certain files in runtime environment, or use your own go), then you can create your own docker image to run in.
    • To do so simply follow these docs on creating lambda container images.
    • Note: SAM should pull the multi-architecture docker image by default (x86, arm supported) but if you are seeing errors like fork exec then you can try pulling the docker image with the specific architecture for your computer. Note that Apple M1 laptops use arm architecture.
  • Testing
    • Test files have to end in _test.go to be runnable via go test. Test files cannot be imported from other files (src can’t import test). Test methods should start with Test to be executed.

Links:


Software Engineering Tutorials & Best Practices