Всем известно, что мобильные приложения люди используют больше и чаще, чем веб-приложения. Они заранее адаптированы дизайном под мобильные устройства (что логично) и заведомо предоставят весь нужный функционал.
Именно поэтому у нас, как у будущих и нынешних программистов, в университете есть дисциплина по созданию мобильных приложений - обучение идёт созданию 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, чтобы можно было сразу тестировать написанные эндпоинты.

По сути, этот UI-swagger - основной способ коммуникации с фронтендерами. Я описывал им эндпоинты отдельно в нашем чате, при необходимости, но тестировать они всегда могли тут. Буквально один раз я выгрузил .json и .yaml форматы сваггера, но из-за недостаточной опытности коллег читать описание API в таком формате было сложно, да и ко всему прочему, как я уже писал, в web-ui можно сразу тестировать эндпоинты, а не только видеть их описание.
Нашей киллерфичей я уже называл встроенный AI - его интеграция оказалось простой задачей. Я просто подключил AI от Сбера, и он генерировал ответ на запрос пользователя учитывая "техническую инструкцию" - всегда генерировать только рецепты. Ниже реализация этого в коде и итоговый результат генерации:
messages = [
SystemMessage(
content="Ты бот, который придумывает рецепты. На любой запрос придумай рецепт"
),
HumanMessage(
content=serializer.data["text"]
)
]
response = self.ai_client.invoke(messages)

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}"
}
}
После применения этих настроек логи пошли в систему:

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

Я понимаю, что для моей цели: получение и анализ логов, их нормализация - можно было взять другой инструмент, сильно более легковесный (например, VictoriaLogs или Loki, как мне подсказали во время редакции статьи).
Но я решил посмотреть Эластик, как с ним работать в Kibana, как настроить получение логов в нём с удалённого сервера из Nginx. Данный пункт основан исключительно на моём интересе и не требуется в проекте (по крайней мере, в такой реализации).
Результат
Приближался конец семестра, наша работа подходила к концу. Оставались мелкие правки, немного дополнений и работа была успешно сдана. В самом конце вскрылась небольшая проблема - кэширование. Django почему-то решил полностью кэшировать все запросы, из-за чего после создания, например, нового элемента в корзине, он там появлялся только спустя время, если исполнять запрос на одном клиенте. Как только я полностью выключил всё кэширование на стороне Django - всё заработало как надо.
Как итог мы получили работающее приложение: с возможностью выкладывать рецепты, добавлять ингредиенты в корзину, с AI, который сгенерирует рецепты, с профилями пользователей.

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