Обещал рассказать про сложности, с которыми я столкнулся на работе.
Долго думал, как описать это интересно, понятно и без нарушения NDA. Вот что получилось! 😊
🧠 Представим кофейню:
Клиент делает заказ. Он участник программы лояльности, у него есть баллы.
☕️ Кассир спрашивает:
"Молодой человек, что делаем с баллами? Списываем или копим?"
👤 Клиент отвечает:
"Списываем!"
Кассир нажимает кнопки и говорит:
"Подтвердите списание!"
Клиент достаёт телефон, заходит в Телеграм и видит сообщение от бота:
💬 "Вы уверены, что хотите списать 324 балла?"
Кнопки: 🔘 Да | 🔘 Нет
Клиент нажимает Да, касса печатает чек.
Как это работает на сервере? 🖥
На сервере процесс списания баллов требует подтверждения. Всё начинается с запроса:
1️⃣ В таблице client_ask фиксируется вопрос клиенту и ожидается его ответ.
2️⃣ На основании ID созданной записи отправляется сообщение клиенту в ТГ.
3️⃣ Сервер ждёт, когда в записи появится ответ в колонке response.
Код выглядит примерно так: 👇
@Service
public class MyService {
@Autowired
private ClientAskRepository clientAskRepository;
@Autowired
private TgMessageService messageService;
@Transactional
public void askClient() {
ClientAsk clientAsk = new ClientAsk(1L, null);
clientAskRepository.save(clientAsk);
// Отправка запроса на подтверждение
messageService.sendClientAsk(clientAsk);
while (clientAsk.getResponse() == null) {
// Ждём ответа
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
clientAsk = clientAskRepository.findById(clientAsk.getId()).orElse(null);
}
}
@Transactional
public void saveClientResponse(Long id, String response) {
clientAskRepository.findById(id).ifPresent(clientAsk -> {
clientAsk.setResponse(response);
clientAskRepository.save(clientAsk);
});
}
}
🤯 Где начинаются проблемы?
Метод
askClient создаёт запись и отправляет запрос в ТГ бот. Он ждёт, пока response обновится.Метод
saveClientResponse должен записывать ответ, но...🚨 Ошибка!
Данные, созданные транзакцией
askClient, недоступны для других транзакций, пока они не закоммичены.Причина: уровень изоляции транзакций по умолчанию —
READ_COMMITTED.Транзакция видит только закоммиченные изменения.
🔧 Как мы решили проблему?
Мы выбрали простой, но спорный метод:
👉
saveClientResponse создаёт новую запись, а askClient ждёт появления новой записи вместо обновления существующей.Да, это костыль, но он работает. 😅
🛠 Какие ещё есть варианты?
1️⃣ Событийно-ориентированная архитектура:
Можно отправлять события вместо прямого обновления БД.
2️⃣ Выделить создание записи в отдельный сервис:
Это откроет новую транзакцию, которая завершится, и данные станут видимыми.
3️⃣ Изменить уровень изоляции:
Попробовать
READ_UNCOMMITTED. Но, увы, в нашем случае это не помогло.А как бы вы решили такую задачу? Делитесь мыслями в комментариях! ✍️
