Grafana has developed really fast in the past few years. It has been the de facto standard for observability. I have been using grafana and loki for a while and I am really impressed by its performance and the quality of the code. Grafana builds most its backend components in Go. Meanwhile I am also using Go to build stuff. So it seems a good idea to learn some good practice from Grafana’s codebase. Then I found this https://github.com/grafana/grafana/blob/main/contribute/README.md which is a really good guide for building Go applications. I will put some of the points I found useful on coding style, database structure, error handling, etc.
Three references are recommended:
- Effective Go - The official Go style guide.
- CodeReviewComments - This is the official Wiki of go repository
- Go: Best Practices for Production Environments This is an intereting one written by Peter Bourgon from soundcloud on their intensive practices with Go.
- Grafana’s backend services encapsulate and expose application logic through related operations using Wire for dependency injection.
- Background services which implement
registry.BackgroundServiceinterface can be regestered and run in background.
- Disabled services can be controlled using the
registry.CanBeDisabledinterface, allowing the service to determine if it should run.
- Methods of a service should have
context.Contextas their first argument, especially if they interact with the bus, other services, or the database.
- Grafana uses SQLlite3 as a default database while also supports MySQL and Postgres.
- Yes it uses ORM called xorm however between service and orm there is another layer called ‘sqlstore’ this is for keeping service layer dedicated to business logic and ORM layer dedicated to database operations.
- Use self developed migration tool to manage database schema changes. It seems a natual choice in go world to use some kind of migration scripts/tool to manage database schema changes.
Grafana defined their owen [errorutil] (github.com/grafana/grafana/pkg/util/errutil.Error) to handle errors. They key idea is to ensure the error is structured and can be handled in a consistent way.
- The custom error type called
errutil.Erroris used to handle errors effectively.
errutil.Errorprovides a structured way to manage errors by carrying information such as error details, localization metadata, log levels, and HTTP status codes.
- Error messages are organized into message categories to maximize public message structures and minimize specific error details for security reasons.
- You can set a static message sent to the client when the error occurs or use dynamic messages and more options.
- You can specify an error source to describe the origin of the error, allowing operators to distinguish between Grafana and non-Grafana errors.
For testing with go , the recommendation is use standard test library and use testify for assertions. Integration tests are seprated from unit tests for the benefits of CI experience. If you need to mock some dependencies, mockery
As grafana is a web application using go as backend and react as frontend, It is quite important to run e2e tests. For this grafana uses a forked version of cypress to run e2e tests.
Instrumenting is about logs and metrics.
Logs are crucial for recording events, warnings, and errors, and they should be structured using lowercase names and context. Metrics play a key role in providing real-time insights into application performance, and they should follow naming conventions, especially for high cardinality issues.
Tracing with OpenTelemetry is recommended for tracking application requests and identifying issues within an application.
There are quite a few things such as commenting, dependency management, and all the frontend stuff I didn’t cover which you can find in this folder contribute. I’m grateful to the grafana team for sharing their experience on how to build a large scale application with go. On the other hand, after all, it’s the beauty of open source community.