Сервис динамического сегментирования пользователей
Содержание
Данная программа содержит реализацию сервиса динамического сегментирования пользователей.
Ниже приведены основные моменты данной реализации:
-
Реализовано на Golang;
-
Есть возможность экспорта истории добавления\удаления сегментов у пользователей;
-
Есть поддержка назначения TTL определенному сегменту у пользователя;
-
Используются следующие библиотеки:
2.1. https://github.com/georgysavva/scany -- Работа с БД;
2.2. https://github.com/jackc/pgconn -- Работа с БД;
2.3. https://github.com/jackc/pgx/ -- Работа с БД;
2.4. https://pkg.go.dev/go.uber.org/mock -- Создание stub'ов и mock'ов для тестов.
-
docker-compose для поднятия и развертывания среды;
-
CI на GitHub Actions и тесты для проверки главного функционала.
Структура проекта выглядит следующим образом:
├── .github
| └── workflows
| └── tests.yml - Файл конфигурации CI
├── build
| └── Dockerfile - Dockerfile для создания образа приложения
├── docs
| └── history.csv - CSV файл, содержащий в себе историю по операциям
├── cmd
| └── main.go - Запускающий файл
├── internal
| ├── app
| | ├── config.go - Конфигурация сервера
| | ├── server.go - Методы для запуска сервера и обработки запросов
| | └── server_test.go - Тесты для проверки работоспособности
| └── pkg
| ├── db
| | ├── mocks
| | | └── mock.go - Авто-генерация моков для интерфейса DBops
| | ├── sql
| | | └── init.sql - Файл для создания таблиц в базе данных
| | ├── client.go - Создание экземпляра PostgreSQL
| | └── database.go - Методы по работе с базой данных
| ├── model
| | ├── segment.go - Структуры для работы с JSON
| | └── user_segment.go - Структуры для работы с JSON
| ├── repository
| | ├── postgresql
| | | ├── segments.go - Реализация паттерна репозиторий для PostgreSQL
| | | └── user_segment.go - Реализация паттерна репозиторий для PostgreSQL
| | ├── repository.go - Паттерн репозиторий
| | └── structs.go - Структуры для базы данных
| └── service
| ├── db
| | ├── mocks
| | | └── mock.go - Авто-генерация моков для интерфейса Service
| | ├── postgres.go - Реализация бизнес-логики для PostgreSQL
| | └── service.go - Интерфес для работы бизнес-логики
| └── history.go
| ├── csv.go - Реализация модуля ведения истории для формата CSV
| └── service.go - Интерфес для работы c модулем истории
├── .gitignore
├── docker-compose.yml - Для поднятия в Docker'е
├── go.mod
├── Makefile
└── README.md
Подразумевается, что ID пользователя нам известен заранее и подается из вне как запрос на этот сервис.
Таблица segments хранит в себе в качестве ключа название сегмента и его описание. Описание может быть пустым.
Таблица user_segments хранит в себе в качестве ключа id записи в этой таблице, user_id хранит в себе уникальный идентификатор пользователя (который мы знаем заранее), seg_title хранит в себе название сегмента, в котором состоит пользователь. seg_title не является FK! Данная зависимость реализована напрямую из кода, дабы избежать ссылок в самих таблицах.
Таким образом, чтение, добавление и удаление сегментов у пользователя реализовано по следующему алгоритму:
- Если данный ID пользователя имеется в таблице user_segments, то будут выведены все сегменты из данной таблицы, где поле user_id равняется нашему запрашиваемому;
- Если данный ID пользователя в таблице НЕ имеется, то соответственно количество найденных строк будет 0. Поэтому, в качестве ответа, вернется сообщение о том, что данный пользователь не состоит ни в одном сегменте;
- Если при добавлении пользователю перечислены несуществующие в таблице segments названия сегментов, то записи не будут созданы в таблице user_segments, а в качестве ответа вернется массив элементов, которые не были добавлены из-за этого несоответсвия. В остальном в таблицу будут добавлены новые записи, содержащие в себе ID пользователя и сегмент (и так для каждого из перечисленных существующих сегментов);
- Если при удалении у пользователя перечислены несуществующие в таблице user_segments названия сегментов, то подходящие записи не найдутся, а следовательно, в качестве ответа, будет возвращен массив элементов, которые удалить не удалось. В остальном у данного пользователя будут убраны соотвествующие сегменты из данной таблицы.
При выполнении операции добавления\удаления в файле (docs/history.csv) автоматически будут появляться записи в формате CSV. Чтобы посмотреть данный файл, нужно перейти по адресу: localhost:3001/docs/history.csv (если запущено в Docker).
Пример вывода:
Для взаимодействия с сервером есть 4 способа:
-
/create-segment -- создать сегмент. Принимает JSON в качестве тела запроса;
-
/delete-segment -- удалить сегмент. Принимает JSON в качестве тела запроса;
-
/user-in-segment -- выполнить операции создания и\или удаления для определенного пользователя. Принимает JSON в качестве тела запроса;
3.1. Поле "ttl" передается как массив int-ов, Так, элемент на позиции i будет назначен элементу на позиции i в массиве "seg_titles_to_add". Если значение 0, тогда назначение TTL игнорируется. Передается в секундах.
-
/get-user-segments -- получить все сегменты, в которых состоит пользователь. Принимает JSON в качестве тела запроса.
В качестве ответа возвращается JSON, который содержит всегда поле "status" и дополнительные.
Сами структуры запросов и ответов в этих файлах:
- https://github.com/Icerzack/avito-backend-internship/blob/main/internal/pkg/model/segment.go
- https://github.com/Icerzack/avito-backend-internship/blob/main/internal/pkg/model/user_segment.go
Примеры запросов и ответов в Postman:
- Создать сегмент (корректный запрос):
- Удалить сегмент (корректный запрос):
- Создать сегмент (некорректный запрос, неправильное наименование полей):
- Добавить пользователя в сегмент (корректный запрос):
- Добавить пользователя в сегмент (корректный запрос, но некоторые названия не существуют):
- Удалить пользователя из сегмента (корректный запрос):
- Удалить пользователя из сегмента (корректный запрос, но некоторые названия не существуют):
- Одновременное создание и удаление (корректный запрос, но некоторые названия не существуют):
- Создать сегменты с временем жизни (корректный запрос)
- Получить сегменты пользователя (корректный запрос):
- Получить сегменты пользователя (корректный запрос, но данный пользователь не содержит сегментов):
Проект содержит Makefile, который имеет следующий вид:
.PHONY: compose-db-up
compose-db-up:
docker-compose build
docker-compose up -d postgres
.PHONY: compose-db-rm
compose-db-rm:
docker-compose down
.PHONY: compose-app-up
compose-app-up:
docker build -f build/Dockerfile -t avito-app .
docker-compose up -d avito-app
.PHONY: compose-app-rm
compose-app-rm:
docker-compose down
.PHONY: compose-all-up
compose-all-up: compose-db-up compose-app-up
.PHONY: compose-all-rm
compose-all-rm: compose-app-rm compose-db-rm
.PHONY: run-tests
run-tests:
go test -v avito-backend-internship/internal/app
Для запуска конкретно одного из компонентов приложения нужно, например, прописать следующее:
$ make compose-db-up
Для поднятие всего целиком:
$ make compose-all-up
Для тестирования использовалась библиотека gomock. С ее помощью были созданы моковые реализации БД и Сервиса на основании их интерфейсов. Данные реализации применяются в файле (internal/app/handlers_test.go), который тестирует главный функционал приложения (корректная обработка URL и добавление в БД).
В проекте был настроен простой CI для запуска кода на тестах. Файл конфигурации может быть найден по следующему пути: (.github/workflows/tests.yml) и имеет следующий вид:
name: tests
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.19
- name: Tests
run: go test -v avito-backend-internship/internal/app
(К началу)