Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
Вот цитата из Линуса Торвальдса за 2006 год:
Что очень похоже на «правило представления» Эрика Реймонда от 2003 года:
Здесь просто резюме идей, подобных мысли Роба Пайка от 1989 года:
Он цитирует Фреда Брукса из 1975 года:
Итак, почти полвека умные люди снова и снова говорят: сначала сосредоточьтесь на данных. Но иногда кажется, что это самый умный совет, который все забывают.
Приведу несколько реальных примеров.
Эту систему изначально создавали с расчётом на невероятную масштабируемость при большой нагрузке на CPU. Ничего синхронного. Везде обратные вызовы, очереди и рабочие пулы.
Но было две проблемы. Первая заключалась в том, что «нагрузка на процессор» оказалась не такой уж и интенсивной — одна задача занимала максимум несколько миллисекунд. Так что бóльшая часть архитектуры приносила больше вреда, чем пользы. Вторая проблема заключалась в том, что «высокомасштабируемая распределённая система» в реальности работала только на одной машине. Почему? Потому что вся связь между асинхронными компонентами осуществлялась с использованием файлов в локальной файловой системе, которая теперь стала узким местом для любого масштабирования. Оригинальный дизайн вообще не привязывался к данным, за исключением защиты локальных файлов во имя «простоты». Основная часть проекта была посвящена всей этой дополнительной архитектуре, которая «очевидно» была необходима, чтобы справиться с «большой нагрузкой» на CPU.
Эта система следовала дизайну микросервисов из одноцелевых приложений с API-интерфейсами REST. Одним из компонентов была база данных, в которой хранятся документы (в основном ответы на стандартные формы и другие электронные документы). Естественно, она выставляла API для сохранения и извлечения данных, но довольно быстро возникла необходимость в более сложной функциональности поиска. Разработчики посчитали, что добавление этой функции к существующему API документа противоречит принципам проектирования микросервисов. Поскольку 'search' по сути отличается от 'get/put', то архитектура не должна их объединять. Кроме того, для работы с индексом они планировали использовать сторонний инструмент, поэтому создание нового сервиса 'search' имело смысл ещё и по этой причине.
В итоге был создан поисковый API и поисковый индекс, который по существу стал дубликатом данных в основной БД. Эти данные обновлялись динамически, поэтому любой компонент, который изменял данные документа через главный API БД, должен был также отправить запрос на обновление индекса через поисковый API. С помощью REST API это невозможно сделать без состояния гонки, поэтому два набора данных время от времени выходили из синхронизации.
Несмотря на то, что обещала архитектура, два API были тесно связаны через их зависимости от данных. Позже разработчики признали, что поисковый индекс следует объединить с общим сервисом документов, и это сделало систему намного более ремонтопригодной. «Делать одно» работает на уровне данных, но не на уровне глаголов.
Эта система была своего рода автоматизированным конвейером развёртывания. Изначальная группа разработчиков хотела сделать достаточно гибкий инструмент, чтобы решить проблемы деплоя по всей компании. Они написали набор подключаемых компонентов с файловой системой конфигурации, которая не только настраивала компоненты, но и выступала в качестве предметно-ориентированного языка (DSL) для программирования того, как компоненты вписываются в конвейер.
Перенесёмся на несколько лет вперед, и инструмент превратился в «ту самую программу». Был длинный список известных ошибок, которые никто никогда не исправлял. Никто не хотел прикасаться к коду из страха что-нибудь сломать. Никто не использовал гибкость DSL. Все пользователи копировали и вставляли одну и ту же гарантированно рабочую конфигурацию, что и все остальные.
Что пошло не так? Хотя в первоначальном проектном документе часто использовались такие слова, как «модульный», «разъединённый», «расширяемый» и «настраиваемый», он вообще ничего не говорил о данных. Таким образом, зависимости данных между компонентами обрабатывались нерегламентированным способом с использованием глобально общего блоба JSON. Со временем компоненты делали всё больше и больше недокументированных предположений о том, что входит или не входит в блоб JSON. Конечно, DSL позволял переставлять компоненты в любом порядке, но большинство конфигураций не работали.
Я выбрал эти три проекта, потому что на их примере легко объяснить общий тезис, не трогая остальных. Однажды я попытался создать веб-сайт, а вместо этого завис над какой-то раболепской базой данных XML, которая даже не решала моих проблем с данными. Был ещё проект, который превратился в сломанное подобие половины функциональности
Обновление:
Видимо, многие всё равно считают, что я пытаюсь кого-то высмеять. Мои реальные коллеги знают, что я гораздо больше заинтересован на исправлении реальных проблем, а не на обвинении тех, кто эти проблемы породил, но, ладно, вот что я думаю о разработчиках, причастных к этим проектам.
Честно говоря, первая ситуация явно произошла по той причине, что архитектор системы был больше заинтересован в применении научной работы, чем в решении реальной проблемы. Многих из нас можно в этом обвинить (меня тоже), но это действительно раздражает коллег. Ведь им придётся помогать в поддержке, когда нам надоест игрушка. Если вы узнаёте себя, не обижайтесь, пожалуйста, просто остановитесь (хотя я бы предпочёл работать с распределённой системой на одном узле, чем с любой системой на моей «базе данных XML»).
Во втором примере нет ничего личного. Иногда кажется, что все вокруг говорят, как замечательно разделить сервисы, но никто не говорит о том, когда лучше этого не делать. Люди всё время учатся на собственном горьком опыте.
Третья история на самом деле произошла с одними из самых умных людей, с которыми мне когда-либо доводилось работать.
(Конец обновления).
Вопрос «Что сказано о проблемах, создаваемых данными?» оказывается довольно полезной лакмусовой бумажкой для хорошего проектирования систем. Он также очень удобен для выявления ложных «экспертов» с их советами. Проблемы с архитектурой сложных, запутанных систем — это проблемы с данными, поэтому ложные эксперты любят их игнорировать. Они покажут вам удивительно красивую архитектуру, но ничего не скажут о том, для каких данных она подходит, и (что важно) для каких данных не подходит.
Например, ложный эксперт может сказать, что вы должны использовать систему pub/sub, потому что системы pub/sub слабо связаны, а слабо связанные компоненты более ремонтопригодны. Это звучит красиво и даёт красивые диаграммы, но это обратное мышление. Pub/sub не делает ваши компоненты слабо связанными; pub/sub сам слабо связан, что может соответствовать или не соответствовать потребностям ваших данных.
С другой стороны, большое значение имеет хорошо спроектированная архитектура, ориентированная на данные. Функциональное программирование, service mesh, RPC, шаблоны проектирования, циклы событий, что угодно, у всех свои достоинства. Но лично я видел, что гораздо более успешные системы в продакшне работают на скучных старых СУБД.
Я огромный сторонник разработки кода вокруг данных, а не наоборот, и я думаю, что это одна из причин, по которой git был довольно успешным… По сути, я утверждаю, что разница между плохим программистом и хорошим заключается в том, считает ли он более важным свой код или свои структуры данных. Плохие программисты беспокоятся о коде. Хорошие программисты беспокоятся о структурах данных и их взаимоотношениях.
Что очень похоже на «правило представления» Эрика Реймонда от 2003 года:
Сверните знания в данные, чтобы логика программы стала глупой и надёжной.
Здесь просто резюме идей, подобных мысли Роба Пайка от 1989 года:
Доминируют данные. Если вы выбрали правильные структуры данных и всё хорошо организовали, то алгоритмы почти всегда будут самоочевидными. Структуры данных, а не алгоритмы, играют центральную роль в программировании.
Он цитирует Фреда Брукса из 1975 года:
Представление — суть программирования
За мастерством стоит изобретательность, благодаря которой появляются
экономичные и быстрые программы. Почти всегда это является результатом
стратегического прорыва, а не тактического умения. Иногда таким стратегическим
прорывом является алгоритм, как, например, быстрое преобразование Фурье,
предложенное Кули и Тьюки, или замена n² сравнений на n log n при сортировке.
Гораздо чаще стратегический прорыв происходит в результате представления
данных или таблиц. Здесь заключена сердцевина программы. Покажите мне блок-схемы, не показывая таблиц, и я останусь в заблуждении. Покажите мне ваши
таблицы, и блок-схемы, скорей всего, не понадобятся: они будут очевидны.
Итак, почти полвека умные люди снова и снова говорят: сначала сосредоточьтесь на данных. Но иногда кажется, что это самый умный совет, который все забывают.
Приведу несколько реальных примеров.
Высокомасштабируемая система, которая не справилась
Эту систему изначально создавали с расчётом на невероятную масштабируемость при большой нагрузке на CPU. Ничего синхронного. Везде обратные вызовы, очереди и рабочие пулы.
Но было две проблемы. Первая заключалась в том, что «нагрузка на процессор» оказалась не такой уж и интенсивной — одна задача занимала максимум несколько миллисекунд. Так что бóльшая часть архитектуры приносила больше вреда, чем пользы. Вторая проблема заключалась в том, что «высокомасштабируемая распределённая система» в реальности работала только на одной машине. Почему? Потому что вся связь между асинхронными компонентами осуществлялась с использованием файлов в локальной файловой системе, которая теперь стала узким местом для любого масштабирования. Оригинальный дизайн вообще не привязывался к данным, за исключением защиты локальных файлов во имя «простоты». Основная часть проекта была посвящена всей этой дополнительной архитектуре, которая «очевидно» была необходима, чтобы справиться с «большой нагрузкой» на CPU.
Сервис-ориентированная архитектура, которая по-прежнему ориентирована на данные
Эта система следовала дизайну микросервисов из одноцелевых приложений с API-интерфейсами REST. Одним из компонентов была база данных, в которой хранятся документы (в основном ответы на стандартные формы и другие электронные документы). Естественно, она выставляла API для сохранения и извлечения данных, но довольно быстро возникла необходимость в более сложной функциональности поиска. Разработчики посчитали, что добавление этой функции к существующему API документа противоречит принципам проектирования микросервисов. Поскольку 'search' по сути отличается от 'get/put', то архитектура не должна их объединять. Кроме того, для работы с индексом они планировали использовать сторонний инструмент, поэтому создание нового сервиса 'search' имело смысл ещё и по этой причине.
В итоге был создан поисковый API и поисковый индекс, который по существу стал дубликатом данных в основной БД. Эти данные обновлялись динамически, поэтому любой компонент, который изменял данные документа через главный API БД, должен был также отправить запрос на обновление индекса через поисковый API. С помощью REST API это невозможно сделать без состояния гонки, поэтому два набора данных время от времени выходили из синхронизации.
Несмотря на то, что обещала архитектура, два API были тесно связаны через их зависимости от данных. Позже разработчики признали, что поисковый индекс следует объединить с общим сервисом документов, и это сделало систему намного более ремонтопригодной. «Делать одно» работает на уровне данных, но не на уровне глаголов.
Фантастически модульный и конфигурируемый комок грязи
Эта система была своего рода автоматизированным конвейером развёртывания. Изначальная группа разработчиков хотела сделать достаточно гибкий инструмент, чтобы решить проблемы деплоя по всей компании. Они написали набор подключаемых компонентов с файловой системой конфигурации, которая не только настраивала компоненты, но и выступала в качестве предметно-ориентированного языка (DSL) для программирования того, как компоненты вписываются в конвейер.
Перенесёмся на несколько лет вперед, и инструмент превратился в «ту самую программу». Был длинный список известных ошибок, которые никто никогда не исправлял. Никто не хотел прикасаться к коду из страха что-нибудь сломать. Никто не использовал гибкость DSL. Все пользователи копировали и вставляли одну и ту же гарантированно рабочую конфигурацию, что и все остальные.
Что пошло не так? Хотя в первоначальном проектном документе часто использовались такие слова, как «модульный», «разъединённый», «расширяемый» и «настраиваемый», он вообще ничего не говорил о данных. Таким образом, зависимости данных между компонентами обрабатывались нерегламентированным способом с использованием глобально общего блоба JSON. Со временем компоненты делали всё больше и больше недокументированных предположений о том, что входит или не входит в блоб JSON. Конечно, DSL позволял переставлять компоненты в любом порядке, но большинство конфигураций не работали.
Уроки
Я выбрал эти три проекта, потому что на их примере легко объяснить общий тезис, не трогая остальных. Однажды я попытался создать веб-сайт, а вместо этого завис над какой-то раболепской базой данных XML, которая даже не решала моих проблем с данными. Был ещё проект, который превратился в сломанное подобие половины функциональности
make
, снова потому, что я не думал, что мне действительно нужно. Я уже писал о времени, потраченном на создание бесконечной иерархии классов ООП, которую следовало закодировать в данных.Обновление:
Видимо, многие всё равно считают, что я пытаюсь кого-то высмеять. Мои реальные коллеги знают, что я гораздо больше заинтересован на исправлении реальных проблем, а не на обвинении тех, кто эти проблемы породил, но, ладно, вот что я думаю о разработчиках, причастных к этим проектам.
Честно говоря, первая ситуация явно произошла по той причине, что архитектор системы был больше заинтересован в применении научной работы, чем в решении реальной проблемы. Многих из нас можно в этом обвинить (меня тоже), но это действительно раздражает коллег. Ведь им придётся помогать в поддержке, когда нам надоест игрушка. Если вы узнаёте себя, не обижайтесь, пожалуйста, просто остановитесь (хотя я бы предпочёл работать с распределённой системой на одном узле, чем с любой системой на моей «базе данных XML»).
Во втором примере нет ничего личного. Иногда кажется, что все вокруг говорят, как замечательно разделить сервисы, но никто не говорит о том, когда лучше этого не делать. Люди всё время учатся на собственном горьком опыте.
Третья история на самом деле произошла с одними из самых умных людей, с которыми мне когда-либо доводилось работать.
(Конец обновления).
Вопрос «Что сказано о проблемах, создаваемых данными?» оказывается довольно полезной лакмусовой бумажкой для хорошего проектирования систем. Он также очень удобен для выявления ложных «экспертов» с их советами. Проблемы с архитектурой сложных, запутанных систем — это проблемы с данными, поэтому ложные эксперты любят их игнорировать. Они покажут вам удивительно красивую архитектуру, но ничего не скажут о том, для каких данных она подходит, и (что важно) для каких данных не подходит.
Например, ложный эксперт может сказать, что вы должны использовать систему pub/sub, потому что системы pub/sub слабо связаны, а слабо связанные компоненты более ремонтопригодны. Это звучит красиво и даёт красивые диаграммы, но это обратное мышление. Pub/sub не делает ваши компоненты слабо связанными; pub/sub сам слабо связан, что может соответствовать или не соответствовать потребностям ваших данных.
С другой стороны, большое значение имеет хорошо спроектированная архитектура, ориентированная на данные. Функциональное программирование, service mesh, RPC, шаблоны проектирования, циклы событий, что угодно, у всех свои достоинства. Но лично я видел, что гораздо более успешные системы в продакшне работают на скучных старых СУБД.