Server and a Database

I have seen quite a few questions on how to setup a basic server and database in Go. I’ll show few versions, with each one handling more variability and more complex situations.

We are going to take a really small website, where you can submit comments and it will display them. We will cover interaction with the server and database, however we will mostly ignore the different ways of rendering the content and writing business logic. Also, you should probably have some familiarity with Go databases and servers.

You can imagine this as an example of “Infinite Possibilities” exercise.

YOLO

Code at https://github.com/egonelbre/db-demo/tree/master/00_yolo

This is the version, that takes the least effort. This approach can be described as “throw everything together”.

https://github.com/egonelbre/db-demo/blob/master/00_yolo/main.go

Everything in this file is mingled together and quite hard to follow. Any longer than this and it becomes unmaintainable.

However, this can be used as our first iteration until we figure out what needs to be done.

Functions

Code at https://github.com/egonelbre/db-demo/tree/master/01_funcs

The first step towards separating HTTP from data storage is to use functions. We create functions for different interactions and then provide the database connection as the first parameter.

https://github.com/egonelbre/db-demo/blob/master/01_funcs/main.go

We could also separate these functions into a separate folder. Regardless the database methods are harder to find, especially when there are many such functions.

Repository

Code at https://github.com/egonelbre/db-demo/tree/master/02_repo

Instead of keeping separate functions we can attach them to a type.

https://github.com/egonelbre/db-demo/blob/master/02_repo/main.go

Where the comments repository looks like:

https://github.com/egonelbre/db-demo/blob/master/02_repo/comments.go

This keeps the comment related database interactions in a single place.

Server

Code at https://github.com/egonelbre/db-demo/tree/master/03_server

Before we refactor the database more, we need to make the server clearer. The endpoint wiring in main didn’t look that nice. Instead let’s try this:

https://github.com/egonelbre/db-demo/blob/master/03_server/main.go

The Server looks like this:

https://github.com/egonelbre/db-demo/blob/master/03_server/server.go

We have removed the global Comments repository and added it as a field to Server. Also, Server has become much easier to test.

Interface

Code at https://github.com/egonelbre/db-demo/tree/master/04_interface

It’s usually nice not to depend on the database implementation directly as it means we have to use specific database implementation for testing. By separating the Comments implementation and interface we make clearer our requirements and how we fulfill those requirements.

https://github.com/egonelbre/db-demo/blob/master/04_interface/site/server.go

Since we wanted to separate them, the folder structure needs to change as well:

Scope

Code at https://github.com/egonelbre/db-demo/tree/master/05_scope

In the future we may need more repositories than Comments. There are few ways that we could handle it. One is to add all methods to a single type. However, that approach can get messy and unorganized after four different repositories.

The other approach is to create a separate implementation and pass them as arguments to Server. It has somewhat similar problem that we might end up with a lot of arguments.

We can also use hierarchical interfaces:

https://github.com/egonelbre/db-demo/blob/master/05_scope/site/server.go

It might look that this is too complicated and unclear why this is beneficial. Let’s take a look at when we have more than one repository and have added basic access control:

We can implement this interface as:

Of course we can use different combinations of extending the DB.

These small things help us avoid forgetting different access checks without having to create a separate argument for each of the methods.

Conclusion

I think these five approaches should cover most of the needs, however specific scenarios might have more interesting solutions. Given a CRUD situation, you might need ORM. Sometimes, you may need a solution with multiple database engines (for example, one for content, one for text-search).

I think the most important thing is:

The right solution is the easiest that works for you.

The complicated version handle more cases, however this comes at the cost of more indirection and more code artifacts. Prefer to start with a simple solution and refactor to a more complicated one when the time is right.