Practical Golang code generation

Patryk Orwat
5 min readSep 10, 2022

--

Let’s boost our project with more automation!

Golang is a relatively simple language, in comparison to other languages like Java/Kotlin. To give developers extra tools to build full-scale projects, developers are armoured with code generation functionality and a wide range of OSS code generation libraries, so that they are able to write comprehensive code without building overly complicated semantics or DSLs based on the language itself.

Okay but how to use code generators? It’s technically not complicated, there are blog posts and talks that describe how they work in depth as well as how to write own generators but in this post, I want to focus on how to be as practical as possible.

I’ll explain couple projects I used in conjunction with Golang project layout in https://github.com/meshuga/code-generation-layout.

The above repository is intended to be used as a reference point when developers want to use various popular code generators, or want to write own generator. I hope to be extended with more practical projects that improve developer performance and help with project maintainability.

Now, let’s go through couple code generation examples provided in the repository.

Generator primer: stringer

Let’s say you want to define an enum in Golang. The most efficient from computation performance perspective is to define an integer type, like below:

All looks good but now, how those statuses will be printed in logs? Those will be just integers, which means it’ll be hard for developers to understand the value, which also means that developers will most likely misunderstand the value when e.g. a new status is added in latest main branch but the new status hasn’t been pushed to production. There’s a dozen of cases how things can go wrong.

To help with debugging, the UserStatus can implement fmt.Stringer interface but then, why should you write this interface yourself, with a potential of introducing an error? Fortunately, there’s a tool that can generate proper code for you — x/tools/cmd/stringer. You can install it, invoke stringer -type=UserStatus and enjoy a readable information about a user status in your application logs.

To help with automation of the code generation, you can pass comment //go:generate stringer -type=UserStatus above type definition and start usinggo generate ./... command in the root of the project, to make sure all of code to be generated, is invoked.

Mocking classes

As good developers, we strive to apply TDD when creating new functionalities, so that our code is maintainable.

Golang has projects like testify/mock to create mock struct that implement interfaces although, once again, why should we write such mocks manually?

Fortunately, there’re projects like vektra/mockery that help us generate mocks automatically. With a simple command mockery --all --keeptree invoked in internal directory (if used in the project), we can generate mocks for all project directories, that can be easily used.

Same as previously, we can, or rather should!, put the code generation directive in the internal.go file, as shown below, to empower the go generate ./... command

Contract based API development

Let’s now go to the API layer. When writing services, they will mostly will be exposing some sort of an API — REST, GraphQL, gRPC etc.

APIs are a connection layer between consumers and providers and the better the API is defined, the better and unambiguous service can be provided. Consumer Driven Contracts are here the key. How code generation can help here? With automation, contracts can be easily generated or server code skeleton can be pre-built.

Below are couple projects that can help with achieving Contract Driven Development in Golang with code generation in mind:

To sum up, if you don’t use code generation at any point of writing APIs, you’re doing something wrong.

SQL in Golang or Golang in SQL?

Let’s talk about SQL for a moment — it’s a tricky part because SQL is a separate language and in every service code, whether it’s Golang, TypeScript, Java, or C#, it’s not perfectly aligned with the main language in which a service is written — there’s still dissonance between declarative nature of SQL vs imperative nature service code (note: I’m aware of LINQ in C#, it’s an amazing tool but it still causes clashes with the SQL declarativity).

Nevertheless, we developers need to live with this mix as SQL is very powerful to define data model and transmit data to and from a database.

To help us dealing with data retrieval and write not only semantically but also structurally correct statements, we can use the power of code generation.

Golang has a great project sqlc which can parse SQL definitions of the calls that interact with database and prebuild us a set of Golang API that helps us with splitting the dissonant code!

With the project, we can define SQLs in a separate file, so that below SQL query:

can be called in Golang code imperatively:

What a beauty! We can just use separate tools to lint, validate and read SQL statements separately to the source code.

When to call generators?

In some regulatory requirements like ISO13485, generated code should be treaded as a handwritten code, which makes sense — it should be included in a Version Control System and be scanned as part of CI pipeline, same as code written by developers.

Looking into the future, this code should be more automated although some changes may result in manual changes done by developers. I hope in the upcoming years, this will be resolved, as more and more automation comes to software developer world (see GitHub Copilot or DeepMind news for glimpse into the future).

For now, it should be a duty of developers that interact with a source code, to make sure the generated code is up to date and issues are resolved after generating latest batch of code.

Placement of generators

When should generators setup and commands to call them be placed? I’m a strong believer of Makefiles as a common tool for developers to help with day-to-day work. Makefile bas been used for a long time, is simple and easy to understand, so when adding more generators, there’s a single place to define them, so that other developers can use them.

Your Makefile (or any analogical tool, if you prefer other), can look like below:

What next?

The next steps, after making sure OSS projects are added to a project, is to start writing own code generators. It’s a way to make sure your project can grow and can be kept maintained.

Examples of inputs for code generation:

  • JSON/YAML files
  • Data sources like CSV, Database
  • Code in other formats like SQL or AST parsing other languages
  • Wizard with a set of questions (Angular Schematics is a good example, see here for a tutorial for an inspiration)
  • Reverse engineering the source code to process complex data structures

I hope the above tips will be helpful in any new Golang project and if you don’t apply code generation practices in Golang, you will find ways to add them to your project. After all, the less code is written manually, the better it is for the project maintenance!

--

--

Patryk Orwat

Tech leader and cloud architect with 10+ years of experience. Nowadays focusing on topic of edge computing