* Edit of several Backend topics (part 5 of doc quality project) * Proofread of files * Prettier fix * Update contribute/backend/upgrading-dependencies.md Co-authored-by: Jack Baldry <jack.baldry@grafana.com> * Update contribute/backend/README.md Co-authored-by: Jack Baldry <jack.baldry@grafana.com> * Update contribute/backend/README.md Co-authored-by: Jack Baldry <jack.baldry@grafana.com> * Update contribute/backend/recommended-practices.md Co-authored-by: Jack Baldry <jack.baldry@grafana.com> * Update contribute/backend/recommended-practices.md Co-authored-by: Jack Baldry <jack.baldry@grafana.com> * Update contribute/backend/recommended-practices.md Co-authored-by: Jack Baldry <jack.baldry@grafana.com> * Update contribute/backend/recommended-practices.md Co-authored-by: Jack Baldry <jack.baldry@grafana.com> * Update contribute/backend/recommended-practices.md Co-authored-by: Jack Baldry <jack.baldry@grafana.com> * Update contribute/backend/recommended-practices.md Co-authored-by: Jack Baldry <jack.baldry@grafana.com> * Rename doc filenames with gerunds --------- Co-authored-by: Jack Baldry <jack.baldry@grafana.com>
8.8 KiB
Currently recommended practices
Grafana Labs occasionally identifies patterns that may be useful or harmful so that we can introduce or remove from the codebase. When the complexity or importance of introducing or removing such idiomatic patterns is sufficiently high, we document it on this page to provide a reference. Because the relevance of these practices may vary over time, we call them currently recommended practices.
Large-scale refactoring
Large-scale refactoring based on a new recommended practice is a delicate matter. It's usually better to introduce the new way incrementally over multiple releases and over time to balance the desire to introduce new useful patterns with the need to keep Grafana stable. It's also easier to review and revert smaller chunks of changes, reducing the risk of complications.
States of refactoring
Refer to the following table to identify important categories of refactoring.
State | Description |
---|---|
Proposed | This is an optional practice that has been proposed and received positively by the Grafana team. Follow this proposal as you choose. |
Ongoing, active | The practice is actively being worked on. New code should adhere to the practice whenever possible. |
Ongoing, passive | There's no immediate active work on refactoring old code. New code should adhere to the practice whenever possible. |
Completed | The work has been done and there is no, or negligible, legacy code left that needs refactoring. New code must adhere to the practice. |
Abandoned | The practice doesn't have any active ongoing work and new code doesn't need to comply with the practice described. |
1 - Idiomatic Grafana code should be idiomatic Go code
Status: Ongoing, passive.
It's easier for contributors to start contributing to Grafana if our code is easily understandable. When there isn't a more specific Grafana recommended practice, we recommend that you follow the practices as put forth by the Go project for development of Go code or the Go compiler itself as appropriate.
Firstly, best practice is the online book Effective Go, which isn't updated to reflect more recent changes since Go was initially released, but which remains a good source for understanding the general differences between Go and other languages.
Secondly, the guidelines for Code Review Comments for the Go compiler can mostly be applied directly to the Grafana codebase. There are idiosyncrasies in Grafana, such as interfaces living closer to their declarations than to their users for services, and the documentation doesn't enforce public declarations. Instead, the documentation prioritizes high coverage aimed at end-users over documenting internals in the backend.
100 - Global state
State: Ongoing, passive.
Global state makes testing and debugging software harder, and it's something we want to avoid whenever possible.
Unfortunately, there's quite a lot of global state in Grafana. We want to migrate away from this state by using Wire and dependency injection to pack code.
101 - Limit use of the init() function
State: Ongoing, passive.
Don't use the init()
function for any purpose other than registering services or implementations.
102 - Refactor settings
State: Ongoing, passive.
We plan to move all settings from package-level vars in the settings package to the setting.Cfg
struct. To access the settings, services and components can inject setting.Cfg
:
103 - Reduce use of GoConvey
State: Completed.
We encourage you to migrate away from using GoConvey.
Instead, we suggest the use of stdlib
testing with testify, because it's the most common approach in the Go community, and we think it will be easier for new contributors.
To learn more about how we want to write tests, refer to the backend style guide.
104 - Refactor SqlStore
State: Completed.
The sqlstore
handlers all use a global xorm
engine variable. Refactor them to use the SqlStore
instance.
105 - Avoid global HTTP handler functions
State: Ongoing, passive.
Refactor HTTP handlers so that the handler methods are on the HttpServer
instance or a more detailed handler struct. For example, AuthHandler
.
Doing so ensures that the handler methods get access to HttpServer
service dependencies (and its Cfg
object), so that global state may be avoided.
106 - Compare dates
State: Ongoing, passive.
Store newly introduced date columns in the database as epoch-based integers (that is, Unix timestamps) if they require date comparison. This permits you to have a unified approach for comparing dates against all the supported databases instead of needing to handle dates differently for each database. Also, when you compare epoch-based integers you no longer need error-pruning transformations to and from other time zones.
107 - Avoid the simplejson
package
State: Ongoing, passive
Don't use the simplejson
package (pkg/components/simplejson
) instead of types (that is, Go structs) because this results in code that is difficult to maintain.
Instead, create types for objects and use the Go standard library's
encoding/json
package.
108 - Enable provisioning
State: Abandoned: The file-based refactoring of Grafana is limited to work natively only on on-premise installations of Grafana. We want to enhance the use of the API to enable provisioning for all Grafana instances in the future.
All new features that require state should be able to configure Grafana using configuration files. For example:
Today it's only possible to provision data sources and dashboards, but we want to support it throughout Grafana in the future.
109 - Use context.Context
State: Completed.
You should use and propagate the package context
through all the layers of your code.
For example, the context.Context
of an incoming API request should be propagated to any other layers being used, such as the bus, service layer, and database layer.
Utility functions and methods normally don't need context.Context
.
To follow Go best practices, any function or method that receives a
context.Context
argument should receive it as its first parameter.
We encourage you to make sure that context.Context
is passed down through all layers of your code.
When you provide contextual information for the full life cycle of an API request, Grafana can use contextual logging. It also provides contextual information about the
authenticated user, and it creates multiple spans for a distributed trace of service calls, database queries, and so on.
Code should use context.TODO
whenever it's unclear which Context
to use,
or if it isn't yet available because the surrounding function hasn't yet
been extended to accept a context.Context
argument. For more details, refer to the documentation:
110 - Move API error handling to service layer
State: Ongoing, passive.
All errors returned from services in Grafana should carry a status and the information necessary to provide a structured message that faces the end-user. Structured messages can be displayed on the frontend and may be internationalized.
To learn more, refer to Errors.