image


Привет! Согласитесь, во многих крупных компаниях рано или поздно возникает проблема — какой-то прибыльный продукт превращается в legacy. Причем обычно менеджмент это осознает, когда хочет "просто" поменять одну цифру на другую к вечеру, а разработчики оценивают это в два спринта. Или когда разработчики один за другим теряют мотивацию и покидают продукт, а новые кандидаты выбирают другие офферы.


Часто эту проблему пытаются решить переписыванием продукта с нуля. Но переписывание с нуля кроет в себе отложенную проблему, так как можно потерять мелкие нюансы и в итоге переписанный продукт будет поначалу болеть "детскими болячками", которые много лет назад были вылечены в legacy. Мы, в АльфаСтраховании считаем, что все члены команды разработки должны понимать, что их материальный успех зависит от того, сколько компания зарабатывает, используя написанный ими продукт. А сколько денег принесет продукт, который постоянно спотыкается и плюется ошибками? И клиентов не удовлетворить ответами из серии: "Ну зато у нас тут микросервисы и неблокирующие стримы". Им важно, чтобы продукт работал быстро и стабильно. А написан ли он в виде скрипта на bash или в виде микросервисов на Scala, потребителям наплевать. Конечно, разработчикам не нужно забывать и о своём развитии — регулярно изучать новые технологии, получать опыт использования их в продуктиве, но не в ущерб бизнесу.


Что с этим делать? Мы нашли для себя ответ, успешно применили на одном продукте и надеемся что этот подход или его части помогут и другим.


Примерно в 2014 г., когда у нас в компании вся разработка, по сути, была только в СУБД Oracle (с помощью PL/SQL), у кого-то возникла идея написать продукт на Java. Поставили разработчикам Java и сказали писать на ней. Экспертизы по Java ни у кого не было, архитектуру никто не проектировал, процедурный стиль программирования просто перенесли на Java, а для отрисовки UI разработчики выбрали Vaadin Framework. На продукте разработчики менялись с регулярностью, кто-то пытался переписать отдельные части, кто-то создавал новые классы, соответствующие ООП, но архитектуры по прежнему не было и официально бизнес не давал времени на рефакторинг. Скорость разработки фич была невысокой, в продукте часто появлялись ошибки, они долго находились и исправлялись, а мотивация разработчиков медленно и верно двигалась к 0. Бизнес это стало не устраивать, разработчиков тоже, и в какой-то момент встал вопрос, что делать с продуктом. И, как водится, ответ был такой: "Выкинуть и написать с нуля". Бизнес обеспокоился таким ответом и решил собрать "экспертную группу", чтобы они посмотрели на продукт и решили, что с ним можно сделать с минимальными потерями.


Вначале мы, как экспертная группа, решили конкретизировать все проблемы продукта, затем попробовать их сгруппировать и найти решения для каждой группы. Для этого мы опросили команду разработки, владельца продукта и даже узнали у некоторых кандидатов почему они отказались от офферов. Основные проблемы оказались такие:
image


Посмотрев на эти проблемы стало ясно, что их можно решить с помощью трех вещей: Архитектурного подхода, Шаблонов проектирования и Spring Framework.
image
В качестве архитектурного подхода (архитектурного паттерна) мы выбрали MVC плюс слой сервисов. Да, мы оставили монолит. Думаю не стоит в этой статье еще раз доказывать, что микросервисы это не серебряная пуля, приведу лишь пару доводов, почему все-таки монолит. UI в продукте уже был (Vaadin), он позволял Java разработчикам довольно быстро разрабатывать нормально выглядящий UI, весь функционал хорошо вписывался в одну доменную область, и монолит справлялся с нагрузкой с запасом. Поэтому плюсы монолита (простое и быстрое локальное развертывание и отсутствие внутренних интеграций) перевешивали минусы (горизонтальное масштабирование и масштабирование отдельных сервисов). Конечно эти плюсы монолита были превращены в архитектурные минусы — разработчики с радостью связывали UI компоненты с БД, добавляли какие-то утилитарные методы в классы, отвечающие только за отрисовку UI, вызывали методы одного UI класса из другого, что также привело к сильному запутыванию кода. Но наш архитектурный принцип жестко регламентировал, что так делать нельзя. Взаимодействие должно быть только таким:
image


Сервисы отвечали за бизнес-логику, чтобы обеспечить доступ к бизнес-функционалу и через API, и через пользовательский интерфейс. Т.е. оперировали бизнес-сущностями, а Controller отвечал только за поведение View. Controller включал в себя нужные сервисы и либо получал из них данные для отображения на UI, либо передавал данные по действиям юзера. Также эти же сервисы использовались через API. В дальнейшем можно расширять RestController (он уже был), менять технологию UI и все это без изменения бизнес-логики.


Из шаблонов проектирования основными стали Абстрактная фабрика и Стратегия. Основная суть приложения была дать партнерам возможность продавать наши страховки. Все было одинаково, менялись только объекты страхования и настройки для партнеров. Поэтому Абстрактная фабрика здесь очень подходила. А Стратегию использовали для реализации различных способов оплаты — страховку можно было оплатить разными способами и это влияло как на передаваемые данные, так и на способ обработки ответа от платежных систем. Позже все увидели, что паттерны очень сильно сократили объем дублированного кода и исключили "размазывание" бизнес-логики по нескольким классам.


С внедрением Spring были свои нюансы. В продукте было много самописных singleton, статических полей и методов — делать их всех бинами, а потом еще заменять их внедрение во всех классах за один раз выглядело нелегкой задачей. Мы нашли такое интересное решение, которое нам помогло переводить самописные singleton на Spring бины постепенно. Вот пример, когда у нас уже есть композиция, и мы сделали наш самописный singleton MyBusinessProcess Spring бином:


class SuperService {
  public void doSuperSmth() {
     Service.getInstance().doSmth();
  }
}

class Service {
  …  
  public static void getInstance() {...}

  public void doSmth() {
    //MyBusinessProcess.getInstance().startProcess();
    Context.getBean(MyBusinessProcess.class).startProcess();
  }
}

Как видите, нам нужно было только поменять вызов одного статического метода MyBusinessProcess.getInstance() на другой Context.getBean(MyBusinessProcess.class). Переведя приложение на SpringBoot и проверив этот подход с бинами выяснили, что это рабочий метод и благодаря ему можно проводить архитектурный и стилистический рефакторинг постепенно в спринтах. Мы договорились с бизнесом, что команда сможет тратить 40% спринта на рефакторинг. А чтобы не тыкаться просто по всему проекту, то рефакторить классы, которые затрагиваются в рамках бизнес-задачи. Если какие-то места не получается просто "зацепить" заодно, в рамках бизнес-задачи, договорились их заводить как задачи на рефакторинг в бэклог.


Ну вот, когда все теоретические и экспериментальные этапы были завершены, надо было приступать к непосредственно рефакторингу. В этом продукте не было тестов. Совсем. Поэтому любые изменения были опасны, а покрыть юнит-тестами всю систему, переполненную статическими полями и методами, было задачей не из легких. Хорошо, что в этом продукте был не сильно сложный UI и были REST методы, затрагивающие основные процессы. Перед началом изменений мы написали функциональные тесты на API — после этого можно было уже заниматься рефакторингом, не боясь кардинально что-то сломать.


Процесс пошел, на код ревью проверялось также соответствие принципам, анализатор кода проверял уровень покрытия тестами и баги. Через несколько спринтов сквозь трущобы начал прорисовываться стройный облик четко выстроенной системы.


Из нашей истории можно сделать минимум три вывода:


  1. Если решили использовать технологию, позаботьтесь о том, чтобы в команде был хотя бы один профессионал, который имеет в ней опыт
  2. Прежде чем делать систему необходимо спроектировать архитектуру, и если через какое-то время бизнес-логика кардинально меняется, необходимо перепроектировать продукт, давать время на рефакторинг и не копить технический долг
  3. Необходимо давать разработчикам возможность использовать в продукте современные mainstream технологии

На все это аргумент один — это экономически выгодно в среднесрочной и долгосрочной перспективе.


Чтобы не превращать пост в трехтомник, мы опустили многие детали. В комментариях с удовольствием ответим на вопросы и можем что-то раскрыть подробнее. Если у вас есть свои успешные подходы, будем рады услышать.

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


  1. bungu
    16.08.2022 23:02
    +2

    Мы, в АльфаСтраховании считаем, что все члены команды разработки должны понимать, что их материальный успех зависит от того, сколько компания зарабатывает, используя написанный ими продукт.

    А если задача была поставлена изначально неверно или продукт вообще оказался не востребован на рынке? Или если разработчик делает хорошо свою работу, но ему нет дела до того как и кто этот продукт продает? Должен ли он задумываться об этом? Или компания не может построить стратегию продажи?

    А если компания заработала оверденег на продукте? Значит ли это что разработчики получат справедливый процент от его продажи? И если нет то в чем будет выражаться их материальный успех?


    1. ilyademidow Автор
      17.08.2022 00:42

      Здесь все-таки указана наша идеология в конкретном контексте. Чуть добавлю деталей: продукт - это система, к которой подключаются компании и через него осуществляют продажи полисов; продукт (система) прибыльный и востребованный; команда состоит из владельца продукта и разработчиков; работа идёт короткими циклами.

      Невостребованные на рынке продукты в Компании не живут годами, если владелец продукта выдвинул неверную гипотезу, то в скором времени он это поймет по метрикам и отложит эту идею и пойдет придумывать новую фичу.

      А что входит в работу разработчика, которую он делает хорошо? На таких продуктах мы приветствуем, когда разработчик смотрит шире чем классы, методы и конфиги. Когда проектируешь структуру в бд, можно пристальнее посмотреть на данные и на их связи и даже задав вопрос продукт оунеру, можно посеять какую-то полезную идею в его голове. Опять же генерить идеи в одиночку, не так эффективно как командой. Но ещё раз, это не обязанность разработчиков, но очень большое преимущество. Если разработчику (да, пожалуй, и другим ролям) все равно на результат его труда, это нездоровая история.

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


    1. IsaninAV
      17.08.2022 12:01

      1) если продукт не востребован на рынке , то всё просто закрыли и всё , обычно более проблемно - если продукт зарабатывает 1 млн в месяц и не больше и сделан из дерьма и палок , что делать ? оставить и ждать пока он совсем бабахнет или вкладываться в рефакторинг ? или вообще закрыть , чтобы сфокусироваться на более успешных вещах
      поэтому здесь не так всё просто , мы стараемся либо рефактринг , либо вообще закрыть
      2) если разработчик делает работу хорошо , но нет дела как работает продукт - ну сложно будет делать продукт и не вовлекаться в работу при этом. Можно просто либо найти команду где продукт интереснее , либо заняться инфраструктурными задачами - core сервисы и тп
      Разработчик должен прежде всего задумываться о том чего он хочет, хочется работать с более классным продуктом => искать другую команду , не хочется вообще с бизнесом сталкиваться => core и здесь хорошо и бизнес интересный - > ну тогда надо продукт тоже понимать иначе это ерунда
      3) справедливый %% от прибыли вообще понятие абстрактное , у каждой партии он свой , акционеры вложили деньги , сейлзы и маркетинг продавали , операционисты поддерживали работу компании , инженеры из ИТ компы всем ставили и сеть тянули, безопасность следила, чтобы не было проблем иначе вся прибыль в трубу (главное не деньги , главное безопасность - я вот с этим согласен на самом деле ) , ну и соответственно команда разработки сделала классный продукт , какой справедливый %% она получит ? действительно будут всякие повышенные премии и легче в корпоративной иерархии продвигаться, быстрее оклад вырастет и в больших %% , но не более


  1. Antharas
    16.08.2022 23:05

    А давайте заведем практику - вместо инъекции зависимостей, будем напрямую инициализировать их из контекста во всем приложени.


    1. ilyademidow Автор
      17.08.2022 00:45
      -2

      Не уловил идею. Как инициализировать зависимость из контекста?


    1. ilyademidow Автор
      17.08.2022 11:27

      Вы про теорию или практику? Русский язык весьма богат и одно и то же можно называть разными словами. Как я понял вашу идею, Spring делает то о чем вы говорите, про другие IoC container фреймворки сказать не могу. А если вы говорите о другом, тогда встречный вопрос: "Является ли это мейнстримом в Java стэке?". Если порассуждать шире, почему в enterprise практически не пользуются вчера изобретенными или не прижившимися технологиями. Потому что у мейнстрим технологий понятны плюсы и минусы, найдены обходные пути, на рынке много людей, которые знакомы с ней. Поэтому если кому-то в голову пришла какая-то идея - отлично, возможно она перевернет индустрию, внедрите ее сначала в стартап, когда она там обкатается, тогда enterprise начнет к ней присматриваться