Что такое Арка?

Вопрос задал Денис Будяк: «понятие Арки, а где оно определено и что означает».

Вот первое определение, которое смог сформулировать:

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

Определение плохое, так как, во-первых, определяет арку через компоненту (через другое неопределенное понятие), и, во-вторых, не разделяет design-time и run-time понятия.

Начнем чистить: есть описание арки (аналог — класс) и экземпляр арки (аналог — объект). Каждая «ссылка» на «описание арки» в (декларативной) архитектурной схеме приводит к созданию нового экземпляра арки. В отличие от модуля (Оберон) или пакета (Go), в программной системе может быть любое количество экземпляров арки. Я дальше буду использовать термин «арка» для «объекта», а если речь идет об описании, то писать «описание арки».

Определение 2Описание арки задает описание изолированного и переносимого «объекта», предназначенного для работы в архитектурной схеме.

Не уверен, что слово «объект» здесь достаточно понятно, но пусть пока так.

Самое сложное (многомерное) в этом определение — это понятие «изолированный», так что начну с более простого.

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

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

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

Вот теперь про «изоляцию«.

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

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

Замечание: я рассматриваю общий случай. При оптимизации набора арок (статически связанных), динамическое знакомство может произойти во время компиляции или сборки. Что собственно, никак не противоречит динамическому знакомству, которое, в данном случае, выполняется ahead of time.

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

Изоляция 3: изоляция от платформы. Арка не может использовать непосредственно функции платформы, все они должны проходит через изолирующий слой — это обеспечение переносимости.

Кажется все пока про изоляцию, хотя потом может добавится изоляция по ниткам исполнения (или по сопрограммам — coroutines).

И последнее на сейчас: Может ли в программной системе быть что-то кроме арок? Конечно может и есть: всегда есть изолирующий от платформы слой, я уже писал о нем достаточно подробно в какой-то статье. И арка может импортировать модули, что собственно, видно на том примере, который я уже приводил: пример.

Ну и в завершении:

  • описание арки не есть класс, есть отличие, особенно от классов в CLOP языках
  • арка похожа на объект (экземпляр класса), но есть отличия
  • арка не есть динамическая библиотека (dll, so)

То есть, аналогии можно использовать, но все они не точны.

 

18 комментариев



  1. Добрый день.

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

    Ответить

    1. Да, формально можно сказать, что есть статический интерфейс IDynamic, но это софистика.
      Клиента компоненты не интересует IDynamic — его интересует, можно ли из этого черного ящика извлечь пользу. А для извлечения пользы можно спросить (во время исполнения) — реализует ли компонента вот такой протокол (статически заданный). Но и это не обязательно, так как можно спросить — а есть ли у компоненты функция с таким именем. И какая у неё сигнатура. Собственно, я всего лишь хочу сказать, что уровень статики-динамики является гибко настраиваемым. Изоляция — это способ достижения гибкости.

      Ответить

      1. Я понимаю, что вы говорите про работу этого динамического взаимодействия. Я говорю немножко другое — это не свойство _изоляции_, это свойство _динамичности_ взаимодействия.

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

        P.S. Почемуто уведомления об ответах мне на e-mail не приходят :(

        Ответить

  2. Про «Изоляцию 2» — по памяти. Интересная мысль, не думал об этом. Означает ли это, что одна арка не мождет послать другой ссылку на объект, с которым нужно поработать и отдать обратно (mut ref в Rust)? Мне кажется, это достаточно удобный и вполне безопасный (если контролировать как в Rust) способ взаимодействия, не хотелось бы от него отказываться.

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

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

    Ответить

  3. Про «Изоляцию 3». Вот это самое интересное. Я в своих размышлениях пришел к тому, что именно этот момент является самым важным, порождающим совершенно новые свойства анализируемости и понимаемости компонеентных программ. Кстати, буду рассказывать и показывать прототип на Rust DSL на Стачке

    https://ul24.nastachku.ru/architecture-is-code-%D1%81%D0%BB%D0%B5%D0%B4%D1%83%D1%8E%D1%89%D0%B0%D1%8F-%D0%BF%D0%B0%D1%80%D0%B0%D0%B4%D0%B8%D0%B3%D0%BC%D0%B0-%D1%8F%D0%BF

    Так вот, мне кажется, что на самом деле эта изоляция должна быть такого рода: компонент (по вашему — арка) может использовать ввод-вывод (IO в Хаскеле) только посредством переданныых ему интерфейсов (по вашему — протоколов) от родительского компонента, и никак иначе. Т.е. НЕЛЬЗЯ вот так просто написать println(«»). Нельзя просто так записать что-то в файл или обратиться по сети куда-то, или даже запросить текущее системное время, это можно сделать только через интерфейс, выданный родительским компонентом (родительской аркой). Жестко, но мне кажется, что это очень продуктивный путь анализа и прототипирования для новой парадигмы.

    Ответить

    1. Думал над обоснованием этого своего, до сих пор интуитивного, тезиса. И, кажется, придумал:

      1. Мы хотим внести сильное отношение «часть-целое» в программные системы для runtime-элементов, которого раньше не было

      2. Часть-целое — это в т.ч. когда «часть не может выходить за границы целого». В 3D-мире: ножка табуретки никак не может выйти за границы табуретки в целом :)

      3. Этот принцип работает не только в геометрии, но и в других аспектах, например, в потреблении ресурсов: фонарик в смартфоне не может потреблять больше электричества чем смартфон целиком, верно? Текстовый редактор в IDE не может потреблять больше памяти или CPU, чем IDE целиком.

      4. А также этот принцип работает во взаимодействиях: если какая-то часть нашей программы пишет в файлы или лазит в сеть, мы должны признать, что программа в целом тоже пишет в файлы или лазит в сеть :)

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

      6. Эта логика разворачивается по индукции по всем уровням схемы. По пути, конечно, интерфейсы преобразовываются, [де]композируются, крупнеют или мельчают, но в конечном счете не могут взяться на нижнем уровне из ниоткуда, в стиле <> DateTime.Now() или <> File.WriteString() без намека на это всеми этажами выше.

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

      Ответить

    2. «Т.е. НЕЛЬЗЯ вот так просто написать println(«»). Нельзя просто так записать что-то в файл или обратиться по сети куда-то, или даже запросить текущее системное время, это можно сделать только через интерфейс, выданный родительским компонентом (родительской аркой)» — по сути, верно, кроме «выданной родительской аркой». На мой взгляд, не надо протаскивать «системные вызовы» через дерево.

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

      Ответить

      1. В комментах к более новым записям я отметил, что с интересом обнаружил аж две концепции, которые призывают ровно к тому-же — таки протаскивать системные интерфейсы через дерево! — это algebraic effects в ФП и capabilties-based security. Последние чаще прототипируют поверх ООП как раз. И те, и другие находят пользу в таком «протаскивании», и понятно, что boilerplate можно засахарить. Интересно было бы спросить вас про аргументы против — почему «не надо протаскивать «системные вызовы» через дерево»?

        Я пришел к этой идее до того, как почитал про эффекты и capabilities, пришел из необходимости — описано выше как «обоснование тезиса». Если я хочу видеть _правдивое_ внешнее взаимодействие компонента на любом уровне декомпозиции, значит я должен как-то обеспечить, что на самом верхнем уровне я увижу, что «программа как черный ящик обращается к NetIO, LocationAPI, GUI API, SystemTimeAPI, SystemLogAPI, ProcessAPI». И при этом я, к примеру, прямо тут, на верхнем уровне мог бы ограничить доступ NetIO каким-то CIDR, FileIO — какой-то директорией и безопасники тут же начнут биться от восторга — это и есть capability based security. А пришел я сюда вообще не от security, а просто от желания видеть автоматически _100%_правдивую_ схему взаимодействий компонент сверху-вниз.

        Так почему бы нет? На мой взгляд, именно эта добавка придает изменениям в ЯП принципиально новое качество, уровня парадигмы.

        Ответить

        1. У меня нет возражений против ограничения доступа на уровне каких-то подсистем. Например, запретить подгруженному плагину лазить в файлы грязными руками.

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

          Если так, то мы говорим не о протаскивании вызовов, а о протаскивании запретов или разрешений. И тут слово протаскивание не уместно, мы, скорее, говорим о некой иерархической модификации множества запретов/разрешений.

          И еще, когда мы хотим «видеть _правдивое_ внешнее взаимодействие компонента на любом уровне декомпозиции»? AOT? Не всегда возможно, вот пример с плагином.

          Еще, вот это: NetIO, LocationAPI, GUI API, SystemTimeAPI, SystemLogAPI, ProcessAPI — это что? Например, делаем SystemLog аркой, разрешаем взаимодействие с SystemLogAPI только для этой арки, и смотрим на наличие/отсутствие взаимодействия в схеме.

          В общем, у меня одни вопросы и никаких ответов. Единственное, в чем я уверен, что не надо права доступа жестко вписывать в код — это дело настройки.

          В Вире доступ к sys api сделан через таблицу функций, причем, она заполняется лениво — в начале вся таблица заполнена заглушками. Заглушка при первом обращении проверяет наличие системной функции и заменяет в таблице себя на эту функцию. Тут же, в заглушке можно проверять права доступа. А если разрешить делать разные таблицы для разных подсистем (верстаков), то можно гибко настраивать доступы. Но это динамика.

          Кажется, я опять смотрю с другой стороны: я считаю, что статика — это частный случай динамики. И архитектура должна это учитывать, так как (почти) любая программа частично динамическая. Например, для запроса времени надо обратится к time-server. Можно сказать, это дело ОС и мы в это не лезем. А зачем нам в данном случае ОС? Почему это не арка SystemTime?

          Ответить

  4. И еще одно. Как я себе мыслю новую парадигму — принципиально важно сказать, что у компонент (арок) есть базовое отношение часть-целое (родитель-дочка), оно задает ряд ограничений на взаимодействие. Вы пока об этом не говорите, хотя, кажется, идея «явной схемы программы» — ровно об этом. И это тоже в каком-то смысле про изоляцию. Не может дочерняя к арке А1 арка А2 взять и вызвать/включить в себя дочернюю арку Б2 от брата ее родителя Б1. Если только это описание арки не взято из библиотеки. В этом месте мы начинаем различать программы и библиотеки, что интересно.

    Иначе можно сказать/реализовать это ограничение в терминах модулей Rust (где есть иерархия):
    * дочерние арки оформляются в дочерних модулях по отношению к модулям родительких арок
    * права на описания дочерних арок даются только модулям родительских арок, т.е. pub(super) ChildrenComponent = …

    Ответить

    1. Еще думал на эту тему. Придумалось следующее:

      1. Модули — это ведь в т.ч. средство управления видимостью/доступом одних определений (инструкций) к другим

      2. Иерархичность модулей типа как в Rust — это прекрасный шаг вперед, это уже позволяет вводить сильное отношение «часть-целое», правда не про комплненты в runtime, а про инструкции в design-time

      3. Для того, чтобы для runtime-компонент обеспечить защиту доступа дочерних компонент от посторонних, достаточно использовать механизмы управления видимостью модулей в design-time. Пргосто положите определения дочерних компонент в дочерний модуль. Собственно, как это делаем в Rust.

      4. Других языковых механизмов наверное и не нужно, если мы только не говорим про создание защищенной среды исполнения

      Ответить

      1. >4. мы только не говорим про создание защищенной среды исполнения

        Конечно говорим, какой смысл говорить о дырявой среде исполнения?

        Rust и desing-time. На мой взгляд, все особенности языка программирования должны остаться внутри арки. И для архитектора/сборщика должно быть не важно, на каком языке написан код арки, ну не считая производительности/памяти.

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

        Если бы у меня был хороший инструмент, я бы не терял времени, а использовал его. Увы…

        Ответить

        1. Я привел пример Rust просто потому что нигде больше такого механизма не видел, и мне он показался достаточным, чтобы не вводить какие-то еще. Но, действительно, если говорить про многоязыковую систему, это значит, что ограничения, которые мы хотим видеть в runtime, нельзя (нежелательно? ненадежно?) обеспечивать средствами design-time, это значит, что у нас должны быть умная (component-hierarchy-aware) среда исполнения, которая понимает иерархию компонент и не дает кому попало обращаться к кому попало. Ну, почему бы нет. Мои интересы как архитектора (а я смотрю с это позиции) это тоже удовлетворяет, устраняя OOP dependency hell.

          Ответить

    2. Я это вижу иначе. Понятие родитель-дочка возникает только в схеме. Сама по себе арка изолирована — из неё торчат разъемы, а что к ним будет подсоединено — это определяется схемой (ну и арка еще полагается на некий обязательный набор — платформенные слой).

      Может ли архитектор соединить А2 с Б2? Почему бы и нет! Например, подключить Б2 как получатель уведомлений или обработчик запросов из А2.

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

      Как видно, у нас с тобой, Игорь, в голове очень разные картинки. И это интересно и полезно.

      Ответить

      1. > Как видно, у нас с тобой, Игорь, в голове очень разные картинки. И это интересно и полезно.

        Они _заметно_ разные, хотя мне кажется, что есть и хороший общий базис :) Понимание ценности архитектуры-в-ЯП.

        Ответить

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *