Давайте поговорим о high-load, high-extensible, ООП, DDD & EDA.
Речь пойдет о разнице между high load & high extensibility — это разные цели и задачи в разработке. Для которых нужны принципиально разные подходы. Которые часто вызывают когнитивный диссонанс среди программистов, предвещающий бурную полемику.
На Хабре много статей про high-load, а вот тема high-extensible как-то упускается из внимания.
Если попытаться примерно представить разность в понятиях и механиках, то вот табличка:
Давайте попробуем разобрать это все подробнее.
Хайлоад это хайлоад. Это быстрые ответы на запросы и постоянные соединения. Обычно тут php RoadRunner или Go или Rust, с асинхронностью и event loop. Проблема 10k и т. д.
ООП было придумано как решение задачи расширения или High Extensible. Тут лучше работают модульная разработка (MP, DDD) & обмен сообщениями (EDA, event driven architecture).
Мотивация
Суть проблемы, которая рассматривается в контексте этой статьи, заключается в том как команды работают с большими системами.
Речь идет о системах где разработку ведут 5-10 или больше команд, в каждой по 5-10 разработчиков.
Если просто в лоб решать задачи, то можно налететь на проблемы, которые описываются так:
- команды начинают конфликтовать в GIT, мешать друг другу, ломать систему, увеличивается рост ошибок
- переиспользование кода затрудняется, часть команд начинает дублировать работу друг друга
- затрудняются релизы, увеличивается тайм-ту-маркет
- сложнее прогнозировать сроки
- множатся зависимости по задачам и блокеры, так как надо долго ждать результатов от смежных команд
Все это можно решить, или существенно снизить проблемы, если грамотно применять модули (MP), иногда домены (DDD) & обмен сообщениями (EDA как вариант), делая архитектуру более антихрупкой.
Быть эффективным или быть антихрупким?
Это две крайности одной и той же сущности.
В книге Антихрупкость, автор Нассим Талеб пишет о том что погоня за эффективностью всегда влечет рост хрупкости. Если мы ставим себе цель делать систему антихрупкой, нам придется жертвовать эффективностью. Иного не дано.
Антихрупкость любит случайность и неопределенность, что означает — и это ключевое свойство антихрупкости — любовь к ошибкам, к определенному классу ошибок. Уникальность антихрупкости состоит в том, что она позволяет работать с неизвестностью, делать что-то в условиях, когда отсутствует понимание, что именно делается, — и добиваться успеха.
Вам нужна система которая будет эффективной или антихрупкой?
Ответ на этот вопрос влияет на решения о том какие технологии следует использовать.
ООП было придумано как решение проблем расширяемости больших систем (большой функционал)
Цитата Алана Кея, автора ООП:
«Я придумал термин «объектно-ориентированный», и могу сказать, что я не имел в виду С++». Алан Кэй, конференция OOPSLA, 1997.
Ключ к созданию хороших масштабируемых систем это проработка механизмов общения модулей, а не проработка их внутренних свойств и поведения.
Я не против типов, но мне не знакома ни одна система типов, которая не вызывала бы боли. Так что мне все еще нравится динамическая типизация.
Позднее связывание позволяет с меньшими усилиями встраивать в проект идеи, которые возникли позже в процессе разработки (по сравнению с системами с более ранним связыванием вроде C, C++, Java, и пр.)
Я считал объекты чем-то вроде модулей, биологических клеток, и/или отдельных компьютеров в сети, которые могут общаться только через сообщения.
Я сожалею о том, что давным-давно придумал термин «объекты» для этого явления, так как его использование приводит к тому, что многие люди уделяют основное значение идее, которая не так важна, как основная. Основная идея — это обмен сообщениями
ООП для меня означает лишь обмен сообщениями, локальное сохранение, и защита, и скрытие состояния, и крайне позднее связывание
Итак, если взять эти идеи, то тезисами это можно сделать так:
- Масштабируемые и расширяемые системы состоят из модулей
- Модули общаются друг с другом через сообщения
- Это обеспечивает позднее связывание — крайне позднее связывание — код и логику можно менять в любой момент не меняя уже существующий код в модулях
- Для работы такой системы важна динамическая типизация, а не статическая
В 80е годы ООП разбилось на 2 типа
В книге Мифический человеко-месяц, автор Фредерик Брукс описывает момент когда ООП разбилось на 2 мира. Часть "Объектно-ориентированное программирование: а медна пуля не
подойдет?"
Разработка из больших частей. Если осуществлять сборку из частей, которые достаточно сложны и имеют однородные интерфейсы, можно быстро образовывать довольно богатые структуры.
Согласно одному из взглядов на объектно-ориентированное программирование эта дисциплина осуществляет модульность и чистые интерфейсы. Другая точка зрения подчеркивает инкапсуляцию — невозможность увидеть и еще менее изменить внутреннюю структуру детали. Еще одна точка зрения отмечает наследование и сопутствующую ему иерархическую структуру классов с виртуальными функциями. И
еще один взгляд: важнейшей является сильная абстрактная типизация данных вместе с гарантиями, что конкретный тип данных будет обрабатываться только применимыми к нему операциями.
В настоящее время не нужен весь пакет Smalltalk или C++, чтобы использовать любой из этих дисциплин — многие из них поглотили объектно-ориентированные технологии. Объектно-ориентированный подход привлекателен, как поливитамины: одним махом (т.е. переподготовкой программиста) получаешь все. Очень многообещающая концепция.
Получается что идея ООП зародилась в 60е годы, в 80е годы уже были разные точки зрения на то что есть ООП.
Сегодня под ООП чаще всего имеют ввиду классы. Хотя Алан Кей и Дэвид Уэст говорят что это ошибка. И что классы это всего лишь структуры данных. Это не про ООП. C++ & Java в базе не имеют ничего общего с ООП.
Речь идет про то ООП которое про модули и обмен сообщениями. Сегодня эта идеология чаще всего встречается в контексте модулей и сообщений или событий (EDA). Ты пишешь модули, которые обмениваются сообщениями через чистые интерфейсы (события, хуки, триггеры) с крайне поздним связыванием.
Так кто прав, а кто лев?
Если вам нужен HighLoad, вам надо топить за эффективность, масштабирование скорости исполнения, статическую типизацию, асинхронность, события и eventloop.
Если вам нужен HighExtensible, вам надо топить за антихрупкость, масштабирование скорости изменений, динамическую типизацию, модули и домены (DDD) и события как EDA (обмен сообщениями между модулями).
Причины проблем
Проблема в том что программисты спорят о том что ООП плохо (классы это медленно и хрупко, а функции быстро и круто, ну или как то там наоборот), о том что вот в PHP не хватает асинхронности и event loop.
Например эти темы поднимались на недавнем митапе SkyEng https://www.youtube.com/watch?v=QrlWrFILjMk
Но не понимают что это разговор о разных идеологиях, парадигмах и архитектурах. Это все равно что говорить что у дома нет колес или у автомобиля не хватает 3го этажа с ковром. Вот бы ложка была вилкой или вилка была бы ложкой. Вообще есть конечно вилка-ложки, но часто ли мы ими пользуемся вне горных походов?
Если вы создаете прикладную бизнес систему, где есть куча реальных пользователей, бизнес требования, бизнес аналитики, никто не знает что хочет, а требования меняются каждый день — вы очень сильно попадете в засаду если пойдете по пути HighLoad, будете делать статическую типизацию, асинхронность, без доменов и событий — вам будет больно. Тут нужно двигаться ближе к архитектуре для HighExtensible.
Если вы создаете микросервисы, которым надо отрабатывать 100 000 операций в секунду, то попытка пойти по пути динамической типизации, доменам, без асинхронности — тоже сделает очень больно. Тут стоит копать в сторону архитектуры под HighLoad.
Сделать одновременно HighLoad & HighExtensible — не реально или очень близко к не реальному. Попытки совместить эти две идеологии приводят к диким проблемам, большим затратам и часто к провалу.
Ну а программисты готовы спорить об этом до посинения. Не понимая что говорят про разные контексты, задачи и механизмы проектирования архитектур.
Примеры
Про high-load:
- NodeJS — пошел с вектором в event loop & асинхронные механики. Есть много интересных кейсов с построением highload микросервисов и REST API.
- Туда же можно отнести Go, Rust и разные механики PHP типа RoadRunner и PHP Swoole.
Про high-extensible
- Можем взять Redmine, это типа система управления задачами, на базе RoR, там также есть модули (плагины), а общение модулей и обмен сообщениями строится на хуках.
- Ребята из FreeScout решили делать систему хелпдеск на Laravel. Это прикладная система с кучей бизнес логики. Они реализовали свою архитектуру модулей (DDD) и обмена сообщениями (EDA) применив Eventy.
- Eventy в свою очередь взял идею хуков из WordPress. Надо ли представлять этого зверя? Который держит более 30% рынка веб сайтов. Его архитектура также базируется на идеи модулей (плагинов, DDD), которые взаимодействуют друг с другом через сообщения (хуки, EDA).
Очень интересный опыт у ребят из iSpring:
https://www.youtube.com/watch?v=xT25xiKqPcI
Там речь о том чем отличается монолит от копролита. О том что если уметь готовить монолит, с DDD & EDA, то можно получить много преимуществ. А если не уметь — то и микросервисы не факт что помогут.
В общем, ребята ступили на путь освоения DDD & EDA. И это радует.
Заключение
Мир технологий в целом и разработок в частности очень многогранен. Если есть какая то технология — значит на то есть причины. Не знание этих причин еще не значит что технология плохая сама по себе.
Динамическая и статическая типизация — обе правильные, если их применять в соответствующих задачах.
ООП тоже очень разное может быть и есть много разных точек зрения на то что это такое. И все они правильные. Просто там разные контексты.
Вилка или ложка — не плохие и не хорошие. И если человек ест спагетти ложкой, а суп вилкой, а потом пишет о том что ложка это плохо, потому что спагетти вываливаются, и вилка плохо потому что суп хлебать ей не удобно — то разве это проблема в вилке или ложке?
Ну и под конец, хочется больше примеров. Кто какие еще примеры знает грамотного применения модулей и сообщений (DDD & EDA)?
Особенно интересует опыт построения HighExtensible систем в РФ. Потому что на западе его хватает. А тема high-load на Хабре и так перегрета. Хочется больше поговорить о практиках high-extensible.
Drag13
Т.е. в HighLoad вы записали Node.JS (который не прямо таки супер быстрый) и Deno, который вышел буквально недавно (и о котором Раян говорил что они только пытаются догнать Node.JS). Зато C# которые работает быстрее (в общем случае), поддерживает многопоточость (и асинхроннсть) из коробки у вас оказался на "другой стороне барикад".
Почему?
algotrader2013
Ага, о том же хотел написать. HE это динамическая типизация (см. табличку), но в нее попали C# с Java. А вот HL статическая, но зато PHP, NodeJs. Классику жанра в виде С и C++ вообще забыли, хотя метод «переписать модуль на С» — это, как правило, достаточно радикальное решение боттлнеков. Ну и обмен сообщениями попал в HE… ну такое. В HL использовать кафку вполне себе вариант, чтобы сгладить пиковые нагрузки, и при этом расбросать поток на пул обработчиков.
anonymous Автор
Обмен сообщениями через кафку это скорее про очереди и многопоточность.
Обмен сообщениями через хуки, события и триггеры это другая механика.
Есть конечно и смешанные истории. Например мессенджер в симфони можно применять для построения обоих механик. Но это в теории. Хороших и интересных примеров построения EDA как механизма взаимодействия модулей через эту штуку я не встречал. Например уровня зрелости как из примера с фрискаут и эвенти.
anonymous Автор
Динамическая и статическая типизация тоже очень условное и примерное деление.
Я за статическую типизацию в части ХЛ, потому что там меньше риск утечки памяти и по моим наблюдениям такие решения работают эффективнее и быстрее.
Однако разделяю точку зрения Алана Кея о том что это больно и что динамическая типизация в системах сложной бизнес логики даёт больше гибкости, антихрупкости.
То что какие то языки поддерживают или не поддерживают это второстепенно. Зачастую почти везде можно выбирать как писать код. Но даже если нельзя то это не самое страшное. И не самое важное.
Ключевое там это домены и события. Просто если в дополнение к ним можно уйти от жёстких типов это даёт плюс к гибкости. Этот момент также отметила докладчик из видео про опыт ispring.
algotrader2013
Ну и, тут можно оценить некую общую картину. В топ5 C++, Rust, Java, C#
anonymous Автор
Мысль о том что C# .net, Java spring, php symfony часто используют для создания расширяемых систем взял из своего опыта и тех мыслей что озвучили ребята на митапе skyeng, ссылка в статье.
Другая сторона баррикад это лишь условность. Популярность применения технологии для соответствующей практики.
Конечно есть хайлоад микросервисы на си шарп, яве или симфони.
Конечно есть попытки делать интернет магазины на nodejs.
Но мой опыт и мои ощущения говорят о том что часто это походит на попытку чесать ногой за ухом. Можно, но больно и не удобно.
Если же бизнес систему, панель управления, делают на Симфони, а какие то хайлоад запросы переводят на go, nodejs, или php roadrunner, то получается лучше.
Опять же это лишь мои наблюдения из моего опыта. Они могу не отражать всей реальности. Может быть много исключений.
Если будут какие то обратные примеры из реального опыта, то будет интересно узнать о них.
Drag13
Примеры по производительности вам уже привели выше.
Что касается статьи — мысль хорошая, тема интересная. Но вы, простите, смешали мягкое с жидким причем в чувствительных местах. Node.JS замечательно расширяется (именно благодаря динамичности языка). С# замечательно быстр (в .NET Core 3.0 уделили много вниманмия производительности + со второй версии появились дополнительные низкоуровненвые способы оптимизации (Span, например).
Возможно, статью стоит или переосмыслить или поменять акценты на собственный опыт.
anonymous Автор
Нода замечательно расширяется. Сишарп быстрый.
У меня нет возражений к этому.
Вообще расширяемую систему можно построить на любом языке. Как и сделать быстрый хайлоад микросервис.
В таблице я привёл своё видение и свои наблюдения о том что чаще в какой практик используется.
Это не значит что нельзя делать иначе. Можно. Если осторожно.
Однако в моей практие были печальные истории когда попытки сделать интернет магазин на ноде приводили очень к большим затратам и провалу проекта. Хотя по соседству такая же команда с такой же задачей на пхп двигалась сильно быстрее.
Плюс изучение практик и рынка показывает что модульные и расширяемые системы чаще живут на пхп. На ноде тоже конечно есть примеры, но их меньше и они проще по функционалу.
Сишарп не беру тк на нем мало примеров в опенсорсе. И я не силен в нём. Но сомневаюсь что он как то сильно отличается от пхп в данном контексте.