Архитектура микроядра

Архитектура микроядра (Microkernel Architecture), её также называют архитектурой плагинов (plug-in architecture), делит приложение на маленькое ядро с минимумом функций и подключаемые к нему плагины. Ядро знает, какие плагины установлены, и вызывает их через общий контракт, а каждая новая функция или правило продукта приходит отдельным плагином. Поэтому возможности продукта растут и настраиваются под конкретного клиента подключением и отключением плагинов, а не правкой ядра - пока новый плагин укладывается в контракт ядра.

Проблема

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

Решение

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

Как это работает

У слова микроядро два значения, и их легко спутать. В операционных системах микроядро - это ядро ОС, в котором оставлен минимум: процессы, память, обмен сообщениями, а драйверы и файловые системы работают отдельными процессами вне ядра. Так устроены Mach (Университет Карнеги - Меллона, с 1985 года), L4 (Йохен Лидтке, 1993) и MINIX 3 (Эндрю Таненбаум, 2005). Linux - наоборот, монолитное ядро, и спор Таненбаума и Торвальдса об этом в 1992 году стал знаменитым.

Эта страница - про второе значение, архитектурный стиль приложения, который перенёс ту же идею с ОС на прикладные программы. В книге «Pattern-Oriented Software Architecture» (Бушман и соавторы, 1996) паттерн Microkernel описан с участниками: микроядро, внутренние серверы, которые расширяют ядро, внешние серверы, которые строят на нём свои интерфейсы, и адаптеры для клиентов. Общая мысль у обоих значений одна: ядро держит только неизменную основу, а всё изменчивое живёт снаружи.

В нынешнем виде стиль описал Марк Ричардс в «Software Architecture Patterns» (O'Reilly, 2015): ядро (core system) с минимумом функций и плагины (plug-in modules), и по его словам это естественный выбор для продуктов, которые ставятся клиентам. В «Fundamentals of Software Architecture» (Ричардс и Нил Форд, 2020) добавлено, что ядро внутри может быть устроено слоями или как модульный монолит, а плагины подключаются либо при сборке, либо во время работы.

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

Плагины должны быть независимы друг от друга: каждый общается только с ядром. Как только один плагин начинает вызывать другой или зависеть от порядка вызова, их уже нельзя включать в любом сочетании, и архитектура теряет главное свойство. В примере no-disposable и corporate-only ничего не знают друг о друге, и порядок, в котором ядро их вызывает, на результат не влияет.

Ошибки плагинов - отдельная забота ядра. Пример перехватывает исключение и работает по «закрытой» политике: проверка не выполнилась - адрес не сохраняется. Но плагин в том же потоке может зависнуть, и тогда никакой try/catch не поможет. Поэтому VS Code запускает расширения в отдельном процессе, а браузеры изолируют свои расширения от страниц - за изоляцию платят сложностью и скоростью вызовов.

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

Нюансы выбора

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

Правила по регионам и клиентам - классический пример Ричардса и Форда: обработка страховых заявок, где правила каждой юрисдикции лежат в своём плагине. Новый регион - новый плагин, а общий процесс обработки в ядре не трогается.

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

Закрытая или открытая политика - если плагин-проверка упал, что безопаснее: отказать или пропустить? Для проверок и безопасности обычно отказ, для необязательных функций вроде подсветки или подсказок - пропустить плагин и работать дальше.

Против микроядра - если набор функций один для всех и меняется вместе с продуктом, реестр и контракт будут лишней церемонией. То же, если «плагины» постоянно зависят друг от друга: это признак, что граница проведена неверно.

Примеры и история

Frank Buschmann и соавторы, «Pattern-Oriented Software Architecture, Volume 1» (1996) - описание паттерна Microkernel: микроядро, внутренние и внешние серверы, адаптеры.

Mark Richards, «Software Architecture Patterns» (O'Reilly, 2015) - глава о микроядре: ядро и плагины, реестр, контракты, применение в продуктах.

Mark Richards, Neal Ford, «Fundamentals of Software Architecture» (2020) - глава о стиле микроядра: плагины при сборке и во время работы, удалённые плагины, адаптеры контрактов, пример со страховыми заявками.

Jochen Liedtke, «On µ-Kernel Construction» (SOSP, 1995) - статья автора L4 о том, каким должно быть минимальное ядро ОС: откуда пришла сама идея «оставить в ядре только необходимое».

Eclipse и OSGi - платформа, где почти всё, включая инструменты для Java, сделано плагинами; с Eclipse 3.0 (2004) плагины работают на модульной среде OSGi.

Когда применять

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

Примеры из практики

  • Eclipse - сама среда разработки почти целиком собрана из плагинов поверх небольшой платформы, включая инструменты для Java. С версии 3.0 (2004) плагины Eclipse работают на модульной среде OSGi.
  • Visual Studio Code - редактор с ядром и расширениями, которые подключаются через описанные точки расширения. Расширения работают в отдельном процессе (Extension Host), поэтому медленное или сломанное расширение не подвешивает интерфейс редактора.

Похожие паттерны

Слоистая архитектура

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

Гексагональная архитектура

Гексагональная архитектура (Hexagonal Architecture), её второе название - Порты и адаптеры (Ports and Adapters), ставит бизнес-правила в центр приложения и отделяет их от всего внешнего: интерфейса, базы данных, тестов. Ядро объявляет порты - интерфейсы, через которые с ним можно говорить и через которые оно само просит данные, - а снаружи к портам подключаются адаптеры. Все зависимости направлены внутрь, к ядру, поэтому интерфейс или хранилище можно заменить другим адаптером того же порта, не меняя ни строчки в правилах.

Клиент-серверная архитектура

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