Действительно проект явно учебный. Я пока не дошел до шифрования, да и не претендую на понимание WebCrypto, но множество нездоровых моментов вынужден подсветить:
Вот код уведомления юзеров. Вы явно тут пропустили N+1 проблему. Тут - participantRepository.findByChatId(chatId) - Для КАЖДОГО чата делается выборка ВСЕХ участников. Если у пользователя 100 чатов, будет 101 запрос (1 на получение его чатов + 100 на участников каждого чата). Кроме того если пользователь состоит в разных чатах, он получит уведомление из каждого. Стоит пересмотреть и сэкономить на количестве отправок (чем больше чатов тем больше будет заметна разница). В итоге ваш чат упадет на реальной нагрузке с >100 чатами и >50 участниками.
@Transactional
public void markChatAsRead(String username, Long chatId) {
User user = requireUser(username);
UserDevice currentDevice = currentDeviceOrNull();
String deviceId = deviceIdOrFallback(currentDevice);
requireParticipant(chatId, user.getId());
unreadService.reset(user.getId(), chatId);
List<Message> messages = messageRepository.findByChatIdAndSenderIdNot(chatId, user.getId());
for (Message message : messages) {
markReceiptRead(message, user.getId(), deviceId);
updateAggregateStatus(message);
sendStatusToSenderDevices(message, message.getStatus().name());
}
incrementCounter("messages_read_total", messages.size());
notifyChatListUpdated(chatId, "chat_read");
}
Или вот другой метод. Он делает слишком много. Если в чате 10 000 сообщений, этот метод
Достанет их все из БД
Пройдёт по каждому в цикле
Пошлёт 10 000 WebSocket уведомлений
И ожидаемо ляжет под нагрузкой.
Кроме того куча элементарного: куча логики в контроллерах (и даже создание дто КАРЛ!), возвращение Map<String, Object> вместо объекта, повсеместная конкатенация строк (String key = "unread:" + userId + ":" + chatId;) которая тоже по капле сожрет вашу память, сортировка в памяти (.sorted((a, b) -> { ... })), что также не даст пользоваться проектом большому количеству людей - все это либо сознательное упущение в пользу учебного проекта, либо Вам есть куда учиться.
Так или иначе я бы не стал заявлять столь громогласно о готовности)))
Я посмотрел не все. Проект большой и очень амбициозный. И я понимаю, что основная концентрация была скорее всего на E2EE, но в остальном данный проект лучше не запускать в большой мир - утонете в багах.
Будет желание - пишите, могу помочь с подсветкой подобных проблем. Могу порекомендовать неплохое видео по пагинации (не реклама).
Привет! В целом полезный, но крайне минималистический материал. Не раскрыто множество нюансов: про чанки на которые режется документ, что за настройка .topK(4)
Для более глубокого погружения в тему смею порекомендовать (не реклама) курс Е.Борисова или это видео.
Все правильно сказано. Да только есть у LLM одна основополагающая проблема - прокладка между креслом и клавиатурой, от которой LLM и получает задания и без них к сожалению пока это все не работает.
И сколько не обожествляйте LLM и не рассказывайте как она хорошо МОЖЕТ писать код - это всего лишь веса между персептронами. И если они настроены неверно, какой бы цифрой не заканчивалось название LLM вы дельного результата не получите.
Я конечно пользуюсь нейросетями (а кто сейчас не пользуется?), но доверять им полное написание кода я бы явно не спешил. Конечно, если только тестовый продукт, где минимум качества в угоду скорости - вполне себе вариант...
Позволил себе проанализировать предоставленный вами код и чего там только нет...
Несколько явных примеров:
Потенциальная OOM
public List getActiveTasks(Long telegramUserId) { ZoneId userZone = ZoneId.of(userService.getTimezone(telegramUserId)); //станет задач много и привет OOM return taskRepository.findByUserTelegramUserIdAndStatus(…) .stream() .map(task -> getTaskDto(task, userZone)) .toList(); }
@Transactional то есть то нет
//нет @Transactional это два отдельных запроса public TaskDto completeTask(Long telegramUserId, Long taskId) { Task task = taskRepository.findByIdAndUserTelegramUserId(...) //SELECT task.setStatus(TaskStatus.COMPLETED); taskRepository.save(task); //UPDATE, а с @Transactional вообще будет не нужен }
Не стал копировать большое дублирование кода, потенциальные race condition при быстром нажатии кнопок, нарушение Single Responsibility.
А ведь кто-то на вашем коде возможно будет учиться.
Вывод: как реклама ускорения процесса через Claude Code - наверно валидно. Но для реального проекта с потенциалом нагрузки, расширения и реального использования - почти нет.
Информационная система всё равно хранит и изменяет состояние, а делают изменения процессы.
Все верно. И в этом проблема, процессы построены на связи микросервисов через средства (Rest, gRPC и тд), которые в свою очередь ломаются и не доходят. Бизнес-логика начинает зависеть от того, как именно мы связали сервисы. Я хочу от этого уйти.
Если нужна согласованность данных, в любом случае будут правила, не допускающие повторных изменений и повторяющие попытки изменений, если они почему-то не произошли
И тут все верно, но в микросервисном мире эти правила размазаны между ними. То есть в одном сервисе 1я часть правил, во 2м вторая и так далее. В итоге согласованность - это не свойство модели, а эффект взаимодействия нескольких компонентов. Хотелось бы собрать эту логику в одном месте и сделать её атомарной и транзакционной.
Т.е. любой оркестратор, если он поддерживает согласованность данных, будет делать то, что вы описываете. И даже без оркестратора (стиль "хореография") будет то же самое, только логика размазана по сервисам.
В классическом оркестраторе согласованность данных является следствием корректно выполненного процесса: если все шаги A - B - C отработали, то состояние считается валидным. Если что-то не дошло — добавляются ретраи, компенсации и т.д. Я сознательно хочу перевернуть приоритеты. Первично не "какие шаги нужно выполнить2, а "какие инварианты состояния должны быть истинны после любого события". Процессов как таковых Engine не знает - он лишь применяет правила, пока состояние не станет согласованным, либо откатывается.
Внешне Engine действительно может выглядеть как оркестратор с жёсткой транзакцией. Отличие для меня в том, что порядок действий не кодируется явно и не является частью бизнес-логики - он выводится из зависимостей/правил между состояниями.
На мой взгляд немного не хватило теоретической глубины)
DLT предназначен не только для складирования ошибок десериализации и прочих но и обеспечения их наблюдаемости в асинхронных системах. Стоит упомянуть, что в крупных системах, сам факт сохранения доказательств и контекста сбоя для дальнейшей обработки - богоугодное дело)) По факту DLT — это не мусорка, а карантинный архив.
Не всегда Вам в pipeline дадут доступ к докеру. В этом случае могу порекомендовать Zonky который поднимет postgresQL в памяти без всяких контейнеров. Удобно и ощутимо быстрее чем с контейнерами.
Кроме того поголовное покрытие каждого метода интеграционными тестами приведет вас в Ад)) (был, видел) поэтому советую придерживаться пирамиды тестирования - много юнитов, чуть меньше модульных без контекста и мало интеграционных.
Полностью согласен) Но иногда и такое встречается. Поэтому не мог не подсветить)
А дальше по это теме пока не особо) StringTemplates и все)
это если его запустить через EscapeAnalysisInvokeDynamicStringTemplatesGarbageCollector
Привет!
За завтраком полистал код на гитхабе.
Действительно проект явно учебный. Я пока не дошел до шифрования, да и не претендую на понимание WebCrypto, но множество нездоровых моментов вынужден подсветить:
Вот код уведомления юзеров.
Вы явно тут пропустили N+1 проблему. Тут - participantRepository.findByChatId(chatId) - Для КАЖДОГО чата делается выборка ВСЕХ участников. Если у пользователя 100 чатов, будет 101 запрос (1 на получение его чатов + 100 на участников каждого чата).
Кроме того если пользователь состоит в разных чатах, он получит уведомление из каждого. Стоит пересмотреть и сэкономить на количестве отправок (чем больше чатов тем больше будет заметна разница).
В итоге ваш чат упадет на реальной нагрузке с >100 чатами и >50 участниками.
Или вот другой метод. Он делает слишком много. Если в чате 10 000 сообщений, этот метод
Достанет их все из БД
Пройдёт по каждому в цикле
Пошлёт 10 000 WebSocket уведомлений
И ожидаемо ляжет под нагрузкой.
Кроме того куча элементарного: куча логики в контроллерах (и даже создание дто КАРЛ!), возвращение Map<String, Object> вместо объекта, повсеместная конкатенация строк (String key = "unread:" + userId + ":" + chatId;) которая тоже по капле сожрет вашу память, сортировка в памяти (.sorted((a, b) -> { ... })), что также не даст пользоваться проектом большому количеству людей - все это либо сознательное упущение в пользу учебного проекта, либо Вам есть куда учиться.
Так или иначе я бы не стал заявлять столь громогласно о готовности)))
Я посмотрел не все. Проект большой и очень амбициозный. И я понимаю, что основная концентрация была скорее всего на E2EE, но в остальном данный проект лучше не запускать в большой мир - утонете в багах.
Будет желание - пишите, могу помочь с подсветкой подобных проблем. Могу порекомендовать неплохое видео по пагинации (не реклама).
Привет! В целом полезный, но крайне минималистический материал. Не раскрыто множество нюансов: про чанки на которые режется документ, что за настройка
.topK(4)Для более глубокого погружения в тему смею порекомендовать (не реклама) курс Е.Борисова или это видео.
Все правильно сказано. Да только есть у LLM одна основополагающая проблема - прокладка между креслом и клавиатурой, от которой LLM и получает задания и без них к сожалению пока это все не работает.
И сколько не обожествляйте LLM и не рассказывайте как она хорошо МОЖЕТ писать код - это всего лишь веса между персептронами. И если они настроены неверно, какой бы цифрой не заканчивалось название LLM вы дельного результата не получите.
Привет.
Я конечно пользуюсь нейросетями (а кто сейчас не пользуется?), но доверять им полное написание кода я бы явно не спешил. Конечно, если только тестовый продукт, где минимум качества в угоду скорости - вполне себе вариант...
Позволил себе проанализировать предоставленный вами код и чего там только нет...
Несколько явных примеров:
Потенциальная OOM
public List getActiveTasks(Long telegramUserId)
{
ZoneId userZone = ZoneId.of(userService.getTimezone(telegramUserId));
//станет задач много и привет OOM
return taskRepository.findByUserTelegramUserIdAndStatus(…) .stream() .map(task -> getTaskDto(task, userZone)) .toList();
}
@Transactional то есть то нет
//нет @Transactional это два отдельных запроса
public TaskDto completeTask(Long telegramUserId, Long taskId) {
Task task = taskRepository.findByIdAndUserTelegramUserId(...) //SELECT task.setStatus(TaskStatus.COMPLETED);
taskRepository.save(task); //UPDATE, а с @Transactional вообще будет не нужен
}
Не стал копировать большое дублирование кода, потенциальные race condition при быстром нажатии кнопок, нарушение Single Responsibility.
А ведь кто-то на вашем коде возможно будет учиться.
Вывод: как реклама ускорения процесса через Claude Code - наверно валидно. Но для реального проекта с потенциалом нагрузки, расширения и реального использования - почти нет.
Так вот почему те кто больше двигаются - дольше живут! Они время для себя замедляют))))
https://habr.com/ru/articles/992060/ - почитайте, есть схожесть идей
Крайне маленькая статья для огромной темы. Таких статей много и гораздо более подробных.
Нужно искать изюминку в теме и выдать ее так чтобы всем было интересно)
В текущем случае - статья ради статьи.
Но Вы продолжайте писать, однажды все получится)
Все верно. И в этом проблема, процессы построены на связи микросервисов через средства (Rest, gRPC и тд), которые в свою очередь ломаются и не доходят. Бизнес-логика начинает зависеть от того, как именно мы связали сервисы. Я хочу от этого уйти.
И тут все верно, но в микросервисном мире эти правила размазаны между ними. То есть в одном сервисе 1я часть правил, во 2м вторая и так далее. В итоге согласованность - это не свойство модели, а эффект взаимодействия нескольких компонентов. Хотелось бы собрать эту логику в одном месте и сделать её атомарной и транзакционной.
В классическом оркестраторе согласованность данных является следствием корректно выполненного процесса: если все шаги A - B - C отработали, то состояние считается валидным. Если что-то не дошло — добавляются ретраи, компенсации и т.д. Я сознательно хочу перевернуть приоритеты. Первично не "какие шаги нужно выполнить2, а "какие инварианты состояния должны быть истинны после любого события".
Процессов как таковых Engine не знает - он лишь применяет правила, пока состояние не станет согласованным, либо откатывается.
Внешне Engine действительно может выглядеть как оркестратор с жёсткой транзакцией. Отличие для меня в том, что порядок действий не кодируется явно и не является частью бизнес-логики - он выводится из зависимостей/правил между состояниями.
Я согласен, что внешне это похоже на оркестрацию, потому что есть центральный компонент. Разница для меня не в топологии, а в том кто за что отвечает.
Оркестратор управляет процессами, сделай A → потом B → если не получилось — компенсируй C, а моя идея управляет состоянием данных.
Идеи схожи, но отличие именно в модели мышления process-first и state-first
Спасибо за статью!
На мой взгляд немного не хватило теоретической глубины)
DLT предназначен не только для складирования ошибок десериализации и прочих но и обеспечения их наблюдаемости в асинхронных системах. Стоит упомянуть, что в крупных системах, сам факт сохранения доказательств и контекста сбоя для дальнейшей обработки - богоугодное дело))
По факту DLT — это не мусорка, а карантинный архив.
Спасибо за статью)
Не всегда Вам в pipeline дадут доступ к докеру. В этом случае могу порекомендовать Zonky который поднимет postgresQL в памяти без всяких контейнеров. Удобно и ощутимо быстрее чем с контейнерами.
Кроме того поголовное покрытие каждого метода интеграционными тестами приведет вас в Ад)) (был, видел) поэтому советую придерживаться пирамиды тестирования - много юнитов, чуть меньше модульных без контекста и мало интеграционных.
Погуглите проект Valhalla
Это да. Уже не раз встречал и каждый раз страдаю)
Сразу хотел добавить демонстрационный код, но побоялся из-за объема. Сейчас подумал и добавил)
Спасибо за подсказку, добавил)
Ну на поверхности или нет, но очень часто вижу, что многие этого вовсе не знали)
Аргументов в пользу field injection кроме как удобства нет)
Ничего не понятно, но очень интересно...