Создал(а) 'Test'

Василий Толстов 2023-06-12 00:21:38 +03:00 committed by github-actions[bot]
commit 6f948f51da
23 changed files with 20332 additions and 0 deletions

44
EN:Client:HTTP.md Normal file

@ -0,0 +1,44 @@
HTTP micro client allows you make a request not only to microservices, but to other simple http clients. The big plus to use http client in micro that you don't have to move away from micro ecosystem at all. All wrappers, metrics, tracings, logs will be continuing work with micro client as it was before. Flexability remains the same, as always you can pass prepared client to service constructor. Marshaling / Unmarshaling is simple, pass the codec into client and it will be doing work for you. For errors mapping is present too.
In order to make it easier to work with requests and handle errors, the micro proto generator complements the generated code with the necessary options and parameters based on proto method annotations. Also, thanks to the analysis of the path for the http request, the micro client is able to map the elements of the request structure to the url path. At the same time, the api annotation rules from Google are almost completely supported.
In the case of tracing, the endpoint will look like the method name. In the example below, the service name + dot + method name will appear in the metrics and tracking when executing the LookupUser request. Github.LookupUser thereby allowing you to make beautiful metrics and tracer spans without using HTTP routers and built-in functions.
At the moment, the work of the http client micro was checked in the case of simple structures and simple messages. The name of the field in the query string must match the name of the structure field. Nested structures are not supported yet.
Example of a pseudo API Github in the form of a proto description:
```
syntax = "proto3";
package github;
option go_package = "github.com/unistack-org/micro-tests/client/http/proto;pb";
import "api/annotations.proto";
import "openapiv2/annotations.proto";
service Github {
rpc LookupUser(LookupUserReq) returns (LookupUserRsp) {
option (micro.openapiv2.openapiv2_operation) = {
operation_id: "LookupUser";
responses: {
key: "default";
value: {
description: "Error response";
schema: { json_schema: { ref: ".github.Error"; } }
}
}
};
option (micro.api.http) = { get: "/users/{username}"; };
};
};
message LookupUserReq {
string username = 1;
};
message LookupUserRsp {
string name = 1;
};
message Error {
string message = 1;
};
```
Thus, calling the LookupUser function in the micro with the LookupUserReq{Username: “vtolstov”} parameter, we will form a get request at /users/vtolstov and fill in the response structure. If you pass a non-existent user, the function returns an error of the Error type.

232
EN:Hello.md Normal file

@ -0,0 +1,232 @@
# The first service
## Proto
Micro does not oblige to use protobuf. But for better experience in communication between microservices (client -
server) strongly recommended to use .proto files. It insures developers on a both sides they are using the same
generated .proto structures and services.
Usually .proto files located in the directory of the project. If amount of services which are using .proto file are
growing up, the best way to share .proto files is using any of storage (github, gitlab etc.).
File .proto usually contains from three part:
- header - contains imports and package names
- service - contains service names and methods with annotations
- messages - description for structures, which are using by service
# Example
See code below with .proto file example for better undestanding:
```protobuf
syntax = "proto3"; // syntax of proto file
package github; // service name
option go_package = "domain.tld/some_repo;pb"; // optional, help code generator names packages in a right way
import "api/annotations.proto"; // for using http next to grpc we have to define import
import "openapiv2/annotations.proto"; // for using openapi generation (describe our services) we have to define import
service Github {// service name - will be using by code generator on the client side and server side support functions (create service client, create service server), in Endpoints and openapi description
rpc LookupUser(LookupUserReq) returns (LookupUserRsp) {// description for method with structures to receive and respond
option (micro.openapiv2.openapiv2_operation) = {// openapi annotaiont
operation_id: "LookupUser"; // operation name in openapi
responses: {// type of responses
key: "default"; // using by any of response type except standart one described in the method
value: {
description: "Error response"; // openapi description
schema: {json_schema: {ref: ".github.Error";}}; // link to message type, consists with package name and message name
}
}
};
option (micro.api.http) = {get: "/users/{username}";}; // describes endpoint which should be used connecting to rpc LookupUser via http with method GET and path /users/username. In order to use POST, PUT, PATCH requests also may contain body. Body is defining the same way as path variable, but instead should be using link to message structure. If body is not pre-defined should be used body:'*' declaration.
};
};
message LookupUserReq {// request description
string username = 1; // the username field. Name have to be identical to path variable declaration in option google.api.http GET /users/{username}
};
message LookupUserRsp {// response description
string name = 1; // here define only one field from api.github.com - name of user
};
message Error {// error description
string message = 1; // message from api.github.com if user not found
};
```
# Code generation
When a proto file has been created our next step will be code generation. For those purposes we are using
protoc-gen-micro. It generates code based on proto description for server and client sides. The key thing here - imports
have to be available. To make this happen we have to install them. The easiest way to do this - using file tools.go like
example below:
```go
// +build tools
package main
import (
_ "github.com/unistack-org/micro-proto/openapiv2"
)
```
For using this file only in compilation time, we have to define // +build tools and pass the flag tools (we won't do
that). The command go build / go get see the import and add the dependency.
Next step - script for code generation:
```shell
#!/bin/sh -e
INC=$(go list -f '{{ .Dir }}' -m github.com/unistack-org/micro-proto)
ARGS="-I${INC}"
protoc $ARGS -Iproto --openapiv2_out=disable_default_errors=true,allow_merge=true:./proto/ --go_out=paths=source_relative:./proto/ --micro_out=components="micro|http",debug=true,paths=source_relative:./proto/ proto/*.proto
```
Here we want to generate swagger/openapi specification, go code with structures, micro interfaces and http client,
server. All generated code will be stored in a proto directory.
# Service
## Client
The code below is describing creation client for Github service
```go
package main
import (
"context"
mhttp "github.com/unistack-org/micro-client-http/v3"
jsoncodec "github.com/unistack-org/micro-codec-json/v3"
pb "github.com/unistack-org/micro-tests/client/http/proto"
"github.com/unistack-org/micro/v3/client"
"github.com/unistack-org/micro/v3/logger"
)
func main() {
hcli := mhttp.NewClient(client.ContentType("application/json"), client.Codec("application/json", jsoncodec.NewCodec()))
cli := client.NewClientCallOptions(hcli, client.WithAddress("https://api.github.com"))
gh := pb.NewGithubClient("github", c)
rsp, err := gh.LookupUser(context.TODO(), &pb.LookupUserReq{Username: "vtolstov"})
if err != nil {
logger.Errorf(context.TODO(), err)
}
if rsp.Name != "Vasiliy Tolstov" {
logger.Errorf(context.TODO(), "invalid rsp received: %#+v\n", rsp)
}
}
```
## Server
The code below is describing creation server for Github service
First step - creating handler
```go
package handler
import (
"context"
"errors"
httpsrv "github.com/unistack-org/micro-server-http/v3"
pb "github.com/unistack-org/micro-tests/client/http/proto"
"net/http"
)
type GithubHandler struct{}
func NewGithubHandler() *GithubHandler {
return &GithubHandler{}
}
func (h *GithubHandler) LookupUser(ctx context.Context, req *pb.LookupUserReq, rsp *pb.LookupUserRsp) error {
if req.GetUsername() == "" || req.GetUsername() != "vtolstov" {
httpsrv.SetRspCode(ctx, http.StatusBadRequest)
return httpsrv.SetError(&pb.Error{Message: "name is not correct"})
}
rsp.Name = "Vasiliy Tolstov"
httpsrv.SetRspCode(ctx, http.StatusOK)
return nil
}
```
Second step - creating server
```go
package main
import (
"context"
mhttp "github.com/unistack-org/micro-client-http/v3"
jsoncodec "github.com/unistack-org/micro-codec-json/v3"
httpsrv "github.com/unistack-org/micro-server-http/v3"
"github.com/unistack-org/micro/v3"
"github.com/unistack-org/micro/v3/client"
"github.com/unistack-org/micro/v3/logger"
"github.com/unistack-org/micro/v3/server"
"github.com/unistack-org/micro-tests/server/http/handler"
pb "github.com/unistack-org/micro-tests/client/http/proto"
)
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
/*
Options for service, pass the context with cancel
*/
options := append([]micro.Option{},
micro.Server(httpsrv.NewServer(
server.Name("github-service"),
server.Version("1.0"),
server.Address(":8080"),
server.Context(ctx),
server.Codec("application/json", jsoncodec.NewCodec()),
)),
micro.Client(mhttp.NewClient(
client.Name("github-client"),
client.Context(ctx),
client.Codec("application/json", jsoncodec.NewCodec()),
client.ContentType("application/json"),
)),
micro.Context(ctx),
)
// create new service
srv := micro.NewService(options...)
// init all stuff
if err := srv.Init(); err != nil {
logger.Fatal(ctx, err)
}
// create handler
eh := handler.NewGithubHandler()
// register handler in server
if err := pb.RegisterGithubServer(srv.Server(), eh); err != nil {
logger.Fatal(ctx, err)
}
// run service
if err := srv.Run(); err != nil {
logger.Fatal(ctx, err)
}
}
```
Important note:
1. In order to use gRPC instead of http - need to replace http to grpc in --micro_out=components="micro|http"
2. Implementation for gRPC in code has only one difference - need to pass grpcsrv.NewServer()
instead of httpsrv.NewServer(). Library could be found here:
````
github.com/unistack-org/micro-server-grpc/v3
````

38
EN:Home.md Normal file

@ -0,0 +1,38 @@
* [[Brokers|EN:Broker]]
* [[Kgo|EN:Broker:Kgo]]
* [[Clients|EN:Client]]
* [[HTTP|EN:Client:HTTP]]
* [[GRPC|EN:Client:GRPC]]
* [[DRPC|EN:Client:DRPC]]
* [[Codecs|EN:Codec]]
* [[XML|EN:Codec:XML]]
* [[JSON|EN:Codec:JSON]]
* [[PROTO|EN:Codec:PROTO]]
* [[YAML|EN:Codec:YAML]]
* [[Configs|EN:Config]]
* [[Env|EN:Config:Env]]
* [[Flag|EN:Config:Flag]]
* [[File|EN:Config:File]]
* [[Consul|EN:Config:Consul]]
* [[Vault|EN:Config:Vault]]
* [[Errors|EN:Errors]]
* [[Transactions|EN:Flow]]
* [[Logging|EN:Logger]]
* [[Metadata|EN:Metadata]]
* [[Metrics|EN:Meter]]
* [[Servers|EN:Server]]
* [[HTTP|EN:Server:HTTP]]
* [[GRPC|EN:Server:GRPC]]
* [[DRPC|EN:Server:DRPC]]
* [[TCP|EN:Server:TCP]]
* [[Stores|EN:Store]]
* [[Redis|EN:Store:Redis]]
* [[S3|EN:Store:S3]]
* [[Tracing|EN:Tracer]]
* [[Generator|EN:Generate]]
* [[Micro|EN:Generate:Micro]]
* [[Tag|EN:Generate:Tag]]
* [[HTTP|EN:Generate:HTTP]]
* [[RPC|EN:Generate:RPC]]
* [[Openapiv3|EN:Generate:Openapiv3]]
* [[Example|EN:Hello]]

1
Home.md Normal file

@ -0,0 +1 @@
Welcome to the micro-docs wiki!

18
RU:Broker.md Normal file

@ -0,0 +1,18 @@
Реализации интерфейса https://pkg.go.dev/go.unistack.org/micro/v3/broker#Broker
Брокер - абстракция отвечающая за реализацию асинхронного взаимодействие (паблишинг/сабскрайбинг). При использовании напрямую в сервисе предоставляет только реализацию асинхронного взаимодействия, а за сериализацию/десериализацию отвечает разработчик. Чаще всего используется внутри клиентов и серверов(прокидывается как зависимость), а те, в свою очередь, берут на себя ответственность за сериализацию/десериализацию(с помощью кодеков) и взаимодействие с брокером (метод Publish клиента и Subscribe сервиса).
Кодек так же присутствует в опциях брокера, он декларирует как маршалить/анмаршалить непосредственно структуру Message(является транспортной структурой брокера, в которую враппается сериализованное содержимое объекта и заголовки), по сути этот кодек константен.
Брокер используются для асинхронной коммуникации между сервисами. Существует два варианта использования брокера в micro. Высокоуровневый и низкоуровневый.
Низкоуровневый вариант заключается в использовании непосредственно broker методов. В этом случае Publish использует *broker.Message сообщение, вся работа по маршалингу сообщения ложится на разработчика. Сообщение в виде массива байт должны быть в поле Body структуры broker.Message. Опционально нужно заполнить заголовки, для передачи метаданных, например идентификатора трейсинга, или контент-тип. Микро ожидает, что все сообщения будут иметь тип *broker.Message. В форке добавлена поддержка прямого получения массива байт из брокера для kafka (segmentio) в этом случае заголовки сообщения будут пустыми, в все сообщение из брокера будет записано в поле Body как есть. Для этого необходимо передать опцию BodyOnly при подписке. Для публикации только тела сообщения следует использовать *codec.Frame тип сообщения. Все кодеки микро поддерживают данный тип и просто записывают байты.
Низкоуровневый вариант позволяет вручную вызывать Ack() метод и вручную оперировать телом сообщения.
Высокоуровневый вариант заключается в использовании micro.RegisterSubscriber функции и сервера микро. В этом случае нет возможности вручную вызывать метод Ack(), сервер автоматически вызывает его, если хендлер не вернул ошибку обработки. Плюсом данного подхода является то, что не нужно вручную выполнять анмаршалинг тела сообщения в структуру. Хендлер уже получает в контексте все метаданных из заголовка сообщения и заполненную структуру из тела сообщения.
Несмотря на высокоуровневый подход был сделан вариант получения всего сообщении в сыром виде посредством использования в качестве типа в хендлере *codec.Frame. В этом случае брокер игнорирует анмаршалинг и передает сообщение как есть.
В описанных выше вариантах используется микро Codec. Его задача маршалинг-анмаршалинг сообщения. Кодек используется как в брокере, так и в сервере.
При паблишинге через клиента кодек в клиенте маршалит структуру в массив байт. После этого заполняет *broker.Message поля и отдает брокеру. Брокер в свою очередь с помощью своего кодека маршалит сообщение в массив байт и передает непосредственно в брокер. При паблишинге непосредственно через брокер из схемы уходит использование клиента и маршалинг в байты ложится на плечи разработчика.
При консуминге кодек в брокере используется для анмаршалинга массива байт в структуру broker.Message. Далее если используется сабскрайбинг брокера то полученная структура отдается в хендлер в виде интерфейса Event. Если используется для сабскрайбинга сервер, то появляется еще один этап в котором сервер заполняет контекст данными из заголовка сообщения и использования серверного кодека для анмаршалинга тела сообщения в нужную структуру хендлера. Как только хендлер завершает свою работу на основе возвращаемого значения решается, нужно ли выполнить Ack() над сообщением.

3
RU:Client.md Normal file

@ -0,0 +1,3 @@
Реализации интерфейса https://pkg.go.dev/go.unistack.org/micro/v3/client#Client
Клиент - это абстракция, которая отвечает за сериализацию передаваемого объекта (с помощью встраивания кодеков) и дальнейшую отправку исходящих запросов в другие сервисы, либо в брокера, будь то синхронные запросы (RPC), или асинхронные (publish в топик брокера к примеру). protoc [[генератор|RU:Generate]] умеет генерировать структуру, реализующую методы из прото и декорирующую метод Call клиента (см. пример ниже). Собственно, используется для взаймодействия с сервисами, у которых есть прото с сгенерированными рпс вызовавами (касается только синхронного взаимодействия - метод Call). Для синхронного взаимодействия, например, с внешними сервисами, proto с контрактом которых мы не поддерживаем, допускается нативная реализация на уровне сервиса.

44
RU:Client:HTTP.md Normal file

@ -0,0 +1,44 @@
Хттп клиент микро позволяет делать запросы не только к микро сервисам, но и обычным хттп серверам посредством рест апи. Самыми большими плюсами использования хттп клиента микро в том, что не нужно выходить за рамки экосистемы микро. Все врапперы метрик, трейсинга, логов и тп продолжат работать с микро клиентом как и раньше. При этом остается полная гибкость, так как посредством опций можно передать уже подготовленный клиент в конструктор. Также не нужно вручную заниматься маршалингом и анмаршалингом запросов и ответов, так как все это делается посредством кодека микро. Стоит отметить что присутствует мапинг ошибок из прото аннотаций.
Для того, чтобы облегчить работу с запросами и обработку ошибок, микро прото генератор дополняет сгенерированный код необходимыми опциями и параметрами на основе прото аннотаций методов. Также благодаря анализу пути для запроса хттп клиент микро умеет мапить элементы структуры запроса в урл путь. При этом поддерживается практически полностью правила аннотаций апи от гугл.
В случае использования трейсинга ендпоинт будет выглядеть по имени метода, в указанном ниже примере в метриках и тресинге при выполнении запроса LookupUser будет фигурировать имя сервиса + точка + имя метода. Github.LookupUser тем самым позволяя делать красивые метрики и трейс спаны без использования хттп роутеров и встроенных функций.
На текущий момент работа хттп клиента микро проверялась в случае простых структур и прото сообщений. Имя поля в строке запроса должно совпадать с именем поля структуры. Вложенные структуры пока не поддерживаются.
Пример псевдо апи гитхаба в виде прото описания
```
syntax = "proto3";
package github;
option go_package = "github.com//unistack-org/micro-tests/client/http/proto;pb";
import "api/annotations.proto";
import "openapiv2/annotations.proto";
service Github {
rpc LookupUser(LookupUserReq) returns (LookupUserRsp) {
option (micro.openapiv2.openapiv2_operation) = {
operation_id: "LookupUser";
responses: {
key: "default";
value: {
description: "Error response";
schema: { json_schema: { ref: ".github.Error"; } }
}
}
};
option (micro.api.http) = { get: "/users/{username}"; };
};
};
message LookupUserReq {
string username = 1;
};
message LookupUserRsp {
string name = 1;
};
message Error {
string message = 1;
};
```
Таким образом, вызывая в микро функцию LookupUser с параметром LookupUserReq{Username: “vtolstov”} мы сформируем гет запрос по адресу /users/vtolstov и заполним структуру ответа. Если передать несуществующего пользователя, то функция вернет ошибку типа Error

7
RU:Codec.md Normal file

@ -0,0 +1,7 @@
Реализация интерфейса https://pkg.go.dev/go.unistack.org/micro/v3/codec#Codec
Кодеки предназначены для встраивания в клиент, сервер и брокер. Но могут использоваться и как самостоятельная единица.
Отвечают за сериализацию/десериализацию сообщений. Внутри клинета/сервера, в зависимости от Content-Type запроса, выбирается кодек (является ключом в мапе одной из опций клиента/сервера) и вызывается метод Marshal/Unmarshal.
Инкапсуляция в интерфейс позволяет легко менять имплементацию (например более производительный вариант декодинга прото или джейсон). Если в кодеке существует метод MarshalAppend, то в некоторых случаях будет использоваться именно он для уменьшения количества мусора. Так как сервер или клиент может использовать буфер пул для массива байт.

54
RU:Config.md Normal file

@ -0,0 +1,54 @@
Реализация интерфейса https://pkg.go.dev/go.unistack.org/micro/v3/config#Config
Конфиг источники представляют собой методы Save/Load/Watch для заполнения конфигурационного файла из различных источников.
Микросервис предоставляет возможность стекировать конфиг сурсы, а также динамически перенастраивать сервис в зависимости от значений конфигурационной структуры посредством метода Watch.
Наиболее известный вариант использования конфигурационных источников в следующем варианте
```
cfg := appconfig.NewConfig(appName, AppVersion) // create new empty config
if err := config.Load(ctx,
[]config.Config{
config.NewConfig( // load from defaults
config.Struct(cfg), // pass config struct
),
fileconfig.NewConfig( // load from file
config.AllowFail(true), // that may be not exists
config.Struct(cfg), // pass config struct
config.Codec(jsoncodec.NewCodec()), // file config in json
fileconfig.Path("./local.json"), // nearby file
),
envconfig.NewConfig( // load from environment
config.Struct(cfg), // pass config struct
),
vaultconfig.NewConfig(
config.AllowFail(true), // that may be not exists
config.Struct(cfg), // load from vault
config.Codec(jsoncodec.NewCodec()), // vault config in json
config.BeforeLoad(func(ctx context.Context, c config.Config) error {
return c.Init(
vaultconfig.Address(cfg.Vault.Addr),
vaultconfig.Token(cfg.Vault.Token),
vaultconfig.Path(cfg.Vault.Path),
)
}),
),
}, config.LoadOverride(true),
); err != nil {
logger.Fatalf(ctx, "failed to load config: %v", err)
}
```
В таком варианте создается стек из источников конфигурации, которые обрабатываются в указанном порядке.
1. Конфигурационная структура заполняется параметрами по-умолчанию (параметры задаются посредством аннотаций к полям структуры)
2. Чтение из локального файла в формате json/yaml/toml (удобно для локального тестирования)
3. Чтение из переменных окружения (обычно используется в кубернетес для задания параметров для следующих конфигурационных источников)
4. Чтение из vault конфигурационного файла в котором помимо прочего могут содержаться различные пароли и сертификаты
В конечном итоге существующие значения перезаписываются (опция config.LoadOverride(true)), для массивов или мап можно указать опцию для дополнения, вместо перезаписи значений.
Методы
func Load(context.Context, ...config.LoadOption) error

3
RU:Config:Consul.md Normal file

@ -0,0 +1,3 @@
Данный источник конфигурации используется для чтения параметров из consul. Самый популярный вариант использования - запуск сервиса в системе контейнерной оркестрации (kubernetes/openshift).
Стоит заметить, что выбирая между Consul и Vault, стоит отдать предпочтение последнему. Так как он в бесплатной редакции поддерживает аудит логи, версионирование. А также позволяет настроить доступ посредством App Role.

3
RU:Config:Env.md Normal file

@ -0,0 +1,3 @@
Данный источник конфигурации используется для чтения параметров из переменных окружения. Самый популярный вариант использования - запуск сервиса в системе контейнерной оркестрации (kubernetes/openshift).
Метод Save поддерживается, но стоит заметить, что область видимости ограничена текущим запущенным процессом.

3
RU:Config:File.md Normal file

@ -0,0 +1,3 @@
Данный источник конфигурации используется для чтения параметров из локального файла. Файл может быть в любом формате, который поддерживается кодеком. Наиболее популярные форматы - json, yaml, toml, ini.
Все методы Load/Save/Watch поддерживаются.

3
RU:Config:Flag.md Normal file

@ -0,0 +1,3 @@
Данный источник конфигурации используется для чтения параметров переданных посредством флагов при запуске приложения. Самый популярный вариант использования - запуск приложения на рабочей машине, отображение справочной информации ( флаг --help ).
Метод Save не поддерживается, так как не имеет смысла.

3
RU:Config:Vault.md Normal file

@ -0,0 +1,3 @@
Данный источник конфигурации используется для чтения параметров из vault. Самый популярный вариант использования - запуск сервиса в системе контейнерной оркестрации (kubernetes/openshift).
Стоит заметить, что выбирая между Consul и Vault, стоит отдать предпочтение последнему. Так как он в бесплатной редакции поддерживает аудит логи, версионирование. А также позволяет настроить доступ посредством App Role.

7
RU:Errors.md Normal file

@ -0,0 +1,7 @@
Ошибки - пакет errors используется для передачи ошибок между сервисами. Особенно важно это при использовании грпц клиента и сервера, так как в противном случае будет утерян оригинальный код ошибки.
Внутри самого сервиса можно использовать любые виды ошибок. Ошибка содержит код, описание и идентификатор сервиса, который выдал ошибку. Стоит заметить, что данный пакет предназначен исключительно для передачи ошибок внутри фреймворка. Не стоит использовать данный тип для отдачи ошибок клиенту.
В качестве идентификатора не стоит использовать uuid или другой идентификатор, так как он присутствует в метаданных контекста.
В случае отдачи клиенту ошибки стоит передавать сквозной идентификатор его запроса, сформированный на сервисе, принимающим запрос от клиента.

1
RU:Generate.md Normal file

@ -0,0 +1 @@
Прото генератор микро позволяет сгенерировать код на основе прото описания. На текущий момент поддерживаются три вида генерации: микро интерфейсы, микро хттп клиент и сервер, микро рпц клиент и сервер. Из-за совпадения имен нельзя сгенерировать одновременно и хттп и рпц файлы. В дальнейшем планируется возможность генерация кода не только в директорию с остальными сообщениями.

227
RU:Hello.md Normal file

@ -0,0 +1,227 @@
# Первый сервис
## Proto
Микро не обязывает использовать protobuf для работы. Тем не менее, описание сервиса посредством proto файла позволяет лучше спроектировать сервис. Спроектированный proto файл можно передать разработчикам frontend системы не дожидаясь окончания разработки сервиса. Тем самым используя опианные в proto/openapi файле соглашения можно быть уверенным, что backend и frontend части сервиса будут работать вместе правильно.
Именно поэтому стоит придерживаться описанного выше порядка разработки. Обычно proto файл для сервиса располагается либо в отдельном репозитории (если потребителей сервиса несколько) либо в том же репозитории в директории proto (если потребителем будет только сам сервис).
Файл обычно состоит (в простейшем случае) из трех частей:
- служебная - содержит импорты и название пакета
- сервис - содержит название и методы сервиса с аннотациями
- сообщения - содержит описание всех сообщений, которые использует сервис
# Пример
Ниже приведен пример простейшего прото файла с комментариями:
```protobuf
syntax = "proto3"; // мы рассматриваем только proto3 как наиболее подерживаемый формат
package github; // обычно имя сервиса
option go_package = "domain.tld/some_repo;pb"; // можно не указывать, помогает go генератору использовать верные имена пакетов
import "api/annotations.proto"; // так как в файле для http клиента и сервера присуствуют аннотации, требуется указать импорт
import "openapiv2/annotations.proto"; // так как в файле для http клиента и сервера присуствуют аннотации, требуется указать импорт
service Github { // название сервиса, оно будет использоваться в сгенерированном коде, а также фигурировать в служебных структурах в виде имени Endpoint
rpc LookupUser(LookupUserReq) returns (LookupUserRsp) { // описание имени метода, принимаемых и отправляемых типов сообщений
option (micro.openapiv3.openapiv3_operation) = { // openapi аннотация
operation_id: "LookupUser"; // название операции в openapi
responses: { // типы ответов
key: "default"; // используется для всех типов ответов, кроме стандартного, описанного в методе
value: {
description: "Error response"; // openapi описание
schema: { json_schema: { ref: ".github.Error"; } } // ссылка на тип сообщения, состоит из имени пакета, которое мы указали в package и имени сообщения
}
}
};
option (micro.api.http) = { get: "/users/{username}"; }; // аннотация, сообщаяющая о том, что для вызова метода требутеся сделать GET запрос на путь /users где username берется из структуры запроса и подставляется в путь, например /users/github_user . В случае методов POST/PATCH/PUT может присуствовать еще body:"*"; сообщающая, что все поля структуры запроса следует передать в теле реквеста.
};
};
message LookupUserReq { // описание сообщения реквеста
string username = 1; // поле сообщения, оно же используется для составления GET запроса
};
message LookupUserRsp { // описание сообщения респонса
string name = 1; // поле сообщения ответа, на самом деле github отдает больше полей, приведено исключительно для примера
};
message Error { // описание сообщения об ошибке, для каждого метода могут быть свои ошибки
string message = 1; // в сообщении github об ошибке присутсвует данное поле
};
```
# Кодогенерация
После того, как proto файл создан необходимо по нему сгенерировать код. Для этих целей используется protoc-gen-micro . Данное приложения на основе proto описания генерирует код клиента и сервера. Основная сложность состоит в том, что proto файлы, указанные в качестве import должны быть доступны на момент генерации. Для этого должны быть установлены пакеты, в которых данные файлы присутствуют. Проще всего этого достичь путем включения в файл tools.go следующего содержимого:
```go
// +build tools
package main
import (
_ "github.com/unistack-org/micro-proto/v3/openapiv3"
)
```
В первой строке мы указваем, что данный файл участвут в компиляции только, если передан флаг tools (что мы передавать не собираемся). Команда go build / go get видит использование импорта и добавит данную зависимость для скачивания без компиляции.
Следующий шаг это создание скрипта, который будет запускаться для генерации
```shell
#!/bin/sh -e
INC=$(go list -f '{{ .Dir }}' -m github.com/unistack-org/micro-proto/v3)
ARGS="-I${INC}"
protoc $ARGS -Iproto --openapiv2_out=disable_default_errors=true,allow_merge=true:./proto/ --go_out=paths=source_relative:./proto/ --micro_out=components="micro|http",debug=true,paths=source_relative:./proto/ proto/*.proto
```
В данном файле мы указываем, что хотим сгенерировать swagger/openapi спецификацию, go код описания структур а также micro интерфейсы и http клиент и сервер. Все сгенерированные файлы будут находится в директории proto
# Сервис
## Клиент
Ниже представлен пример клиента, использующего сгенерированный нами код:
```go
package main
import (
"context"
mhttp "github.com/unistack-org/micro-client-http/v3"
jsoncodec "github.com/unistack-org/micro-codec-json/v3"
pb "github.com/unistack-org/micro-tests/client/http/proto"
"github.com/unistack-org/micro/v3/client"
"github.com/unistack-org/micro/v3/logger"
)
func main() {
cli := client.NewClientCallOptions(
mhttp.NewClient(
client.ContentType("application/json"),
client.Codec("application/json", jsoncodec.NewCodec()),
),
client.WithAddress("https://api.github.com"),
)
ghc := pb.NewGithubClient("github", c)
rsp, err := ghc.LookupUser(context.TODO(), &pb.LookupUserReq{Username: "vtolstov"})
if err != nil {
logger.Errorf(context.TODO(), err)
}
if rsp.Name != "Vasiliy Tolstov" {
logger.Errorf(context.TODO(), "invalid rsp received: %#+v", rsp)
}
}
```
## Сервер
Код, представленный ниже объясняет создание базового сервиса Github
Первый шаг - создание обработчика
```go
package handler
import (
"context"
"errors"
httpsrv "github.com/unistack-org/micro-server-http/v3"
pb "github.com/unistack-org/micro-tests/client/http/proto"
"net/http"
)
type GithubHandler struct{}
func NewGithubHandler() *GithubHandler {
return &GithubHandler{}
}
func (h *GithubHandler) LookupUser(ctx context.Context, req *pb.LookupUserReq, rsp *pb.LookupUserRsp) error {
if req.GetUsername() == "" || req.GetUsername() != "vtolstov" {
httpsrv.SetRspCode(ctx, http.StatusBadRequest)
return httpsrv.SetError(&pb.Error{Message: "name is not correct"})
}
rsp.Name = "Vasiliy Tolstov"
httpsrv.SetRspCode(ctx, http.StatusOK)
return nil
}
```
Следующий шаг - создание сервера
```go
package main
import (
"context"
mhttp "github.com/unistack-org/micro-client-http/v3"
jsoncodec "github.com/unistack-org/micro-codec-json/v3"
httpsrv "github.com/unistack-org/micro-server-http/v3"
"github.com/unistack-org/micro/v3"
"github.com/unistack-org/micro/v3/client"
"github.com/unistack-org/micro/v3/logger"
"github.com/unistack-org/micro/v3/server"
"github.com/unistack-org/micro-tests/server/http/handler"
pb "github.com/unistack-org/micro-tests/client/http/proto"
)
func main() {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
/*
Опции для сервиса, контекст с функцией cancel()
*/
options := append([]micro.Option{},
micro.Server(httpsrv.NewServer(
server.Name("github-service"),
server.Version("1.0"),
server.Address(":8080"),
server.Context(ctx),
server.Codec("application/json", jsoncodec.NewCodec()),
)),
micro.Client(mhttp.NewClient(
client.Name("github-client"),
client.Context(ctx),
client.Codec("application/json", jsoncodec.NewCodec()),
client.ContentType("application/json"),
)),
micro.Context(ctx),
)
// Создание нового сервиса и передача в него опций
srv := micro.NewService(options...)
// Инициализируем все опции в сервисе
if err := srv.Init(); err != nil {
logger.Fatal(ctx, err)
}
// Создаем реализацию для сервера
eh := handler.NewGithubHandler()
// Регистрируем реализацию в сервере
if err := pb.RegisterGithubServer(srv.Server(), eh); err != nil {
logger.Fatal(ctx, err)
}
// Запускаем сервис
if err := srv.Run(); err != nil {
logger.Fatal(ctx, err)
}
}
```
Важные замечания:
1. Чтобы использовать grpc вместо http - нужно заменить http на grpc в опции компонентах генератора --micro_out=components="micro|http"
2. Для использования GRPC сервера в коде нужно поменять импорт и использовать grpcsrv.NewServer() вместо httpsrv.NewServer()
````
https://github.com/unistack-org/micro-server-grpc/v3
````

38
RU:Home.md Normal file

@ -0,0 +1,38 @@
* [[Брокеры|RU:Broker]]
* [[Kgo|RU:Broker:Kgo]]
* [[Клиенты|RU:Client]]
* [[HTTP|RU:Client:HTTP]]
* [[GRPC|RU:Client:GRPC]]
* [[DRPC|RU:Client:DRPC]]
* [[Кодеки|RU:Codec]]
* [[XML|RU:Codec:XML]]
* [[JSON|RU:Codec:JSON]]
* [[PROTO|RU:Codec:PROTO]]
* [[YAML|RU:Codec:YAML]]
* [[Конфигурация|RU:Config]]
* [[Env|RU:Config:Env]]
* [[Flag|RU:Config:Flag]]
* [[File|RU:Config:File]]
* [[Consul|RU:Config:Consul]]
* [[Vault|RU:Config:Vault]]
* [[Ошибки|RU:Errors]]
* [[Транзакции|RU:Flow]]
* [[Логирование|RU:Logger]]
* [[Метаданные|RU:Metadata]]
* [[Метрики|RU:Meter]]
* [[Cерверы|RU:Server]]
* [[HTTP|RU:Server:HTTP]]
* [[GRPC|RU:Server:GRPC]]
* [[DRPC|RU:Server:DRPC]]
* [[TCP|RU:Server:TCP]]
* [[Хранилища|RU:Store]]
* [[Redis|RU:Store:Redis]]
* [[S3|RU:Store:S3]]
* [[Трассировка|RU:Tracer]]
* [[Генератор|RU:Generate]]
* [[Micro|RU:Generate:Micro]]
* [[Tag|RU:Generate:Tag]]
* [[HTTP|RU:Generate:HTTP]]
* [[RPC|RU:Generate:RPC]]
* [[Openapiv3|RU:Generate:Openapiv3]]
* [[Пример|RU:Hello]]

3
RU:Server.md Normal file

@ -0,0 +1,3 @@
Реализация интерфейса https://pkg.go.dev/go.unistack.org/micro/v3/server#Server
Сервер - это абстракция, которая отвечает за десериализацию входящего объекта (с помощью встраивания кодеков) и дальнейшую логику обработки входящих запросов, будь то синхронные запросы RPC, или асинхронные SUB. Для синхронных запросов protoc [[генератор|RU:Generate]] умеет генерировать интерфейс с RPC вызовами из protobuf, которые нужно реализовать, и метод декорирующий регистрацию хэндлеров (вызовы s.Handle(s.NewHandler()).

24
Test.md Normal file

@ -0,0 +1,24 @@
Для сервисов, которые сделаны на базе micro или имеют protobuf описание есть возможность использовать автоматические юнит-тесты с использованием csv+json файлов.
Основная идея заключается в том, что для добавления нового тест-кейса не нужно никаких правок код или знаний программирования. Достаточно положить группу файлов в соотвествии с определенной структурой директорий. Примеры ниже приведены для MVC версии тестов в рамках deposit-grpc сервиса и директории с тестами tests
tests/config.json - конфигурационный файл сервиса, является копией конфига из консула и не содержит логинов и паролей к внешним системам. Вместо настроек подключения к бд следует использовать в секции dsn (или в той секции, где указываются параметры подключения к бд слово mock://)
tests/common_test.go - основной файл, запускающий тесты. Должен содержать две части:
TestMain(m *testing.M) - парсинг конфигурационного файла, запуск сервиса с последующим запуском тестов
Test_All(t *testing.T) - получение экземпляра мок для бд, создание грпц или хттп клиента, редирект stdout/stderr в буфер и последующий запуск тестов
tests/data/NN_Service.Method -
NN любое число, лучше с ведущим нулем для натуральной сортировки в списке
Service - имя сервиса в protobuf файле
Method - имя метода сервиса в protobuf файле
Таким образом для каждого метода сервиса может быть больше одного теста. Если результат теста зависит от предыдущего запроса, такие запросы следует располагать в нумерации в порядке следования. 
Возьмем для примера метод GetInfo сервиса grpc с директорией tests/data/01_Service.GetInfo:
GetInfo_db.csv - файл csv, в котором содержатся данные используемые для mock ответов бд. Таких файлов может быть несколько для разных версий запросов. Данный файл содержит следующую мета-информацию:
*  # query .*get_info_v3.* - после ключевого слова query и пробела после него идер pcre регулярное выражение. По данному условию mock для бд будет понимать какие данные нужно отдавать на какой запрос
* # args {"ID_List":["12345"]} - после ключевого слова args следуют аргументы запроса, в зависимости от значения аргументов могут mock может выдавать разные данные на один и тот же запрос
* # columns ID|VARCHAR|NULL,.... - после ключевого слова columns следуют имена колонок и типы данных, которые могут быть. Так как mock при обработке csv файла не умеет определять где NULL значение строки, а где пустая строка, данное описание помогает подсказать правильный тип при отдаче данных
GetInfo_req.json - файл, содержащий тестовый запрос к сервису в формате json. Так как для запуска тестов используется фреймворк micro и все grpc/rest сервисы понимают входящий запрос и ответ в виде json
GetInfo_rsp.json - файл, содержащий тестовый ответ от сервиса в формате json. Так как для запуска тестов используется фреймворк micro и все grpc/rest сервисы понимают входящий запрос и ответ в виде json
GetInfo_err.json - файл, содержащий тестовый ответ об ошибке в формате json. Так как для запуска тестов используется фреймворк micro и все grpc/rest сервисы понимают входящий запрос и ответ в виде json. Если файл присутствует, считается, что запрос должен вернуть ошибку. Если файла нет, считается, что запрос должен вернуть ответ без ошибки.

2
_Sidebar.md Normal file

@ -0,0 +1,2 @@
* [[English|EN:Home]]
* [[Русский|RU:Home]]

19573
coverage.html Normal file

File diff suppressed because it is too large Load Diff

1
coverage.svg Normal file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="47" height="20" role="img" aria-label="41.2%"><title>41.2%</title><linearGradient id="s" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><clipPath id="r"><rect width="47" height="20" rx="3" fill="#fff"/></clipPath><g clip-path="url(#r)"><rect width="0" height="20" fill="#e05d44"/><rect x="0" width="47" height="20" fill="#e05d44"/><rect width="47" height="20" fill="url(#s)"/></g><g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" text-rendering="geometricPrecision" font-size="110"><text aria-hidden="true" x="235" y="150" fill="#010101" fill-opacity=".3" transform="scale(.1)" textLength="370">41.2%</text><text x="235" y="140" transform="scale(.1)" fill="#fff" textLength="370">41.2%</text></g></svg>

After

Width:  |  Height:  |  Size: 907 B