Framework Agnostic длиной в 12 лет

Framework Agnostic длиной в 12 лет

Кирилл Егоров

Мой канал: https://t.me/HeadOfWeb

Доклад для: https://t.me/php_nn

Роберт Мартин в секции ответов на вопросы после выступления на очередной конференции в 2019 году:

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

Когда вы используете фреймворк, вы берете на себя огромную ответственность за то, что этот фреймворк привязывает ваш код к себе. В самом худшем случае вы наследуетесь от базовых классов этого фреймворка. С другой стороны у авторов фреймворков нет таких обязательств перед вами. Это ужасно ассиметричные отношения. Если вы architect или lead designer, вам стоит очень скептически относиться к таким отношениям. Вам следует осознавать, что этот фреймворк собирается вас «поиметь». Вам с огромной осторожностью следует следить за тем, на сколько вы завязываетесь на него.

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

Автор любит свой фреймворк, а вы совершенно не обязаны.

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

Сколько из вас использовали фреймворк жестко связанный с вашей системой? Потом, через пару лет, узнавали что есть другой фреймворк, намного лучше, но у вас уже нет возможности перейти на него? Все это потому что вы неразрывно завязаны с кодом старого фреймворка.

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

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

Сколько из вас использует Dependency Injection? - Почти все

Сколько из вас внедряют сотни зависимостей ? - Всего пара из вас.

Кто использует Spring? У кого @Autowired (аннотация в Spring) везде, тут и там @Autowired разбросан по вашему коду? Я не хочу этого видеть! Я не хочу видеть, что @Autowired находится в ваших бизнес объектах. Если вы используете autowiring для ваших бизнес объектов, вам придется завязать ваши бизнес объекты на Spring. Задумайтесь, почему вообще ваш бизнес объект должен что-то знать про Spring?

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

Что же такое framework agnostic architecture

Чаще всего она используется в DDD и гексагональной архитектуре. Основная цель этой архитектуры - отделить логику вашего приложения от фреймворка, хранилища данных и сторонних компонентов.

Но сегодня не об этом, а о личном опыте в этом направлении и на примерах. Это не рецепт правильного подхода, а пример для понимания проблематики.

Текст про то как люди жили и писали работающий софт без Symfony, DI container, autowiring и Angular/React/Vue.

«It does matter»

Как я понимаю, когда это имеет значение. На моем опыте agnostic architecture имела смысл исключительно для долгоживущих (от 5-7 лет) enterprise систем и InHouse разработки. Обычно проблемы появляются через 3-5 лет, пару лет еще можно как-то протянуть.

Компании покрупнее создают свои платформенные решения.

Есть системы для которых 5 лет жизни - вполне нормальный жизненный цикл. Пять лет поработало, можно смело переписывать чуть ли не с нуля. Там это не важно.

Бодишопы, фриланс, веб студии - задумываться об agnostic не так уж и рентабельно.

Вопрос сейчас важен для JS разработчиков, которым приходится писать компоненты на проектах с разными фреймворками Angular / React / Vue. Чтобы не делать одну и ту же работу несколько раз, не тратить на это время и силы. Но сегодня не про них.

Вспомните, сколько докладов про то «как мы пилили монолит», «переходили с Framework A на Framework B», «переписали все на Go», сколько в них боли.

Немного исторической ретроспективы

 В PHP уже пережили взрывы популярности:

  • phpNuke, чуть ли не первая массовая CMS 1998 года, дырявая на столько, что сайты ломались за 2 минуты. До 2004 была популярна она и ее клоны;
  • ADOdb Library for PHP - 2000-2006 одна из первых библиотек предоставляющих уровень абстракции от БД;
  • xajax, одна из первых библиотек открывших нам мир Ajax 2006 год;
  • расцвет крупных фреймворков 2006-2007: Zend Framework (2006), Code Igniter (2006), Symfony(2007), YII 1 (2008);
  • хайп по noSQL (2011) Mongo DB Is Web Scale;
  • взрыв популярности Plalcon framework 2012-2016, в 2020 core contributor покинул проект;
  • рост интереса к асинхронным реализациям ReactPHP, Swoole, Road Runner;
  • активные похороны php vs Ruby on Rails (где-то 2007-2011)
  • активные похороны php vs python (около 2011-2013)
  • активные похороны php vs nodejs (около 2013-2014)
  • активные похороны php vs Golang (около 2016-2019)

Сегодня в моде микросервисы, которы даже могут не допустить комбинаторный взрыв версий (интересный момент).

Технологии и предпочтения разработчиков меняются очень быстро, 5 лет - это целая эпоха для разработки, а 10 лет целая эра.


Опыт № 1

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

Начала разрабатываться в 2009 году, вернее компоноваться, наработки были с 2006 года. PHP 5.2 без неймспейов (5.3 вышла в июне 2009), библиотеки от Zend Framework.

Для админ панели использовался JS framework ExtJS 3 с переходом на ExtJS 4. Эти версии ExtJS еще не были полноценными frontend фреймворками, скорее набором компонент.

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

Были и архитектурные просчеты, но они лежали в плоскости компетенции самого проекта, решались эволюционно.

Причины принятия архитектурных решений далеко не всегда были связанны с дальновидением. Немного интуиции и удачное стечение обстоятельств, возможно чтение книг типа «Совершенный код».

Ключевые архитектурные решения

Все сторонние зависимости помещены в отдельную папку со структурой вендор / библиотека (сейчас это стандарт). Написан свой автозагрузчик классов (в ZF-1 его еще не было), добавлена поддержка карт автозагрузки. Это дало простой переход на PSR-0 и PSR-4, легкое добавление сomposer, хотя проблемы с composer были, но об этом позже.

Код, который использовал сторонние библиотеки работал с ними через прослойки и абстрагирующие адаптеры. Например, для работы с БД приложение общалось со своим адаптером, который в свою очередь обращался к Zend Db. Это дало очень простую миграцию библиотек от ZF1 -> ZF2 > Laminas, можно было выбрать вовсе другого вендора. Стало возможно использовать отдельные более производительные методы работы с БД именно под наш кейс. (У фреймворка общее представление о работе бд, не во всех случаях оптимальное)

Написана своя прослойка для выборки данных через Model, и свой query builder для core уровня.

Написан свой Active Record, который постепенно перерос в ORM. Это было одной из особенностей проекта, другие фреймворки не позволяли сделать нужные вещи: создавать и редактировать объекты ORM через интерфейс без программирования, как это было у Salesforce.

Запись данных производилась через ORM, чтение через Model прослойку, при этом модель могла читать c реплики / сервера с агрегатами / sphinx.

Не напоминает «новомодный» CQRS ?

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

Доктрина уже была (выпуск 2006 год), но она не позволяла сделать задуманное. Более того сильно связывала проект.

Cвои Request, Response (сегодня дружат с PSR 7,17,18).

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

Промахнулись с ServiceLocator, но он использовался только на core уровне c 5-6 сервисами.

Свой роутинг. На тот момент не существовало FastRoute, да не такой уж он и «fast», если сравнивать с простым прикладным решением без парсинга uri.

UI использовал ExtJS, но большая часть интерфейсов создавалась на уровне встроенного дизайнера интерфейсов. Это было навеяно гейм индустрией, тогда частенько создавались редакторы уровней, а на их базе уже строилась дальнейшая разработка. Дополнительно повлияли продукты Borland и Microsoft. Встроенный редактор хранил настройки текущего интерфейса без привязки к JS коду, JS код создавался во время рендеринга проекта. Разработка первой версии дизайнера заняла около 2ух недель, это не было чем-то архи-сложным, эволюционировал со всей системой и это одно из ключевых решений в дальнейшей войне с ExtJS.

Еще одно интересное решение, которое позволило выжить. Случай, когда глубокое наследование было отличным решением. В библиотеке ExtJs сотни визуальных компонентов, построенных на иерархии от простых к сложным. Наследуются и расширяют настройки, свойства, события, методы друг друга. Base->Component->Container->Panel->Table->Grid. Решено не отступать от этой иерархии для рендеринга проекта, повторить ее в php коде и настройках.

Не смотря на заложенную независимость от сторонних решений, войн избежать не удалось.

Война первая ExtJS

Из Sencha уходит Ed Spencer - архитектор и основатель ExtJS (2012 год). Анонсируется новая версия библиотеки 5.0 (2014 год). До этого переход 3->4 у нас прошел безболезненно. Из релиза понятно, что теперь это полноценный фреймворк с большим количеством внутренних правил, особенностей и ограничений. Совершенно несовместим c кодом использующим предыдущую версию, поменялись даже методы создания объектов. Позже архитектура ExtJS менялась несколько раз MVC-> MVVM, разделение на modern и classic, своя автозагрузка js на основе сигнатур в свойствах классов, свой cli tool.

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

Первая победа

Поскольку все наследовалось от базовых объектов, нам необходимо было переопределить всего несколько методов генерации кода (около 5), где и были основные изменения связанные со структурой компонентов и их инициализацией. В генераторе пришлось немного изменить иерархическое дерево.

Весь этот слой не был связан с конечными бизнес интерфейсами системы, их не трогали. Изменения были связаны с тем, как генерировать код под новый JS Framework.

На доработки ушло около 2ух недель и проекты созданные под ExtJS 3-4 магическим методом начали запускаться на ExtJS 5. При дальнейших миграциях 5->6->7 происходило так же и даже намного проще.

Мы полностью отвязались от архитектуры ExtJs framework и использовали только нужные компоненты (по сути как и с ZF1).

Вторая война ExtJS

В ExtJs 4 были большие утечки памяти (memory leaks), ExtJS 5 в первых версиях тёк безбожно. Особенности нашего проекта приводили к дополнительным утечкам.

Частично это решалось тем, что мы не делали Single Page Application. В приложении были сотни модулей, каждый из которых был независимым Single Page Aplication`ом. Чаще всего это решало проблему, пользователи переходили на другую страницу и утечка обнулялась.

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

Спасло наследование в иерархии визуальных компонент и свои правила компоновки интерфейса (те, что шли вразрез с архитектурой ExtJS). После дебага стало ясно, что достаточно добавить генерацию деструктора для всех компонентов. Деструктор подчищал ссылки на внутренние структуры. На research ушло несколько дней, на реализацию пару часов.

Проблема была решена полностью.

Борьба с Composer

Для начала - борьбы не было, так как не существовало Composer (релиз 1.0 в 2016, через 7 лет после запуска проекта) За его разработкой с предвкушением наблюдали с 2012-2013 года.

Существовала проблема - установка модулей системы из интерфейса без использования консоли (пользователь по клику мог установить модуль). Проблема решалась загрузкой и распаковкой архива с исходниками.

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

Но было несколько печальных нюансов:

  • composer встраивал свой автозагрузчик в режиме prepend и перехватывал вызовы стандартного автозагрузчика системы. Это удалось быстро решить, загружая свой автозагрузчик вторым по очереди, но тоже в режиме prepend. Перехватываем очередь у вражеского перехватчика. :) Не сказать, что большая проблема, но неприятная.
  • некоторые зависимости (например js библиотеки) необходимо устанавливать в другие папки, тут помогли composer/installers и сторонний проект oomphinc/composer-installers-extender
  • с 2012 до 2015-11-14 1.0.0-alpha11 было совсем грустно без --classmap-authoritative
  •  "фатальный недостаток" - отсутствие в первых версиях composer runtime api (появился только в версии 1.10.6 2020-05-06) Карл! :-) К тому времени мы уже перестали следить за логом изменения этой библиотеки, узнал об этом совершенно случайно, подсказал Александр Макаров.

Не смотря на все преимущества, пришлось отложить полный переход на него до лучших времен, использовали совместно со своими решениями. Окончательно для установки модулей внедрен в 2020 году, через 11 лет после старта проекта и через 8 лет после сomposer initial release в 2012 году. В проекте до сих пор 2 загрузчика, связано это c особенностями модульной архитектуры бизнес слоя и поддержкой обратной совместимости.


Война лицензий

Используемый фреймворк заражает ваш проект своей лицензией.

В случае ExtJS проект можно было выпускать под коммерческой лицензией как финальный продукт, под коммерческой лицензией OEM как среду разработки или под GPLv3 как OpenSource.

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

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

Сделал сайт на GPLv3, дай исходники всем заходящим на него? Суть проволочки заключалась в том что JavaScript UI код интерпретируется и исполняется на стороне клиента в отличие от PHP кода, который исполняется на сервере. На это и ссылались юристы Sencha.

Таким образом MIT не возможен. Только коммерческое или использование GPLv3 для внутренних нужд.

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

Можно отказаться от UI на этом фреймворке, но альтернатив со схожим функционалом просто нет.

Перестроить генератор на какой-нибудь Vue.js вполне возможно, но уже нет необходимости и желания.


Опыт №2, неудачное наследство

Достался проект ZF-2 + Doctrine, на первый взгляд все сделано красиво по стайл гайдам фреймворков, академично, хорошо оформлено. Проглядывался заход на DDD и зачатки гексагональной архитектуры.

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

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

Проект был запущен в 2014 году на ZF-2 и Doctrine 0.8 и php 5.3, кроме всего прочего использовал MongoDB с родным драйвером первой версии.

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

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

Уже вышел PHP 7, перейти на него оказалось либо невозможно, либо слишком дорого. Доктрина до такой степени устарела, что было невозможно разрешить зависимости. Еще одна боль MongoDB к тому времени перешла на новый драйвер с другой оберткой не поддерживаемой старой доктриной. Старая версия проекта на PHP 7 не завелась.

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

Сущности были завязаны на ZF2 Service Locator и Doctrine\ORM\Mapping. Контроллеры отнаследованы от ZF, по 50 use в классе. Код размазан по модулям, хранил состояния.

Получается DDD жестко и навсегда связанный с конкретными версиями фреймворков, как об этом и говорил Дядя Боб (Роберт Мартин).

Переписывание заняло год, подробнее: Спасаем базу от разрушительной нагрузки


Опыт №3, удачный framework agnostic

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

Суть состояла в том, что контроллеры были заменены независимыми сервисами которые могли работать как standalone, так и в связке одного рантайма.

Сейчас планируем переводить с RoadRunner-1 на RoadRunner-2 и PHP 8, не имея при этом никаких обозримых проблем связанности.

RoadRunner PHP в специализированном сервисе. Год использования, полет нормальный

Посмотрим что будет через 4 года.





Report Page