Github template for Golang services
Tags: go, github, database, docker
Published: 2022-06-19 20:26:51

As a weekend project I created a github template that can be very handy for creating go services with relational databases.
Let's take a look at what is included.

## Task Runner
For many years, GNU `make` has been my to-go tool to run `rules` and `tasks` for any sort of project. It is
fairly simple to use but it can also become complex as some rules might require to execute external tools or
even declare bash functions with in a rule definition. I must admit that the developer experience can
be rough for those who haven't used `make` before.

Developer experience is a very important topic to me and I decided to use this project to find a reliable
alternative to `make`. And that's how I found [Task](https://taskfile.dev/):
> Task is a task runner / build tool that aims to be simpler and easier to use than, for example, GNU Make.

After checking some examples and its [API](https://taskfile.dev/api/) docs, I got convinced I should give it a try.
Although I'm not a fan of `yaml` I found some neat features that I prefer over `make`:

### Import env variables
Makefile
```make
include .env
$(eval export $(shell sed -ne 's/ *#.*$$//; /./ s/=.*$$// p' .env))
```
Taskfile
```yml
dotenv: ['.env']
```

### Showing help
Although `make` doesn't create a `help` command, there is a very common pattern to define one:
```make
help: ## print this help
	@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST) | sort
```

`Task` provides a list of available commands when running `task -l`, but you need to add a `desc` field to each command:
```
run:
  desc: Run go app
  cmds:
    - go run cmd/main.go
```

### CLI args
When working with `make` you can pass arguments/variables a target, for instance
```make
hello:
  @echo $(name)
```
To pass the argument you would call it like `make hello name=dev`. This can become tedious when passing multiple args to the inner command.
A simple hack to allow cli args is to filter out those `args` from the make `goals` and also avoid errors when
targets are not found:

```make
# Filter out make goals from CLI args
args = $(filter-out $@,$(MAKECMDGOALS))
# Do nothing if target not found
%:
	@:
hello:
  @echo $(args)
```

It works fine unless the arguments have a value that matches the name of any target.

On the other hand, `task` provides a simple template variable with the arguments `CLI_ARGS`
```yml
hello:
  cmds:
    - echo {{.CLI_ARGS}}
```
The command can then be called as `task hello -- world`

### Verbose file targets definition
Although `make` file targets are not difficult to understand, I think `task` syntax is easier to understand
for a new dev. Let's compare them:
```make
pkg/db/%.go: db/queries/%.sql
	@docker run --rm -v ${CURDIR}:/src -w /src kjconroy/sqlc generate
```

Taskfile
```yml
db-gen:
  desc: Generate queries code using Sqlc
  cmds:
    - docker run --rm -v $pwd:/src -w /src kjconroy/sqlc generate
  sources:
    - db/queries/*.sql
  generates:
    - pkg/db/*
```

So far `task` seems to be a very good alternative to `make`, at least for my personal use case.

## Folder structure
The folder structure of this template is based on folder structures I've seen across many go repos,
which I really like:
```
- `cmd/`: app entry points
- `db/`:
  - `migrations/`: SQL migrations files
  - `queries/`: SQL query files used by `sqlc`
- `pkg/`: app sources
  - `db/`: code generated by `sqlc`
```

## Database
As mentioned before, this template is for a go service with a relational database.
As a personal preference I chose `postgres` as db engine.

### Schema
I have recently adopted the practice of defining my db schemas using [dbml](https://www.dbml.org/)
and generating the sql code using their CLI tool. Example:
This schema
```
Table users {
  id uuid [pk]
  user_name text [not null, unique]
  password_hash bytea [not null]
  created_at timestamptz [not null, default: `now() at time zone 'utc'`]
  updated_at timestamptz [not null, default: `now() at time zone 'utc'`]
}
```
Will generate this SQL code:
```sql
CREATE TABLE "users" (
  "id" uuid PRIMARY KEY,
  "user_name" text UNIQUE NOT NULL,
  "password_hash" bytea NOT NULL,
  "created_at" timestamptz NOT NULL DEFAULT (now() at time zone 'utc'),
  "updated_at" timestamptz NOT NULL DEFAULT (now() at time zone 'utc')
);
```
### Queries and migrations
After having worked with different ORMs (goent, gorm) and plain `go sql` code, I find that having a 
middle ground is always the most versatile option. This middle ground is about having control over raw SQL
queries and the ability to generate code to run all those queries and represent models in code.

For this matter I chose [sqlc](https://github.com/kyleconroy/sqlc) which offers a good amount of features like
support for different dbs and db drivers. Regarding db migrations, I prefer to run them isolated from the code. There are countless tools to manage
migrations but recently I've been sticking with [go-migrate](https://github.com/golang-migrate/migrate)
which also provides a go library in case I want to integrate the migrations into the code.

To run both tools, I've created `tasks` that will use their docker images.
Alternatively, there could be a task to install them into the system.

## Docker
The template provides a multi-stage `Dockerfile`. It uses the official `golang:1.18` image for building and
a `scrath` image to copy the binaries.

A `docker-compose` file can be used to get a db instance and run migrations on it. In this cases the migrations
service waits for the postgres service to be ready, so we only need to run `docker-compose up -d`.

## CI
A github actions workflow is provided to run `go fmt, vet, test` and [gosec](https://github.com/securego/gosec).
An initial configuration for `dependabot` is also provided.

That's it, go take a look at the repo [here](https://github.com/protiumx/template-go-service).

Thanks for reading :alien:

Other posts:
- [Creating a Text-based UI with rust](https://protiumx.dev/blog/posts/creating-a-text-based-ui-with-rust/)
- [My profile website is now a terminal](https://protiumx.dev/blog/posts/my-profile-website-is-now-a-terminal/)
- [Publish your blog articles everywhere with this github action](https://protiumx.dev/blog/posts/publish-your-blog-articles-everywhere-with-this-github-action/)