Найти в Дзене

Пишем финансовое приложение на GatsbyJS с бэкендом на Go-лямбдах.

Или как фронтенд разработчик полез туда, куда его не просили В этот раз пришла в голову идея небольшого проекта, где я, наконец, смогу познакомиться поближе с Go. На первый взгляд мне нравится этот язык, заложенная в него философия, быстродействие, возможности параллелизма, встроенный функционал для тестирования и достаточно простой старт для новичка.  Проект будет представлять собой набор различных финансовых инструментов, которые я время от времени сам ищу в сети. Начать решил с кредитного калькулятора. Математические рассчеты — это то, в чем Go особенно хорош со своими горутинами, и в чем NodeJS не так хорош со своим однопоточным циклом событий. Это не той сложности проект, где разница в производительности будет заметна, но давайте представим, что у нас популярный высоконагруженный проект, и как мы будем рады заведомо правильному выбору инструмента на старте! В роли фронтенд-фреймворка я буду использовать GatsbyJS. Это отличное решение, в котором из коробки есть сервер-сайд рендери
Оглавление

Или как фронтенд разработчик полез туда, куда его не просили

Photo by Burst from Pexels
Photo by Burst from Pexels

В этот раз пришла в голову идея небольшого проекта, где я, наконец, смогу познакомиться поближе с 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:

-2

Установим необходимые зависимости:

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’а для красивого отображения процесса выполнения рассчетов;
  • Пары абзацев из Википедии, поясняющих разницу между типами выплат по кредиту на начальном экране.

Остальные компоненты приложения предлагаю вам к самостоятельному изучению в репозитории. А мы заканчиваем с визуальным интерфейсом, на текущем этапе он выглядит вот так:

-3

Пришло время вдохнуть в него жизнь!

Бэкенд

Я опущу шаги по начальной установке 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. Проверим, что у нас получилось:

-4

Отлично, все работает! Хотя и есть некоторые проблемы с отображением svg-чарта в Safari :).

Для деплоя проекта в продакшн нужно всего-то набрать:

now

В заключении

Serverless-приложения в последние годы обретают все большую и большую популярность, что вполне заслуженно. Мне абсолютно не пришлось ломать голову над компиляцией и запуском в продакшне моего сервер-сайд кода. На месте Go мог быть JavaScript, либо Python, но я ни разу не пожалел, что выбрал Go в этот раз. С одной стороны язык имеет достаточно простую концепцию и производит впечатление C на стероидах. С другой стороны он обеспечивает приличный сдвиг парадигмы для JavaScript-разработчика, например, своей неявной имплементацией интерфейсов. 

Экспериментируйте и расширяйте свой кругозор. До скорого!

Весь исходный код и ссылку на запущенный проект вы сможете найти в репозитории.