image


Как изменить архитектуру монолитного продукта, чтобы ускорить его развитие, и как поделить одну команду на несколько, сохранив согласованность работы? Для нас ответом на эти вопросы стало создание нового API. Под катом вас ждёт обстоятельная история о пути к такому решению и обзор выбранных технологий, но для начала — небольшое лирическое отступление.


Несколько лет назад я прочёл в научной статье, что для полноценного обучения нужно всё больше и больше времени, а в недалёком будущем на получение знаний будет уходить восемьдесят лет жизни. Видимо, в IT это будущее уже наступило.


Мне посчастливилось начать программировать в те годы, когда не было разделения на бэкенд и фронтенд-программистов, когда не звучали слова «прототип», «продуктолог», «UX» и «QA». Мир был проще, деревья выше и зеленее, воздух чище и во дворах играли дети, а не парковались автомобили. Как бы мне ни хотелось вернуться в то время, нужно признать, что всё это не замысел суперзлодея, а эволюционное развитие общества. Да, общество могло развиваться иначе, но, как известно, история не терпит сослагательного наклонения.


Предыстория


BILLmanager появился как раз в те времена, когда не было жёсткого разделения по направлениям. Он имел согласованную архитектуру, умел управлять поведением пользователя и его даже можно было расширять плагинами. Шло время, команда развивала продукт, и вроде всё было хорошо, но стали наблюдаться странные явления. К примеру, когда программист занимался бизнес-логикой, он начинал плохо верстать формы, делал их неудобными и сложными для восприятия. Или добавление, казалось бы, простой функциональности отнимало несколько недель: архитектурно модули были жёстко связаны, поэтому при изменении одного приходилось корректировать другой.


Про удобство, эргономику и глобальное развитие продукта вообще можно было забыть, когда приложение падало с неизвестной ошибкой. Если раньше программист успевал делать работу в разных направлениях, то с ростом продукта и требований к нему это стало невозможно. Разработчик видел картину в целом и понимал, что если функция не будет правильно и стабильно работать, то формочки, кнопочки, тесты и продвижение не помогут. Поэтому откладывал всё и садился за исправление злосчастной ошибки. Совершал свой маленький подвиг, который оставался никем не оценённым (сил на правильную подачу клиенту уже просто не было), но функция начинала работать. Собственно, чтобы эти маленькие подвиги доходили до клиентов, в команде и появились люди, ответственные за разные направления: фронтенд и бэкенд, тестирование, дизайн, поддержку, продвижение.


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


Фронтенд и бэкенд


Стать профессионалом во всём — долго и дорого, поэтому современный мир прикладных программистов делится, в основной своей массе, на фронтенд и бэкенд.


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


Для какого-то абстрактного нового проекта этого будет достаточно, но у нас уже было написанное приложение, а объёмы планируемых работ и сроки их реализации явно указывали, что одной командой обойтись не получится. В баскетбольной команде пять человек, в футбольной — 11, а у нас было около 30. Под идеальную скрам-команду из пяти — девяти человек это никак не подходило. Надо было разделиться, но как при этом сохранить связность? Чтобы сдвинуться с места, нужно было решить архитектурную и организационную проблемы.


image
«Всё сделаем в одном проекте, так будет удобнее» — говорили они...


Архитектура


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


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


Нужно было разделять фронтенд и бэкенд, делать отдельные программные приложения: только так можно было начать развивать их требуемыми темпами и объёмами. Но как делать два проекта параллельно, менять их структуру, если они сильно зависят друг от друга?


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


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


image
Повысили устойчивость, разделив зоны ответственности.


Коммуникации


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


Кто-то скажет, что согласованность достигают с помощью повышения софт-скиллов. Да, их нужно развивать, но это не панацея. Посмотрите на дорожное движение, там тоже важно, чтобы водители были вежливы, умели объезжать случайные препятствия и помогали друг другу в сложных ситуациях. Но! Без правил дорожного движения мы даже при наилучших коммуникациях получили бы аварии на каждом перекрёстке и риск не доехать до места вовремя.


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


Мы называем эту команду «API», хотя техническая реализация нового API — это только малая часть её задач. Как общие участки кода выносят в отдельную функцию, так и команда API разбирает общие вопросы продуктовых команд. Именно здесь происходит соединение нашего фронтенда и бэкенда, поэтому участники этой команды должны понимать специфику каждого направления.


Возможно, «API» — не самое подходящее название для команды, больше подошло бы что-то про архитектуру или масштабное видение, но, думаю, это мелочь и сути не меняет.


API


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


Цели для нового API сформировались из ежедневных трудностей в реализации новых продуктовых и дизайнерских идей. Нам были нужны:


  1. Слабая связанность компонентов системы, чтобы бэкенд и фронтенд можно было развивать параллельно.
  2. Высокая масштабируемость, чтобы новый API не мешал наращивать функциональность.
  3. Стабильность и согласованность.

Поиск решения для API начали не с бэкенда, как это обычно принято, а, наоборот — подумали, что нужно пользователям.


Наиболее распространены разного рода REST API. В последние годы к ним добавляют описательные модели через инструменты типа swagger, но нужно понимать, что это тот же REST. И, по сути, его главный плюс и в то же время минус — это правила, которые носят исключительно описательный характер. То есть никто не запрещает создателю такого API отклоняться от постулатов REST при реализации отдельных частей.


Другим распространённым решением является GraphQL. Он тоже не идеален, но в отличие от REST, GraphQL API — это не просто описательная модель, а настоящие правила.


Выше я говорил про систему, которая должна была согласовывать работу фронтенда и бэкенда. Прослойка (interlayer) — это именно тот промежуточный уровень. Рассмотрев возможные варианты работы с сервером, мы остановились на GraphQL в качестве API для фронтенда. Но, так как бэкенд написан на C++, то реализация GraphQL-сервера оказалась нетривиальной задачей. Не буду здесь описывать все возникшие сложности и ухищрения, на которые мы шли, чтобы их преодолеть, реального результата это не принесло. Посмотрели на проблему с другой стороны и решили, что простота — залог успеха. Поэтому остановились на проверенных решениях: отдельный Node.js сервер с Express.js и Apollo Server.


Далее нужно было решить, как обращаться к API бэкенда. Сначала смотрели в сторону поднятия REST API, потом пробовали использовать аддоны на C++ для Node.js. В итоге поняли, что это всё нам не подходит, и после подробного анализа для бэкенда выбрали API на базе gRPC-сервисов.


Собрав воедино полученный опыт использования C++, TypeScript, GraphQL и gRPC, мы получили архитектуру приложения, позволяющую гибко развивать бэкенд и фронтенд, продолжая при этом создавать единый программный продукт.


Получилась схема, где фронтенд общается с промежуточным сервером с помощью GraphQL-запросов (знает, что спросить и что получит в ответ). GraphQL-сервер в резолверах вызывает API функции gRPC-сервера, при этом для связи они используют Protobuf-схемы. API-сервер на базе gRPC знает, у какого микросервиса взять данные, или кому передать полученный запрос. Сами микросервисы при этом тоже построены на gRPC, что обеспечивает скорость обработки запросов, типизацию данных и возможность использования различных языков программирования для их разработки.


image
Общая схема работы после изменения архитектуры


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


Результат


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


  1. За отображение отвечает фронтенд, а за данные — бэкенд.
  2. На фронтенде сохранилась гибкость в плане запросов и получения данных. Интерфейс знает, что можно попросить у сервера и какие ответы должны быть.
  3. На бэкенде появилась возможность менять код с уверенностью, что интерфейс у пользователя продолжит работать. Стал возможным переход на микросервисную архитектуру без необходимости переделывать весь фронтенд.
  4. Появилась возможность использования mock-данных для фронтенда, когда ещё не готов бэкенд.
  5. Создание схем совместной работы исключило проблемы взаимодействия, когда команды понимали одну и ту же задачу по-разному. Сократилось количество итераций по переделке форматов данных: действуем по принципу «семь раз отмерь, один раз отрежь».
  6. Появилась возможность планировать работы на спринт параллельно.
  7. Для реализации отдельных микросервисов теперь можно набирать разработчиков, не знакомых с C++.

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


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


Здесь я поверхностно затронул вопросы командной и межкомандной работы над одним продуктом, выбор технологи API (REST vs GraphQL), связь Node.js приложения с C++ и т. д. Каждая из этих тем тянет на отдельную статью, и если вам будет интересно, то мы их обязательно напишем.

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


  1. fukkit
    11.04.2019 15:16

    На КДПВ бэкендер огораживает свой стройный и прекрасный мир от результатов обсуждения фронтами последнего спринта по переползанию с React на Vue.

    (Не замечая, что его любимый тул ездит гусеницами по кривым рельсам)


  1. not_ice
    11.04.2019 17:48

    Проблематику как с языка сняли.
    Мы идём ровно тем же путём, только команда у нас чуть меньше, и стек бэкенда — Java.


    На этапе пилота мы столкнулись с низкой производительностью Apollo Server, а именно фазы сериализации данных. Вы проводили нагрузочные тесты? Какая планируется вычислительная мощность под Apollo?


    1. Sumbad Автор
      12.04.2019 07:14

      Пока детальных нагрузочных тестов не проводили. Замеряли производительность клиентских приложений до и после перевода на GraphQL, просадку не заметили. Вообще судя по обзорам и статьям сам по себе GraphQL сильных тормозов давать не должен. В любом случае спасибо за наводку, покопаю в этом направлении.


  1. Romario21
    11.04.2019 17:53

    Хоть бы кто показал готовый продукт, что бы понять имеет ли смысл подобного рода статьи.
    Я это вот к чему, ИТ тема стала хайповой последние пару лет. Народ пачками лезет в эту тему, появляется конкуренция, соответственно всем приходится что-то постоянно придумывать, что бы как то обосновать свою зп.
    И вот что я наблюдаю:
    1) Разросшиеся команды программистов
    2) Кучу продакт менеджеров
    3) Кучу аналитиков
    4) UI дизайнеров
    5) Бесконечные переговоры, встречи, согласования
    6) Туда-сюдашечки со стеками

    А по факту мы имеем обычные GUI приложения, которые писались еще и 5 и 10 и 15 лет назад, ну да более технологичные, но смысловая нагрузка вообще никак не поменялась.

    Хотелось бы видеть о какого рода продуктах вы говорите? Пощупать, потыкать… т.к сказать убедится, что все о чем вы написали действительно имеет практический смысл.

    Вот честно залез на ваш сайт, по скриншотам сразу бросаются в глаза стыренные с инета иконки, стилистически относящиеся к 2007-2008 году, судя по описанному функционалу и картинкам — это максимум 3 разработчика. Опять судя по картинкам не понятно зачем вам вообще front разработчик?


    1. not_ice
      11.04.2019 18:03

      А слова Gmail, Facebook и Twitter вам ни о чем не говорят? :)


      1. a-tk
        12.04.2019 17:53

        А бюджеты перечисленных выше сопоставляли со своими? :)


    1. Sumbad Автор
      12.04.2019 07:39
      +1

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

      Всё о чем я пишу в статье к сайту компании никакого отношения не имеет, им занимается отдельная команда. Мы работаем над продуктом BILLmanager (почитать про него можно здесь www.ispsystem.ru/software/billmanager, там же есть ссылка на demo). На demo нет новой API, но общие объемы задач, которые подтолкнули нас к изменениям можно увидеть.

      Правильно говорите тема IT хайповая. Разобраться в чем-то бывает не просто, всё усложняется ещё и тем, что многие зарабатывают на этом хайпе. В итоге чтобы найти реально полезные вещи приходится сначала наступить на много граблей. Я описал реальную ситуацию — огромный проект с большим легаси, чтобы развиваться пришлось начать многое изменять (что-то из этого получилось, что-то нет). Надеюсь кому-то это подскажет решение их проблем и получится пройти наш путь проще и быстрее.


    1. VolCh
      12.04.2019 10:48

      Почти дословно это многие говорят лет 15 уже минимум. А ещё так говорили, когда GUI пошёл в массы, только сравнивали с TUI (как у Norton Commander :) )


  1. 411
    11.04.2019 21:03

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


    Но эти затраты окупятся, когда пользователей API станет больше.

    Надеюсь, вы правы.


    1. Sumbad Автор
      12.04.2019 07:54
      +1

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


  1. kataus
    11.04.2019 23:55

    На самом деле статья про очередные микросервисы. Разделяй и властвуй (в теории)


  1. frankmasonus
    12.04.2019 04:05

    вот уже почти 30 лет одно и то же — Тонкие клиенты / толстые клиенты… rinse-wash-repeat. GraphQL или нет, но головой придется думать


    1. Sumbad Автор
      12.04.2019 07:55

      С этим не поспоришь, головой лучше всегда думать)


  1. andreyiq
    12.04.2019 06:40

    Тоже думали про GraphQL, но в итоге отказались, он решает одни проблемы и приносит новые. Одно не пойму, синтаксис GraphQL и gRpc похож на json, почему его не сделать полностью совместимым, была бы сразу поддержка парсинга на всех языках. В итоге пока как самый простой вариант остановился на json-rpc. Простой стандарт, реализация вызовом между сервисами на разных языках делается за пару часов и никого не надо долго обучать как с этим работать


    1. Gugic
      12.04.2019 07:22

      Постойте, но ведь вполне можно использовать json в качестве транспорта в grpc.


      Можно использовать json совместно с protobuf (когда вы хотите чтобы фронт, например, звал бэк с json, а разные сервисы на бэке звали друг-друга протобафами) или вместо protobuf.


  1. andreyiq
    12.04.2019 06:45

    Как дела у всех этих модных GraphQL и gRpc обстоят с документированием api? У GraphQL на сколько помню все довольно не плохо, а у gRpc?


    1. Gugic
      12.04.2019 07:28

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


  1. jetcar
    12.04.2019 14:07

    новомодные BE/FE разделения иногда выглядят глупо, есть же MVC при котором клиентскую часть будет рендерить сервер, фронтендеры при этом не страдают, а пишут темплейты с байдингом ну или как у автора Апи комманда которая создаёт прослойку которая только клиентской частью занимается и вместо базы данных будет сервис с которым каким-то образом можно общаться, REST или любой другой тут вообще нет ограничений в отличии от подхода где клиент из браузера может только по хттп спросить данные

    А вот насчёт формата данных от BE нужно подумать, у нас BE пишет сначала свой кусок и учит работать с ним FE, что иногда не очень потому что FE могут не понимать зачем им сложные куски и из них чтото собирать когда надо одну пропертю, иногда правы одни, иногда другие. BE хотят всё универсальным и быстродейственным зделать, a FE им написать табличку/формочку и забыть, а значит данные должны быть максимально простыми, и усложнения ведут к тому что может чтото поламаться и тогда клиенту вообще ничего не покажется


    1. VolCh
      14.04.2019 12:19

      Стоит различать BE/FE разделения организационные и технические. Если разделение чисто организационное, то часто страдают обе стороны. А даже введение отдельных темплейтов для фронтендеров уже по сути техническое разделение. Вопрос лишь как далеко заходить в этом разделении. А браузерный клиент ещё может данные по websocket запрашивать. Оно, конечно, не совсем независимо от http, но очень близко к тому


  1. VolCh
    14.04.2019 12:21

    Прочитал два раза пост, но так и не понял до конца, можно ли считать, что изобрели паттерн API Gateway с технической точки зрения?