Как я реализовал публикации постов в Telegram-боте
Нужно было прорубить окно между ботом и общим каналом обсуждения. Самой простой фичей стало — позволить пользователям бота публиковать свои блюда в телеграм-канал.
Вроде всё просто: уже есть таблица meals, формируем текст из её данных и отправляем в канал.
Но мое архитектурное мышление привыкло продумывать систему наперёд — с прицелом на развитие. Следующей фичей я хочу сделать возможность, чтобы пользователи канала могли сохранять публикации себе — тогда у них появится новый приём пищи в избранных, и они смогут переиспользовать его
Значит, публикации требуют отдельную таблицу. Хорошо, не вопрос — создаём meal_publications и будем добавлять туда запись после публикации блюда из meals:
I проблема
В боте уже реализовано, что любой пользователь может изменить своё блюдо в meals — например, указать новое количество калорий. Но тогда опубликованный пост будет показывать данные, не соответствующие реальности, ведь значения в meals изменились. И если кто-то потом сохранит себе это блюдо из поста, он получит уже изменённую версию, а не ту, что была опубликована.
Значит, нужно хранить не meal_id, а копию данных из meals на момент публикации:
II проблема
Опыт разработчика заставляет заранее заботиться о возможных проблемах. И одна из них — спам. Что если пользователь будет публиковать одно и то же блюдо несколько раз подряд? Я сразу решил: один и тот же человек не должен публиковать одно и то же блюдо, пока он его не изменит.
Казалось бы, просто: добавляем поле is_published в таблицу meals и переключаем его из true в false после редактирования.
Решено? На первый взгляд — да. Но идеализм архитектора меня остановил. Поле is_published придётся менять вручную, а это значит — появится лишнее правило, которое либо нужно автоматизировать триггером БД, либо документировать для других разработчиков. Мне хотелось решения, которое само будет поддерживать целостность данных, без костылей.
Идеальное решение
Перед публикацией блюда (то есть перед добавлением копии из meals в meal_publications) я считаю хэш.
Это слепок всех значимых и изменяемых пользователем полей meals, на основе которых формируется ключ.
Например:
После генерации хэша я сравниваю его с уже существующими записями в meal_publications. Если такой хэш уже есть — значит, это повторная публикация того же блюда, и она запрещается. Если нет — публикуем и сохраняем snapshot_hash:
Такое решение мне нравится тем, что сама база данных теперь требует наличие snapshot_hash и следит за его уникальностью. Больше не нужно никаких искусственных флагов is_published и ручных проверок. Система сама защищает себя от дубликатов и сохраняет чистую архитектуру.
На этом всё. До встречи. Пишите, как бы вы реализовали подобную механику