Дисклеймер
Всем привет! В данной статье я поделюсь своим опытом написания сервиса. Я не являюсь опытным или профессиональным разработчиком, я пишу свой проект и мои решения могут быть не самыми оптимальными. Эта статья состоит в основном из ошибок, которые я совершил. Мой путь не является правильным и потому - судите "строго".
Создание структуры проекта
Первое, что приходит в голову при создании своего проекта, это как правильно организовать работу всего приложения, что с чем должно взаимодействовать, и как вообще это должно работать. Поэтому первым делом я решил накидать какую-то структуру проекта.

На картинке выше, я привел схему, которая появилась у меня в голове, когда я только начинал, и эта схема очень сильно напоминает MVC паттерн (По крайней мере мне так кажется). Что из этого выходит. Весь сервис можно разделить на 3 большие части:
Frontend - часть, которую видят пользователи, и где они могут загружать свои аудио/видео файлы
Controller - предоставляет api для frontend, чтобы можно было получать данные из бд и хранилище, писать в них и ставить задачи для бэка и получать некий callback, для обновления задач
Backend - выполняет сложные задачи, диаризации, транскрибации и т.д.
Давайте быстро пробежимся по тому, что это за сервис. Автопротоколирование - это некоторое приложение в которое ты можешь загрузить свою видео/аудио-запись, а на выходе получишь протокол и транскрипцию этого файла.
Так как проект домашний, для базы данных выбрал Postgres, для хранилища файлов - minio. Накидал docker-compose, запустил, всё супер, работает.
Почему я решил работу с базой оставить контроллеру а не бэку. Всё просто, задачи, которые обрабатывает бэк - крайне ресурсоёмкие, и в моём случае, распараллелить их не удается (один whisper кушает 6-8ГБ ОЗУ, а если брать нормальную модельку, то там минимум гигов 40). В совокупности, если бы бэку еще нужно было общаться с базой и отвечать на запросы фронта, это стоило бы ооооочень дорого.)
Вот тут я понял, что бэк я в любом случаю оставлю на Python, потому что там уже есть удобные библиотеки для работы с разными моделями, на чем писать контроллер и фронт.
Выбор языков для проекта
Для контроллера я выбрал то, что давно хотел начать изучать - Go, что касается фронта, на рандоме тыкнул в React с TypeScript, что в целом оказалось не плохо (Если бы я, конечно, умел писать фронт нормально)
Выбор сделан, работа закипела, но только по контроллеру и бэкенду.
Сделал простую структуру для конфигурации бэка и контроллера, и запихнул конфигурацию в yaml
Контроллер:
pg:
host: 127.0.0.1
port: 5432
user: virus
password: postgres
database: db
server:
host: 0.0.0.0
port: 8080
s3:
endpoint: http://127.0.0.1:9000
access_key: virus
secret_key: password
bucket: bucket
region: ru-central1
minio: true
Бэк:
tokens:
hf_token: ${HF_TOKEN}
ollama:
url: localhost
model: llama3.3
num_context: 5000
Ну и сделал им структуры. (Не знаю, стоит ли их приводить, всегда можно глянуть на гитхабе, тут, пожалуй, будет кода минимум)
Структура баз данных
Как видно из конфигурации, у меня есть подключение к базе данных (у бэкэнда она тоже была, но я уже не найду). Ну и раз мне нужна база, сразу же сделал структуру базы.


Идея была тупой простой, у меня есть контроллер, который записывает всё необходимое в базу данных. Все таблицы задач (diarize, convert, transcribe, report) связаны с таблицей с Совещаниями, плюс еще несколько вспомогательных таблиц. Как это должно было работать:
Приходит пользователь и загружает свою аудио/видео-запись
Контроллер обрабатывает и кладет в S3 файл, а в бд добавляет запись
Пользователь нажимает кнопочку
Контроллер достает данные по совещанию и по порядку ставит задачи в бэк
Бэк получает задачу и записывает её в свою базу, как задачу в очереди
Параллельно с этим бэк читает поставленные задачи из бд, и отдает ответ контроллеру по API
Контроллер пишет полученные результаты задачи и ставит новую по необходимости
Вы великолепны
Вроде бы всё идеально. Позже до меня дойдет, что я делаю какой-то бред, что я реализую очередь через дополнительную базу данных, но это где-то в будущем.)
Что делаем дальше, простой клиент для базы в бэке и в контроллере (я выбрал базу pg, так как в целом с ней знаком). Показывать буду на примере Go, но в целом в питоне было сделано так же стандартно.
type DBConfig struct {
Host string `yaml:"host"`
Port int `yaml:"port"`
User string `yaml:"user"`
Password string `yaml:"password"`
Database string `yaml:"database"`
}
type PGdatabase struct {
Config DBConfig
Pool *pgxpool.Pool
}
func NewDatabase(ctx context.Context, cfg DBConfig) (*PGdatabase, error) {
connectionInfo := fmt.Sprintf(
"postgres://%s:%s@%s:%d/%s?sslmode=disable",
cfg.User,
cfg.Password,
cfg.Host,
cfg.Port,
cfg.Database)
pool, err := pgxpool.New(ctx, connectionInfo)
if err != nil {
return nil, fmt.Errorf("Database connection error: %s", err)
}
if err = pool.Ping(ctx); err != nil {
return nil, fmt.Errorf("Database ping error: %s", err)
}
return &PGdatabase{
Pool: pool,
Config: cfg,
}, nil
}
func (d *PGdatabase) CloseConnection() {
if d.Pool != nil {
d.Pool.Close()
}
}
То есть я создал структуру конфигурации подключения к базе, проверку, всё ли нормально работает, есть ли база и т.п, и создал свою структуру в которую писал pool и конфигурацию, чтобы не нужно было перечитывать (просто так захотелось, окей).
То же самое проделал на бэке, всё завелось, добавил ручку чтобы можно было покидать запросы, посмотреть, что пишется в базу, что читается. Для запросов к базе использовал sqlc, крайне удобная вещь, где ты просто пишешь свой запрос и получаешь готовый код (спасибо тем людям, которые пишут штуки для генерации кода)
-- name: GetConversations :many
SELECT * FROM Conversations;
Привел пример простого запроса, он сам генерирует нужные типы данных, сам запрос на Go и всё что к нему прилагается, но нужно освоить конфиг, чтобы типы были нормальными а не pg.string и т.п. (с этим я тоже не мало пострадал, но 1 раз настроил и забыл)
Ну вот, первый запуск, все стартовало успешно, кинул запрос, записал в базу, прочитал из базы (писал curl'ом). Всё отлично, а что дальше...
Что же делать дальше...
А дальше нужно было делать постановку задач для бэка. И вот тут я понял свои ошибки. Использовать базу данных как очередь - плохо. Когда у тебя две базы данных - а какой смысл. Бэк принимает запросы по API? Вообще ужас. Callback с выполненной задачей по API? Тоже тяжело и бред, задачи могут потеряться. Но в тот момент я не видел этих проблем.
Ручки
Поэтому что я решил сделать? Правильно, сделать CRUD (Create, Read, Update, Delete) ручки для работы с базой. Это было просто, пишешь запрос в sqlc, генерируешь код, пишешь функцию, засовываешь эту функцию в ручку, которую потом дергаешь curl'ом по необходимости.) Сделал ручку для каждой таблички, для каждого значения, для создания строк, для изменения каждого значения.
Классно, супер, но зачем. Задача же стоит сделать общение с бэком. Что делать в этом случае. Правильно, отрефакторить бэк. В итоге переделал бэк, сделал там на fastAPI уже новые ручки, больше ручек. Я мог через swagger поставить разные задачи, закинуть туда файл по ссылке на S3. Всё так классно работает, но как это связать...
Рефакторинг
Я начинаю сам себе закапывать, пытаюсь понять, как сделать лучше, пишу доп функции, пишу генерики, чтобы было меньше повторений кода. Пишу пишу пишу пишу... А до результата ближе не оказываюсь. Переписываю, переписываю, переписываю, и вновь, я ухожу от главного, как поставить задачу в бэк.
В итоге на бессмысленное переписывание я убил неделю времени. Чтобы как-то сдвинуться с места, я решил спросить у друга, что я делаю не так. В ответ получаю, что почти всё. Но самое тупое - очередь через базу данных, бесконечное количество ручек и бесполезное улучшение того, что просто никак не будет использоваться при работе приложения.
Вывод
Для чего я это написал. Я описал то, как я начинал писать своё приложение. Описал решения, которые принимал, куда потратил много времени. Написал то, на какие грабли я наступил, этот этап, именно начала своего сервиса кроме как хождение по граблям - охарактеризовать никак не могу.
Если есть возможность, всегда лучше спросить у более опытных друзей.
Скоро напишу продолжение, про то, как я исправлял ошибки, менял базу, и многое другое.