Проект “Обмен валют” #
REST API для описания валют и обменных курсов. Позволяет просматривать и редактировать списки валют и обменных курсов, и совершать расчёт конвертации произвольных сумм из одной валюты в другую.
Веб-интерфейс для проекта не подразумевается.
Комментарии по проекту - https://www.youtube.com/watch?v=013b_b7PszM (видео записано для Java версии роадмапа).
Что нужно знать #
- Python - коллекции, ООП
- Паттерн MVC(S)
- Backend
- http.server
- HTTP - GET и POST запросы, коды ответа
- REST API, JSON
- Базы данных - sqlite3
- Деплой - облачный хостинг, командная строка 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 коды ответов:
- Успех - 201
- Отсутствует нужное поле формы - 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
- USDtargetCurrencyCode
- EURrate
- 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 коды ответов:
- Успех - 201
- Отсутствует нужное поле формы - 400
- Валютная пара с таким кодом уже существует - 409
- Одна (или обе) валюта из валютной пары не существует в БД - 404
- Ошибка (например, база данных недоступна) - 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:
- В таблице
ExchangeRates
существует валютная пара AB - берём её курс - В таблице
ExchangeRates
существует валютная пара BA - берем её курс, и считаем обратный, чтобы получить AB - В таблице
ExchangeRates
существуют валютные пары USD-A и USD-B - вычисляем из этих курсов курс AB
Остальные возможные сценарии, для упрощения, опустим.
Для всех запросов, в случае ошибки, ответ может выглядеть так:
{
"message": "Валюта не найдена"
}
Значение message
зависит от того, какая именно ошибка произошла.
Деплой #
Будем вручную деплоить приложение, на удалённом сервере. При использовании SQLite, установка внешней SQL БД не требуется.
Шаги:
- В хостинг-провайдере по выбору арендовать облачный сервер на Linux
- Подключиться к серверу через SSH, установить Python
- Используя SFTP, скопировать на удалённый сервер приложение
- Написать команду для запуска приложения в background режиме, чтобы после отключения от консоли сервера оно продолжало работать
Ожидаемый результат - приложение доступно по адресу http://$server_ip:$port
.
План работы над приложением #
- Создать заготовку Python бэкенд приложения с
http.server
- Создать таблицы в базе данных, и вручную их заполнить начальными данными (несколько валют, обменных курсов)
- Используя паттерн MVC, реализовать методы REST API для работы с валютами и обменными курсами
- Реализовать метод REST API с подсчётом обмена валюты
- Деплой на удалённый сервер
Ресурсы для работы над ошибками #
- Реализации проекта другими студентами и мои ревью этих реализаций
- Чеклист для самопроверки с типовыми ошибками (в конце страницы)
- Готовый проект можете отправить мне на ревью -
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 можно посмотреть в ревью, основные шаги:
- Установить корень REST API в app.js - https://github.com/zhukovsd/currency-exchange-frontend/blob/main/js/app.js#L2
- Реализовать поддержку CORS в вашем API
- Запустить проект currency-exchange-frontend. Я для этого пользовался скриптом, запускающим проект через Nginx + Docker
Чеклист для самопроверки #
❗️Спойлеры: советую не читать этот список до того момента, пока не допишете первую самостоятельную работающую версию проекта❗️
Функциональные проблемы:
- Реализация не всех сценарий обмена (всего их 3 - по прямому курсу, обратному курсу, кросс-курсу)
- Расхождение с ТЗ в формате REST ответов, особенно в случае ошибок (4xx, 5xx)
- Некорректное или отсутствующие округление сконвертированных сумм до двух десятичных знаков
Код:
- Нечёткие границы между слоями Controller/Service/DAO.
- Контроллеры, отвечающие за слишком большое количество задач. Они должны отвечать за валидацию, сериализацию json ответов, обработку ошибок от слоёв DAO/Service
- Неправильная или неполная обработка ошибок. Предпочтительный вариант - слои DAO/Service кидают исключения, контроллер их обрабатывает и формирует ответ с нужным кодом и телом
- Уязвимость к race conditions, подробно на примере разбирал здесь - https://t.me/zhukovsd_it_chat/16351 (ревью другого проекта, но суть та же)
- БД:
- Отсутствие unique индексов на поля или комбинации полей, которые должны быть уникальными. Например - код валюты в таблице
currencies
- Отсутствие внешних ключей между таблицами
- Некорректный тип данных для хранения курсов и сумм (лучше всего подходит
Decimal
)
- Отсутствие unique индексов на поля или комбинации полей, которые должны быть уникальными. Например - код валюты в таблице
- Перед вставкой валюты - проверка на существование валюты с таким же кодом через
SELECT
, вместо того чтобы положиться на unique индекс и получить исключение от БД в случае нарушения уникальности - Уязвимость к SQL injections, следует использовать параметризированные запросы
- Использование double/float для операций с суммами
- Неиспользование DTO классов для формирования ответов REST API
Мелочи:
- Неаккуратное форматирование кода
- Неиспользование пакетов для структурирования классов, все классы в корне проекта