Обмен валют

Проект “Обмен валют” #

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

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

Комментарии по проекту - https://www.youtube.com/watch?v=013b_b7PszM.

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

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

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

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

База данных #

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

Таблица Currencies #

КолонкаТипКомментарий
IDintАйди валюты, автоинкремент, первичный ключ
CodeVarcharКод валюты
FullNameVarcharПолное имя валюты
SignVarcharСимвол валюты

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

IDCodeFullNameSign
1AUDAustralian dollarA$

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

Индексы:

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

Таблица ExchangeRates #

КолонкаТипКомментарий
IDintАйди курса обмена, автоинкремент, первичный ключ
BaseCurrencyIdintID базовой валюты, внешний ключ на Currencies.ID
TargetCurrencyIdintID целевой валюты, внешний ключ на Currencies.ID
RateDecimal(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 - 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 коды ответов:

  • Успех - 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:

  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.

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

  • Создать заготовку 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 можно посмотреть в ревью, основные шаги:

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

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

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

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

Проблемы и ошибки в коде:

  • Нечёткие границы между слоями Controller/Service/DAO.
  • Контроллеры, отвечающие за слишком большое количество задач. Они должны отвечать за валидацию, сериализацию JSON ответов, обработку ошибок от слоёв DAO/Service
  • Неправильная или неполная обработка ошибок. Предпочтительный вариант - слои DAO/Service кидают исключения, контроллер их обрабатывает и формирует ответ с нужным кодом и телом
  • Уязвимость к race conditions, подробно на примере разбирал здесь (ревью другого проекта, но суть та же) - проверка на существование валюты с таким же кодом через SELECT перед INSERT, вместо того чтобы положиться на unique индекс и получить исключение от БД в случае нарушения уникальности
  • БД:
    • Отсутствие unique индексов на поля или комбинации полей, которые должны быть уникальными. Например - код валюты в таблице currencies
    • Отсутствие внешних ключей между таблицами
    • Некорректный тип данных для хранения курсов и сумм (лучше всего подходит Decimal)
  • Уязвимость к 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, разворачивание артефакта
    • Лекция с разбором типовых ошибок и тому, как их исправить
  • Чат для вопросов по материалам