Skip to content

Commit

Permalink
Docs: edit of 3 files in backend contributor guide
Browse files Browse the repository at this point in the history
  • Loading branch information
josmperez committed Jun 21, 2024
1 parent bc43078 commit 3346285
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 92 deletions.
54 changes: 25 additions & 29 deletions contribute/backend/communication.md
Original file line number Diff line number Diff line change
@@ -1,30 +1,29 @@
# 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:

```
Expand All @@ -39,32 +38,29 @@ 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
`func(ctx context.Context, msg interface{}) error`
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 {
Expand Down Expand Up @@ -95,9 +91,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

Expand All @@ -116,11 +112,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 {
Expand Down
22 changes: 12 additions & 10 deletions contribute/backend/database.md
Original file line number Diff line number Diff line change
@@ -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):

Expand All @@ -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_.
Expand All @@ -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:

Expand All @@ -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 to store and retrieve objects, to and from the database.

To use the `SQLStore`, inject it in your service struct:

Expand All @@ -73,21 +75,21 @@ 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, it may become necessary 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:

- 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

Expand All @@ -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`

Expand Down
95 changes: 42 additions & 53 deletions contribute/backend/errors.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,25 @@
# 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 forth.

## Basic use

### Declaring 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). To do so, globally
construct variables using the `errutil.<status>(status, messageID, opts...)`
functions. For example:

- `errutil.NotFound(messageID, opts...)`
- `errutil.BadRequest(messageID, opts...)`
Expand All @@ -33,41 +32,38 @@ 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
Errors should be grouped 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.

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
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 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
Expand Down Expand Up @@ -95,28 +91,21 @@ 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 are considered to be part of `errutil.Base` and
`errutil.Template`, and whatever errors are wrapped for the purposes of the
`errors.Is` function.

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

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

Expand Down

0 comments on commit 3346285

Please sign in to comment.