Сервис на языке Dart: введение, инфраструктура бэкэнд

Оглавление

1. Введение
2. Backend
2.1. Инфраструктура.
2.2. Доменное имя. SSL.
2.3. Серверное приложение на Dart.

3. Web
3.1. Заглушка «Under construction»

4. Mobile


Введение


Меня, Flutter-разработчика, знакомые часто спрашивают: «Что же такое язык Dart?». Качают головой со словами: «А вот Петя серьёзные транспорты на Java пишет, а в Яндексе вообще плюсы в проде…». Ну что ж, пожалуй, действительно, Dart далёк от практик «фабрик для создания фабрик» из Java. Однако если стоит задача реализовать клиентские приложения сразу для нескольких платформ, не утонув в потоке задач по синхронизации разработчиков разных целевых ОС; создать целостный UI, узнаваемый, но специфичный для Android, iOS и веб и в целом уложиться в адекватные бюджет и сроки, — здесь Flutter не имеет конкурентов. И эти вопросы стоят вдвойне если у вас… стартап.

Итак, легенда: некий стартап решил создать новый сервис… ну, например, для 

обмена списками покупок

Такая себе идея для стартапа, я знаю, но если я выпущу ещё один ToDo лист в этот мир, мне будет стыдно :)

между пользователями сервиса. Цель стартапа — выпустить MVP за три месяца на трех платформах (плюс четвертая — сервер, конечно).

10 лет назад я бы сказал, что этот кейс не имеет решения и постарался бы держаться от него подальше, 3 года назад решением мог стать стек ReactNative/React/NodeJs, в 2020 году для этого есть Dart. Добро пожаловать в атмосферу разработки альфа версии сервиса, я постараюсь наглядно пройти и объяснить весь процесс разработки. Код всех приложений будет выложен в паблик. Комментарии, включая набросы и холивары, приветствуются. Спросить автора «по существу» или просто посоветоваться можно в Telegram канале нашего отдела.

zhwz2jcgvcuryjuhlu_r4oooc_8.png

Инфраструктура бекэнд


Типовым способом размещения серверного приложения является, конечно, VPS (виртуальный приватный сервер). Фактически — это часть физического сервера в дата центре, ресурсы которого (ядра процессора и оперативная память) разделены с помощью технологии виртуализации (о наиболее распространённых технологиях аппаратной виртуализации можно почитать здесь XEN, KVM). Внимание: технологии программной виртуализации (OpenVZ, Virtuozzo) для нашей задачи могут не подойти из-за конфликтов с Docker и агрессивным оверселлингом (зачастую, при внимательном прочтении договора аренды такого VPS, можно обнаружить, что провайдеры гарантируют «не менее 5%» (!) утилизации ядра арендуемого процессора. Это означает, что провайдер планирует продать наше ядро процессора 20 (!) раз).

Итак, приобретём бюджетный VPS со следующими характеристиками: 1 ядро процессора, 1ГБ оперативной памяти, 10ГБ накопителя (в моём случае, это гибридный HDD). В качестве операционной системы выберем Ubuntu, желательно одной из LTS версий. После чего в электронную почту придёт сообщение об активации сервера с логином и паролем доступа по SSH (зашифрованный доступ к консоли операционной системы нашего VPS) в формате SSH:

IP-адрес: 91.230.60.120
Пользователь: root
Пароль: <Пароль>

Проверим подключение, введя в командной строке:

ssh root@91.230.60.120


и, по запросу:

password: <Пароль>


Результатом должен быть вывод сведений о виртуальном сервере и поле ввода внизу:

Server is hosted by хххххххххх

Hostname: 91.230.60.120
Kernel: 3.19.0–22-generic (Ubuntu хх.хх LTS)
Uptime: 09:07:06 up 3 days, 17:17, 1 user, load average: 0.00, 0.01, 0.05
CPU: Intel® Xeon® CPU 0 @ 2.00GHz (1 cores)
Memory: 989 MB total / 723 MB free

root@91.230.60.120:~$

Поздравляю, наш виртуальный сервер создан и доступен для работы.

Теперь определимся со структурой бекэнд. Нам понадобится HTTP сервер. Мы будем использовать NGINX. Его задачами будут:

  1. Раздача статических файлов (файлы веб приложения).
  2. Раздача служебных ресурсов, например, файлов подтверждения владения доменом для мобильных приложений, сведений о владельце для получения SSL сертификатов Let«s encrypt и пр.
  3. Reverse proxy для доступа к серверным приложениям.
  4. Шифрование соединений — https.


Два серверных приложения:

  1. Приложение регистрации и авторизации пользователей. Назовём его auth_app.
  2. Приложение с данными. Назовём его app.
  3. Для каждого из приложений п.2 нам понадобится отдельная база данных PostgreSQL.
  4. Приложение для автоматического получения и обновления сертификатов шифрования SSL (в следующей статье).


Очевидно, что такой «зоопарк» приложений необходимо изолировать друг от друга, а также заблокировать доступ извне. Для этого мы будем использовать технологию контейнеризации Docker и менеджер контейнеров Docker compose. В виде схемы это можно представить так:

image

Разработку будем вести в IDE Visual Studio Code от Microsoft, которая, благодаря множеству доступных плагинов, позволит работать со всеми необходимыми технологиями. Также необходимо установить следующие расширения:


После перезапуска VScode подключимся к нашему VPS. Нажимаем F1 и начнем вводить команду:

Remote-SSH: connect to  Host…


далее новое подключение:

+ Add New Ssh Host


затем:

ssh root@


Откроем окно терминала VScode (Menu/Terminal/New terminal) и проверим системные ресурсы командой:

top


Готово, доступ к консоли и файловой системе VPS получен:

t3hj1-phv2fh0kssphmbexs8aym.png

fo14xoqlxgvzvo74pscvpbcnffs.png

Утилита top будет использоваться довольно часто, поэтому установим её псевдографическую версию htop:

Ctrl-C #Завершаем выполнение утилиты top
apt-get update #Обновляем установленные пакеты
apt-get install htop #Устанавливаем htop 
htop #Запускаем 


image

Теперь необходимо установить Docker и Docker compose:

Ctrl-C #Завершаем выполнение утилиты htop


Поскольку docker отсутствует в официальном репозитории Ubuntu, установим дополнительный репозиторий

apt-get install apt-transport-https ca-certificates curl gnupg-agent software-properties-common #Устанавливаем необходимые утилиты 
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - #Устанавливаем ключ репозитория docker 
add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" #Добавляем репозиторий 
apt-get install docker-ce docker-ce-cli containerd.io #Устанавливаем
curl -L "https://github.com/docker/compose/releases/download/1.26.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose #Скачиваем менеджер Docker compose 
chmod +x /usr/local/bin/docker-compose #Устанавливаем разрешение для загруженного файла «исполняемый файл»
ln -s /usr/local/bin/docker-compose /usr/bin/docker-compose #Добавляем символьную ссылку в директорию исполняемых файлов 
 docker  --version #Проверяем
docker-compose --version


Отлично, сервер готов к тестовому развёртыванию сервиса.

Теперь установим Docker desktop на нашем локальном ПК для разработки. Установщик для Windows 10, версия для MacOS здесь. Будет установлен не только Docker, но и Docker toolbox, в который входят Docker compose и графические утилиты для работы с контейнерами.

Откроем новое окно VScode, Menu/File/Open folder… Создадим новую папку нашего проекта, например, Srv и откроем её. В этой папке создадим файл docker-compose.yaml:

image

В этом файле описывается сценарий запуска контейнеров сервиса, их зависимости, переменные, команды, сети, хранилища и пр.

Здесь необходимо остановиться и уточнить разницу между образом и контейнером Docker. Контейнер — это приложение + его зависимости (например, пакеты и библиотеки) + операционная система, которую можно с помощью Docker запустить как обычное приложение. А образ — это подготовленный к запуску и упакованный в архив контейнер. Таким образом, весь наш бекэнд будет представлять собой набор контейнеров с описанием сценария их запуска.

Первым контейнером, который мы запланировали станет HTTP сервер NGINX. И давайте подготовим необходимый образ… или нет? Дело в том, что для очень многих веб приложений и сред исполнения их разработчиками или комьюнити уже собраны необходимые образы и размещены в публичном реестре DockerHub. Разумеется такой широко используемый образ уже собран и ожидает нас по этой ссылке.

Обратите внимание на список — это различные версии, они отличаются и версиями самого NGINX и дополнительными инструментами (например, установленным PERL). Для разработки можно использовать тэг «latest» (последняя стабильная версия на момент запроса образа), но для развёртывания на сервере, конечно, стоит использовать конкретную версию. В данный момент это образ nginx:1.19.0.

Здесь и далее необходимые пояснения к содержимому docker-compose.yaml я буду указывать в комментариях в самом листинге файла:

plbn42iebqhf22slx6kus5qtwve.png

Сохраним изменения в файле, откроем консоль VScode и выполним команду запуска сценария

docker-compose up


Этой командой был не только запущен сценарий, но и вывод консоли контейнера был направлен в консоль хоста. Теперь, если в адресной строке браузера обратиться к локальному хосту на порте 8081 будет получена стандартный ответ NGINX:

mf4agwuoigugzr-8l3njf_hanus.png

Наш первый контейнер уже работает. Обычно запуск сценария выполняется командой:

docker-compose up -d


это позволяет запустить контейнеры в режиме сервиса (без вывода в консоль). Остановка контейнеров сценария выполняется командой:

docker-compose down


Для тестирования http запросов в VScode есть удобное расширение REST client.

Установим его и напишем первый отладочный тест нашего сервиса. Для этого создадим файл client.http в папке test/http_dev/:

9jqxekrt95q0rxupalgt7ic8yss.png

46dgdj9luso9ede1lq19a9sdcyu.png

Таким образом можно выполнять тестовые запросы, просматривая подробную информацию об ответах сервера.

Теперь давайте заглянем внутрь контейнера. Остановим выполнение сценария в консоли:

Ctrl-C


и запустим с флагом:

docker-compose up -d


Теперь запросим список выполняемых в данный момент контейнеров (процессов):

docker-compose ps


hn5mjc-csbkod4w_ybmpimqfayc.png

В списке выполняемых только один контейнер. Давайте откроем его:

docker exec -it srv_web_1 bash


Эта команда выполняет (exec) приложение bash (командная оболочка Linux) в контейнере srv_web_1 и не дает закрываться консоли (флаги -it):

wyx7vk7qtk-8cshxafvb41quzsc.png

Команда ls покажет стандартную структуру папок Linux:

tcvchx1a-kikucgemdbgnahjn2m.png

Нас интересует файл /etc/nginx/conf.d/default.conf — настройки NGINX, для просмотра можно использовать утилиту cat

cat /etc/nginx/conf.d/default.conf


kbhkaunjbuy8vfebxi5egrdimp4.png

В настройках NGINX один блок (так называемый location), в котором включено прослушивание порта 80 и раздача статических файлов из папки контейнера /usr/share/nginx/html. Можно попробовать внести изменения в файл настройки NGINX и перезапустить его, применив изменения, но при перезапуске сценария контейнер будет восстановлен из образа и никакие наши изменения не сохранятся. Это неправильный путь.

Выйдем из консоли контейнера:

Ctrl-D


Мы напишем свой файл настройки и разместим свои статические файлы, а при запуске будем их монтировать в контейнер NGINX. Создадим файл default.conf в папке /conf.d нашего проекта:

87uwd9i9w22r5bwjpjd1eyeh-8y.png

klmfwr_21mrht6l0hqobibst7-i.png

Создадим заглушку статического файла /public/index.html:

ftmtj7vaqnoz7gyv-ei2bopzpeo.png

Теперь в сценарии запуска docker-compose.yaml смонтируем наши папки в файловую систему контейнера:

1wdaxrqlde5s0r6q0yi1su5vwpk.png

Обратите внимание, что содержимое папки проекта ./conf.d заменит содержимое контейнера в /etc/nginx/conf.d/, а в корневую папку папку контейнера будет смонтирована папка ./public.

Перезапустим сценарий:

docker-compose restart


Тестовый запрос:

8mblcv0rmkqjqjui0tprvu9hsz8.png

Давайте посмотрим на файл default.conf. Обратите внимание, что мы отключили логирование доступа к статическим файлам access_log off. Это хорошее решение для прода, но очень неудобное при тестировании и разработке. Давайте создадим тестовые файл конфигурации NGINX /conf.dev.d/default.conf и сценарий docker-compose.dev.yaml.

cjku19nk01x6z5lehxj9nlx1bdy.png

d8xibcvylhjj-o4h817jduq1ezw.png

sso0lz3wstkpvtivkqvwimjkf7e.png

Остановим сценарий:

docker-compose down


и запустим уже с флагами имен файлов:

docker-compose -f docker-compose.yaml -f docker-compose.dev.yaml up


При таком запуске сценария сначала будет прочитаны настройки из файла docker-compose.yaml, а затем будут добавлены или заменены совпадающие поля из docker-compose.dev.yaml (ports, volumes). Проверим логирование повторив запрос:

_v3lw8wiq76pyka-_-_eyxmjlc4.png

sli_ert2ru0e7kf5oqsnqhptuvw.png

Итак, нам осталось выполнить копирование и запуск на сервере. Создадим на сервере папку /opt/srv_0/ (мы ведь ещё не закрыли окно VScode c SSH соединением к VPS) и скопируем в неё всё содержимое нашего проекта командой:

scp scp -r ./* root@91.230.60.120:/opt/srv_0/ 


mbede0qosm8ah1ca4fdtfhiaoeg.png

Теперь на сервере в папке проекта /opt/srv_0/ выполним команду:

docker-compose up -d


Напишем ещё один http тест, теперь уже для VPS:

s5rba4sepcfcnejq16s5bon3t38.png

Ну или откройте в браузере ссылка.

→ Исходный код github

Вместо заключения


Итак, сделан первый шаг. Мы успешно раскатили на боевой сервер приложение. Во второй статье мы продолжим настройку сервера: назначим доменное имя и установим сертификат шифрования SSL. В третьей статье напишем flutter web приложение с обратным отсчётом времени до запуска нашего сервиса, соберём его и разместим на нашем сервере. В четвертой статье напишем и соберём нативный сервер под Linux на языке Дарт, который станет основой для приложений авторизации и данных нашего сервиса.

Комментарии и предложения приветствуются. Пообщаться с автором можно в Telegram-канале.

© Habrahabr.ru