Сервис реализован на Go. Общение клиентов с сервисом происходит по протоколу gRPC посредством protobuf-месаджей. Доступны следующие gRPC - вызовы:
Стандартные CRUD операции c конфигом:
CreateConfig(Config)
GetConfig(Service)
UpdateConfig(Config)
DeleteConfig(Service)
Доступ к архивным версиям конфига:
GetArchivedConfig(Timestamp)
ListConfigTimestamps(Service)
Управление подпиской клиентов на конфигурации (с целью запрета удаления конфигов на которые имеются подписки):
SubscribeClientApp(SubscriptionRequest)
UnSubscribeClientApp(SubscriptionRequest)
ListConfigSubscribers(Service)
Хранимые конфигурации приложений представляют собой простые списки пар ключ-значение типа string
.
Идентификация конфигов сервисом происходит по уникальному имени конфига.
Для удобства обновления клиентом конфигов, апдейт происходит в режиме слияния - т.е. чтобы изменить имеющийся конфиг, достаточно передать сервису только те пары ключ-значение у которых хотим поменять значения (либо же вообще новые пары добавляем), остальные ранее хранящиеся в конфиге пары останутся нетронутыми и будут неотьемлемой частью обновлённого конфига. Если же хочется вообще удалить какую-то прежнюю пару ключ-значение из конфига - достаточно присвоить ей значение пустой строки (!не пробела).
Пока конфиг не удалён - можно запросить любую его прежнюю версию из истории изменений (по таймстемпу): GetArchivedConfig(Timestamp)
.
Список таких таймстемпов для конкретного конфига можно запросить с помощью ListConfigTimestamps(Service)
.
Конфиг невозможно удалить пока на него подписано хоть одно приложение.
Приложение можно подписать/отписать соответствующими gRPC вызовами.
Просмотреть список подписанных на конфиг приложений можно вызовом ListConfigSubscribers(Service)
.
Пример работы с сервисом:
package main
import (
"context"
"fmt"
"log"
"google.golang.org/grpc"
pb "distributed-cfg-service-mk/proto"
)
func main() {
conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure())
if err != nil {
log.Fatalf("Did not connect: %v", err)
}
defer conn.Close()
c := pb.NewDistributedCfgServiceMKClient(conn)
service := "Service number one"
params := []*pb.Parameter{
{Key: "Tcp port", Value: "80"},
{Key: "Memory limit", Value: "2gb"},
{Key: "Root dir", Value: "/root"},
}
resp, err := c.CreateConfig(context.Background(), &pb.Config{Service: service, Parameters: params})
if err != nil {
fmt.Println(err)
} else {
fmt.Println(resp.Service)
fmt.Println(resp.Timestamp.AsTime())
}
resp2, err := c.GetConfig(
context.Background(),
&pb.Service{Name: service},
)
if err != nil {
fmt.Println(err)
} else {
fmt.Println(resp2.Service)
fmt.Println(resp2.Parameters)
}
В файле client/main.go
можно посмотреть дополнительные примеры работы клиента с сервисом.
Код самого сервиса находится в файле server/main.go
плюс protobuf-сгенерённые файлы в директории proto/*.pb.go
.
С помощью приложенного докер-компоуз файла можно оперативно запустить тестовый экземпляр сервиса скомпонованного с БД и админкой для БД (соответствующий докер-имидж уже выложен на Докерхабе, если что), но докер-имидж можно собрать и из приложенного Dockerfile, он вполне рабочий.
В академических целях исходим из предположения, что работа сервиса происходит в стерильных условиях, все друг-другу доверяют, поэтому все что касается безопасности - авторизаця, шифрование канала, валидация и т.п. исключаем (ну разве что немного валидации имеется чтобы не поломать логику хранилища).
Хранилище реализовано на БД PostgreSQL, через ORM-прослойку gorm.
Версионирование конфига реализовано посредством поля updated_at
с соотв таймстемпом в записях в таблицах БД.
- Самое главное - конечно же адекватное тестирование, будь то полноценные юнит тесты или хотя бы просто сравнение по набору got/want. То что сейчас есть, это просто несерьезно.
- Лаконичная организация кода по файлам/функциям/методам.
- Абстракция работы с хранилищем.
- Совершенно точно имеются баги, тесты не успел, ну и вот...