Clean Architecture in Go With Docker

A squeaky clean Go project starter

While playing around with building the OneLogin Go SDK, I’ve been thinking about ways people build REST APIs using Go. Based on the amazing feedback I got on my last clean architecture article, I decided to see how difficult it would be to do the same thing but using Go.

I really like using Go because it is a minimalistic, no-frills tool that really forces you to consider what it is you’re trying to do. Nothing is implicit, which makes mistakes in return values or function signatures immediately obvious, however there are also no generics, which make logic duplication harder to eliminate. All in all, I thought this was an interesting challenge. Here’s what I learned.

The Clean Architecture

Clean architecture is this idea that there are entities at the core of an app and everything depends on them. In a RESTful API that manages resources, this would be your data models. Then you have a layer on top of that which represents how entities are stored and managed, and this layer depends on the shape of the entities. Then there’s a layer on top of that, which controls (it’s the controller layer, get it?) the when and why entities are stored. Finally, theres an interface layer that dictates how outside things like databases and web clients talk to the app.

Enterprise what? I’m just an indie dev [source]

As much as I love how this looks, I found it really complicated to understand in the context of a small project like a simple CRUD app. I remember flipping back and forth between various examples of code using this architecture and this diagram and constantly scratching my head. Finally I came up with a simplified drawing to accompany the example.

The Ripple Architecture

This is essentially the same diagram as the Clean Architecture. I call it the ripple diagram because changes ripple outward like a water droplet. A change in an entity field for example, would cause a change in how that field is validated by the repository for storing it, and that may influence the business logic in the controller and perhaps serialization will change before its returned to the caller.

Code to the Interface

I noticed with this pattern, every time you jump out a level, it’s a good idea to define an interface (except for entities since I kept them as simple go structs). Here I define a Repository interface before making the jump to a controller, that way all controllers know how to handle a repository, no matter if this is an Animal, Mineral, or Vegetable repository. Same idea applies when going from Controller to handler.go (the web interface) so my request handler can just worry about directing []byte arrays to the right controller action since all controllers will implement the appropriate CRUD actions, taking those []byte arrays and not worrying about the entity or the business logic.

Obviously, it’s not required to create interfaces and you can ostensibly get away with duck typing, however, I believe having a strong interface makes the code more cohesive, easy to extend, and leverages the compiler to catch mistakes in how a layer is used.

Another benefit to this style is that each resource package is independently testable. To test the Animal controller, I just need to inject a blank repository struct and stub out the methods that connect to a database. Same idea applies to the repository, where I can inject a blank database struct and add stub methods that don’t actually call a database.

Where Does Docker Come In?

Setting up Docker was actually pretty straightforward. Like any Docker project, you need a Dockerfile.

# Using golang (yes its larger than golang:alpine) but it has more out of box stuff
FROM golang:latest
# Standard copy from host to container stuff
WORKDIR /go/src/echo
COPY ./ /go/src/echo
# Download the modules we import
RUN go mod download
# Using this to trigger rebuild on code change since doing this ourselves takes effort
RUN go get github.com/githubnemo/CompileDaemon
# Fire it up!
ENTRYPOINT CompileDaemon --build="go build -o ./echo main.go" --command=./echo

This one should work for most cases. If you are importing private repositories, the easiest thing I can recommend is to compile your app locally with go build ./... and then copy the build file or directory into the container, rather than running the COPY and RUN commands shown.

I’m using CompileDaemon to re-build my app inside the container every time a change happens to the code. This really sped up my dev cycle as iterating on things was really easy to do.

The major benefit of using Docker was that I didn’t have to set up PG which was what I chose for this example. I used compose to load a Postgres sidecar container. If you forget to do this, you will see a bunch of weird nil pointer errors. That’s because your database didn’t connect and the handle for the database is nil. This took me a while to figure out as I thought it was a problem with the actual data I was trying to insert. Don’t let it happen to you, log all your DB connection issues, and remember to turn on the DB!

Take it for a Spin

What better way to show this than with a demo of what I’m talking about! This app is a contrived example implementing Animal Mineral or Vegetable. Pretend you’re someone who just joined the project, and you’re asked to add a new resource like, city. The folder structure should be an obvious indicator of how you should go about adding the city resource.

You can start with adding the city.go which defines what a city looks like. Then you can add a repository for how the city gets stored in the database. Side note, if we decided to use MongoDB for whatever reason, you can still add a repository, with the same interface, that has details for adding a city to MongoDB.

Finally, you can add a city controller and as long as the compiler doesn’t yell at you for not implementing the controller interface, you can drop those in the main.go file and call it a day. No implicit fields or methods, no uncertainty in how your code should fit in.

Looking for a Code-Along?

A code-along for this would actually be quite long. Generally, you’ll want to set up enough to start the app, accept HTTP requests, and connect to a database. Then go from the entity and code out. What I recommend for those who want to implement this and get that hands-on experience is this:

  1. From an empty folder, set up the Dockerfile, docker-compose.yml go.mod and main.go
  2. Create handler/handler.go database/database.go files and a interfaces folder
  3. Copy the database.go code from the project. This is all standard setup for the go-pg module that will result in a pointer to a db connection, in the context of this article, it’s uninteresting.
  4. Addinterfaces/controller.go and interfaces/repository.go files. These are recommended to define how you jump from circle to circle. Copy the handler.go code from the project. Note the exported method Handle this is needed for the Go HTTP library to work. Notice the Controller that gets injected must implement the interfaces.Controller interface we defined in this step.
  5. Finally, think of a resource, create a folder for it, and add thing/thing_controller.go thing/thing_repository.go and thing/thing_model.go files. Define your thing_model as a struct however you like (look at animal_model.go for inspiration).
  6. In the repository, make a struct that holds a reference to the database and a constructor that sets up the table according to your database library of choice, and creates the struct with the database reference. Then add the method signatures according to the interfaces/repository.go interface.
  7. Repeat step 6 for the controller, except now you’ll inject a repository (notice in animal_controller.go it accepts a interfaces.Repository item).
  8. Finally, string everything together in main.go in essentially the same order in which we coded. Inject the db instance into a new thing repository, then inject that into a thing controller, inject the controller into a handler instance, then add the handler to http.Handle .
thingRepo, _ := thing.New(&db)
thingController := thing.NewController(&thingRepo)
thingHandler := handler.NewHandler(&thingController)
http.Handle("/things", http.HandlerFunc(thingHandler.Handle))
Notice the ripple effect going on here?

Conclusion and Learnings

In step 8 it wasn’t impossible to inject a thingRepo into a different type of controller. This would cause a runtime issue about interface conversion. This is a result of Go’s lack of generics. It would be great for the controller to accept any type of struct and then we could have one controller file and delegate business logic out and let the controller handle logic around parsing requests and passing them along.

There is an upshot to that though, as it is also possible for business logic to depend heavily on the entity being managed. There is a bit of logic duplication among the controllers and repositories, but now they are allowed to change independent of each other without risking introducing any weird bugs to code you didn’t touch. That tradeoff was probably the most interesting observation when going through this exercise.

I thought this would be an appropriate follow up to one of my most popular posts to try re-creating the Clean Architecture when all the niceties of NodeJS are removed. Spot an error? Want to debate? Feel free to open an issue on Github or hit me up on LinkedIn or Twitter.

Software Engineer | Product Leader