Data Modifier
Data Modifier - gRPC-сервер для расширения/модификации данных о пользователе на основе сторонней системы.
Используемые технологии:
- toml, godotenv (конфигурация)
- ozzo-validation (валидация)
- go.uber.org/zap (логирование)
- google.golang.org/grpc (gRPC сервер)
- grpc-ecosystem/go-grpc-middleware/v2/interceptors (gRPC интерсепторы)
- testify, go.uber.org/mock/gomock (unit-тесты)
Код организован согласно Clean Architecture, инъекция зависимостей обеспечивает низкую связанность слоев и упрощает тестирование.
Также реализован Graceful Shutdown для корректного завершения работы сервера.
Getting Started
Перед запуском приложения необходимо добавить в директорию проекта .env
файл с логином и паролем от сторонней системы, как в примере .env.example.
А также настроить конфигурацию приложения через .toml
файл, как в примере config.toml. Путь к этому файлу можно передать с флагом -config-path
при запуске приложения.
Если требуется записать логи и ошибки в файлы, необходимо создать эти файлы и передавать путь к ним в опциях output_path
/error_output_path
(подробнее см. ниже).
Описание всех опций с примерами:
tcp_ip
/tcp_port
- IP-адрес и порт, на который gRPC сервер будет принимать запросы . Заметьте, что порт указывается с :
.
max_clients
- максимальное количество подключений (размер очереди запросов).
num_pool_workers
- количество горутин для обработки запросов (размер пула обработчиков).
Пример конфигурации gRPC сервера:
tcp_ip="127.0.0.1"
tcp_port=":8081"
max_clients=10
num_pool_workers=5
web_api_ip
/web_api_port
- IP-адрес и порт сторонней системы. Заметьте, что порт указывается с :
.
protocol_type
- HTTP протокол ("http", "https").
employee_path
- путь для формирования запроса на получение данных о пользователе со сторонней системы. Заметьте, что путь указывается без /
.
absence_path
- путь для формирования запроса на получение данных о статусе отсутствия пользователя со сторонней системы. Заметьте, что путь указывается без /
.
Пример конфигурации для работы со сторонней системой:
web_api_ip="127.0.0.1"
web_api_port=":8082"
protocol_type="http"
employee_path="employees"
absence_path="absences"
log_format
- формат кодировки логов ("json", "console").
Пример лога в формате "json" и "console":
{"L":"INFO","T":"2023-11-29T15:24:59.674+0300","C":"app/app.go:61","M":"Initializing UserWebAPI..."}
2023-11-29T13:22:41.524+0300 INFO app/app.go:61 Initializing UserWebAPI...
log_level
- уровень логирования ("debug", "info", "warn", "error"). Заметьте, что если поднять log_level
до уровня "error", то логер пропустит сообщение уровня "info", так как все сообщения ниже установленного уровня игнорируются.
encoder_type
- определяет формат ключей у полей логов ("dev", "prod"), если установлен log_format
="json". Описание для dev и prod.
Пример лога типа "dev" и "prod":
{"L":"INFO","T":"2023-11-29T16:36:59.832+0300","C":"app/app.go:62","M":"Initializing UserWebAPI..."}
{"level":"info","ts":1701264983.1611516,"caller":"app/app.go:62","msg":"Initializing UserWebAPI..."}
output_path
/error_output_path
- списки путей к файлам для записи выходных логов и ошибок.
Пример конфигурации логера:
log_format="console"
log_level="debug"
encoder_type="dev"
output_path=[ "stdout", "./tmp/logs/rpc_traffic.txt" ]
error_output_path=[ "stderr", "./tmp/logs/rpc_traffic_errors.txt" ]
Usage
Для сборки проекта необходимо выполнить команду make
.
Для запуска сервиса необходимо выполнить команду make up
. Для запуска сервиса с тестовым сервером - make test-up
.
Для запуска unit-тестов необходимо выполнить команду make test
.
Для запуска линтера необходимо выполнить команду make linter
.
Examples
Для тестирования сервиса использовался Postman и тестовый сервер для имитации сторонней системы, который запускается с помощью флага -test-server
(именно это делается при выполнении команды make test-up
).
По заданию требовалось:
... по входящему gRPC запросу с информацией о пользователе по email найти того же пользователя на внешнем HTTP сервере, и обогатить входящее имя пользователя (ФИО) статусом отсутствия, если оно есть (в конец ФИО дописать emoji с соответствующим статусом).
Был описан интерфейс сервиса DataModifier
с Unary RPC методом AddAbsenceStatus
.
Добавление emoji, соответствующего статусу отсутствия, в поле display_name
Для добавления emoji, соответствующего статусу отсутствия, в поле display_name
необходимо передать информацию о пользователе (все поля обязательные и валидируются системой) и интервал времени (формат времени по умолчанию "2006-01-02T15:04:05"):
{
"user_data": {
"display_name": "Иванов Семен Петрович",
"email": "petrovich@mail.ru",
"mobile_phone": "+71234567890",
"work_phone": "1234"
},
"time_period": {
"date_from": "2022-07-01T00:00:00",
"date_to": "2022-09-01T23:59:59"
}
}
Пример ответа:
{
"modified_user_data": {
"display_name": "Иванов Семен Петрович 🏠",
"email": "petrovich@mail.ru",
"mobile_phone": "+71234567890",
"work_phone": "1234"
}
}
Правила валидации для каждой сущности определены в соответствующих методах (метод Validate для структуры пользователя).
display_name
- обязательное поле, должно состоять только из букв unicode, может содержать составные имена/фамилии (например, Иванов-Сидоров).
email
- обязательное поле, должно быть валидной электронной почтой.
mobile_phone
- обязательное поле, должно состоять только из символов [0-9], может начинаться на "+", длина от 10 до 12 символов.
work_phone
- обязательное поле, должно состоять только из символов [0-9], может начинаться на "+", длина от 1 до 12 символов.
Decisions
Чтобы организовать очередь запросов, можно использовать буферизированный канал, как показано здесь. Либо можно ограничить число подключений на уровне слушателя с помощью netutil.LimitListener.
Чтобы организовать воркер-пул, можно использовать цикл горутин, считывающих задачи из общего канала, как показано здесь. Либо можно задать количество воркеров для gRPC сервера из пакета grpc с помощью экспериментальной функции NumStreamWorkers.
Подходит для Unary RPC запросов (под капотом в исходном коде)
Заходим в grpc-go/server.go. Находим функцию Serve
, которая на каждое новое соединение запускает горутину handleRowConn
. handleRowConn
запускает горутину, в которой последовательно вызываются методы serveStreams
и removeConn
. В serveStreams
вызывается HandleStreams
, в которой в канал s.serverWorkerChannel
передается функция с методом handleStream
.
Далее в функции serverWorker
эта функция считывается из канала и вызывается. Если сервис и метод существуют, handleStream
вызывает метод processUnaryRPC
или processStreamingRPC
.
Commands
Использованные команды
go get github.com/BurntSushi/toml
go get google.golang.org/grpc
go get google.golang.org/protobuf
go get golang.org/x/net
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.55.2
protoc --proto_path=api/proto --go_out=pkg --go-grpc_out=pkg api/proto/datamodifier.proto
go get go.uber.org/zap
go get github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/recovery
go get github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/logging
go get github.com/go-ozzo/ozzo-validation
go get github.com/go-ozzo/ozzo-validation/is
go get github.com/stretchr/testify
go install go.uber.org/mock/mockgen
go get go.uber.org/mock/mockgen
mockgen -source=./internal/webapi/interfaces.go -destination=./internal/webapi/webapi_mocks.go -package=webapi
go get github.com/gorilla/mux
Полезные ссылки