Всем известно, что мобильные приложения люди используют больше и чаще, чем веб-приложения. Они заранее адаптированы дизайном под мобильные устройства (что логично) и заведомо предоставят весь нужный функционал.

Именно поэтому у нас, как у будущих и нынешних программистов, в университете есть дисциплина по созданию мобильных приложений - обучение идёт созданию Java-приложений. Но в рамках дисциплины можно не идти простым путём - просто выполняя практики по методичке на Java - можно выбрать сложный путь, путь ниндзя - создать собственное мобильное приложение в команде. И мы пошли именно этим путём.

*Данная статья является обзором пути длиной 4 месяца - не рекламой продукта. Наше приложение нельзя нигде скачать, оно не приносит дохода. Цель данной статьи: описать весь процесс разработки и работы в команде, в комментариях получить советы, рекомендации более опытных разработчиков. Все упоминания хостингов не являются рекламой, я рассказываю про свой опыт, в том числе, учитывая используемые сервисы. Это учебный проект, его цель - научиться новому. Мне как тимлиду и бэкендеру, сокомандникам своим сферам.

**Здесь не будет везде примеров кода и примеров реализации конкретных вещей. Я описываю процесс создания приложения, а не один из этапов - кодинг.

Концепция

У нас как у людей нетворческих было не очень много идей. Собственно, идея - и была самая сложная часть поначалу. Попросили помочь искусственный интеллект: он выдал ряд предложений. Одним из них и было то, что мы взяли на вооружение и с чем имели дело 4 месяца - приложение с рецептами.

Но таких десятки. Я сел за техническое задание, в процессе написания которого мы командой придумывали, какая же киллерфича должна быть. Мы решили встроить ИИ, который бы генерировал интересные рецепты* (*за возможные последствия приготовления сгенерированных рецептов не ручаемся).

Параллельно с написанием техзадания, после того, как были продуманы функциональные требования, наша дизайнер сделала в Figma вид результата работы. Я, ни разу ранее не пользовавшись Adobe Illustrator, открыл его и по какой-то обучалке сделал простое лого. Его добавили в дизайн в Фигме и мы получили внешний вид того, к чему мы должны прийти в течение следующих 3 месяцев труда.

*Ремарка: в процессе придумывания названия для приложения мы остановились на вариантах "Вкуснолаб" и "Вкусолаб". Так как отличие лишь в одну букву, а репозитории уже были созданы, название может быть где-то одним, где-то другим.

Несколько страничек нашего приложения в Фигме
Несколько страничек нашего приложения в Фигме

В техзадании прописывается также и стек технологий. Нами был Flutter ради кроссплатформенности (спойлер на будущее: она не была использована, я не успел сделать порт на iOS) и Python Django на бэкенде. Конечно, приложение не приложение без серверной части. Иначе как юзерам смотреть рецепты не только свои сохранённые локально, но и рецепты других пользователей?

Немного про архитектуру приложения

Коротко архитектуру приложения можно обрисовать схемой.

Вся архитектура в одной картинке
Вся архитектура в одной картинке

Есть часть, которую я назвал основной, Main, - собственно, мобильное приложение и backend с базой данных, которые обмениваются данными. По запросу мобильного приложения бэкенд отсылает картинки на отдельно стоящий сервер S3, записывая в базу данных путь к файлу. Сам сервер S3 публичный, там не хранится критически важная информация пользователей, поэтому мобильное приложение может без авторизации получать доступ к картинкам на S3 по публичным ссылкам.

В Elasticsearch записываются только логи через Filebeat и Logstash. Тут всё просто. На самом деле для таких целей можно было найти сильно более легковесный сервис для красивого просмотра логов, но я захотел и решил поэкспериментировать с Эластиком на отдельном сервере и с отправкой логов с Nginx'а в него.

Разработка

При наличии готового дизайна фронтендеры приступили к созданию приложения. Необходимо было перенести дизайн из Фигмы по Флаттер, временно без логики нажатия на кнопки.

Моя коллега, бэкендер, нарисовала схему базы данных. У нас не было цели строить сначала концептуальную модель, затем логическую и от неё физическую, она нарисовала условную схему базы данных (её сложно назвать UML-диаграммой).

Схема базы данных
Схема базы данных

Мы приступили к написанию самого бэкенда. Для этого выбрали django rest framework, который позволяет нам быстро писать эндпоинты целыми пачками. В процессе возникали сложности, например, связанные с тем, что моя коллега использовала ИИ DeepSeek при выполнении задач, но во-первых, сама не вникла в суть задачи, из-за чего в коде были логические ошибки, во-вторых, неправильно формулировала задачу ИИ, - эта проблема вытекает из пункта 1. В итоге часть "мусорной", "неправильной" логики отсеялась во время моего ревью, а часть из того, что я посчитал нормальным, но таковым не являлось, проникло в master ветку и оттуда на сервер.

Я добавил в приложение на бэкенд с помощью библиотеки yasg визуальный красивый swagger, чтобы можно было сразу тестировать написанные эндпоинты.

Swagger, сформированный с помощью yasg
Swagger, сформированный с помощью yasg

По сути, этот UI-swagger - основной способ коммуникации с фронтендерами. Я описывал им эндпоинты отдельно в нашем чате, при необходимости, но тестировать они всегда могли тут. Буквально один раз я выгрузил .json и .yaml форматы сваггера, но из-за недостаточной опытности коллег читать описание API в таком формате было сложно, да и ко всему прочему, как я уже писал, в web-ui можно сразу тестировать эндпоинты, а не только видеть их описание.

Нашей киллерфичей я уже называл встроенный AI - его интеграция оказалось простой задачей. Я просто подключил AI от Сбера, и он генерировал ответ на запрос пользователя учитывая "техническую инструкцию" - всегда генерировать только рецепты. Ниже реализация этого в коде и итоговый результат генерации:

messages = [
  SystemMessage(
    content="Ты бот, который придумывает рецепты. На любой запрос придумай рецепт"
  ),
  HumanMessage(
    content=serializer.data["text"]
  )
]

response = self.ai_client.invoke(messages)
Ответ от AI
Ответ от AI

DevOps

На определённом этапе разработки я понял, что пора кидать бэкенд на сервер. Я арендовал VPS на Aeza. Настроил nginx на нём. Захотел настроить CI/CD. Посмотрел-почитал про Ansible, настроил его в GH Workflow. В процессе разработки были добавлены тесты в бэкенде - CI/CD настраивал с учётом их. При пуше в master ветку вызывается workflow, который сначала прогоняет все тесты, а потом по SSH отправляет файлы на сервер, делает миграции и перезапускает gunicorn.

В качестве СУБД я выбрал MySQL.

Следующим шагом является настройка S3. Я арендовал его на Timeweb, настроил в Django. Теперь картинки отправляются из бэкенда в облачное S3 хранилище, в базу данных сохраняется ссылка, которую получает фронтенд и фронтенд подгружает картинку напрямую из S3.

Elasticsearch

Небольшое ответвление в разделе девопса. Elasticsearch я решил использовать для сбора логов с nginx. Чтобы поднять Эластик, я арендовал отдельный для этого сервер на Timeweb, нашел репозиторий на GitHub с настроенным стеком ELK: "Elasticsearch, logstash, kibana". Донастроил пользователей под себя - и Kibana запустилась.

Необходимо было настроить конфигурацию nginx на сервере с бэкендом и формат получаемых логов в самом Elasticsearch (logstash pipeline):

input {
  beats {
    port => 5044
  }
  tcp {
    port => 50000
  }
}

filter {
  grok {
    match => { "message" => '"%{WORD:method} %{URIPATH:request} HTTP/%{NUMBER:http_version}" %{NUMBER:status} %{NUMBER:bytes_sent} "%{DATA:referrer}" "%{DATA:user_agent}"' }
  }
  mutate {
    convert => {
      "status" => "integer"
      "bytes_sent" => "integer"
    }
    remove_field => ["message"]
  }
}

output {
  elasticsearch {
    hosts => "elasticsearch:9200"
    user => "logstash_internal"
    password => "${LOGSTASH_INTERNAL_PASSWORD}"
    index => "weblogs-%{+YYYY.MM.dd}"
  }
}

После применения этих настроек логи пошли в систему:

Список логов в Kibana
Список логов в Kibana

Далее для большего интереса и большей наглядности я настроил один дэшборд:

Дэшборд в Kibana
Дэшборд в Kibana

Я понимаю, что для моей цели: получение и анализ логов, их нормализация - можно было взять другой инструмент, сильно более легковесный (например, VictoriaLogs или Loki, как мне подсказали во время редакции статьи).

Но я решил посмотреть Эластик, как с ним работать в Kibana, как настроить получение логов в нём с удалённого сервера из Nginx. Данный пункт основан исключительно на моём интересе и не требуется в проекте (по крайней мере, в такой реализации).

Результат

Приближался конец семестра, наша работа подходила к концу. Оставались мелкие правки, немного дополнений и работа была успешно сдана. В самом конце вскрылась небольшая проблема - кэширование. Django почему-то решил полностью кэшировать все запросы, из-за чего после создания, например, нового элемента в корзине, он там появлялся только спустя время, если исполнять запрос на одном клиенте. Как только я полностью выключил всё кэширование на стороне Django - всё заработало как надо.

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

Скриншоты самого приложения
Скриншоты самого приложения

Спасибо за работу бэкендеру Евгении, дизайнеру-фронтендеру Алисе и фронтендеру Вячеславу. Я получил кайф от данного проекта. И спасибо за прочтение, уважаемый читатель!

Комментарии (0)