Или как фронтенд разработчик полез туда, куда его не просили
В этот раз пришла в голову идея небольшого проекта, где я, наконец, смогу познакомиться поближе с Go. На первый взгляд мне нравится этот язык, заложенная в него философия, быстродействие, возможности параллелизма, встроенный функционал для тестирования и достаточно простой старт для новичка.
Проект будет представлять собой набор различных финансовых инструментов, которые я время от времени сам ищу в сети. Начать решил с кредитного калькулятора.
Математические рассчеты — это то, в чем Go особенно хорош со своими горутинами, и в чем NodeJS не так хорош со своим однопоточным циклом событий. Это не той сложности проект, где разница в производительности будет заметна, но давайте представим, что у нас популярный высоконагруженный проект, и как мы будем рады заведомо правильному выбору инструмента на старте!
В роли фронтенд-фреймворка я буду использовать GatsbyJS. Это отличное решение, в котором из коробки есть сервер-сайд рендеринг, навигация, GraphQL и отличный developer experience. Для деплоя и хостинга я выбрал решения от Zeit. Очень удачно, что платформа Zeit поддерживает запуск лямбда функций на Go, а значит нам не придется отдельно озадачиваться сборкой и запуском Go-бэкенда.
Исходя из требований, у меня получился вот такой стек технологий:
- Gatsby, как фронтенд-фреймворк. Любимый React и server side rendering из коробки;
- Material UI, как библиотека компонентов, в том числе форм. Взрослое, проверенное временем решение;
- Emotion, как CSS-in-JS библиотека. Подход styled components, но лучшая производительность и меньший размер бандла;
- Formik, как form-менеджер. Для удобной инкапсуляции всей работы с формами, включая валидацию и избавления от необходимости тащить Redux в проект;
- TypeScript, как язык фронтенда;
- Jest, как фреймворк для тестирования TypeScript-кода;
- i18next для мультиязычности;
- Go, как язык бэкенда.
Темы тестирования и мультиязычности оставим за рамками этой статьи, но в исходном коде можно посмотреть мою реализацию. Ссылку на репозиторий я приведу в конце статьи.
Дизайн
Я далеко не дизайнер и считаю, что этим должны заниматься специальные одаренные от природы люди(если это люди вообще 👽), к которым я, к сожалению, не отношусь. Поэтому я пошел на dribbble, вбил в поиск "loan calculator" и отчаянно вдохновился одной из работ.
https://dribbble.com/shots/6766975-Mortgage-Calculator-Dashboard-Exploration
Rian Darma, спасибо тебе за твой талант! Надеюсь, ты не будешь сильно злиться на меня.
Фронтенд
Для экономии времени воспользуемся одним из GatsbyJS-стартеров. В нашем случае это будет gatsby-starter-typescript. Есть проблема в том, что он несколько устарел, поэтому придется обновить некоторые зависимости вручную на этапе установки пакетов.
yarn global add gatsby-cli
gatsby new gatsby-starter-typescript https://github.com/haysclark/gatsby-starter-typescript
cd gatsby-starter-typescript
yarn develop
Если не возникло никаких проблем, то пройдя по адресу http://localhost:8000/, мы увидим приветственную страницу стартера Gatsby:
Установим необходимые зависимости:
yarn add react react-dom gatsby gatsby-link @date-io/date-fns @emotion/core @emotion/styled @material-ui/core @material-ui/pickers @material-ui/styles date-fns formik formik-material-ui gatsby-plugin-emotion gatsby-plugin-material-ui react-helmet react-icons react-minimal-pie-chart react-number-format round-to thousands yup
yarn add --dev typescript t@types/node @types/react @types/react-dom @types/react-helmet @types/yup prettier tslint tslint-config-prettier
Активируем установленные Gatsby плагины:
Отредактируем tsconfig.json под наши нужды:
И подружим prettier с tslint. Для этого немного изменим секцию “extends” в tslint.json:
{
"extends": ["tslint:recommended", "tslint-config-prettier"]
}
На первых порах в приложении будет лишь одна страница, собственно с кредитным калькулятором, поэтому пойдем в /src/pages и удалим все лишнее, кроме index.tsx. Визуально страница будет состоять из 3 частей:
- основная с формой для ввода данных пользователем;
- сайдбар, где мы будем выводить результаты рассчетов;
- модальное окно с помесячным графиком платежей;
Далее сюда добавится еще боковое меню навигации между инструментами.
По возможности я стараюсь обходится без Redux в проектах, мне не нравится его многословность. Однако у компонентов на странице все-таки должно быть общее состояние, поэтому я реализую работу с ним на связке React Context API и хука useReducer. Дальше я буду создавать свой контекст для каждой страницы приложения, если будет необходим общий стейт.
Создадим необходимые типы данных(я обозначу только некоторые из них, остальные можно будет найти в репозитории):
src/types/index.ts
Первое, это интерфейс IFormData, описывающий состояние формы. Все достаточно понятно: порядок погашения кредита(аннуитетный или дифференцированный), дата начала выплат для построения графика платежей, срок в месяцах, сумма кредита и процентная ставка.
Второе, ILoanState — тот самый общий стейт приложения, содержащий в себе выбранную валюту кредита, результат рассчетов, данные формы, флаги и некоторые другие данные.
Вся остальная часть кода файла будет узнаваема всеми, кто знаком с Redux. Это константы типов экшнов, и сами экшны. Я люблю оформлять их в виде классов и экспортировать, как union-тип. Так VSCode обеспечивает отличное автодополнение при выборе типа экшна.
Давайте напишем начальное состояние, редьюсер, контекст и компонент-провайдер контекста:
src/context/loanContextProvider.tsx
В провайдер мы обернем компоненты на верхнем уровне. Это позволит получить доступ к общему стейту и функции dispatch в любом компоненте приложения, вызвав в нем useContext c аргументом в виде созданного контекста.
Отлично, двигаемся дальше. Посмотрим содержимое нашей главной страницы:
src/pages/index.tsx
Здесь мы имеем:
- MuiPickersUtilsProvider, в который мы должны передать библиотеку для работы с датами и необходимую локаль, в нашем случае date-fns с английской локалью;
- LoanContextProvider;
- Layout с верхнеуровневой HTML-разметкой, мета-данными и глобальными стилями, который будет использоваться на этой и всех других страницах приложения;
- два верхнеуровневых компонента.
Ниже приведен код компонента Main. Листинг достаточно длинный, я оставил все за исключением стилизующих компонентов Emotion:
src/components/loan/Main.tsx
Здесь можно видеть использование библиотек Material UI и Formik в связке. В компонент Field от Formik передается компонент формы от Material UI через prop с названием component(больше компонентов, пожалуйста :)). В виде клея для работы этих библиотек в паре я использовал небольшой набор байндингов formik-material-ui. Для DatePicker’а и поля ввода суммы кредита я написал байндинги сам.
Скажу немного о валидации введенных данных. Вы могли заметить импорт схемы в файле:
import schema from '../../schema/loan';
Эта схема далее передается через prop validationSchema в компонент Formik. Здесь я последовал совету автора Formik и использовал небольшую библиотеку yup для валидации. Она действительно оказалась очень приятной в использовании с простым и понятным API. Схема для валидации данных формы выглядит так:
src/schema/loan.ts
Это просто объект с набором полей, соответствующим именам инпутов формы с необходимыми правилами валидации. Очень просто и понятно!
Давайте теперь посмотрим на компонент Aside:
src/components/loan/Aside.tsx
Aside состоит из трех частей:
- Компонента Calculation с рассчетами по кредиту. Он же присутствует в Main, но видим там только в мобильных разрешениях экрана;
- Loader’а для красивого отображения процесса выполнения рассчетов;
- Пары абзацев из Википедии, поясняющих разницу между типами выплат по кредиту на начальном экране.
Остальные компоненты приложения предлагаю вам к самостоятельному изучению в репозитории. А мы заканчиваем с визуальным интерфейсом, на текущем этапе он выглядит вот так:
Пришло время вдохнуть в него жизнь!
Бэкенд
Я опущу шаги по начальной установке Go и настройке $GOPATH. Всю недостающую информацию вы можете найти здесь. Начиная с версии 1.11 в Go включили поддержку модулей — новой системы управления зависимостей. Теперь мы отвязаны от $GOPATH и можем создавать Go-модули, где угодно за ее пределами. Это нам очень на руку, учитывая то, что бэкенд нашего приложения — это лямбда-функции. Вернее, одна лямбда-функция, служащая точкой входа, в которой происходит дальнейший роутинг. Давайте создадим наш модуль:
cd src && mkdir lambda && cd lambda
// Не забудьте изменить имя модуля на свое
go mod init github.com/prplx/finance/lambda
Go создаст файл go.mod в директории, в который запишет название модуля, версию Go и в дальнейшем все зависимости модуля с их версиями.
Создадим файл index.go и напишем в нем нашу первую лямбда-функцию:
touch index.go
src/lambda/index.go
Обратите внимание, что по правилам Go из пакета автоматически экспортируются все идентификаторы, названные с заглавной буквы. В нашем случае в файле должен быть единственный экспортируемый идентификатор —функция с названием Handler! В этой функции мы инициализируем роутер, в качестве которого я выбрал chi за его простату и лаконичность и создадим один единственный маршрут:
router.Post("/api/loan", loanHandler)
Это адрес API эндпоинта. Именно туда мы будем ходить за рассчетами. Обработчиком запроса служит функция loanHandler, в которой устанавливаются некоторые HTTP-заголовки и возвращаются результаты работы функции Calculate из пакета loan. Именно его импорт мы видим вверху после импорта роутера. Давайте создадим этот пакет:
mkdir loan && cd loan && touch loan.go
В этом файле определяются структуры входных и выходных данных с соответствующими полями JSON-данных через механизм тэгов, экспортируемая функция Calculate, которая в зависимости от типа кредита вызывает соответствующие хелперы, определенные внизу файла. Я не буду грузить вас математическими формулами для подсчета процентов того или иного вида кредита, думаю, что вы легко найдете их в сести, если потребуется. В завершении нужно навести порядок в зависимостях:
go mod tidy
Что ж, теперь нужно запустить как-то то, что мы написали и протестировать. Для этого мы воспользуемся утилитой Now от Zeit. Проделайте необходимые шаги по установке и базовой настройке по данной инструкции.
Напишем небольшой конфигурационный файл для Now.
touch now.json
В секции “builds” мы даем понять Now, что у нас 2 стадии сборки. Первая для статики Gatsby, вторая для Go-кода. В секции “routes” мы даем понять, что хотим запускать функцию “/src/lambdas/index.go”, когда запрос следует по пути “/api/(.*)”, в остальных случаях отдавать браузеру нашу собранную HTML-статику.
В Now есть режим локальной разработки, который поддерживает и запуск лямбда-функций в том числе:
now dev
Проект соберется и станет доступным по адресу http://localhost:3000. Проверим, что у нас получилось:
Отлично, все работает! Хотя и есть некоторые проблемы с отображением svg-чарта в Safari :).
Для деплоя проекта в продакшн нужно всего-то набрать:
now
В заключении
Serverless-приложения в последние годы обретают все большую и большую популярность, что вполне заслуженно. Мне абсолютно не пришлось ломать голову над компиляцией и запуском в продакшне моего сервер-сайд кода. На месте Go мог быть JavaScript, либо Python, но я ни разу не пожалел, что выбрал Go в этот раз. С одной стороны язык имеет достаточно простую концепцию и производит впечатление C на стероидах. С другой стороны он обеспечивает приличный сдвиг парадигмы для JavaScript-разработчика, например, своей неявной имплементацией интерфейсов.
Экспериментируйте и расширяйте свой кругозор. До скорого!
Весь исходный код и ссылку на запущенный проект вы сможете найти в репозитории.