Skip to content

Latest commit

 

History

History
374 lines (298 loc) · 19.8 KB

task.md

File metadata and controls

374 lines (298 loc) · 19.8 KB

Проект "Обмен валют"

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. Это позволит встроить в ресурсы проекта файл с заполненными таблицами БД, что упростит деплой (детали ниже).

Таблица 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 - 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://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 можно посмотреть в ревью, основные шаги:

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

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

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

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

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

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