When I started learning Golang (a couple of months ago) one of the things that I concerned most with was the project structure.

It may sound irrelevant to bother with this when you are learning the language; but, every time I am browsing .net core applications or libraries on GitHub, I find it extremely unappealing when certain projects are not structured in a conventional way.

My starting point was the following talk:

Being that I did not know how to manage dependencies, I started investigating and saw that lots of people were using dep, and how bad could it be?

I started building a sample app that that could be containerised (again, just to see how the Dockerfile would look like), and the following happened:

FROM golang:1.12-alpine AS builder

COPY . /go/src/github.com/pmorelli92/go-ddd-cqrs/
WORKDIR /go/src/github.com/pmorelli92/go-ddd-cqrs/

RUN set -x && go get github.com/golang/dep/cmd/dep && dep ensure -v

RUN CGO_ENABLED=0 go build -a -o goapp ./cmd/server/main.go

FROM scratch
WORKDIR /root/
COPY --from=builder /go/src/github.com/pmorelli92/go-ddd-cqrs/goapp .

EXPOSE 8080
ENTRYPOINT ["./goapp"]

That Dockerfile, as ugly as it looks, was the result of some iterations were I was getting path errors, dependency errors, and others. The constraint of only working on GOPATH gave me headaches and I had to use a really bad WORKDIR as you can see above.

But then after some searching, I came across that GO Modules was supported from version 1.11, so I decided to give it a try.

And this is how the Dockerfile looks like now:

FROM golang:1.12-alpine AS builder

WORKDIR /app

ADD go.mod go.sum ./
RUN go mod download

COPY . .

RUN CGO_ENABLED=0 go build -a -o goapp ./cmd/main.go

FROM scratch
COPY --from=builder /app/goapp .

EXPOSE 8080
ENTRYPOINT ["./goapp"]

We can achieve the same using golang:1.11 but we wil have to execute an additional step ENV GO111MODULE=on before RUN go mod download.

So how do we do this? Easily!

mkdir my-demo // Create a project folder, or clone a github repository
cd my-demo
touch main.go // Create a file if we are starting the project
go mod init my-demo // This will initiate the module

After doing this, we can proceed installing dependencies by doing go get -u <path>

go get -u github.com/labstack/echo/v4

This will be tracked on the go.mod where it will state all the dependencies required for the project:

my-demo > cat go.mod
module my-demo

go 1.12

require (
	github.com/davecgh/go-spew v1.1.1 // indirect
	github.com/labstack/echo/v4 v4.0.0 // indirect
	github.com/mattn/go-colorable v0.1.1 // indirect
	github.com/mattn/go-isatty v0.0.7 // indirect
	github.com/stretchr/objx v0.1.1 // indirect
	github.com/valyala/fasttemplate v1.0.0 // indirect
	golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a // indirect
	golang.org/x/sys v0.0.0-20190312061237-fead79001313 // indirect
)

And we are done! Now we can edit the main.go to this:

package main

import "net/http"
import "github.com/labstack/echo/v4"

func main() {
	e := echo.New()
	e.GET("/hello", func(c echo.Context) error {
		return c.JSON(http.StatusOK, "hey there")
	})

	_ = e.Start(":8080")
}

If now we look again at go.mod we should see that the module github.com/labstack/echo/v4 v4.0.0 is no longer indirect, that means that it is being actively used on the code.

Things to remember

  • Both go.mod and go.sum have to be committed.
  • When building a docker file, in order to take advantage of the layered cache steps, we should first copy go.mod and go.sum and then executing RUN go mod download; after that, we can safely copy our code.