Skip to content
This repository has been archived by the owner on Jan 19, 2023. It is now read-only.

Simple app to perform financial operations with account balance safely written in Go lang and postgres

Notifications You must be signed in to change notification settings

CrissNamon/go-balance-server

Repository files navigation

Задача

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

Запуск

Для развертывания dev-среды в Docker перейдите в папку с проектом и выполните:

docker-compose up --build

В результате будет создано Docker приложение с 2-мя контейнерами:

  • Postgres с созданными БД (Основная и тестовая)
  • Контейнер с приложением

При запуске приложения будут выполнены тесты.

Запросы

Приложение принимает и отдает запросы и ответы в формате JSON. В репозитории содержится файл balance-server.postman_collection.json - коллекция с запросами для Postman.

Ответ сервиса всегда содержит следующие поля:

  • status (Статус запроса, целое положительное число)
    • 0 = Запрос успешно выполнен
    • Не 0 = Произошла ошибка
  • data (Ответ сервиса)
    • Может быть любого типа в зависимости от запроса

Сервис принимает запросы на endpoint'ы:

  • GET /balance (Баланс пользователя)
    • Обязательные

      • id (ID пользователя, целое число > 0)
    • Необязательные

      • currency (Валюта для конвертации, код из 3 символов)
    • Пример запроса

      {
          "id": 1, 
          "currency": "USD"
      }
    • Пример ответа

      {
          "status": 0,
          "data": 1060
      }
  • POST /transaction (Списание/Зачисление средств)
    • Обязательные

      • id (ID пользователя для списания/зачисления)
      • sum (Сумма платежа, вещественное число. Если отрицательное -> списание, положительное -> зачисление)
    • Необязательные

      • desc (Описание платежа, текст)
    • Пример запроса

      {
          "id": 1, 
          "sum": 51.7, 
          "desc": "Random income"
      }
    • Пример ответа

      {
          "status": 0,
          "data": "Transaction completed"
      }
  • POST /transfer (Перевод средств между пользователями)
    • Обязательные

      • id (ID пользователя для списания)
      • to (ID пользователя для зачисления)
      • sum (Сумма платежа, вещественное положительное число)
    • Пример запроса

      {
          "id": 1, 
          "sum": 27.43, 
          "to": 2
      }
    • Пример ответа

      {
          "status": 0,
          "data": "Transfer completed"
      }
  • GET /transactions (История транзакций)
    • Обязательные

      • id (ID пользователя)
    • Необязательные

      • sort (Сортировка, текст)
        • По умолчанию по дате
        • sum (По сумме транзакции)
        • date (По дате транзакции)
      • from (Указатель на транзакцию для вывода)
        • Без указания или с 0 выводит с первой транзакции
        • Указатель на следующую страницу передается в теле ответа в параметре next, который необходимо использовать в качестве from для загрузки следующих транзакций
        • Если параметр next в ответе = -1, то получена последняя страница
      • start (Время в формате Unix timestamp)
        • Выводит транзакции, произведенные после указанной даты
        • По умолчанию - 0
      • end (Время в формате Unix timestamp)
        • Выводит транзакции, произведенные до указанной даты
        • По умолчанию - текущая дата
    • Пример запроса

      {
          "id": 1, 
          "sort": "sum", 
          "from": 0, 
          "start": 1637602965, 
          "end": 1669138965
      }
    • Пример овтета

      {
          "status": 0,
          "data": {
              "next": 6,
              "transactions": [
                  {
                      "date": "2021-12-24T19:17:30Z",
                      "desc":"Random income",
                      "operation": 0,
                      "sum": 14.53
                  },
                  {
                      "date": "2021-12-24T19:21:20Z",
                      "desc": "Random income",
                      "operation": 1,
                      "sum": -0.83
                   }
               ]
           }
       }

Решенные проблемы

База данных

Работа с балансом: Баланс пользователя не хранится в явном виде. Вместо этого хранятся только произведенные операции. Это позволяет избежать проблем с одновременным доступом. Все операции с балансом выполняются в транзакциях с использованием рекомендательных блокировок (advisory lock). В каждой новой транзакции выполняется блокировка на идентификатор пользователя и код операции и автоматически снимается по завершению транзакции. Во время активной блокировки другие транзакции не смогут получить блокировку на тот же идентификатор и операцию. Таким образом баланс всегда будет поддерживаться в валидном состоянии, а транзакции для разных пользователей и операций не будут блокировать друг друга. На ожидание блокировки выделено 10 сек, если по истечении этого времени транзакция не сможет получить блокировку, то клиенту вернется HTTP 408 с сообщением о таймауте.

Сортировка и пагинация: Для быстрого отображения транзакций отсортированных по сумме с пагинацией, создается materialized view transactions_sum_order хранящее сортировку транзакций по сумме. Представление хранит только id транзакции и позицию транзакции в сортировке. Для вывода транзакций с пагинацией выполняется запрос к представлению для получения id транзакций. Все данные о выбранных транзакциях получаются с помощью INNER JOIN с оригинальной таблицей. Это позволяет избежать использования OFFSET, производительность которого падает с количеством данных в таблице. Представление обновляется каждые 3 минуты с помощью планировщика задач в main.go Обновление представления на 5 млн строк занимает ~30 сек. Любой запрос транзакций с пагинацией на любой странице выполняется за < 100 ms. В тоже время OFFSET на оригинальной таблице с сортировкой по сумме на 4999990 записей выполняется за > 4 секунд.

Для таблицы с транзакциями и представления созданы индексы для ускорения запросов.

Реализация

Структура

  • server
    • содержит основной код контроллера, сервиса и репозитория
  • test
    • содержит код для тестов
  • sql/init.sql
    • содержит код для создания БД

Библиотеки и фреймворки

  • Web фреймворк - gin-gonic
  • Работа с Postgres через драйвер pgx
  • Assertions для тестов из testify
  • Для быстрого развертывания dev среды используется Docker с Compile Daemon

About

Simple app to perform financial operations with account balance safely written in Go lang and postgres

Topics

Resources

Stars

Watchers

Forks