← Назад к вопросам

Был ли случай когда отстаивал инициативу и оказался не прав

1.0 Junior🔥 91 комментариев
#Soft Skills и карьера

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Случай когда я отстаивал инициативу и оказался не прав

Да, было несколько таких случаев. Рассказываю о одном из самых поучительных.

Контекст

Я работал в fintech компании, где мы мигрировали legacy Java 8 приложение на Java 17 с переходом на новый фреймворк. Я был одним из архитекторов этого проекта.

Моя инициатива

На этапе дизайна API я настоял на использовании полностью асинхронного подхода с Project Reactor (Spring WebFlux) вместо традиционного Spring MVC:

// Мой предложенный подход
@RestController
@RequestMapping("/api/transactions")
public class TransactionController {
    
    @GetMapping("/{id}")
    public Mono<TransactionResponse> getTransaction(@PathVariable String id) {
        return transactionService.getTransactionAsync(id)
            .map(TransactionMapper::toResponse);
    }
    
    @PostMapping
    public Mono<TransactionResponse> createTransaction(
        @RequestBody Mono<TransactionRequest> request) {
        return request
            .flatMap(transactionService::createTransactionAsync)
            .map(TransactionMapper::toResponse);
    }
}

Почему я это отстаивал

Мои аргументы были:

  1. Масштабируемость — асинхронность позволит обрабатывать больше запросов с меньше потоков
  2. Современность — Java 17 стоит использовать с современными паттернами
  3. Производительность — меньше context switches, меньше потребление памяти
  4. Будущеность — Virtual Threads (Project Loom) будут лучше работать с асинхронным кодом

Я провел presentation, показал бенчмарки нагрузочного тестирования, убедил архитектуру комитет. Команда согласилась.

Что пошло не так

После 3 месяцев разработки выяснилось несколько проблем:

1. Сложность отладки

Отладка асинхронного кода в боевых условиях оказалась намного сложнее. Stack trace'ы невозможно читать:

java.lang.RuntimeException: Something went wrong
    at reactor.core.publisher.FluxFilterFuseable$FilterFuseableSubscriber.onNext
    at reactor.core.publisher.MonoFlatMap$FlatMapInner.onNext
    at reactor.core.publisher.Flux$5.subscribe
    at reactor.core.publisher.FluxDefer.subscribe
    [... 50 more lines]

Отследить откуда именно пришла ошибка — nightmare для junior разработчиков.

2. Усложнение бизнес-логики

Просто логика вроде "сначала получить транзакцию, потом проверить баланс, потом обновить" превращалась в:

// Вместо простого
Transaction tx = transactionService.getTransaction(id);
if (!accountService.hasBalance(tx.getAccountId(), tx.getAmount())) {
    throw new InsufficientFundsException();
}
transactionService.updateStatus(tx.getId(), "CONFIRMED");

// Писали вот это
return transactionService.getTransactionAsync(id)
    .filterWhen(tx -> accountService.hasBalanceAsync(
        tx.getAccountId(), tx.getAmount())
        .map(has -> {
            if (!has) {
                throw new InsufficientFundsException();
            }
            return has;
        })
    )
    .flatMap(tx -> transactionService.updateStatusAsync(tx.getId(), "CONFIRMED")
        .map(ignore -> tx)
    );

Многие разработчики выбирали неправильные операторы Reactor (flatMap vs map vs concatMap), что приводило к deadlock-подобным ситуациям.

3. Нехватка опыта в команде

Оказалось, что реактивное программирование — это не просто async/await. Это другой парадигма:

  • Моноиды, композиция, функциональный подход
  • Backpressure
  • Scheduler'ы
  • Marble diagrams

Треть команды получила проблемы с production:

reactor.core.Exceptions$ErrorCallbackNotImplementedException: 
Received fatal signal:
java.lang.OutOfMemoryError

Оказалось, junior developer забыл добавить .onErrorResume() и все ошибки буферизировались в памяти.

4. Интеграция с legacy системами

Оказалось, что несколько critical сервисов (платежные шлюзы, compliance system) были синхронными и не поддерживали асинхронный вызовы. Пришлось делать оберки с .block(), что убило весь смысл асинхронности:

// Убийца асинхронности
return transactionService.getTransactionAsync(id)
    .map(tx -> {
        // block() = death of async
        var paymentResult = legacyPaymentService.process(tx).block();
        return tx;
    });

Что произошло дальше

Менеджер проекта провел meeting. Я признал свою ошибку и предложил две опции:

Опция A: Откатиться

Перейти назад на Spring MVC, потратить 2 недели на рефакторинг.

Опция B: Гибридный подход

Комбинировать асинхронность только где она имеет смысл (внешние HTTP API), остальное оставить синхронным.

Мы выбрали опцию B. Это было правильное решение.

Что я выучил

  1. Не влюбляйся в технологию — выбирай инструмент для конкретной проблемы, не наоборот

  2. Уважай уровень команды — асинхронный код требует старшего уровня разработчиков

  3. Проверяй assumptions — я не проверил реальную пропускную способность legacy систем

  4. Инвести в education — нужно было провести training до начала разработки

  5. Выслушивай feedback — когда видишь первые проблемы, это сигнал к переоценке

Вывод

Этот случай научил меня быть скромнее в отношении своих идей. Теперь я:

  • Начинаю с MVP (минимальный жизнеспособный продукт)
  • Слушаю feedback дольше
  • Не отстаиваю идею, если команда против
  • Предоставляю escape routes в своих дизайнах

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

Был ли случай когда отстаивал инициативу и оказался не прав | PrepBro