Skip to content

Viltonhoy/http-avito-test

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Go Report Card

Тестовое задание на позицию стажер-бекенд. Микросервис для работы с балансом пользователей.

Ссылка на тестовое задание

Данный HTTP API микросервис предназначен для работы с балансом пользователей (зачисление и списание средств, перевод средств в любой мировой валюте от пользователя к пользователю, а также для получения информации о балансе пользователя и истории всех транзакций).

Важные заметки:

  • .env файл был оставлен в репозитории для удобста проверки тестового задания;
  • В .env файле предоставлены данные по сервису, БД и exchangertestapi;
  • Swagger файл лежит в директории api/schema.yaml

План:

  • Выбрать базу данных из имеющихся для хранения и работы со счетом пользователя;
  • Подобрать и реализовать способ работы с банковским счетом пользователя (где и каким образом хранить средства);
  • Реализовать базовые методы зачисления, списания и перевода средств;
  • Организовать работу с удаленным API калькулятором валют;
  • Реализовать метод вывода истории переводов пользователя, включая идентификатор пользователя, тип перевода, сумму, дату и время, потенциального получателя и описание;
  • Изучить и использовать docker и docker-compose;
  • Написать unit/интеграционные тесты;
  • Выполнить нагрузочное тестирование микросервиса;
  • Написать генератор рандомных записей для roll-up таблицы;
  • Реализовать методы резервирования, разрезервирования средств и признания выручки;
  • Реализовать метод составления финансового месячного отчета;
  • Написать файловый сервер для предоставления ссылки на директорию с CSV файлом;

Для работы с базой данных была выбрана реляционная СУБД PostgreSQL. Средсвта пользователя хранятся представлены целочисленным значением (в копейках). Изначально такой формат был выбран для работы с балансом пользователя с целью избежать округления. В дальнейшем, для работы с финансами, используется тип с фиксированной точкой Decimal, что позволяет избавиться от необходимости хранить значения в целочисленном формате.

Roll-up таблица:

Для решения проблемы пересчета баланса пользователей использовалось материализованное или обычное представление, которое можно обновлять в случае необходимости. Однако при большом количестве записей скорость и эффективность данного способа снижалась, из-за постоянного пересчета всех записей из основной таблицы. На замену была выбрана roll-up таблица, которая вставляет новую строку и обновляет существующие строки в случае конфликта ограничений. Данный вариант требует меньшее количество запросов, а так же не требует постоянного пересчета записей.

posting:

tx_id amount account_id type addressee
1 1000 2 deposit null
2 -1000 0 deposit null
3 1000 1 deposit null
4 -1000 0 deposit null
5 -100 2 withdrawal null
6 100 0 withdrawal null
7 -100 1 transfer 3
8 100 3 transfer 1
9 -100 3 withdrawal null
10 100 0 withdrawal null
11 -100 1 withdrawal null
11 100 0 withdrawal null

balances с roll-up:

перед выполнением операции tx_id = 3

account_id balance last_tx_id
1 1000 -> 800 3 -> 11
2 900 5
3 100 8

Итог: 2 обновления

balances без roll-up:

перед выполнением операции tx_id = 3

account_id balance
1 1000 -> 900 -> 800
2 100 -> 900
3 100 -> 0

Итог: 4 обновления

Так же, для проверки эффективности работы roll-up таблицы, были проведены нагрузочные тесты.

На графике зависимости задержки (в миллисекундах) от процентиля наглядно предоставлены преимущества работы roll-up таблицы над своими конкурентами.

Для более подробного ознакомления - https://stefan-poeltl.medium.com/views-v-s-materialized-views-v-s-rollup-tables-with-postgresql-2b3824b45330

Тип с фиксированной точкой:

Тип с фиксированной точкой Decimal позволяет работать с дяситичными и целочисленными значениями. Данный тип был вабран для работы с финансами, чтобы избежать окргуления и потери "лишней" копейки.

Функционал decimal:

  • Нулевое значение равно 0, и его можно безопасно использовать без инициализации;
  • Сложение, вычитание, умножение без потери точности;
  • Деление с заданной точностью;
  • Сериализация/десериализация базы данных/sql;

Ссылка на подробную документацию типа Decimal - https://pkg.go.dev/github.com/shopspring/decimal

Двойная запись:

Для работы с банковским счетом пользователя был выбран способ двойной бухгалтерской записи. Особенность данного способа заключается в том, что в системе с "двоичной записью" каждое значение записывается дважды - как кредит и дебет (положительное и отрицательное значение).

Данная запись имеет набор правил:

  • Каждая запись в системе должна быть сбалансированной, т.е. сумма всех значений в рамках одной операции должна давать ноль;
  • Сумма всех значений во всей системе в любой момент времени должна давать ноль (правило т.н. "пробного баланса");
  • Уже занесенные в БД значения нельзя редактировать или удалять. При необходимости исправлений операция сперва должна быть отменена другой операцией с противоположным знаком, а затем повторена с правильным значением. Это позволяет реализовать надежный аудиторский след (полный лог всех транзакций, часто требуемый при проверках);

Преимущество такой записи над "единичной записью":

  • Отсутствие возможности редактирования и удаления записей, что позволяет контролировать историю записей, не боясь каких либо изменений извне;
  • Возможность построить очень комплексные системы контроля ценностями;
  • Контьроль всей истории транзакций, возможность разбить все записи по периодам (месяц, год, рабочий квартал);

Для более подробного ознакомления предоставляю ссылки на статьи:

Резервирование, разрезервирование средств и признание выручки

Резервирование

По правилам бухгалтерии резервирование средств производится на 97-й счет (расходы будущих периодов). За резервный 97-й счет был взят account_id = 1. При покупке услуги пользователем деньги переводятся на нулевой аккаунт (внутри метода Reservation вызывается вложенный метод Transfer), запись о переводе добавляется в основную таблицу posting. В таблице deferred_expenses (отложенные покупки) фиксируется запись о покупке услуги со статусом 'reservation'.

Разрезервирование

При невыполнении услуги (отсутствие записи выполненной услуги в таблице consolidated_report) или ее отмене производится разрезервирование средств с резервного счета на счет пользователя. Запись о переводе средств фиксируется в основной таблицу posting (внутри метода Unreservation вызывается вложенный метод Transfer), в таблице deferred_expenses (отложенные покупки) фиксируется запись о разрезервации со статусом 'unreservation'.

Признание выручки

При выполнении услуги (отсутствие записи о разрезервировании средств в таблице deferred_expenses) компания переводит деньги за ее выполнение с резервного счета на счет компании (account_id = 0). Может производиться как частичное, так и полное снятие средств за выполненную услугу, в зависимости от условий. Например, компания сама предоставляет выполнение услуги или через посредника. Во втором случае компания снимает только свой процент, а оставшиеся деньги остаются в резерве для дальнейшего перевода посреднику. Запись о переводе средств фиксируется в основной таблицу posting (внутри метода Revenue вызывается вложенный метод Transfer), в таблице consolidated_report (сводный отчет) фиксируется запись о начислении денег на счет компании.

Месячный (бухгалтерский) отчет

В данной реализации была выбрана общая система налогообложения (выручка компании считается по факту выполненных работ).

Формирование месячного отчета

При формировании месячного отчета происходит считывание данных из таблицы consolidated_report (сводный отчет) и подсчет общей выручки для каждой выполненной услуги. Файловый сервер предоставляет ссылку на директорию, где месячный отчет в формате CSV.

Генератор рандомных записей для таблицы postgres:

Для удобных нагрузочных и интеграционных тестов был написан генератор записей для roll-up таблицы.

Пример запуска:

 go run .go 100 200000       

На вход дается количество пользователей (id пользователя от 2 до n) и записей (суммарное колличество записей, добавляемых в таблицу);

Запуск локально с помощью Docker:

  1. Убедитесь, что у вас самые последние образы контейнеров Docker:
docker-compose -f ./deployments/docker-compose.yaml pull
  1. Запустите службу в локальном Docker:
docker-compose -f ./deployments/docker-compose.yaml up

Справочник по выполнению запросов:

Для выполнения запросов к сервису использовался HTTP-клиент Postman;

  1. deposit:
  • тип запроса: POST;
  • URL запроса: http://localhost:9090/deposit;
  • Пример запроса:
{"User_id":2, "Amount":1000}
  1. withdrawal:
  • тип запроса: POST;
  • URL запроса: http://localhost:9090/withdrawal;
  • Пример запроса:
{"User_id":2, "Amount":1000, "Description":"test"}
  1. transfer:
  • тип запроса: POST;
  • URL запроса: http://localhost:9090/transf;
  • Пример запроса:
{"Sender":2, "Recipient":2, "Amount":1000, "Description":"test"}
  1. readUser:
  • тип запроса: POST;
  • URL запроса: http://localhost:9090/read;
  • Пример запроса:
{"User_id":2, "Currency":"EUR"} или {"User_id":2} 
  1. readUserHistory:
  • тип запроса: POST;
  • URL запроса: http://localhost:9090/history;
  • Пример запроса:
{"User_id":2, "order":"date", "limit":100, "offset":0}
  1. reservationOfFunds:
  • тип запроса: POST;
  • URL запроса: http://localhost:9090/reserve;
  • Пример запроса:
{user_id":2, "service_id":2, "order_id":2, "price":100}
  1. unreservationOfFunds:
  • тип запроса: POST;
  • URL запроса: http://localhost:9090/unreserve;
  • Пример запроса:
{user_id":2, "service_id":2, "order_id":2}
  1. RevenueRecognition:
  • тип запроса: POST;
  • URL запроса: http://localhost:9090/revenue;
  • Пример запроса:
{"user_id":2, "service_id":2, "order_id":2, "sum":100.00}
  1. MonthlyReport:
  • тип запроса: POST;
  • URL запроса: http://localhost:9090/report;
  • Пример запроса:
{"year":2022, "month":10}

Список вопросов и проблем:

  1. Получение баланса пользователя из таблицы с двойной записью;
  • Для получения баланса решено было использовать Roll-up таблицу;
  1. Генерация объемного количества данных в таблице для тестирования, sql запрос не мог генерировать большое количество данных за раз, но возвращал положительный ответ;
  • Был написан отдельный генератор значений на go;
  1. Корректная работа с docker в wsl 2. Проблема запуска docker на системе windows 10 pro;
  2. Первые коммиты не имели в себе нормального описания;
  • Было принято решение не трогать старые коммиты, так как их изменения могли привести к проблемам. Последующие коммиты имеют в себе полное описание изменений в проекте;
  1. Проблема с уровнем изоляции транзакций postgres, при параллельном выполнении несколько сериализуемых транзакций, результат фиксации успешной транзакции оказывается несогласованным (аномалия сериализации);

  • При начале транзакции устанавливается уровень изоляции транзакции - Serializable (Сериализуемость), при котором невозможно "грязное чтение", неповторяемое чтение, фантомное чтение и аномалия сериализации. Данное ограничение имеет свое влияние на производительность приложения, но оно не критично, при условии возможности конкурентно выполнять несколько сериализуемых транзакций;
  1. Ошибка с вложенными транзакциями. При выполнении метода разрезервирования или получения выручки при возникновении ошибки, записи из основной таблицы posting не удалялись;
  • Причиной ошибки был вложенный метод Transfer, вызов которого происходил вне транзакции. Были прописаны и учтены условия вызова метода в и вне транзакции.
  1. При выполении методов разрезервирования или признания выручки иногда может возникать зависание запроса (зпрос не выполняется);
  • На данный момент проблема решается. Предположительно проблема связаны с завершениями транзакции. Вторая транзакция пытается приступить к выполнению, пока вторая еще не закончила свое выполнение;
  1. (Недочет) При записи CSV файла с отчетом пользователь может получить устаревший отчет если, при выполнении метода MonthlyReport была произведена покупка. При повторном запросе на получение отчета новый отчет перепишет старый.
  • Можно использовать полную дату в имени файла или 1-й байт хеш-суммы

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages