Это сервис для динамического назначения пользователей сегментам в рамках проекта для отбора в Avito. Все текстовые значения, передаваемые сервису не должны превышать длину строки 100 (пустые строки запрещены). Массивы значений должны содержать не повторяющиеся значения. Для выполнения этих условий организована валидация. Сервис принимает запросы как с помощью gRPC, так и с помощью HTTP/JSON, в нём частично реализована идея model-view-control, однако слой-адаптер был исключен ввиду простоты выполняемых операций. Так как в задании, выданном авито операция модификации сегментов, привязанных к пользователю обозначена не атомарной (одновременно и добавление и удаление сегментов), была применена оболочка, позволяющая превращать операции в транзакции (в методе ModifySegments service-слоя). К сожалению, на юнит-тесты не хватило времени
Дополнительное задание 1. Было реализовано с помощью хранения времени добавления записи о связи пользователя и сегмента в таблицу-связку (многие-ко-многим). При этом при отвязке сегмента от пользователя сменяется флаг и запись превращается в архивную (id клиента, id сегмента, время создания связи, время ее удаления, статус). При удалении пользователя или сегмента, связанные с ним записи в таблице-связке каскадно удаляются. Задание выполнено частично – поток байтов сериализованного csv можно получить только на gRPC клиенте, так как обработчик http позволяет отправлять ответы только с content-type application/json
Дополнительное задание 2. Было реализовано с помощью поля, отвечающего за время удаления (в случае операции удаления в это поле записывается текущее время, а в случае назначения Time-To-Live, используется отдельный метод, который назначает время отвязки сегмента от пользователя). Процесс исполнения этой отвязки вне рамок задания. Тесты и прочие возможности, которые не получилось добавить в срок, находятся в ветке late-features.
Для работы сервиса минимально необходим Docker а также Linux-система, либо WSL для запуска сервиса в контейнере.
Для сборки сервиса на базе данного репозитория нужны следующие утилиты:
Параметры соединения с базой данных находятся в файле config.yml, а также в docker-compose. При изменении конфигурационного файла, необходимо также внести изменения в файл docker-compose. При локальном запуске сервиса имя хоста базы данных localhost, в случае контейнеризованного запуска -- postgres. Для запуска сервиса нужно ввести следующие команды:
git clone https://github.com/nikitads9/segment-service-api.git
cd segment-service-api/
make deps
make vendor-proto
make generate
docker-compose up -d
make deps
команда устанавливает зависимости для работы проекта.make vendor-proto
скачивает необходимые для работы protobuf и validate структур пакеты. После выполнения этой команды в корне проекта появится папка со всеми необходимыми.proto
файлами.make generate
создает:grpc.pb.go
,pb.go
,pb.gw.go
иpb.validate.go
для сервисов user и segment на основании контрактов, описанных в user_v1.proto и segment_v1.proto. Эти файлы содержат структуры, интерфейсы и методы для работы API, сгенерированные с помощью Protobuffer.docker-compose up -d
скачивает образ alpine3.15 с DockerHub (если он еще не загружен), собирает исполняемый файл и два контейнера: один для серверного приложения с API, а второй для непосредственно сервера базы данных. Оба контейнера организуются в связанную сеть докер, которая позволяет им обращаться друг к другу по имени.
Этот сервис частично реализует концепцию CRUD. С его помощью возможно создавать и удалять сегменты и пользователей, а также назначать сегменты пользователям и прекращать участие пользователей в сегментах. При прекращении связи пользователя и сегмента запись об участии пользователя в сегменте сохраняется с флагом state false
и пометкой о времени удаления из сегмента time_of_expire. При удалении сегмента, записи о входящих в него пользователях полностью удаляются. Инструкция, размещенная ниже, предназначена для запросов HTTP+JSON в RESTful API стиле. Для тестирования gRPC клиента см. контракты в файлах segment.proto и user.proto.
1. метод AddUser
POST host:port/user/add-user
Объект JSON, передаваемый этому методу должен выглядеть так:
{
"user_name": "user1"
}
Метод возвращает объект JSON с вложенным id добавленного пользователя
{
"id": "1"
}
2. метод GetSegments
GET host:port/user/get-segments/{id}
Этот метод не нуждается в JSON объекте. Вместо этого требуется id сегмента.
Метод возвращает JSON с массивом названий сегментов:
{
"slugs": [
"segment1"
]
}
3. метод ModifySegments
PATCH host:port/user/modify-segments
Объект JSON, передаваемый этому методу должен выглядеть так:
{
"id": "1",
"slug_to_add": [
"segment1"
],
"slug_to_remove": [
"segment2"
]
}
Возвращаемое значение должно выглядеть как пустой объект JSON.
4. метод RemoveUser
DELETE host:port/user/remove-user/{id}
У запроса нет тела. Возвращаемое значение должно выглядеть как пустой объект JSON.
5. метод SetExpireTime
POST host:port/user/set-expire-time
Объект JSON, передаваемый этому методу должен выглядеть так:
{
"id": "1",
"slug": "segment1",
"expiration_time": "2023-08-31T09:47:57.917Z"
}
Возвращаемое значение должно выглядеть как пустой объект JSON.
6. метод AddSegment
POST host:port/segment/add-segment
Объект JSON, передаваемый этому методу должен выглядеть так:
{
"slug": "segment1"
}
Метод возвращает id добавленного сегмента.
{
"id": "1"
}
7. метод RemoveSegment
DELETE host:port/segment/remove-segment/{id}
У запроса нет тела. Возвращаемое значение должно выглядеть как пустой объект JSON.
8. метод GetUserHistoryCsv
GET host:port/user/download-history/{id}
У запроса нет тела, передается только id пользователя. В ответ приходит такая структура, в которой содержится массив байтов csv файла с историей сегментов пользователя:
"result": {
"chunk": "string"
}
}