Обновить
4K+
13
Денис@Snaret

Java developer

-1,1
Рейтинг
21
Подписчики
Отправить сообщение

А если ты правильно формулируешь технические мысли, как тебе кажется, но большинству читателей это непонятно?

У вас однобокое мнение. Это как если Пикассо в начале карьеры должен был сразу уметь рисовать на уровне поздних картин.

По делу - это не меньше)) Написать 2 строчки - кому такая статья будет интересна?

Если бы так - вы бы увидели ссылку. Но ее нигде нет, вот неожиданность да?)

Отчаянно плюсую

Оставлю свое мнение. Давно искал повода.

Я полностью согласен с автором о том что нейрослоп захватывает Хабр. И делается это в 90% прочитанных мной статей. Но есть более важная на мой взгляд проблема - это именно мусорная реклама обернутая в нейрослоп.

Я, из-за специфики подписан только на 1 хаб - это Java, там каждая вторая статья - это прикрытая какойто технологией реклама сервиса, компании, продукта или личных амбиций. И бог с ним, когда статья несет реальную технологическую ценность, я готов с этим мириться. Но чаще нет.

И обратная сторона монеты.
Я давно работаю с кафка. И долго искал решение в соотношении типов данных разных микросервисов (кто знает тот знает). Не совсем очевидный и часто встречаемый вопрос, чаще всего решаемый разрабами кастомными способами.
Я искал варианты на Хабре, но их практически нет. И когда я нашел для этого, как мне кажется один из удобнейших способов, встроенных как оказалось в кафка, я решил написать статью.
Да, я пользовался нейросетью. Да. я не умею писать 10000 знаков от руки, мне это кажется сложным. И ожидаемо мою статью заблокировали. Я переписал ее от руки и ее тоже заблокировали. Таким образом Хабр, как считаю, лишил искателей решения одного из вариантов.

Вот тут я хотел бы чтобы меня услышали - в тоннах нейрослопа который все равно публикуют даже крупные компании - Хабр, вы хоть немного фильтруйте между рекламой и реально полезными техническими материалами, несмотря на источник статьи. Ведь вы реально теряете часть полезных материалов от тех авторов, которые не умеют писать длинные и умные статьи.

В итоге я расстроился и написал свой сайт (как и автор данного поста) куда спокойно публикую то, что считаю важным и пусть часть его сгенерировано нейросетью. Данные статьи проходят хорошую редакцию и делают именно то что должны - дают читателю ту информацию которую он ищет.

А на Хабр я практически перестал заходить, хотя раньше пользовался им каждый день.

Переведу для остальных: "я навайбкодил что-то, что работает. Я этому рад и делюсь с вами своей радостью. Мне известен принцип его работы, но если что-то сломается - я надеюсь лимиты у меня еще есть"

Спасибо за статью. Правда. Приятно понимать, что еще не все сошли с ума на хайпе ИИ и есть трезвомыслящие люди которые осознают, что что-то идет не так.

Подписываюсь под всеми вашими словами. Лень, хайп, заблуждение, алчность - вот фундамент сегодняшней системы разработки с псевдоИИ (подозреваю, что на последнее место куда я собесился меня не взяли из того, что я на вопрос об отношении к разработке и ИИ, ответил, что крайне им не доверяю и вынужден проверять каждый шаг. Привет Газпром)

Вы скопировали мои мысли

"Всяк кулик свое болото хвалит"

Жаль, а я надеялся в статье найти реальный кейс. Хотя и работы то нет...))

Вот одного не понял - а Java где?

На самом деле там все просто) при отсутствии явного ордеринга перехватчики будут выставлены по алфавиту))
То есть сначала CacheInterceptor потом TransactionInterceptor а AspectJAroundAdvice преобразуется в InstantiationModelAwarePointcutAdvisorImpl и будет последним в очереди

Вариант - "А у него длинее...", аххах)))))

Спасибо на добром слове) Не было комментариев - думал уже ерунду написал что-ли))))

Поразмышлял над вашей статьей еще раз.

Оформил экспериментус. Дипсику скормил хороший промпт и вашу задачу.

Вот что он мне выдал

Скрытый текст
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;

// ---------- Request DTO ----------
class NotificationRequest {
    private final String requestId;
    private final NotificationType type;
    private final String recipient;
    private final String message;

    private NotificationRequest(Builder builder) {
        this.requestId = builder.requestId;
        this.type = builder.type;
        this.recipient = builder.recipient;
        this.message = builder.message;
    }

    public String getRequestId() { return requestId; }
    public NotificationType getType() { return type; }
    public String getRecipient() { return recipient; }
    public String getMessage() { return message; }

    public static class Builder {
        private String requestId;
        private NotificationType type;
        private String recipient;
        private String message;

        public Builder requestId(String requestId) { this.requestId = requestId; return this; }
        public Builder type(NotificationType type) { this.type = type; return this; }
        public Builder recipient(String recipient) { this.recipient = recipient; return this; }
        public Builder message(String message) { this.message = message; return this; }
        public NotificationRequest build() { return new NotificationRequest(this); }
    }
}

// ---------- Enum ----------
enum NotificationType {
    EMAIL,
    SMS,
    PUSH
}

// ---------- Validator ----------
interface Validator {
    boolean isValid(NotificationRequest request);
}

class EmailValidator implements Validator {
    @Override
    public boolean isValid(NotificationRequest request) {
        return request.getRecipient() != null && request.getRecipient().contains("@");
    }
}

class SmsValidator implements Validator {
    private static final int EXPECTED_DIGITS_AFTER_PLUS = 11;

    @Override
    public boolean isValid(NotificationRequest request) {
        String recipient = request.getRecipient();
        if (recipient == null) return false;
        if (!recipient.contains("+")) return false;
        int plusIndex = recipient.indexOf('+');
        String afterPlus = recipient.substring(plusIndex + 1);
        return afterPlus.length() == EXPECTED_DIGITS_AFTER_PLUS;
    }
}

class PushValidator implements Validator {
    @Override
    public boolean isValid(NotificationRequest request) {
        String msg = request.getMessage();
        return msg != null && !msg.trim().isEmpty();
    }
}

// ---------- Validator Factory ----------
class ValidatorFactory {
    private static final Map<NotificationType, Validator> VALIDATORS = new EnumMap<>(NotificationType.class);

    static {
        VALIDATORS.put(NotificationType.EMAIL, new EmailValidator());
        VALIDATORS.put(NotificationType.SMS, new SmsValidator());
        VALIDATORS.put(NotificationType.PUSH, new PushValidator());
    }

    public static Validator getValidator(NotificationType type) {
        Validator validator = VALIDATORS.get(type);
        if (validator == null) {
            throw new IllegalArgumentException("Unsupported notification type: " + type);
        }
        return validator;
    }
}

// ---------- Sender ----------
interface Sender {
    void send(NotificationRequest request);
}

class EmailSender implements Sender {
    @Override
    public void send(NotificationRequest request) {
        System.out.println("Sending EMAIL to " + request.getRecipient());
    }
}

class SmsSender implements Sender {
    @Override
    public void send(NotificationRequest request) {
        System.out.println("Sending SMS to " + request.getRecipient());
    }
}

class PushSender implements Sender {
    @Override
    public void send(NotificationRequest request) {
        System.out.println("Sending PUSH to " + request.getRecipient());
    }
}

// ---------- Sender Factory ----------
class SenderFactory {
    private static final Map<NotificationType, Sender> SENDERS = new EnumMap<>(NotificationType.class);

    static {
        SENDERS.put(NotificationType.EMAIL, new EmailSender());
        SENDERS.put(NotificationType.SMS, new SmsSender());
        SENDERS.put(NotificationType.PUSH, new PushSender());
    }

    public static Sender getSender(NotificationType type) {
        Sender sender = SENDERS.get(type);
        if (sender == null) {
            throw new IllegalArgumentException("Unsupported notification type: " + type);
        }
        return sender;
    }
}

// ---------- Main Service ----------
class NotificationService {
    private static final Logger LOGGER = Logger.getLogger(NotificationService.class.getName());

    private final Set<String> processedIds = ConcurrentHashMap.newKeySet();
    private int processedCount = 0;
    private int duplicateCount = 0;
    private int failedCount = 0;

    /**
     * Processes a single request: validates, checks for duplicates, and sends if valid.
     */
    public void send(NotificationRequest request) {
        // 1. Duplicate check
        if (request.getRequestId() == null) {
            // requestId null is considered invalid (will be counted as failed)
            // but we still need to process? We'll treat as failed, but not as duplicate.
            // However, duplicate check requires requestId; if null, we cannot track it.
            // According to spec: requestId == null is invalid, so we skip duplicate check.
            // We'll directly mark as failed.
            LOGGER.log(Level.WARNING, "Request with null requestId – marking as failed");
            failedCount++;
            return;
        }

        if (!processedIds.add(request.getRequestId())) {
            // Already processed
            duplicateCount++;
            LOGGER.log(Level.INFO, "Duplicate request skipped: {0}", request.getRequestId());
            return;
        }

        // 2. Common null checks (as per spec: requestId, recipient, message must not be null)
        if (request.getRecipient() == null || request.getMessage() == null) {
            failedCount++;
            LOGGER.log(Level.WARNING, "Request {0} failed: recipient or message is null", request.getRequestId());
            return;
        }

        // 3. Type‑specific validation
        Validator validator = ValidatorFactory.getValidator(request.getType());
        if (!validator.isValid(request)) {
            failedCount++;
            LOGGER.log(Level.WARNING, "Request {0} failed validation", request.getRequestId());
            return;
        }

        // 4. Send
        Sender sender = SenderFactory.getSender(request.getType());
        sender.send(request);
        processedCount++;
        LOGGER.log(Level.INFO, "Request {0} processed successfully", request.getRequestId());
    }

    /**
     * Processes a list of requests.
     */
    public void processAll(List<NotificationRequest> requests) {
        for (NotificationRequest req : requests) {
            send(req);
        }
    }

    /**
     * Prints the final statistics.
     */
    public void printStats() {
        System.out.println("Processed: " + processedCount);
        System.out.println("Duplicates skipped: " + duplicateCount);
        System.out.println("Failed: " + failedCount);
    }
}

// ---------- Main Demonstration ----------
public class Main {
    public static void main(String[] args) {
        NotificationService service = new NotificationService();

        List<NotificationRequest> requests = Arrays.asList(
                // Valid requests
                new NotificationRequest.Builder()
                        .requestId("1")
                        .type(NotificationType.EMAIL)
                        .recipient("user1@mail.com")
                        .message("Hello email")
                        .build(),
                new NotificationRequest.Builder()
                        .requestId("2")
                        .type(NotificationType.SMS)
                        .recipient("+79990001122")
                        .message("Hello sms")
                        .build(),
                new NotificationRequest.Builder()
                        .requestId("3")
                        .type(NotificationType.PUSH)
                        .recipient("device-123")
                        .message("Hello push")
                        .build(),
                // Duplicate of requestId "1"
                new NotificationRequest.Builder()
                        .requestId("1")
                        .type(NotificationType.EMAIL)
                        .recipient("duplicate@mail.com")
                        .message("Duplicate")
                        .build(),
                // Invalid: null requestId
                new NotificationRequest.Builder()
                        .requestId(null)
                        .type(NotificationType.EMAIL)
                        .recipient("nullid@mail.com")
                        .message("null id")
                        .build(),
                // Invalid: recipient null
                new NotificationRequest.Builder()
                        .requestId("4")
                        .type(NotificationType.EMAIL)
                        .recipient(null)
                        .message("no recipient")
                        .build(),
                // Invalid: email without '@'
                new NotificationRequest.Builder()
                        .requestId("5")
                        .type(NotificationType.EMAIL)
                        .recipient("invalid-email")
                        .message("bad email")
                        .build(),
                // Invalid: SMS without '+'
                new NotificationRequest.Builder()
                        .requestId("6")
                        .type(NotificationType.SMS)
                        .recipient("79990001122")
                        .message("no plus")
                        .build(),
                // Invalid: SMS with wrong length after '+'
                new NotificationRequest.Builder()
                        .requestId("7")
                        .type(NotificationType.SMS)
                        .recipient("+123456")
                        .message("short")
                        .build(),
                // Invalid: PUSH empty message
                new NotificationRequest.Builder()
                        .requestId("8")
                        .type(NotificationType.PUSH)
                        .recipient("device-456")
                        .message("")
                        .build()
        );

        service.processAll(requests);
        service.printStats();
    }
}


Теперь вывод: у сеньора промпт лучше. За 10 минут он еще кофе с сигаретой прикончить успел.

Полюбому

С посылом статьи на 100% согласен, к оформлению доказательств вопросы...

КОД каждого участника в студию!
А так субъективному оценочному суждению привык не доверять, уж извините.

И мидла незаслуженно унизили) Ему задачки закрывать надо, быстро и более менее качественно. Ну нет у него 2-3 часа на неспешные раздумия, на таких как он IT держится.

И по соотношениям время исполнения/качество/цена работы в час он явно будет впереди.

А этому в принципе не верю (Сеньор с нейросетью 10 минут Все преимущества, что и без нейросетей 9/10 Все преимущества, что и без нейросетей).
Нейросеть то та же. А значит выдаст тот же примитив. Ну и если сеньор за 9 минут 30 секунд реализовал все как боженька, то я Жанна Дарт Вейдер))))

Хоть это больше и реклама, спасибо за труд.

Но вы почему-то делаете из сеньора какого-то зазнайку. Он по вашему постоянно съезжает с темы, начинает погружаться туда, куда не просили и действительно, как написано выше в комментах, забалтывать вопрос.

И не смотря на то что в статье действительно много полезных фактов в целом она не несет какой-то ценности для любого грейда кроме понимания - "Если сеньор такой - то я не сеньор"

Хорошая статья, немного затянутая, но подробная. Могу посоветовать эту серию видео где данная Observabilty (в т.ч.) разбирается.

Спасибо за код. Если это действительно из собесов, то тут поле не паханное))

Каюсь, сначала нашел не то что вы указали)

Но предлагаю в список добавить и эти (сори не читал комментарии может уже что-то есть):

Отсутствие версионирования (/v1/payments) - API без версии ломает обратную совместимость при любом изменении. Клиенты, завязанные на этой версии, перестанут работать. Вообще показывает способность к расширению

ResponseEntity<?>
сырой дженерик теряет информацию о типе тела ответа. В Swagger/OpenAPI это превратится в object, клиенты не смогут сгенерировать типизированные модели

@RequestBody Map<String, Object> 
теряется контракт API. Нет @Valid, нет документации полей, нет контроля типов. Любое изменение структуры ломается в рантайме ClassCastException

new Date()
java.util.Date - мутабельный, устаревший класс, не рекомендуется к использованию хотя и допустимф

p.setStatus("PENDING")
строковый литерал для статуса - это нонсенс. Опечатка ("PENING") не будет поймана компилятором, думаю выводы сделаете сами)

!"OK".equals(r)
Не совсем плохо но лучше - !Objects.equals("OK", r), более явно проверяет что r != null

Вызов kafka.send без обработки результата
метод асинхронный, но исключения теряются. Нужно добавить callback или использовать синхронную отправку.

RuntimeException - слишком общее исключение. Оно не говорит вызывающему коду (и глобальному обработчику) о том, что именно пошло не так. Лучше - кастомную

Далее более архитектурная проблема:

@Transactional на методе контроллера и отсутствие сервисного слоя
Когда вы ставите @Transactional на контроллер, контроллер берёт на себя ответственность за управление транзакциями - это нарушает принцип SR
Что ждет с таким подходом:
- нормально не протестировать - придется кучу зависимостей тянуть и все либо мокать либо подымать
- невозможно переиспользовать логику, для написания похожей обработки - новая ручка
- расширение приведет только к росту проблем



В чем выгода по сравнению с кейклок или аутентик?

Информация

В рейтинге
Не участвует
Откуда
Россия
Дата рождения
Зарегистрирован
Активность

Специализация

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