5. Эволюционная архитектура

Анатолий Левенчук · Поиск в руководстве · MCP

Открыть это место в полном тексте руководства →

5. Эволюционная архитектура

Архитектура: было «про трудно изменяемое», стало «про облегчение изменений»

Архитектор — это на древнегреческом «главный строитель», arkitekton, αρχι‑ (arkhi‑, «главный») +‎ τέκτων (téktōn, «строитель»). Он определял что и как строить, а также следил за рабочими-строителями. Дальше понятие архитектуры перешло во все остальные виды инженерии, при этом понятие непрерывно менялось и было удивительно трудно определяемым, оно было неотличимым от понятия «самый опытный разработчик».

Тем не менее, во всех этих ранних понятиях оставалось указание на архитектуру как определение структуры (структура — это понимание, как система разделена на части, прямая отсылка к системному мышлению, разбирающемуся в способах деления системы на части, причём мы помним, что статистически термин «структура» тяготеет к продуктным/модульным/конструктивным разбиениям), определение чего-то важного, чем бы оно ни было (Ralf Johnson), а также принятие решений по основным принципам организации системы (ибо выяснилось, что не всегда понятно про части системы, но можно сформулировать принципы появления этих частей. Например, так устроен интернет, и именно такое определение было дано в ISO 42010:2011, но помним, что «организация» тяготеет статистически к функциональным разбиениям).

В 2017-2022 году понятие архитектуры, метода архитектурной работы и роли архитектора претерпели значительные изменения, прежде всего в программной инженерии, а дальше это изменение, получившее наименование «эволюционная архитектура» (evolutionary architecture), будет всё больше и больше ощущаться во всех остальных видах инженерии, хотя и с лагом (обычно такой лаг 10 лет, поэтому ожидаем существенных массовых изменений для архитектуры «железа» с примерно с 2027 года, но передовые предприятия вроде SpaceX или NVIDIA работают с таким пониманием уже сегодня):

  • Архитектор начал заниматься не всем важным, а только тем важным, что входило в его concerns/«предметы интереса»/«важные характеристики», к которым отнесли всевозможные -ости/-ilities (производительн-ость, доступн-ость, надёжно-ость, и т.д.). Эти архитектурные характеристики иногда раньше в системной и программной инженерии называли «нефункциональными требованиями», но это не требования, а предметы интереса (требования вообще по факту исчезли из инженерии), но даже не это плохо: плохо использование слова «нефункциональные», никто ведь не хочет заниматься чем бы то ни было, у чего нет функции/назначения. И это не «требования качества», потому как никакого отношения к качеству у этих характеристик нет, это оказались совершенно особенные предметы интереса, архитектурные. Если функциональность отличалась от одной системы к другой (валенки тёплые, электростанция мощная), то архитектурные характеристики более-менее одни и те же для самых разных систем (ремонтопригодность и надёжность валенок и электростанции).

  • Задачей архитектора стало нахождение архитектурных решений (architectural decisions), которые стали пониматься, как решения по конструкции системы, гарантирующие квазиоптимальные значения архитектурных характеристик (а не функциональных характеристик, связанных с domain). Тем самым задачи архитектора чётко были отделены от задач команд разработчиков/developers/прикладных инженеров для domain: прикладные инженеры разных команд делают части системы, которые обеспечивают функциональность, а системный архитектор принимает решения, которые направляют деятельность разработчиков из разных команд так, чтобы достичь оптимума/баланса в архитектурных характеристиках. Если речь шла о функциональных характеристиках, то начали уточнять «функциональный архитектор» и «функциональная архитектура», а не просто «архитектура», но всё чаще и чаще отказывались тут от такого термина и переходили к «функциональному проектировщику/designer» и результату его работы «функциональному проекту/design». Термин «архитектор» тем самым оставался закреплённым за ролью, занимающейся конструктивной структурой в связи с архитектурными характеристиками. Конечно, это всё не происходило одномоментно, даже в программной инженерии, изменения идей и терминологии в профессиональных сообществах происходят за годы, а не за ночь — людей переучить много дольше, чем обновить софт в компьютере или даже переобучить большую языковую модель в AI-системе. Как термин «архитектура нейронной сети» использовался для описания dataflow в 2017 году, так и продолжает использоваться в инженерии нейронных сетей и сейчас. Но вот в разработке корпоративного софта идеи по поводу того, что такое архитектура, как её разрабатывать, а также терминология разговора об этом — они уже существенно поменялись.

  • Изменился сам состав архитектурных характеристик. По John Ralf в архитектуру входило всё важное, что бы это ни было, а важное чаще всего определялось как то, что трудно менять: при альтернативном прохождении развилки в какой-то концепции системы пришлось бы переделывать всю систему. Скажем, принимается решение использовать реактивный двигатель, а не турбореактивный двигатель — и всё, самолёт нужно переделывать, вот такое решение называлось архитектурным, и требования для него (скажем, скорость самолёта, которая связана со звуковым барьером, возможность преодолеть который зависит от типа двигателя) назывались тоже архитектурными. Вот такие решения сейчас оказались связанными с функциями системы в её предметной области и стали решениями функциональных проектировщиков. А у архитектора появилась главная архитектурная характеристика evolvability/развиваемость, которая как раз и означает возможность «изменять трудноизменяемое»: гарантирует, что архитектура системы позволяет легко менять даже важные решения разработчиков. Архитектурные методы работы стали поддерживать эволюцию системы, их результат начали называть эволюционной архитектурой (evolutionary architecture, подразумевает изменяемость в ходе проекта, а не незыблемость). Архитектурные методы работы начали поддерживать agile lean разработку, работа архитектора сама стала agile lean (гибкой элегантной архитектурной работой).

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

Разрабатываемая система оказалась постоянно эволюционирующей, от концепции жизненного цикла отказались, «водопад» исчез, окружение системы оказалось постоянно меняющимся в ходе эксплуатации, появлялись новые идеи, как использовать систему, концепция использования оказалась просто «документирована», а не «фиксирована» (и использование термина «фиксировать» начало считаться моветоном, как отсылка к «водопаду» с его «фиксацией требований», после которых вносить в них изменения становилось катастрофически трудно, «теоретически возможно», а не практически), разработчики стали выпускать непрерывно уточняющиеся версии систем, и не раз в пару лет (как все уже привыкли), а по нескольку раз в день (до десятка раз, в случае программных систем). Даже ракетостроители выпускают уникальный по своей конструкции каждый следующий экземпляр ракеты. Китайские автомобилестроители в 2025 году говорят, что время внесения существенных изменений в проект легкового автомобиля до момента выхода готового автомобиля — три месяца, а если не успел в такой срок — вылет с высококонкурентного рынка. Архитектурная работа сейчас должна если не гарантировать, то хотя бы стараться, чтобы быстрые изменения функциональности были возможны, была высокая скорость обновления системы (возможность изменений — evolvability) — за короткие сроки возможности внесения функциональных изменений без увязания в огромном числе согласований стал отвечать архитектор. Если кто-то говорит «добавить эту новую функцию в текущую конфигурацию невозможно, для этого надо переделать всю систему с нуля» — это провал архитектурной работы.

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

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

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

Вообще, сама идея о том, что архитектура должна помогать системе быть легкоизменяемой в разработке, а не наоборот, класть в основу что-то неизменяемое, а ещё идея о том, что архитектура сама по себе должна меняться по ходу разработки и даже уже эксплуатации, а не определяться поближе к началу проекта как что-то «основное и незыблемое» — вот эта идея была в инженерии достаточно революционна. Впервые подробно эта идея изложена в книжке 2017 года «Building Evolutionary Architectures» на примере программной архитектуры для корпоративного программного обеспечения (софт для поддержки работы предприятий — бухгалтерия, маркетинг и т.д.), а в декабре 2022 года вышло второе издание книги, ответившее на неизбежную критику первых лет:

В этой книге обращается внимание на то, что архитектура не разрабатывается сейчас «после требований, но до изготовления/строительства», а тоже меняется по мере того, как постоянно меняются требования (это 2017 год, требования ещё были довольно распространены, но признавалось, что «они постоянно меняются») и ещё отслеживаются изменения в самой разработке. В книге подробно рассказывается, как evolvability/развиваемость системы становится едва ли не главной архитектурной характеристикой (предметом ролевого архитектурного интереса).

Уже окончательно признано, что архитектура связана не только со структурой самой целевой системы, но и со структурой создающего эту целевую систему инженерного коллектива, состоящего из многих команд. Закон Конвея (Conway’s Law)1 — это наблюдение, что архитектура (модульная структура системы) обязательно отразит оргструктуру предприятия-создателя, ибо взаимодействие между модулями требует согласования — и поэтому архитектура нарежет систему на куски, которые попадут в зону ответственности отдельных подразделений. Взаимодействий внутри частей одного модуля будет много, но они будут обсуждаться внутри одного подразделения. Взаимодействия отдельных модулей будут проходить через строго определённые интерфейсы, но обсуждаться они будут проще — между подразделениями, ибо будут минимизированы.

Отсюда родилась идея обратного манёвра Конвея (reverse Conway maneuver)2 — что вы должны разработать лучшую структуру системы, которая будет успешна на рынке, а также позволять быстрые изменения функциональности, а структура предприятия-создателя (нарезка коллектива инженеров на команды) должна пройти так, чтобы она отражала разработанную лучшую структуру системы. Если не выполнить обратного манёвра Конвея, то через закон Конвея организационная структура обязательно приведёт к ухудшению разработанной лучшей структуры, и система будет неуспешна. Так что архитектор системы и архитектор предприятия оказались работающими в общей связке, а изменяемость архитектуры системы, которую разрабатывает предприятие, ведёт ещё и к изменяемости архитектуры предприятия.

Архитектурные характеристики

Окончательно новый подход к эволюционной архитектуре, которая занимается архитектурными характеристиками, абстрагируясь от функциональности («функциональной архитектуры») на примере архитектуры для корпоративного софта был сформулирован в книге «Fundamentals of Software Architecture», 2020.

В этой книге чётко говорится о различении прикладных разработчиков (разбитых на команды developers, в число которых входят и «функциональные архитекторы»/«прикладные методологи предметной области») и архитекторов (architects):

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

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

Скажем, если система отлично справляется со своими предметноспецифичными функциями, за которые вы готовы платить (скажем, игровой сервер вас может отлично развлечь, котельная может отлично обогреть), но вам недоступна (плохие значения архитектурной характеристики доступности/availability3), то систему нельзя считать успешной. Когда-то на заре интернета поисковые системы были реализованы на огромных серверах Sun, но поскольку к ним обращались одновременно тысячи пользователей, они по факту были недоступными: ответа от них было нельзя дождаться. Это как попасть в город со знаменитым музеем на один день, и обнаружить, что в этот музей очередь на пять дней! Вроде музей есть, и работает, но из-за очереди нельзя попасть! И дальше начинаются разговоры о том, что легко спутать производительность и доступность (скажем, музей в городе может пропустить за день сколько хочешь человек, очереди нет и в принципе быть не может, но только работает этот музей по субботам 2 часа с 14 часов до 16 часов, а в другое время просто закрыт).

Этих архитектурных характеристик оказалось множество4, и для многих видов систем они оказались более-менее повторяющимися. Вот какой-то их приблизительный перечень (взят как раз из упомянутой книги, и там строго-настрого предупреждается, что в каждой организации эти архитектурные характеристики будут трактоваться по-своему):

  • Операционные (времени эксплуатации системы) предметы интереса архитектора, которые пересекаются во многом с предметами интереса ответственных за изготовление, ремонт и эксплуатацию целевой системы (в программной инженерии это инженеры внутренней платформы разработки, до сих пор их называют более старым термином DevOps, и там же многочисленные варианты XOps как общее имя методов работы и одноимённых ролей самых разных Ops — PlatformOps, MLOps, DataOps, and ModelOps5):

    • Доступность/availability: часы работы системы (скажем, 24 часа в сутки 7 дней в неделю, и на сколько она может закрываться на профилактику, ремонт, отдых персонала)

    • Непрерывность/continuity: оргвозможность восстановиться после какой-то катастрофы (наводнение, удар молнии, война).

    • Производительность/performance: включает и устойчивость к постоянной нагрузке, и устойчивость к пиковым нагрузкам (часто выделяют как отдельный предмет интереса — эластичность/elasticity), и частоту обращений к функциям системы, и время ответа на запросы, и требования к запасу мощности. Удовлетворение ожиданий по производительности само по себе будет отдельным приключением в разработке, может потребовать экстраординарных решений в части архитектуры.

    • Возможность восстановления/recoverability: с какой скоростью в случае катастрофы всё восстановится? Бэкапы, дублирование оборудования — это всё тут.

    • Надёжность и безопасность/reliability and safety: если что-то поломается, то это никому не должно повредить (никто не должен быть покалечен или убит, или никто не должен потерять большие деньги).

    • Устойчивость/robustness (у «железных» инженеров чаще resilience) — возможность хоть как-то работать и обрабатывать неизбежные ошибки в критических ситуациях, когда произошли довольно крупные поломки (скажем, пропало интернет-соединение, или выключилось энергопитание, или пошёл дым из компьютерного блока).

    • Масштабируемость/возможность наращивать производительность по мере возрастания числа пользователей или интенсивности их запросов.

  • Характеристики времени создания:

    • Конфигурируемость/configurability — возможность пользователю поменять конфигурацию системы самому через удобный пользовательский интерфейс.

    • Расширяемость/extencibility — насколько легко добавлять новую функциональность.

    • Устанавливаемость/installability — насколько легко устанавливать систему в самых разных вариантах использования.

    • Повторное использование/leverability and reuse — насколько легко переиспользовать систему в разных проектах.

    • Локализация/localization — поддержка множества языков, единиц измерения, часовых поясов

    • Сопровождаемость/maintainability — как легко обслуживать систему по ходу её эксплуатации?

    • Переносимость/portability — как легко перенести систему в другое окружение (например, на другую платформу, программное обеспечение на другую операционную систему).

    • Поддерживаемость/supportability — уровень технической поддержки, который ожидается для системы: как быстро отзываются, что могут поправить, сколько стоит.

    • Возможность апгрейда/upgradability — как легко и быстро перейти с одной версии системы на другую, будут ли вообще доступны следующие версии.

Этих характеристик, конечно, много больше. Например, прайвеси/privacy, безопасность/security, которые как предметы интереса архитектор имеет общие со специалистами по безопасности. ISO 25010 Product Quality Characteristics архитектурные характеристики называет характеристиками качества, и предлагает свой список6, но первым пунктом там стоит «1. Functional suitability», как «уместность функциональности», то есть насколько система выполняет ожидаемые от неё функции. И вот это предмет заботы прикладных инженеров. А вот остальные группы характеристик там как раз архитектурные. И тут нужно сказать, что каждая «сводная характеристика» тут разбивается на множество.

Так, часто поминается «возможность положиться на систему»/dependability как набор более дробных характеристик: availability, reliability, maintainability, durability, safety and security как один вариант, или availability, reliability, safety, integrity (сопротивление к несанкционированным изменениям), maintainability, и ещё бывают разные другие варианты.

Современный тренд — это работа не с интуитивной оценкой каждой характеристики, но с метриками для каждой характеристики, причём значения этих метрик замеряются специальным методом/функцией, проверяющей соответствие «того, что есть у системы» и «того, что надо для успешности системы в её эволюционной нише». Этот метод/функцию соответствия системы её экологической нише по какой-то архитектурной характеристике так и называют — функция соответствия, fitness function.

Для программного обеспечения этот метод/функция соответствия программируется в виде кода какого-то алгоритма (разного для разных архитектурных характеристик), который прогоняется наряду с функциональными тестами и показывает, как изменилась та или иная характеристика в ходе изменений в системе, которые вносят разработчики. Если тесты/испытания новой функциональности (помним о BDD, само определение новой функциональности будет сегодня, скорее всего, записано в формате теста этой функциональности) прошли успешно, то архитектор смотрит на то, как прошли его архитектурные тесты, то есть как поменялись значения функций соответствия, то есть автоматически вычисленные замеры значений архитектурных характеристик по каким-то им выбранным метрикам. Не ухудшились ли архитектурные характеристики после очередного обновления системы? Если ухудшились, то это

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

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

Методы/функции расчёта значений архитектурных характеристик, выполненные в ходе прогона тестов наряду с тестами функциональности — это и есть fitness functions. Проверка безопасности и защиты — она тоже может делаться fitness functions, ибо характеристики безопасности и защиты тоже можно считать архитектурными характеристиками, это не функциональные характеристики. Мы уже обсуждали, что «безопасники» в части security/защиты только по недоразумению представляют отдельную инженерную роль, проще считать их вариантом роли системного архитектора. Они ведь тоже про «видимость модулей» и «удержание характеристик системы при ошибках в отдельных модулях». Можно условно считать, что они пытаются уменьшить влияние на проект команд «отрицательных разработчиков», которые разрабатывают свои модули, пытающиеся нарушить все архитектурные решения по межмодульной связи. Архитектурные fitness functions должны ловить саму возможность таких нарушений.

Лучший способ разобраться со всеми этими архитектурными характеристиками (бывшими «требованиями качества») — это понять, как их отслеживать/мониторить в ходе проекта. Для программных проектов на эту тему написана отдельная книжка «Software Architecture Metrics», 2022:

Если же у вас системы другой природы и/или какие-то другие метрики, не упомянутые в этой книге, то напомним про существование книги «How to Measure Anything: Finding the Value of Intangibles in Business», 2014 (в какой-то момент вам всё же придётся её прочесть, равно как и все остальные книги, обложки которых приведены в нашем руководстве):

Архитектор в своей работе думает ровно об этих архитектурных характеристиках системы основное своё время, примерно так же, как прикладной инженер думает о функциональных характеристиках. Он лично беседует со всеми внешними проектными ролями, чтобы разобраться, какие значения этих характеристик должны быть, чтобы система была успешна. Хороший совет: сосредоточиться не на всех этих характеристиках/предметах интереса, а только на реально необходимых для успеха системы! Например, ограничиться всего тремя предметами архитектурного интереса как важнейшими, а остальные учитывать, но не очень тщательно. Это рекомендация книги «Fundamentals of Software Architecture», со всеми прилагающимися к ней оговорками, что это число и сам состав отобранных архитектурных характеристик сильно зависит от конкретной ситуации в проекте. И дальше нужно принять архитектурные решения, которые позволяют для достижения успешности системы удовлетворить именно эти наиболее важные архитектурные интересы, а не вообще все (что невозможно). Для этого архитектор (равно как и разработчик) много общается с внешними проектными ролями и понимает ситуацию, в которой происходит эксплуатация и разработка системы (они связаны через закон Конвея).

Скажем, вы реализуете какую-то поисковую систему. Архитектор понимает, что для этого приложения ему крайне важна характеристика скорости ответа на поисковый запрос. Он выбирает метрику: «время ответа на поисковый запрос» (не любое время ответа! Только на поисковый запрос). Выбирает метрику: это не «быстро» или «медленно», а «среднее время ответа в миллисекундах». Выбирает приемлемый диапазон значений метрики: «отлично» тут — меньше 150мс, «хорошо» — 150мс-200мс (в литературе по UX указано, что задержка более 200мс хорошо заметна пользователю как «тормозит»), «удовлетворительно» — 200мс-300мс, и «неудовлетворительно/неприемлемо» — больше 300мс. Дальше пишем функцию соответствия в виде теста времени отклика — для этого придётся закупить софт нагрузочного тестирования, определить параметры тестирования (скажем, замер идёт при одновременном обращении 100 пользователей). Вставляем эту нашу fitness function (тест архитектурной характеристики) в общий пул тестов, которые будут прогонять разработчики в ходе их BDD. И отслеживаем замеры, которые поначалу показывают «отлично». Разные команды разработчиков добавляют по чуть-чуть функциональность в поиск: усложняют язык запросов, обращаются к каким-то удалённым микросервисам по сети, чтобы не возиться самим с каким-то вычислением. И в какой-то момент груз этих эволюционных изменений оказывается настолько тяжёл, что время ответа на поисковый запрос стало «удовлетворительным». Это повод архитектору обратить внимание на ситуацию и вмешаться — разобраться, почему так, и что надо делать.

По поводу эволюционной архитектуры и функций соответствия очень сложно искать информацию (как поисковыми системами, так и AI-системами), ибо одни и те же термины используются в самых разных областях:

  • Эволюционные алгоритмы (меметические и генетические, проблемы open-endedness). Скажем, вам будут рассказывать про fitness functions для этих алгоритмов, никакого отношения к обсуждаемой fitness function для архитектуры в инженерной разработке. И даже если это будет инженерная разработка, вам покажут статьи по эволюционным алгоритмам в поиске оптимальной функциональной архитектуры (например, evolutionary neural architecture search, NAS7).

  • Строительная архитектура, где речь пойдёт, например, о важности sustainability, понимаемой как неухудшение экологических характеристик той местности, где стоит здание. Вы долго будете удивляться, почему от вашего софта требуют не загрязнять окружающую среду и быть в нулевом энергетическом балансе с окружающим миром. Кстати, в строительной архитектуре вместо evolvability говорят об adaptability8 — здание ведь тоже эволюционирует, но это понимается как адаптация к изменившимся условиям, design for adaptability. В теории эволюции указывается, что эволюция не имеет своей целью адаптацию! Поэтому выбор термина evolvability более удачный и многообещающий, чем adaptability.

  • Архитектура может пониматься как «всё важное», «функциональная» (без упоминания этого слова), «структура конструкции» (без функциональности, чаще всего это в литературе по эволюционной архитектуре), как синоним «концепции системы». Всегда проверяйте, что имеет ввиду автор, говоря об архитектуре, архитектурной работе, архитектурном методе, роли архитектора.

Моделирование: архитектурные характеристики

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

Номер п/пАнглоязычное названиеРусскоязычное названиеКраткое описание (на русском)Важность для вашей системы
Пример.evolvabilityразвиваемостьСпособность системы инкрементально (а не путём полной переделки «с нуля») изменяться в самых различных аспектах (прежде всего изменения функциональности, изменения архитектурных характеристик).8
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.

Архитектурные решения

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

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

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

  • изменения в одном модуле, сделанные разработчиком (время создания), приводят к необходимости изменений в другом модуле. Это статическая связность, которую нужно уменьшать — но до оптимума, а не ниже.

Например, если вы хотите повторно использовать какой-то фрагмент кода программы (в 90е годы была ценность reuse, «повторного использования», выноса за скобки любой функциональности и реализации её один раз, «отсутствие дублирования»), то неожиданно оказывается, что ошибка в этом фрагменте влияет на выполнение всей программы во всех местах его использования — скажем, в 1000 мест. А если вы занимаетесь улучшением этого фрагмента, то улучшение его исполнения для 3 случаев из этого задействования может привести к ухудшению для остальных 997 случаев изо всех 1000 (динамическая связность). А если меняете интерфейс (в архитектуре часто говорят «контракт», упирая на то, что интерфейс должен быть стабилен), так это требует изменения во всей тысяче модулей (статическая связность). Так что мы имеем тут U-образную «экономическую кривую» полезности уровня зацепления: связность в системе должна быть не слишком маленькой, но и не слишком большой. Архитектурные решения ищут оптимум, для этого проходят самые разные развилки в группировке функций по модулям.

Архитектура (хотя, конечно, речь идёт об архитектурном описании — но слово «описание» часто опускают) тем самым не сводится к каким-то структурным (то есть «разбиения на части») диаграммам, а сводится к набору архитектурных решений по прохождению архитектурных развилок (trade-offs), прежде всего:

  • какие архитектурные стили (шаблоны структуры системы, им будет в нашем руководстве посвящён отдельный подраздел) использовать («монолит», «слоёный монолит», «микроядро» и так далее), в том числе конкретная нарезка на модули верхнего уровня, соответствующих стилю для принятого решения.

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

Архитектурные решения предположительно ведут к успешности системы в части архитектурных интересов/характеристик (всяческих -ilities/-остей). То, что их архитектор принял не «чуйкой», а подумав, надо подтверждать обоснованиями, как и любые другие инженерные решения. В нашем руководстве есть целый раздел по инженерным обоснованиям: в момент принятия это rationale, а обоснование измерением по факту реализации (assurance) — оно делается задействованием fitness functions как «архитектурных тестов».

Лучший метод по оформлению архитектурных решений на сегодня — это форматирование их в виде записи архитектурного решения (ADR, architectural decision record, предложено Michael Nygard в 2011 году9). ADR — это документ на пару страниц текста (или экранов), имеющий примерно следующий формат (текст пишется в виде рассказа для разработчиков):

  • Заголовок/title: короткое имя для принятого решения.
  • Ситуация: описывает различные соображения в части влияния на архитектурные характеристики, в том числе именно тут описываются конфликты разных системных уровней, которые требуют оптимизации.

  • Решение: что делать, чтобы достичь предположительного квазиоптимума (например, использовать какой-то архитектурный стиль или стандарт для межмодульного интерфейса)

  • Статус: это предложение, это обязательно для выполнения, это решение уже отменено каким-то другим предложением (всё меняется, в том числе архитектурные решения)

  • Последствия: что ожидается после реализации решения, в том числе «позитивные» (скажем, «производительность вырастет впятеро») и отрицательные (скажем, «вносить изменения придётся не в один, а в три разных модуля»).

Конечно, есть множество вариантов оформления ADR10, равно как и других (чаще всего более простых) форм документирования архитектурных решений. Интересное развитие тут дало распространение fitness functions в качестве формы документирования архитектурных решений по выбору важных архитектурных характеристик. Тут прямое соответствие тому, что происходило с требованиями: сначала были use cases, по которым разрабатывались требования, по которым разрабатывались тесты, а закончилось тем, что сценарии использования начали сразу писать на языке тестирования. Так и тут: решение по выбору характеристики записывается сразу в виде текста процедуры замера этой характеристики. Но вот инженерное обоснование (почему принято такое решение) будет записано всё-таки в ADR.

Про принятие архитектурных решений путём прохождения развилок (trade-offs) на примере корпоративного программного обеспечения в крупных компаниях, где требуется обеспечить высокую скорость разработки и высокую производительность (распараллеливание работы между вычислителями) говорится в книге «Software Architecture: The Hard Parts».

Вот пример архитектурного решения из этой книги, оно относится к разбивке на модули:

  • ADR: Общий сервис для назначения и маршрутизации заявок.

  • Ситуация: когда заявка создана и принята системой, она должна быть назначена эксперту и затем маршрутизирована на мобильное устройство этого эксперта. Это может быть сделано одним общим сервисом назначения заявок или отдельными сервисами назначения и маршрутизации.

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

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

В этом примере не приведён статус, ибо это «проект из книжки» (в реальном проекте нужно указывать статус, ставить архитектурные решения под контроль конфигурации).

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

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

  • Проверок нет вообще

  • Вася ответственный за то, чтобы в мясорубку гвозди не засовывать (и даже мясо чтобы было не гнилое)

  • Мясорубка имеет входной контроль (и на гвозди, и на мясо, и на попытки работы без сырья вообще)

  • И Вася, и мясорубка проверяют: один что кладёт, вторая --- что ей дали

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

Наиболее быстрое выполнение происходит в том случае, когда входного контроля нет, но есть жёсткий выходной контроль у Васи (ибо на вход мясорубки Вася подаёт в конечном итоге свою работу, и хорошо бы её проверять).

Если заранее непонятно, кто когда что подаёт на вход мясорубки и где её будут эксплуатировать, то выполнение контракта (контрактом/contract называют правила взаимодействия на интерфейсе с модулем, иногда это же называют протоколом) должна проверять мясорубка. В тех случаях, когда Вася тоже проверяет то, что он даёт мясорубке, возникает дублирование. Но в архитектуре есть принцип, что в большинстве случаев развязывание/расцепление/decoupling модулей важнее отсутствия дублирования.

Так как же надо? Если делаете публичный сервис, то лучше бы мониторить то, что подаётся на вход, вести логи для ловли ошибок (кто когда почему подал на вход мясорубки гвозди: это не должно повториться!). Если модуль выполняет чётко определённую функцию, должен работать сверхбыстро, то проверки входных объектов для обработки (сырья, информации) в нём не должно быть, это дорого. Но выходные проверки лучше бы делать. Ну, или добиваться такой работы, чтобы выходные проверки были не нужны «по определению», но такое придётся доказывать (писать обоснования).

Пример: в Tesla на заводе agile производство устроено так, что каждый автомобиль собирается в уникальной конфигурации, ибо отдельные производственные участки часто дублируются, чтобы опробовать какой-нибудь новый способ производства — новый станок, новый приём работы. Старый участок работает по-старому, новый — по-новому. Через новый участок отправлятся часть деталей или подсборок, чтобы «попробовать». В случае успеха старый участок закрывается и все полуфабрикаты дальше идут через новый участок. В случае неуспеха — экспериментальный участок закрывается, продолжает работать старый участок.

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

Однозначного ответа «как надо» в архитектурных выборах нет, всё зависит от вашего проекта, и даже конкретной ситуации в вашем проекте (сегодня оптимальный ответ будет один, а через год в этой же системе ответ будет другим — и придётся всё переделывать. Это нормально, это и есть развитие/evolving системы и её архитектуры).

Решение о том, в каком модуле делать проверки — архитектурное решение, оно по факту не относится к целевой функциональности, оно относится к модульному синтезу для функциональности «проверка». Рассуждения примерно одинаковы, идёт ли речь о Васе и мясорубке, или программе искусственного интеллекта, вызывающей модуль определения кошки и дающей не ожидаемую этим модулем фотографию с потенциальной на ней кошкой, а текст со словом «кошка», в надежде, что этот модуль эту «кошку» найдёт (как Вася надеется, что мясорубка сделает из гвоздей гвоздяной фарш, ибо ему сказали, что «желудки этих людей даже гвозди переварят!», и он просто хотел проверить, так ли это). Архитекторов тренируют на подобного сорта рассуждения: развязывание/расцепление против дублирования (и предупреждают, что дублирование часто лучше!).

Понятие межмодульной связи довольно сложное, и это один из основных объектов, с которым работает архитектор. У вас радиотехническое устройство. Правильно ли будет, если вы все резисторы поместите на одну плату, конденсаторы на другую плату, катушки::модуль индуктивности::функция на третью плату? Заметим, что в предприятии более чем часто делают «отдел со всеми программистами», «отдел со всеми юристами», и хорошо, что не делают «отдел со всеми менеджерами» (рассуждение по той же линии: «они все там одного типа, с одним образованием, пусть сидят вместе»). Это ведь ровно такой же архитектурный вопрос, архитектура-то универсальное понятие для всех типов систем — архитектура радиотехнического устройства и архитектура предприятия по факту требуют одинакового архитектурного мышления.

Или на одной плате должен быть «усилитель высокой частоты», а на другой плате «усилитель низкой частоты» и все резисторы-конденсаторы-катушки индуктивности должны быть там? Или нужно преодолеть огромные трудности и сделать всё на одной гигантской плате, но это «объединение в один модуль» потребует перестройки всего производства? Такой путь, например, был выбран в попытке сделать один чип на 4 триллиона транзисторов (третье поколение чипов Cerebras11): целью было получить максимальную производительность, и отчасти это даже удалось, но пришлось решать множество нестандартных проблем, типа отвести 3.5КВт тепла с площади чипа размером примерно с листок А412.

Прохождение архитектурных развилок

При прохождении архитектурных развилок (architectural trade-offs) между разными архитектурными решениями используются эвристики, известные как архитектурные паттерны/шаблоны. Эти паттерны на сегодня являются основными способами передачи архитектурного опыта между архитекторами. Принятие согласованных между собой наборов отдельных чётко сформулированных решений, которые делаются в той или иной ситуации делается в виде набора паттернов, который называется языком паттернов/шаблонов. Использование языков паттернов впервые предложил в строительной архитектуре Кристофер Александер с коллегами в 1977 году13. Дальше это было подхвачено программными архитекторами, расцвет использования языков паттернов случился в нулевые годы 21 века. Предлагались самые разные языки паттернов, включая паттерны свободного программного обеспечения14 и организационные паттерны15, но постепенно в организационной архитектуре были приняты другие способы организации знания (методы стали передаваться через разнообразные варианты корпусов знаний/body of knowledge, в которых преобладал «процессный подход»), а вот в программной архитектуре такой подход остался16.

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

Главное отличие разных языков паттернов/шаблонов (pattern languages) от разных корпусов знаний (body of knowledge)17 и процессных стандартов (process standard)18 описаний методов (типа ISO 15288 для методов системной инженерии киберфизических систем, или ISO 12207 для методов программной инженерии) то, что они предполагают объяснения в своём составе, а не просто говорят «делай вот такие действия», причём эти объяснения прямо исходят из идей системного творчества (поиск решения через выход на противоречия описан в подразделе по системному творчеству руководства по системному мышлению).

В книге «Fundamentals of Software Architecture» первым законом архитектуры предлагается «в архитектуре все решения являются результатом прохождения развилок», но вот второй закон — это ««почему» в архитектуре важнее «как»». Архитектурные паттерны как раз выполняют этот второй закон: они дают в своих описаниях «почему». Описание паттерна представляет собой несколько страниц структурированного текста, в котором могут встречаться (часто не все) разделы:

  1. Имя паттерна — короткое название, которое будет использоваться в речи, типа «архитектура, центрированная на данных» (data-centric architecture, паттерн 1 в работе «Patterns for Light-Weight Fault Tolerance and Decoupled Design in Distributed Control Systems»19).

  2. Тип (классификация паттернов в языке как наборе паттернов, место в таксономии. Скажем, в оригинальном языке паттернов строительной архитектуры Кристофера Александера всего 253 паттерна, поэтому они разбиты на группы, соответствующие масштабу: города, здания, строительство).

  3. Этот паттерн так же известен как (синоним имени).

  4. Ситуация/context — описание ситуации, в которой нужно применять паттерн.

  5. Проблема — по возможности сформулированная как противоречие проблема, которую призван решить паттерн. Например, «если выключится произвольный один блок, система должна продолжать работать».

  6. Соображения/влияния/силы/forces — рассуждения (по-русски иногда говорят «расклады»), которые необходимо принять во внимание, когда выбираешь предлагаемое паттерном решение проблемы. Если это архитектурный паттерн, то именно тут говорится о том, куда решение паттерна двигает архитектурные характеристики (надёжность, масштабируемость, ремонтопригодность, развиваемость и т.д.). Обычно все эти характеристики двигаются разнонаправленно: что-то улучшается, что-то ухудшается.

  7. Решение — что, собственно, нужно сделать.

  8. Формальное обоснование — использование какого-то стандарта инженерных обоснований для обоснования предлагаемого решения, подробней это обсуждается в нашем руководстве в разделе по инженерным обоснованиям. Например, популярно использование языка GSN (goal structured notation), а в паттернах защиты/security могут использовать «модели целей» STRIDE. Чаще всего формальные обоснования требуют в паттернах решений для безопасности и защиты, ибо формализация дорогая, но в части безопасности на цену обращают внимания мало, «не хочешь сидеть в тюрьме — плати».

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

  10. Общий сценарий — часто приводят как часть, по которой дальше делается формальное обоснование.

  11. Известные использования — если это и впрямь «паттерн» (шаблон), то он был выявлен как успешное повторение какого-то решения. Где этот паттерн оказался успешен? Или это не паттерн, а просто только что в первый раз придумали, и никто ещё этот «паттерн» не использовал?

  12. Литература по прежним работам с паттерном.

  13. Добавляют часто также варианты паттерна (если они не различаются настолько, чтобы делать отдельный паттерн) и связанные паттерны (в рамках языка и из других языков паттернов).

Не всегда описание паттерна именно «структурированный текст» с указанными разделами, хотя в разных сборниках паттернов используется ровно вот такой вид. Но в современной литературе от структурированного представления по большей части ушли, и используют не «букву», а «дух» работы с паттернами. Скажем, глава «5. Component-Based Decomposition Patterns» книги «Software Architecture: The Hard Parts» приводит язык паттернов с шестью паттернами. Эти паттерны даны как более-менее связный рассказ, в котором присутствуют все важные для описания каждого паттерна разделы, но эта структура описания не выделена в тексте, и текст ей следует не слишком строго.

Во многих архитектурных статьях, постах в блогах упоминаются «паттерны», часто говорится о каких-то forces (и они почему-то оказываются архитектурными характеристиками, но не всегда). Когда вы встречаете эти упоминания, вы должны знать: они связаны с организацией мышления архитекторов в форме паттернов, а терминология и стиль архитектурного мышления «паттернами» идёт ещё от строительной архитектуры, из книжки Кристофера Александера с коллегами, из далёкого 1977 года.

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

Как и любое другое решение в инженерии, использование паттернов конкурирует с другими способами описания деятельности. По большому счёту, языки паттернов должны быть описаны в руководстве по методологии. В архитектуре (прежде всего программной архитектуре) они закрепились именно за счёт того, что много внимания уделялось достижению трудноуловимых архитектурных характеристик, которые Кристофер Александер в строительной архитектуре называл «целостностью» или даже «жизнью»: то, что невозможно было выразить простыми указаниями на функциональность, но это был 1977 год! За это время стало понятно, что эти самые forces/расклады могут быть чётко определены и даже измерены как архитектурные предметы интереса, и архитектурные решения ищут оптимум для значений этих характеристик. Паттерны с их попытками не просто сказать, «что делаем», но и «почему делаем» сыграли тут большую роль. Выкиньте всё, связанное с обоснованиями и объяснениями — и вы получите упрощённый вариант, процессные стандарты и body of knowledge. OMG Essence как раз один из таких стандартов: он говорит, как описывать метод, но в этом описании нет места для «почему так» и «чего хотелось бы, и что в этом друг другу противоречит», и «чего мы при этом добьёмся». Конечно, языки паттернов — это более трудоёмкая форма документирования методологического знания!

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

Основная цель, для которой создавались языки паттернов — это документирование опыта собственной работы, а также обучение коллег лучшим методам достижения нужных архитектурных характеристик. Поэтому опытные люди, которым не нужно никого учить, и которые не нуждаются в обучении сами, и которые не признают полезности «мышления письмом» для улучшения собственной работы, в языках паттернов не нуждаются. Их мнение тоже широко представлено, они паттерны критикуют (хотя часто они критикуют и процессные стандарты, и body of knowledge, и даже учебники!). Их тезис: «все эти ваши шаблоны только стесняют творчество, это просто способ зарабатывать деньги для тех, кто эти шаблоны пишет!». В руководстве по системному мышлению об этом говорится как аргументе против «рельсов мышления»: призыв изобретать велосипед каждый раз заново приводит к тому, что мы постоянно имеем дело не с самыми современными велосипедами, а с теми первыми неуклюжими велосипедами, которые делали первые изобретатели велосипедов, ибо современные изобретатели велосипедов нельзя считать, что умней и опытней предыдущих. Конечно, лет через двадцать это будут уже очень неплохие велосипеды, но денег инвесторов и нервов покупателей жалко.

Поэтому предлагается паттерны из опубликованных уже языков паттернов использовать как минимум для обучения, получения кругозора в том, что встречается в жизни (в нашем случае встречается в архитектуре, но в общем случае — в каких бы то ни было методах, для которых был сделан какой-то язык паттернов как способ описать метод). И дальше уже изобретаем велосипеды, но хотя бы что-то уже знаем из учебного материала с языком паттернов. Это решает противоречие: «изобретать паттерны по ходу дела» (вместо «использовать накопленное знание в проекте») и «использовать накопленное знание в обучении».

Во всех учебниках по паттернам (а также во всех других вариантах описания методов: процессных стандартах, корпусах знаний и т.д.) предупреждается, что паттерны из предложенного их языка нужно обязательно адаптировать к конкретному проекту. Для адаптации нужно понимать, как устроены паттерны, как описывается деятельность — это означает, что для адаптации архитектурных паттернов нужно быть «немножко методологом», использовать фундаментальное знание об описании методов работы. И тогда паттерны «из книжки» будут адаптированы к проекту, или даже придуманы новые паттерны (которые в этот момент будут не паттернами, а просто архитектурными решениями). Прямое задействование паттернов «из учебника архитектуры» обычно в проекте не поощряется.

Итого: прохождение архитектурных развилок (trade-offs) сводится к принятию решений в соответствии с какими-то паттернами этих решений, но всегда есть возможность породить какую-то альтернативу, задействовать другой паттерн, или даже сделать архитектурное изобретение, которое потом станет новым архитектурным паттерном.

Языки паттернов развиваются со временем. Так, в программной архитектуре лучшим методом было использование «шестиугольной архитектуры»20, предложенной Alistair Cockburn, часто называемой «порты и адаптеры». Делались модули, которые со всеми внешними сервисами разговаривали через определённые порты, к которым изготавливались адаптеры/коннекторы, чтобы изолировать структуру модуля от изменений в окружении (нужно тогда только немного изменить адаптер, но не сам модуль). Всё это хорошо работало, но база данных тоже была «через адаптер» — и когда менялась структура данных, всё равно нужно было менять начинку модуля, а не только адаптер. Дальше этот паттерн стал паттерном «мотоцикла с коляской»: базу данных втащили внутрь модуля, ибо «зацепление/coupling хуже дублирования», а «мотоцикл с коляской» тут оттого, что сам модуль стали делить на предметную/domain часть и инфраструктурную (коммуникация, мониторинг и т.д.) техническую часть. Базу данных сдублировали в разных модулях, и отдельно организовали синхронизацию содержания этих баз данных. Это решение (паттерн «мотоцикл с коляской») оказался чуть сложней, чем «порты и адаптеры», но лёг в основу движения микросервисов21, которое существенно повлияло на архитектуру корпоративных приложений.

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

Задания

Поставьте отметку о выполнении.

  1. Написан пост с описанием какого-то метода вашей работы с использованием формата языка паттернов, взятого из примера архитектурного паттерна (в руководстве приведено 13 возможных разделов описания паттерна, вами использовано не менее 8 из них).

Архитектурные стили

Одним из видов архитектурных паттернов являются архитектурные стили: это общий способ организации взаимодействия модулей в системе. Архитектурные стили непрерывно развиваются, выбор архитектурного стиля — очень сложное архитектурное решение, оно делается одним из первых, а потом постоянно уточняется (да, смена предпочтительного архитектурного стиля, например, монолитного на какие-нибудь «сервисы», в ходе жизни системы вполне может быть, и даже не один раз). Так, в программировании корпоративных приложений можно выделить вот такие основные стили (и множество гибридных, и множество появляющихся каждый год новых вариантов):

  • Слоистые (layered, «слоёные»): все модули бьются на слои, которые формулируются в терминах разработчиков, а не терминах предметной области. Скажем, весь ввод-вывод для пользователя делается презентационным/presentation слоем, вся предметная функциональность в слое «организационной логики» (business logic), вся конвертация данных из объектного формата в реляционный в слое «постоянного хранения» (persistence), а собственно хранение данных в базе данных как отдельном слое. Обсуждается в такой архитектуре «видимость», хотя само понятие «слоя» бралось из функциональной архитектуры (мы обсуждали в разделе проектирования слоёные архитектуры, они там давались именно как функциональные, но в архитектурных стилях это обсуждалось уже как «сгруппируем реализацию функционального слоя в модули одного модульного слоя»): нельзя модулю из презентационного слоя писать прямо в базу данных, даже если это быстро! Это хоть как-то ограничивает связность, предотвращает появление «большого кома грязи»/«big ball of mud» как архитектурного стиля, где «всё связано со всем, пошевелить ничего нельзя». Это самая дешёвая и простая архитектура, только беда: если нужно внести какую-то новую функциональность, то потребуется вносить изменения в каждый слой. Если каждым слоем занимаются отдельные программисты (это общий закон: в модульной структуре отдельными модулями занимаются отдельные команды, играет закон Конвея и обратный манёвр Конвея), то это потребует согласования действий множества людей, что медленно (люди будут ждать друг друга, чтобы сделать изменения) и наверняка будет вносить ошибки (не смогут точно договориться, что же где надо для чего изменить). Поэтому от таких архитектур стараются отказаться при первой же возможности, но «так уж получается, что у нас архитектура именно такая»: на саму архитектуру уходит практически ноль усилий на поддержание, она очень удобна для быстрого старта разработки!

  • Конвейеры (pipeline, «трубопроводы»): все модули представляются как «обработчики» и «фильтры», выходы одних подаются на входы других. В операционных системах есть «оболочки» (shell), которые позволяют «скриптовать», то есть прописывать вот такие «конвейеры» из разных программ-утилит. Это очень мощная архитектура! Но и тут проблемы: работы конвейера плохо распараллеливаются, производительность конвейера определяется производительностью самого медленного звена и числом звеньев (скажем, если задержка на передачу данных между модулями через сеть в конвейере 300мс, то пять модулей — и вы «изниоткуда» получаете 1.5 секунд задержки), масштабируемость при этом минимальна.

  • Микроядерная/microkernel архитектура представляет собой «основной блок» (ядро, core) и многочисленные к нему плагины/plug-ins, которые делают те или иные обработки. Это уже сильный прогресс: если хочется поддержать какую-то особую функцию, то можно сделать плагин, и не трогать всей остальной системы. Разработчики плагина могут считать себя отдельными. Но масштабируемость будет определяться одним модулем: микроядром, а не плагинами, поэтому эта архитектура тоже не решает всех проблем. Скажем, в компьютере у вас ровно такая архитектура, «plug-and-play», вы можете подключить дополнительный экран, проектор, мышь, принтер, сканер и т.д. — но всё-таки основные возможности развития совокупной системы будут ограничиваться самим компьютером, подключение разной периферии к нему удобно, но не гарантирует удобства развития самого компьютера. Когда такая архитектура была предложена как основная для операционной системы Windows, её слоганом стало plug-and-play (воткни устройство и играй), но все грустно шутили про plug-and-pray (воткни устройство, и молись, что заработает). В первые несколько лет так и было, всё работало очень ненадёжно. Но сейчас всё работает надёжно, и даже сам принцип plug-and-play не воспринимается как радикальный и новый, его просто забыли, «метанойя, не помним, как оно было до появления этой микроядерной архитектуры».

  • Сервис-ориентированная архитектура (SOA, service-oriented architecture) включает множество независимых сервисов, которые сами обслуживают в себе все «слои», но обращаются к одной базе данных, чтобы синхронизировать свои операции над ней. И у неё тоже есть недостатки: одна база данных даёт сильные зависимости, особенно концепция «транзакции» в этой базе данных: если какой-то модуль «подвис», то он может «подвесить» и базу данных, система с сервис-ориентированной архитектурой в этом плане ненадёжна. Не путаем: в наших руководствах «сервисом» называют внешнее поведение какого-то сервера/службы по изменению чего-то в окружении сервера, а в программной архитектуре так будут называть и сервис, и сервер, не особо их различая (грубо говоря, путая бег и бегуна, «сервис оказывает сервис»).

  • Микросервисная (microservices) архитектура — это развитие сервис-ориентированной архитектуры, просто база данных у каждого модуля (они тут называются «микросервисы») своя. Это обеспечивает слабую связанность, но требует повышенного внимания к синхронизации работы: все трансакции нужно программировать вручную. Возникают интересные паттерны, типа «Смыв сбоя»: если где-то в середине трансакции обнаружилась ошибка, то не нужно пытаться исправить её (это очень дорого!), нужно просто выкинуть обработку кейса, сбросив тем самым сложное сочетание состояний разных микросервисов в ходе обработки кейса. Микросервисы нарезаются по принципу «один микросервис-модуль — одна пользовательская функция/метод, она же bounded context, она же workflow/кейс как работа, выполняемая человеком и компьютером». И разрабатывает микросервисы одна команда. Управление микросервисами выполняет оркестратор, специальный модуль, отвечающий за правильность назначения прикладных микросервисов на работы по обслуживанию пользовательских кейсов (как дирижёр в оркестре, отсюда название). Есть вариант и «без оркестратора», это называется хореография (как в танце, когда дирижёра нет, а все просто прилаживаются к движениям друг друга). Это всё сложно, очень сложно, в таких архитектурах много неудач: если делать микросервисы слишком мелкими, это приводит к «распределённому большому кому грязи», что много хуже (например, медленней и запутанней в плане внесения изменений) монолитного «большого кома грязи», где все связаны со всеми, но не через сеть (микросервисы связаны через компьютерную сеть). Но зато масштабирование и производительность, а также развиваемость на таких архитектурах могут достигать таких значений, которых нельзя получить в других архитектурах.

Есть и много других видов архитектур: «пространственная», «управляемая событиями».

Подробней об архитектурных стилях можно узнать в книгах «Fundamentals of Software Architecture» и «Software Architecture: The Hard Parts», которые мы уже вам рекомендовали, если вы занимаетесь разработкой программного обеспечения. Но мы настоятельно советуем читать книги из раздела (в том порядке, в котором мы их давали в этом разделе, приводили их обложки) и в том случае, если вы занимаетесь разработкой систем другой природы, а также если вы даже не выполняете роль архитектора — но ведь вы наверняка будете общаться с архитекторами, поэтому должны понимать, что у них в головах. В этих книгах как раз фронтир сегодняшней архитектурной мысли, оттуда архитектурные паттерны потихоньку будут использоваться в проектах создания и развития систем самой разной природы, а не только программных систем.

Архитектурные стили непосредственно влияют на то, как архитектор разбивает проект на так называемые архитектурные кванты (quanta, единственное число quantum, это латынь), по-другому называемые самыми разными терминами: сборки, компоненты и т.д. Это такие наборы модулей, которые имеют общий ввод в эксплуатацию (delivery) если заменять какой-то из них на другую версию, поэтому ими занимается обычно одна команда разработчиков. Скажем, замена резистора на плате не может произойти независимо от замены платы: впаянный в плату резистор вводится в эксплуатацию только в составе целой платы, куда он намертво впаян. Так что сборкой/квантом тут будет плата, а резистор — просто модулем в составе сборки.

Одно из достоинств микросервисного архитектурного стиля (часто говорят проще: «микросервисной архитектуры») является то, что база данных микросервиса находится внутри этого микросервиса, поэтому микросервисы можно вводить-выводить из эксплуатации независимо друг от друга, так что каждый из них может иметь свою команду разработчиков, которые мало зависят друг от друга. Это позволяет (за счёт усложнения архитектуры, это большой минус, учитываемый при выборе стиля) упростить разработку функциональности. Время ввода в эксплуатацию отдельных фич (time to market — от появления идеи до момента ввода в эксплуатацию, то есть доступности для использования) при микросервисной архитектуре становится существенно меньше. Но работа архитектора для микросервисной архитектуры становится сложнее, ибо нужно как-то оркестровать работу микросервисов, налаживать для них хореографию, а ещё микросервисы будут чудовищно медленны в обработке каждого запроса (обращения микросервисов друг ко другу весьма дороги по времени, это же чаще всего передача сообщений по компьютерной сети, а не вызов программы в той же оперативной памяти). Чудес не бывает: выбирается при прохождении развилок по выбору архитектурного стиля, как и при прохождении любых других развилок не лучший вариант, а наименее плохой.

Текущий раздел недаром называется «эволюционная архитектура»: вы можете начать с одного архитектурного стиля, продолжить другим, затем перейти к третьему — и всё это по ходу проекта, не прерывая эксплуатации системы.

Задания

Поставьте отметки о выполнении следующих дел:

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

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

  3. Вы нашли и откомментировали не менее трёх чужих постов на тему разработки архитектуры системы.

4

Пример architecture characteristics worksheet — https://www.developertoarchitect.com/downloads/architecture-characteristics-worksheet.pdf, в классической инженерии их продолжают называть system quality attribute — https://en.wikipedia.org/wiki/List_of_system_quality_attributes

13

https://www.artlebedev.ru/izdal/yazyk-shablonov/ — по-русски pattern language переведён как «язык шаблонов».

19

В сборнике Transactions on Pattern Languages of Programming IV, 2019