REST API для описания валют и обменных курсов. Позволяет просматривать и редактировать списки валют и обменных курсов, и совершать расчёт конвертации произвольных сумм из одной валюты в другую.
Веб-интерфейс для проекта не подразумевается.
Комментарии по проекту - https://www.youtube.com/watch?v=013b_b7PszM.
- [Java]({{ site.baseurl }}/Technologies/Java/) - коллекции, ООП
- [Паттерн MVC(S)]({{ site.baseurl }}/Technologies/Java/#mvc)
- [Maven/Gradle]({{ site.baseurl }}/Technologies/BuildSystems/)
- [Backend]({{ site.baseurl }}/Technologies/Backend/)
- Java сервлеты
- HTTP - GET и POST запросы, коды ответа
- REST API, JSON
- [Базы данных]({{ site.baseurl }}/Technologies/Databases/) - SQLite, JDBC
- [Деплой]({{ site.baseurl }}/Technologies/DevOps/#деплой) - облачный хостинг, командная строка Linux, Tomcat
Фреймворки не используем.
- Знакомство с 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 коды ответов:
- Успех - 201 ✔️
- Отсутствует нужное поле формы - 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 коды ответов:
- Успех - 201 ✔️
- Отсутствует нужное поле формы - 400 ✔️
- Валютная пара с таким кодом уже существует - 409 ✔️
- Одна (или обе) валюта из валютной пары не существует в БД - 404 ✔️
- Ошибка (например, база данных недоступна) - 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
.
- Создать заготовку Java бэкенд приложения с
javax.servlet
/jakarta.servlet
- Создать таблицы в базе данных, и вручную их заполнить начальными данными (несколько валют, обменных курсов)
- Реализовать методы REST API для работы с валютами и обменными курсами
- Реализовать метод REST API с подсчётом обмена валюты
- Деплой на удалённый сервер
- Реализации проекта другими студентами и мои ревью этих реализаций - https://zhukovsd.github.io/java-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. Я для этого пользовался скриптом, запускающим проект через Nginx + Docker
❗️Спойлеры: советую не читать этот список до того момента, пока не допишете первую самостоятельную работающую версию проекта❗️
Функциональные проблемы:
- Реализация не всех сценарий обмена (всего их 3 - по прямому курсу, обратному курсу, кросс-курсу)
- Расхождение с ТЗ в формате REST ответов, особенно в случае ошибок (4xx, 5xx)
- Некорректное или отсутствующие округление сконвертированных сумм до двух десятичных знаков
Проблемы и ошибки в коде:
- Нечёткие границы между слоями Controller/Service/DAO.
- Контроллеры, отвечающие за слишком большое количество задач. Они должны отвечать за валидацию, сериализацию json ответов, обработку ошибок от слоёв DAO/Service
- Неправильная или неполная обработка ошибок. Предпочтительный вариант - слои DAO/Service кидают исключения, контроллер их обрабатывает и формирует ответ с нужным кодом и телом
- Уязвимость к race conditions, подробно на примере разбирал здесь (ревью другого проекта, но суть та же)
- БД:
- Отсутствие unique индексов на поля или комбинации полей, которые должны быть уникальными. Например - код валюты в таблице
currencies
- Отсутствие внешних ключей между таблицами
- Некорректный тип данных для хранения курсов и сумм (лучше всего подходит
Decimal
)
- Отсутствие unique индексов на поля или комбинации полей, которые должны быть уникальными. Например - код валюты в таблице
- Перед вставкой валюты - проверка на существование валюты с таким же кодом через
SELECT
, вместо того чтобы положиться на 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
) - Неаккуратное форматирование кода
- Неиспользование пакетов для структурирования классов, все классы в корне проекта