Skip to content

Latest commit

 

History

History
365 lines (290 loc) · 18.7 KB

File metadata and controls

365 lines (290 loc) · 18.7 KB

Проект "Обмен валют"

REST API для описания валют и обменных курсов. Позволяет просматривать и редактировать списки валют и обменных курсов, и совершать расчёт конвертации произвольных сумм из одной валюты в другую.

Веб-интерфейс для проекта не подразумевается.

Комментарии по проекту - https://www.youtube.com/watch?v=013b_b7PszM (видео записано для Java версии роадмапа).

Что нужно знать

  • [Python]({{ site.baseurl }}/Technologies/Python/) - коллекции, ООП
  • [Паттерн MVC(S)]({{ site.baseurl }}/Technologies/Python/#mvc)
  • [Backend]({{ site.baseurl }}/Technologies/Backend/)
    • http.server
    • HTTP - GET и POST запросы, коды ответа
    • REST API, JSON
  • [Базы данных]({{ site.baseurl }}/Technologies/Databases/) - sqlite3
  • [Деплой]({{ site.baseurl }}/Technologies/DevOps/#деплой) - облачный хостинг, командная строка Linux

Фреймворки не используем.

Мотивация проекта

  • Знакомство с MVC
  • REST API - правильное именование ресурсов, использование HTTP кодов ответа
  • SQL - базовый синтаксис, создание таблиц

База данных

В качестве базы данных предлагаю использовать SQLite. Это позволит встроить в ресурсы проекта файл с заполненными таблицами БД, что упростит деплой (детали ниже).

Таблица Currencies

Колонка Тип Комментарий
ID int Айди валюты, автоинкремент, первичный ключ
Code Varchar Код валюты
FullName Varchar Полное имя валюты
Sign Varchar Символ валюты

Пример записи в таблице для австралийского доллара:

ID Code FullName Sign
1 AUD Australian dollar A$

Коды валют мира - https://www.iban.com/currency-codes.

Индексы:

  • Первичный ключ по полю ID
  • Unique индекс по полю Code для гарантий уникальности валюты в таблице, и для ускорения поиска валюты по её аббревиатуре

Таблица ExchangeRates

Колонка Тип Комментарий
ID int Айди курса обмена, автоинкремент, первичный ключ
BaseCurrencyId int ID базовой валюты, внешний ключ на Currencies.ID
TargetCurrencyId int ID целевой валюты, внешний ключ на Currencies.ID
Rate Decimal(6) Курс обмена единицы базовой валюты к единице целевой валюты

Decimal(6) - десятичное число с 6 знаками после запятой. Полезно для валют, отличающихся на порядки. Например, одна Японская иена равна 0.0073 USD.

Индексы:

  • Первичный ключ по полю ID
  • Unique индекс по паре полей BaseCurrencyId, TargetCurrencyId для гарантий уникальности валютной пары, и для ускорения поиска курса по паре валют

REST API

Методы REST API реализуют CRUD интерфейс над базой данных - позволяют создавать (C - create), читать (R - read), редактировать (U - update). В целях упрощения, опустим удаление (D - delete).

Валюты

GET /currencies

Получение списка валют. Пример ответа:

[
    {
        "id": 0,
        "name": "United States dollar",
        "code": "USD",
        "sign": "$"
    },   
    {
        "id": 0,
        "name": "Euro",
        "code": "EUR",
        "sign": "€"
    }
]

HTTP коды ответов:

  • Успех - 200
  • Ошибка (например, база данных недоступна) - 500

GET /currency/EUR

Получение конкретной валюты. Пример ответа:

{
    "id": 0,
    "name": "Euro",
    "code": "EUR",
    "sign": "€"
}

HTTP коды ответов:

  • Успех - 200
  • Код валюты отсутствует в адресе - 400
  • Валюта не найдена - 404
  • Ошибка (например, база данных недоступна) - 500

POST /currencies

Добавление новой валюты в базу. Данные передаются в теле запроса в виде полей формы (x-www-form-urlencoded). Поля формы - name, code, sign. Пример ответа - JSON представление вставленной в базу записи, включая её ID:

{
    "id": 0,
    "name": "Euro",
    "code": "EUR",
    "sign": "€"
}

HTTP коды ответов:

  • Успех - 200
  • Отсутствует нужное поле формы - 400
  • Валюта с таким кодом уже существует - 409
  • Ошибка (например, база данных недоступна) - 500

Обменные курсы

GET /exchangeRates

Получение списка всех обменных курсов. Пример ответа:

[
    {
        "id": 0,
        "baseCurrency": {
            "id": 0,
            "name": "United States dollar",
            "code": "USD",
            "sign": "$"
        },
        "targetCurrency": {
            "id": 1,
            "name": "Euro",
            "code": "EUR",
            "sign": "€"
        },
        "rate": 0.99
    }
]

HTTP коды ответов:

  • Успех - 200
  • Ошибка (например, база данных недоступна) - 500

GET /exchangeRate/USDRUB

Получение конкретного обменного курса. Валютная пара задаётся идущими подряд кодами валют в адресе запроса. Пример ответа:

{
    "id": 0,
    "baseCurrency": {
        "id": 0,
        "name": "United States dollar",
        "code": "USD",
        "sign": "$"
    },
    "targetCurrency": {
        "id": 1,
        "name": "Euro",
        "code": "EUR",
        "sign": "€"
    },
    "rate": 0.99
}

HTTP коды ответов:

  • Успех - 200
  • Коды валют пары отсутствуют в адресе - 400
  • Обменный курс для пары не найден - 404
  • Ошибка (например, база данных недоступна) - 500

POST /exchangeRates

Добавление нового обменного курса в базу. Данные передаются в теле запроса в виде полей формы (x-www-form-urlencoded). Поля формы - baseCurrencyCode, targetCurrencyCode, rate. Пример полей формы:

  • baseCurrencyCode - USD
  • targetCurrencyCode - EUR
  • rate - 0.99

Пример ответа - JSON представление вставленной в базу записи, включая её ID:

{
    "id": 0,
    "baseCurrency": {
        "id": 0,
        "name": "United States dollar",
        "code": "USD",
        "sign": "$"
    },
    "targetCurrency": {
        "id": 1,
        "name": "Euro",
        "code": "EUR",
        "sign": "€"
    },
    "rate": 0.99
}

HTTP коды ответов:

  • Успех - 200
  • Отсутствует нужное поле формы - 400
  • Валютная пара с таким кодом уже существует - 409
  • Ошибка (например, база данных недоступна) - 500

PATCH /exchangeRate/USDRUB

Обновление существующего в базе обменного курса. Валютная пара задаётся идущими подряд кодами валют в адресе запроса. Данные передаются в теле запроса в виде полей формы (x-www-form-urlencoded). Единственное поле формы - rate.

Пример ответа - JSON представление обновлённой записи в базе данных, включая её ID:

{
    "id": 0,
    "baseCurrency": {
        "id": 0,
        "name": "United States dollar",
        "code": "USD",
        "sign": "$"
    },
    "targetCurrency": {
        "id": 1,
        "name": "Euro",
        "code": "EUR",
        "sign": "€"
    },
    "rate": 0.99
}

HTTP коды ответов:

  • Успех - 200
  • Отсутствует нужное поле формы - 400
  • Валютная пара отсутствует в базе данных - 404
  • Ошибка (например, база данных недоступна) - 500

Обмен валюты

GET /exchange?from=BASE_CURRENCY_CODE&to=TARGET_CURRENCY_CODE&amount=$AMOUNT

Расчёт перевода определённого количества средств из одной валюты в другую. Пример запроса - GET /exchange?from=USD&to=AUD&amount=10.

Пример ответа:

{
    "baseCurrency": {
        "id": 0,
        "name": "United States dollar",
        "code": "USD",
        "sign": "$"
    },
    "targetCurrency": {
        "id": 1,
        "name": "Australian dollar",
        "code": "AUD",
        "sign": "A€"
    },
    "rate": 1.45,
    "amount": 10.00
    "convertedAmount": 14.50
}

Получение курса для обмена может пройти по одному из трёх сценариев. Допустим, совершаем перевод из валюты A в валюту B:

  1. В таблице ExchangeRates существует валютная пара AB - берём её курс
  2. В таблице ExchangeRates существует валютная пара BA - берем её курс, и считаем обратный, чтобы получить AB
  3. В таблице ExchangeRates существуют валютные пары USD-A и USD-B - вычисляем из этих курсов курс AB

Остальные возможные сценарии, для упрощения, опустим.


Для всех запросов, в случае ошибки, ответ может выглядеть так:

{
    "message": "Валюта не найдена"
}

Значение message зависит от того, какая именно ошибка произошла.

Деплой

Будем вручную деплоить war артефакт в Tomcat, установленный на удалённом сервере. При использовании встроенной в проект SQLite базы данных, установка внешней SQL БД не требуется.

Шаги:

  • Локально собрать war артефакт приложения
  • В хостинг-провайдере по выбору арендовать облачный сервер на Linux
  • Установить JRE и Tomcat
  • Зайти в админский интерфейс Tomcat, установить собранный war артефакт

Ожидаемый результат - приложение доступно по адресу http://$server_ip:8080/$app_root_path.

План работы над приложением

  • Создать заготовку Python бэкенд приложения с http.server
  • Создать таблицы в базе данных, и вручную их заполнить начальными данными (несколько валют, обменных курсов)
  • Используя паттерн MVC, реализовать методы REST API для работы с валютами и обменными курсами
  • Реализовать метод REST API с подсчётом обмена валюты
  • Деплой на удалённый сервер

Ресурсы для работы над ошибками

  • Реализации проекта другими студентами и мои ревью этих реализаций - https://zhukovsd.github.io/python-backend-learning-course/Projects/FinishedProjects
  • Чеклист для самопроверки с типовыми ошибками (в конце страницы)
  • Готовый проект можете отправить мне на ревью - https://t.me/zhukovsd
    • [Обновление от 7 сентября 2023] - целевое количество видео и текстовых ревью проекта "Обмен валют" накоплено, новые реализации к ревью не принимаются. В любом случае призываю отправлять законченные проекты в чат, добавляю их в список. Подробности - https://t.me/zhukovsd_it_mentor/57

Тестовый фронтенд

Для тестирования ваших реализаций REST API и визуализации результата я написал тестовый фронтенд - https://github.com/zhukovsd/currency-exchange-frontend.

Проект представляет из себя набор статических HTML/CSS/JS файлов и состоит из одной веб-страницы, отвечающей за работу со всеми эндпоинтами API.

Пример интеграции фронтенда с API можно посмотреть в ревью, основные шаги:

Чеклист для самопроверки

❗️Спойлеры: советую не читать этот список до того момента, пока не допишете первую самостоятельную работающую версию проекта❗️

Функциональные проблемы:

  • Реализация не всех сценарий обмена (всего их 3 - по прямому курсу, обратному курсу, кросс-курсу)
  • Расхождение с ТЗ в формате REST ответов, особенно в случае ошибок (4xx, 5xx)
  • Некорректное или отсутствующие округление сконвертированных сумм до двух десятичных знаков

Код:

  • Нечёткие границы между слоями Controller/Service/DAO.
  • Контроллеры, отвечающие за слишком большое количество задач. Они должны отвечать за валидацию, сериализацию json ответов, обработку ошибок от слоёв DAO/Service
  • Неправильная или неполная обработка ошибок. Предпочтительный вариант - слои DAO/Service кидают исключения, контроллер их обрабатывает и формирует ответ с нужным кодом и телом
  • Уязвимость к race conditions, подробно на примере разбирал здесь - https://t.me/zhukovsd_it_chat/16351 (ревью другого проекта, но суть та же)
  • БД:
    • Отсутствие unique индексов на поля или комбинации полей, которые должны быть уникальными. Например - код валюты в таблице currencies
    • Отсутствие внешних ключей между таблицами
    • Некорректный тип данных для хранения курсов и сумм (лучше всего подходит Decimal)
  • Перед вставкой валюты - проверка на существование валюты с таким же кодом через SELECT, вместо того чтобы положиться на unique индекс и получить исключение от БД в случае нарушения уникальности
  • Уязвимость к SQL injections, следует использовать параметризированные запросы
  • Использование double/float для операций с суммами
  • Неиспользование DTO классов для формирования ответов REST API

Мелочи:

  • Неаккуратное форматирование кода
  • Неиспользование пакетов для структурирования классов, все классы в корне проекта