Pull to refresh
8K+
12
Денис@Snaret

Java developer

15,1
Rating
10
Subscribers
Send message

Полностью согласен) Но иногда и такое встречается. Поэтому не мог не подсветить)

А дальше по это теме пока не особо) StringTemplates и все)

это если его запустить через EscapeAnalysisInvokeDynamicStringTemplatesGarbageCollector

Привет!

За завтраком полистал код на гитхабе.

Действительно проект явно учебный. Я пока не дошел до шифрования, да и не претендую на понимание WebCrypto, но множество нездоровых моментов вынужден подсветить:

private void notifySharedChatsAboutProfileUpdate(Long updatedUserId) {
        Set<Long> chatIds = new LinkedHashSet<>();

        participantRepository.findByUserId(updatedUserId)
                .forEach(participant -> chatIds.add(participant.getChatId()));

        for (Long chatId : chatIds) {
            participantRepository.findByChatId(chatId).forEach(participant ->
                    userRepository.findById(participant.getUserId()).ifPresent(user ->
                            messagingTemplate.convertAndSend(
                                    "/topic/users/" + user.getUsername() + "/chats",
                                    Map.of(
                                            "chatId", chatId,
                                            "reason", "profile_updated",
                                            "updatedUserId", updatedUserId,
                                            "timestamp", System.currentTimeMillis()
                                    )
                            )
                    )
            );
        }
    }

Вот код уведомления юзеров.
Вы явно тут пропустили 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 - наверно валидно. Но для реального проекта с потенциалом нагрузки, расширения и реального использования - почти нет.

Так вот почему те кто больше двигаются - дольше живут! Они время для себя замедляют))))

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 кроме как удобства нет)

Ничего не понятно, но очень интересно...

1

Information

Rating
522-nd
Location
Россия
Date of birth
Registered
Activity

Specialization

Бэкенд разработчик
Средний
From 250,000 ₽
Java
SQL
REST
Spring Boot
Hibernate
ООП
Docker
Redis
Apache Kafka
PostgreSQL