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. Это позволит встроить в ресурсы проекта файл с заполненными таблицами БД, что упростит деплой (детали ниже).
| Колонка | Тип | Комментарий |
|---|---|---|
| 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 для гарантий уникальности валюты в таблице, и для ускорения поиска валюты по её аббревиатуре
| Колонка | Тип | Комментарий |
|---|---|---|
| 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 реализуют CRUD интерфейс над базой данных - позволяют создавать (C - create), читать (R - read), редактировать (U - update). В целях упрощения, опустим удаление (D - delete).
Получение списка валют. Пример ответа:
[
{
"id": 0,
"name": "United States dollar",
"code": "USD",
"sign": "$"
},
{
"id": 0,
"name": "Euro",
"code": "EUR",
"sign": "€"
}
]
HTTP коды ответов:
- Успех - 200
- Ошибка (например, база данных недоступна) - 500
Получение конкретной валюты. Пример ответа:
{
"id": 0,
"name": "Euro",
"code": "EUR",
"sign": "€"
}
HTTP коды ответов:
- Успех - 200
- Код валюты отсутствует в адресе - 400
- Валюта не найдена - 404
- Ошибка (например, база данных недоступна) - 500
Добавление новой валюты в базу. Данные передаются в теле запроса в виде полей формы (x-www-form-urlencoded). Поля формы - name, code, sign. Пример ответа - JSON представление вставленной в базу записи, включая её ID:
{
"id": 0,
"name": "Euro",
"code": "EUR",
"sign": "€"
}
HTTP коды ответов:
- Успех - 200
- Отсутствует нужное поле формы - 400
- Валюта с таким кодом уже существует - 409
- Ошибка (например, база данных недоступна) - 500
Получение списка всех обменных курсов. Пример ответа:
[
{
"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
Получение конкретного обменного курса. Валютная пара задаётся идущими подряд кодами валют в адресе запроса. Пример ответа:
{
"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
Добавление нового обменного курса в базу. Данные передаются в теле запроса в виде полей формы (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 коды ответов:
- Успех - 200
- Отсутствует нужное поле формы - 400
- Валютная пара с таким кодом уже существует - 409
- Ошибка (например, база данных недоступна) - 500
Обновление существующего в базе обменного курса. Валютная пара задаётся идущими подряд кодами валют в адресе запроса. Данные передаются в теле запроса в виде полей формы (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=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 зависит от того, какая именно ошибка произошла.
Будем вручную деплоить 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 можно посмотреть в ревью, основные шаги:
- Установить корень REST API в app.js - https://github.com/zhukovsd/currency-exchange-frontend/blob/main/js/app.js#L2
- Реализовать поддержку CORS в вашем API
- Запустить проект currency-exchange-frontend. Я для этого пользовался [скриптом]((https://github.com/zhukovsd/currency-exchange-frontend/blob/main/launch-local-nginx.sh), запускающим проект через 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
Мелочи:
- Неаккуратное форматирование кода
- Неиспользование пакетов для структурирования классов, все классы в корне проекта