mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Docs: Edit of 3 files in backend contributor guide (part 6 of doc quality improvement project) (#89582)
* Docs: edit of 3 files in backend contributor guide * Update contribute/backend/database.md Co-authored-by: brendamuir <100768211+brendamuir@users.noreply.github.com> * Fix from review * Update contribute/backend/database.md * Update contribute/backend/errors.md * Update contribute/backend/errors.md * Fix header levels * Style edits * Prettier fixes --------- Co-authored-by: brendamuir <100768211+brendamuir@users.noreply.github.com>
This commit is contained in:
parent
594deb2cbe
commit
d49a3e98dc
@ -1,30 +1,30 @@
|
||||
# Communication
|
||||
|
||||
Grafana use dependency injection and method calls on Go interfaces to
|
||||
Grafana uses dependency injection and method calls on Go interfaces to
|
||||
communicate between different parts of the backend.
|
||||
|
||||
## Commands and queries
|
||||
|
||||
Grafana structures arguments to [services](services.md) using a command/query
|
||||
Grafana structures arguments to [services](services.md) using a "command/query"
|
||||
separation where commands are instructions for a mutation and queries retrieve
|
||||
records from a service.
|
||||
|
||||
Services should define their methods as `func[T, U any](ctx context.Context, args T) (U, error)`.
|
||||
Services should define their methods as follows:
|
||||
|
||||
- `func[T, U any](ctx context.Context, args T) (U, error)`
|
||||
|
||||
Each function should take two arguments. First, a `context.Context` that
|
||||
carries information about the tracing span, cancellation, and similar
|
||||
runtime information that might be relevant to the call. Secondly, `T` is
|
||||
a `struct` defined in the service's root package (see the instructions
|
||||
for [package hierarchy](package-hierarchy.md)) that contains zero or
|
||||
runtime information that might be relevant to the call. Secondly, `T`,
|
||||
a struct defined in the service's root package. Refer to the instructions
|
||||
for [package hierarchy](package-hierarchy.md) that contains zero or
|
||||
more arguments that can be passed to the method.
|
||||
|
||||
The return values is more flexible, and may consist of none, one, or two
|
||||
values. If there are two values returned, the second value should be
|
||||
either an `bool` or `error` indicating the success or failure of the
|
||||
call. The first value `U` carries a value of any exported type that
|
||||
makes sense for the service.
|
||||
The return values are more flexible, and these may consist of none, one, or two values.
|
||||
If the function returns two values, the second value should be either a `bool` or `error` to indicate the success or failure of the call.
|
||||
The first value `U` carries a value of any exported type appropriate for the service.
|
||||
|
||||
Following is an example of an interface providing method signatures for
|
||||
The following example shows an interface that provides method signatures for
|
||||
some calls adhering to these guidelines:
|
||||
|
||||
```
|
||||
@ -39,23 +39,20 @@ type Alphabetical interface {
|
||||
}
|
||||
```
|
||||
|
||||
> Because we request an operation to be performed, command are written in imperative mood, such as `CreateFolderCommand`, `GetDashboardQuery` and `DeletePlaylistCommand`.
|
||||
> **Note:** Because we request an operation to be performed, command are written in imperative mood, such as `CreateFolderCommand`, `GetDashboardQuery` and `DeletePlaylistCommand`.
|
||||
|
||||
The use of complex types for arguments in Go means a few different
|
||||
things for us, it provides us with the equivalent of named parameters
|
||||
from other languages, and it reduces the headache of figuring out which
|
||||
argument is which that often occurs with three or more arguments.
|
||||
things for us. Most importantly, it provides us with the equivalent of named parameters from other languages, and it reduces the headache of figuring out which argument is which that often occurs with three or more arguments.
|
||||
|
||||
On the flip-side, it means that all input parameters are optional and
|
||||
that it is up to the programmer to make sure that the zero value is
|
||||
useful or at least safe for all fields and that while it's easy to add
|
||||
another field, if that field must be set for the correct function of the
|
||||
service that is not detectable at compile time.
|
||||
However, it means that all input parameters are optional and
|
||||
that it's up to the developer to make sure that the zero value is
|
||||
useful or at least safe for all fields.
|
||||
Also, although it's easy to add another field, the field must be set for the correct function of the service that isn't detectable at compile time.
|
||||
|
||||
### Queries with Result fields
|
||||
|
||||
Some queries have a Result field that is mutated and populated by the
|
||||
method being called. This is a remainder from when the _bus_ was used
|
||||
Some queries have a `Result` field that is mutated and populated by the
|
||||
method being called. This is a remainder from when the `_bus_` was used
|
||||
for sending commands and queries as well as for events.
|
||||
|
||||
All bus commands and queries had to implement the Go type
|
||||
@ -63,8 +60,8 @@ All bus commands and queries had to implement the Go type
|
||||
and mutation of the `msg` variable or returning structured information in
|
||||
`error` were the two most convenient ways to communicate with the caller.
|
||||
|
||||
All `Result` fields should be refactored so that they are returned from
|
||||
the query method:
|
||||
You should refactor all `Result` fields so that they are returned from
|
||||
the query method. For example:
|
||||
|
||||
```
|
||||
type GetQuery struct {
|
||||
@ -95,9 +92,9 @@ func (s *Service) Get(ctx context.Context, cmd GetQuery) (ResultType, error) {
|
||||
|
||||
## Events
|
||||
|
||||
An event is something that happened in the past. Since an event has already happened, you can't change it. Instead, you can react to events by triggering additional application logic to be run, whenever they occur.
|
||||
An _event_ is something that happened in the past. Since an event has already happened, you can't change it. Instead, you can react to events by triggering additional application logic to be run, whenever they occur.
|
||||
|
||||
> Because they happened in the past, event names are written in past tense, such as `UserCreated`, and `OrgUpdated`.
|
||||
> **Note:** Because events happened in the past, their names are written in the past tense, such as `UserCreated` and `OrgUpdated`.
|
||||
|
||||
### Subscribe to an event
|
||||
|
||||
@ -116,11 +113,11 @@ func (s *MyService) UserCreated(event *events.UserCreated) error {
|
||||
}
|
||||
```
|
||||
|
||||
**Tip:** Browse the available events in the `events` package.
|
||||
> **Tip:** To learn about the available events, refer to the documentation in the `events` package.
|
||||
|
||||
### Publish an event
|
||||
|
||||
If you want to let other parts of the application react to changes in a service, you can publish your own events:
|
||||
If you want to let other parts of the application react to changes in a service, you can publish your own events. For example:
|
||||
|
||||
```go
|
||||
event := &events.StickersSentEvent {
|
||||
|
@ -1,6 +1,8 @@
|
||||
# Database
|
||||
|
||||
Grafana uses a database to persist settings between restarts. In fact, if you don't specify one, Grafana creates a [SQLite3](https://www.sqlite.org/) database file on your local disk. This guide explains how to store and retrieve data from the database.
|
||||
Grafana uses databases to persist settings between restarts. If you don't specify one, Grafana creates a [SQLite3](https://www.sqlite.org/) database file on your local disk. This guide explains how to store and retrieve data from the default or other databases.
|
||||
|
||||
## Supported databases and services
|
||||
|
||||
Grafana supports the [following databases](https://grafana.com/docs/installation/requirements/#database):
|
||||
|
||||
@ -16,7 +18,7 @@ Grafana uses the [XORM](https://xorm.io) framework for persisting objects to the
|
||||
|
||||
> **Deprecated:** We are deprecating `sqlstore` handlers in favor of using the `SQLStore` object directly in each service. Since most services still use the `sqlstore` handlers, we still want to explain how they work.
|
||||
|
||||
The `sqlstore` package allows you to register [command handlers](communication.md#handle-commands) that either store, or retrieve objects from the database. `sqlstore` handlers are similar to services:
|
||||
The `sqlstore` package allows you to register [command handlers](communication.md#handle-commands) that either store or retrieve objects from the database. The `sqlstore` handlers are similar to services:
|
||||
|
||||
- [Services](services.md) are command handlers that _contain business logic_.
|
||||
- `sqlstore` handlers are command handlers that _access the database_.
|
||||
@ -27,7 +29,7 @@ The `sqlstore` package allows you to register [command handlers](communication.m
|
||||
|
||||
To register a handler:
|
||||
|
||||
- Create a new file `myrepo.go` in the `sqlstore` package.
|
||||
- Create a new file, `myrepo.go`, in the `sqlstore` package.
|
||||
- Create a [command handler](communication.md#handle-commands).
|
||||
- Register the handler in the `init` function:
|
||||
|
||||
@ -48,7 +50,7 @@ Here, `inTransactionCtx` is a helper function in the `sqlstore` package that pro
|
||||
|
||||
## `SQLStore`
|
||||
|
||||
As opposed to a `sqlstore` handler, the `SQLStore` is a service itself. The `SQLStore` has the same responsibility however: to store and retrieve objects, to and from the database.
|
||||
As opposed to a `sqlstore` handler, the `SQLStore` is a service itself. Like the handler, the `SQLStore` is responsible for storing and retrieving objects, to and from the database.
|
||||
|
||||
To use the `SQLStore`, inject it in your service struct:
|
||||
|
||||
@ -73,13 +75,13 @@ For transactions, use the `WithTransactionalDbSession` method instead.
|
||||
|
||||
## Migrations
|
||||
|
||||
As Grafana evolves, it becomes necessary to create _schema migrations_ for one or more database tables.
|
||||
As your use of Grafana evolves, you may need to create _schema migrations_ for one or more database tables.
|
||||
|
||||
To see all the types of migrations you can add, refer to [migrations.go](/pkg/services/sqlstore/migrator/migrations.go).
|
||||
|
||||
Before you add a migration, make sure that you:
|
||||
|
||||
- Never change a migration that has been committed and pushed to main.
|
||||
- Never change a migration that has been committed and pushed to `main`.
|
||||
- Always add new migrations, to change or undo previous migrations.
|
||||
|
||||
Add a migration using one of the following methods:
|
||||
@ -87,7 +89,7 @@ Add a migration using one of the following methods:
|
||||
- Add migrations in the `migrations` package.
|
||||
- Implement the `DatabaseMigrator` for the service.
|
||||
|
||||
**Important:** If there are previous migrations for a service, use that method. By adding migrations using both methods, you risk running migrations in the wrong order.
|
||||
> **Important:** If there are previous migrations for a service, use that method. Don't add migrations using both methods or you risk running migrations in the wrong order.
|
||||
|
||||
### Add migrations in `migrations` package
|
||||
|
||||
@ -97,11 +99,11 @@ To add a migration:
|
||||
|
||||
- Open the [migrations.go](/pkg/services/sqlstore/migrations/migrations.go) file.
|
||||
- In the `AddMigrations` function, find the `addXxxMigration` function for the service you want to create a migration for.
|
||||
- At the end of the `addXxxMigration` function, register your migration:
|
||||
- At the end of the `addXxxMigration` function, register your migration (refer to the following example).
|
||||
|
||||
> **NOTE:** Putting migrations behind feature flags is no longer recommended as it may cause the migration skip integration testing.
|
||||
- [Example](https://github.com/grafana/grafana/blob/00d0640b6e778ddaca021670fe851fe00982acf2/pkg/services/sqlstore/migrations/migrations.go#L55-L70)
|
||||
|
||||
[Example](https://github.com/grafana/grafana/blob/00d0640b6e778ddaca021670fe851fe00982acf2/pkg/services/sqlstore/migrations/migrations.go#L55-L70)
|
||||
> **Note:** We no longer recommend putting migrations behind feature flags because this could cause the migration to skip integration testing.
|
||||
|
||||
### Implement `DatabaseMigrator`
|
||||
|
||||
|
@ -1,26 +1,24 @@
|
||||
# Errors
|
||||
|
||||
Grafana introduced its own error type [github.com/grafana/grafana/pkg/util/errutil.Error](../../pkg/util/errutil/errors.go)
|
||||
in June 2022. It's built on top of the Go `error` interface extended to
|
||||
contain all the information necessary by Grafana to handle errors in an
|
||||
informative and safe way.
|
||||
Grafana includes its own error type [github.com/grafana/grafana/pkg/util/errutil.Error](../../pkg/util/errutil/errors.go).
|
||||
Introduced in June 2022, the type is built on top of the Go `error` interface.
|
||||
It extends the interface to contain all the information necessary by Grafana to handle errors in an informative and safe way.
|
||||
|
||||
Previously, Grafana has passed around regular Go errors and have had to
|
||||
Previously, Grafana passed around regular Go errors and therefore had to
|
||||
rely on bespoke solutions in API handlers to communicate informative
|
||||
messages to the end-user. With the new `errutil.Error`, the API handlers
|
||||
can be slimmed as information about public messaging, structured data
|
||||
related to the error, localization metadata, log level, HTTP status
|
||||
code, and so forth are carried by the error.
|
||||
can be streamlined. The error carries information about public messaging,
|
||||
structured data related to the error, localization metadata, log level,
|
||||
HTTP status code, and so on.
|
||||
|
||||
## Basic use
|
||||
|
||||
### Declaring errors
|
||||
## Declare errors
|
||||
|
||||
For a service, declare the different categories of errors that may occur
|
||||
from your service (this corresponds to what you might want to have
|
||||
specific public error messages or their templates for) by globally
|
||||
constructing variables using the `errutil.<status>(status, messageID, opts...)`
|
||||
functions, e.g.
|
||||
from your service (this corresponds to categories for which you might want
|
||||
to have specific public error messages or templates).
|
||||
|
||||
Globally construct variables using the `errutil.<status>(status, messageID, opts...)
|
||||
functions. For example:
|
||||
|
||||
- `errutil.NotFound(messageID, opts...)`
|
||||
- `errutil.BadRequest(messageID, opts...)`
|
||||
@ -33,44 +31,41 @@ functions, e.g.
|
||||
- `errutil.NotImplemented(messageID, opts...)`
|
||||
- `errutil.ClientClosedRequest(messageID, opts...)`
|
||||
|
||||
Above functions uses `errutil.NewBase(status, messageID, opts...)` under the covers, and that function should in general only be used outside the `errutil` package for `errutil.StatusUnknown`, e.g. when there are no accurate status code available/provided.
|
||||
The previous functions use `errutil.NewBase(status, messageID, opts...)` under the covers, and that function should in general only be used outside the `errutil` package for `errutil.StatusUnknown`. For example, you can use that function when there are no accurate status code available.
|
||||
|
||||
The status code loosely corresponds to HTTP status codes and provides a
|
||||
default log level for errors to ensure that the request logging is
|
||||
properly informing administrators about various errors occurring in
|
||||
Grafana (e.g. `StatusBadRequest` is generally speaking not as relevant
|
||||
as `StatusInternal`). All available status codes live in the `errutil`
|
||||
package and have names starting with `Status`.
|
||||
default log level for errors.
|
||||
The default log levels ensure that the request logging is properly informing administrators about various errors occurring in Grafana (for example, `StatusBadRequest` isn't usually as relevant as `StatusInternal`).
|
||||
All available status codes live in the `errutil` package and have names starting with `Status`.
|
||||
|
||||
The messageID is constructed as `<servicename>.<errorIdentifier>` where
|
||||
The `messageID` is constructed as `<servicename>.<errorIdentifier>` where
|
||||
the `<servicename>` corresponds to the root service directory per
|
||||
[the package hierarchy](package-hierarchy.md) and `<errorIdentifier>`
|
||||
is a camelCased short identifier that identifies the specific category
|
||||
of errors within the service.
|
||||
|
||||
Errors should be grouped together (i.e. share `errutil.Base`) based on
|
||||
their public facing properties, a single messageID should represent a
|
||||
translatable string and what metadata is carried with it.
|
||||
_service.MissingRequiredFields_ and _service.MessageTooLong_ are likely
|
||||
Group errors together (that is, share `errutil.Base`) based on
|
||||
their public-facing properties. A single `messageID` should represent a
|
||||
translatable string and its metadata.
|
||||
`_service.MissingRequiredFields_` and `_service.MessageTooLong_` are likely
|
||||
to be two different errors that are both validation failures, as their
|
||||
user-friendly expansions are likely different. This is the maximization
|
||||
rule of declaring as many `errutil.Error`s as you need public message
|
||||
structures.
|
||||
user-friendly expansions are likely different.
|
||||
This is the maximization rule of declaring as many errors with `errutil.Error` as you need public message structures.
|
||||
|
||||
The other side of this is that even though a login service's
|
||||
"user is ratelimited", "user does not exist", "wrong username", and
|
||||
"wrong password" are reasonable errors to separate between internally,
|
||||
for security reasons the end-user should not be told which particular
|
||||
The other side of the coin is that even though such messages as
|
||||
"user is rate limited", "user doesn't exist", "wrong username", and
|
||||
"wrong password" are reasonable errors to distinguish internally,
|
||||
for security reasons the end-user shouldn't be told which particular
|
||||
error they struck. This means that they should share the same base (such
|
||||
as _login.Failed_). This is the minimization rule of grouping together
|
||||
distinct logged errors that provide the same information via the API.
|
||||
as _login.Failed_).
|
||||
This is the minimization rule of grouping together distinct logged errors that provide the same information via the API.
|
||||
|
||||
To set a static message sent to the client when the error occurs, the
|
||||
`errutil.WithPublicMessage(message string)` option may be appended to
|
||||
the NewBase function call. For dynamic messages or more options, refer
|
||||
To set a static message sent to the client when the error occurs, append the
|
||||
`errutil.WithPublicMessage(message string)` option to
|
||||
the `NewBase` function call. For dynamic messages or more options, refer
|
||||
to the `errutil` package's GoDocs.
|
||||
|
||||
Errors are then constructed using the `Base.Errorf` method, which
|
||||
You can then construct errors using the `Base.Errorf` method, which
|
||||
functions like the [fmt.Errorf](https://pkg.go.dev/fmt#Errorf) method
|
||||
except that it creates an `errutil.Error`.
|
||||
|
||||
@ -95,34 +90,27 @@ func Look(id int) (*Thing, error) {
|
||||
}
|
||||
```
|
||||
|
||||
Errors consider themselves to be both its `errutil.Base` or
|
||||
`errutil.Template` and whatever errors it wraps for the purposes of the
|
||||
`errors.Is` function.
|
||||
Errors are considered to be part of `errutil.Base` and
|
||||
`errutil.Template`, and whatever errors are wrapped for the purposes of the
|
||||
`errors.Is` function.a
|
||||
|
||||
Check out the package and method documentation for
|
||||
github.com/grafana/grafana/pkg/util/errutil for details on how to
|
||||
construct and use Grafana style errors. This documentation is
|
||||
unfortunately not readily available on pkg.go.dev because Grafana is not
|
||||
fully Go modules compatible, but can be viewed using
|
||||
[godoc](https://go.dev/cmd/godoc/) from the Grafana directory.
|
||||
Refer to the package and method documentation for
|
||||
`github.com/grafana/grafana/pkg/util/errutil` for details on how to
|
||||
construct and use Grafana style errors.
|
||||
This documentation isn't readily available on `pkg.go.dev`, but it can be viewed using [godoc](https://go.dev/cmd/godoc/) from the Grafana directory.
|
||||
|
||||
### Error source
|
||||
## Error source
|
||||
|
||||
You can optionally specify an error source that describes from where an
|
||||
error originates. By default, it's _server_ and means the error originates
|
||||
from within the application, e.g. Grafana. The `errutil.WithDownstream()`
|
||||
option may be appended to the NewBase function call to denote an error
|
||||
originates from a _downstream_ server/service. The error source information
|
||||
is used in the API layer to distinguish between Grafana errors and
|
||||
non-Grafana errors to include this information when instrumenting the
|
||||
application and by that allowing Grafana operators to define SLO's
|
||||
based on actual Grafana errors.
|
||||
You can optionally specify an error source that describes an error's origin.
|
||||
By default, it's `_server_` and means the error originates from within the application, for example, Grafana.
|
||||
The `errutil.WithDownstream()` option may be appended to the `NewBase` function call to denote an error originates from a _downstream_ server or service.
|
||||
The error source information is used in the API layer to distinguish between Grafana errors and non-Grafana errors. Error source information is given for use when instrumenting the application, allowing Grafana operators to define SLOs based on actual Grafana errors.
|
||||
|
||||
### Handling errors in the API
|
||||
## Handle errors in the API
|
||||
|
||||
API handlers use the `github.com/grafana/grafana/pkg/api/response.Err`
|
||||
or `github.com/grafana/grafana/pkg/api/response.ErrWithFallback`
|
||||
(same signature as `response.Error`) function to create responses based
|
||||
on `errutil.Error`.
|
||||
|
||||
Using `response.Err` requires all errors to be Grafana style errors.
|
||||
> **Note:** Using `response.Err` requires all errors to be Grafana style errors.
|
||||
|
Loading…
Reference in New Issue
Block a user