Проект “Обмен валют” #
REST API для описания валют и обменных курсов. Позволяет просматривать и редактировать списки валют и обменных курсов, и совершать расчёт конвертации произвольных сумм из одной валюты в другую.
Веб-интерфейс для проекта не подразумевается.
Комментарии по проекту - https://www.youtube.com/watch?v=013b_b7PszM.
Что нужно знать #
- Java - коллекции, ООП
- Паттерн MVC(S)
- Maven/Gradle
- Backend
- Java сервлеты
- HTTP - GET и POST запросы, коды ответа
- REST API, JSON
- Базы данных - SQLite, JDBC
- Деплой - облачный хостинг, командная строка Linux, Tomcat
Фреймворки не используем.
Мотивация проекта #
- Знакомство с 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": 2,
"name": "Russian Ruble",
"code": "RUB",
"sign": "₽"
},
"rate": 80
}
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": 2,
"name": "Russian Ruble",
"code": "RUB",
"sign": "₽"
},
"rate": 80
}
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
зависит от того, какая именно ошибка произошла.
Деплой #
Будем вручную деплоить war артефакт в Tomcat, установленный на удалённом сервере. При использовании встроенной в проект SQLite базы данных, установка внешней SQL БД не требуется.
Шаги:
- Локально собрать war артефакт приложения
- В хостинг-провайдере по выбору арендовать облачный сервер на Linux
- Установить JRE и Tomcat
- Зайти в админский интерфейс Tomcat, установить собранный war артефакт
Ожидаемый результат - приложение доступно по адресу http://$server_ip:8080/$app_root_path
.
План работы над приложением #
- Создать заготовку Java бэкенд приложения с
javax.servlet
/jakarta.servlet
- Создать таблицы в базе данных, и вручную их заполнить начальными данными (несколько валют, обменных курсов)
- Реализовать методы 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, подробно на примере разбирал
здесь (ревью другого проекта, но суть та же) - проверка на существование валюты с таким же кодом через
SELECT
передINSERT
, вместо того чтобы положиться на unique индекс и получить исключение от БД в случае нарушения уникальности - БД:
- Отсутствие unique индексов на поля или комбинации полей, которые должны быть уникальными. Например - код валюты в таблице
currencies
- Отсутствие внешних ключей между таблицами
- Некорректный тип данных для хранения курсов и сумм (лучше всего подходит
Decimal
)
- Отсутствие unique индексов на поля или комбинации полей, которые должны быть уникальными. Например - код валюты в таблице
- Уязвимость к SQL injections, следует использовать параметризированные prepared statements
- Использование
double
/float
для операций с суммами. Следует использоватьBigDecimal
- Неиспользование DTO классов для формирования ответов REST API
- Дублирование кода вместо использования filter для установки заголовков ответов во всех сервлетах - content-type и character encoding
Не обязательно, но полезно:
- Реализовать connection pool вместо того чтобы открывать по новому соединению на каждую SQL операцию
- Использовать MapStruct или ModelMapper чтобы совершать преобразования между классами-моделями и DTO
Мелочи:
- Неиспользование
.gitignore
, из-за чего в репозиторий попадают лишние файлы и папки (например,target
,out
) - Неаккуратное форматирование кода
- Неиспользование пакетов для структурирования классов, все классы в корне проекта
Расширенные материалы #
Для проекта созданы расширенные материалы - https://boosty.to/zhukovsd/posts/08a542e8-5503-4331-a82b-7b6bcf04314b
Для кого созданы материалы и какую проблему решают #
- Доведение проекта до конца и рефакторинг до качественного уровня
- Помощь в преодолении трудностей
Подробнее про цель продукта, баланс между открытыми и закрытыми материалами - https://telegra.ph/Moj-pervyj-produkt—rasshirennaya-versiya-roadmapa-04-21.
Что внутри #
- Эталонная реализация по шагам (7 шагов, включая деплой)
- 3 часа лекций по разработке проекта. Разобранные темы:
- Maven - создание, сборка и запуск проекта, установка Tomcat, jar, war
- Java EE, Jakarta EE - сервлеты, фильтры, валидация запросов
- Работа с БД - создание SQLite БД, создание и заполнение таблиц, JDBC, HikariCP, классы-модели, Lombok
- MVC - DAO, сервисы, контроллеры, модели, обработка ошибок, DTO
- Бизнес логика обмена валют, ручные тесты
- Тестовый фронтенд - интеграция двумя способами, CORS заголовки
- Деплой - сборка war артефакта, создание и подготовка Linux VM, установка Java и Tomcat, разворачивание артефакта
- Лекция с разбором типовых ошибок и тому, как их исправить
- Чат для вопросов по материалам