The post has been translated automatically. Original language: Russian
When people talk about modular architecture, they usually mean dividing a large system into parts.
One module is responsible for the memory.
The other is for input and output.
The third one is for executing commands.
It seems that the main thing is to distribute the functions correctly.
But back in 1972, David Parnas proposed a much deeper criterion.
He argued that the system should be divided into modules not according to the sequence of operations performed, but according to solutions that may change.
Each module must hide one important part of the architecture from the rest.
It was this idea that got the name concealment of information.
A module is not just a separate file.
In practice, the word "module" refers to almost anything.
File.
The library.
A folder in the project.
Service.
Package.
But the physical separation of the code does not yet create a modular architecture.
If changing the internal solution of one component requires edits throughout the system, there is no real separation.
According to Parnas, a good module should hide its internal structure behind a stable interface.
The rest of the system needs to know what the module is doing.
But it shouldn't depend on how exactly he does it.
This is the point of hiding information.
What exactly should the module hide?
Let's imagine a component that manages memory.
You can divide its code into functions for allocating, freeing, and checking memory.
But this still does not guarantee modularity.
If other components directly know the format of the tables, the location of the service areas, and the internal allocation algorithm, any change to the memory manager will affect the entire system.
Parnas proposed a different approach.
The memory module must hide:
- internal data representation;
- memory management algorithms;
- changeable architectural solutions;
- limitations of a specific implementation;
- details that are not needed by other parts of the system.
Only the contract remains outside.
The internal implementation can be changed as long as the contract is maintained.
This is what allows a complex system to evolve without constantly destroying already written code.
We came across this during the development of Memora8.
We are developing our own Memora8 32-bit processor, the Reganta OS operating system, the Sekura JS programming language and tools around them.
All these components form a single computing platform.
At the same time, each level should be able to develop independently.
The processor architecture should not disclose all the details of the hardware implementation to the programming language.
Sekura JS should not depend on the internal device of the scheduler.
The application module does not need to know how the Firmware Kernel prepares it for execution.
Reganta OS should not force each module to understand the internal structure of the entire system.
Otherwise, any change to Memora8, the Firmware Kernel, or the runtime environment will require rewriting the entire platform.
The modular Reganta OS model
In a traditional operating system, the unit of execution is usually considered a process.
In the Reganta OS architecture, the execution unit is the Sekura JS module.
Modules can be linked dynamically or statically combined at startup.
The Firmware Kernel receives ready-to-execute modules, prepares them, and launches them.
This model provides great flexibility.
But it requires very clear boundaries.
The module must understand the interface available to it.
It should not depend on the internal mechanisms of the Firmware Kernel.
The Firmware Kernel, in turn, does not need to know the business logic of each module.
Connections between modules should be built through known contracts, and not through knowledge of someone else's internal implementation.
In fact, the ideas of Parnas become more than just a principle of organizing the source code.
They become the basis of the execution model.
Sekura JS as a modular environment
Sekura JS is created not only as a programming language.
It is an environment for creating executable modules that can connect, merge, and interact with each other.
In such an architecture, it is especially important to distinguish between the interface and the implementation.
For example, one module can provide operation with a device.
The other is to use this device.
The user module must know the available commands and the exchange format.
But it should not depend on how the device is physically implemented inside Memora8 or the Firmware Kernel.
If the internal implementation changes, the external contract should remain the same if possible.
This is what makes the module replaceable.
Without hiding information, modularity quickly becomes a formality.
Why is there not enough documentation?
Even if the interfaces are well designed, another problem arises over time.
The team stops remembering which decisions the module is required to hide.
The API can be described in the documentation.
But it is not always fixed.:
Why is the border drawn here?
What details are considered internal?
What can change independently?
Which dependencies are prohibited?
Which part of the contract is stable?
What architectural solutions does the module protect against spreading through the system?
Without this knowledge, the developer may accidentally create a new dependency on the internal implementation.
Formally, the code will continue to work.
But the architectural boundary will be destroyed.
That's how Noda appeared.
Noda was originally created as an internal development tool for Memora8, Reganta OS, and Sekura JS.
It was not enough for us to store only the description of functions and interfaces.
It was necessary to preserve the meaning of architectural boundaries.
Therefore, canonical knowledge is fixed in Noda.:
- module assignment;
- his public contract;
- hidden architectural solutions;
- acceptable dependencies;
- prohibited dependencies;
- reasons for the chosen separation;
- the conditions under which the canon must be revised.
This article explains more than just how to use the module.
She explains why it exists in this form.
Canonical knowledge as border protection
In a complex system, the interface can be broken not only by code.
It can be violated by misunderstanding.
The developer can start using the internal data structure.
An AI tool can generate code that bypasses a public contract.
The new component may gain extra knowledge about the neighboring module.
Each such decision creates a hidden connectedness.
Noda helps to make architectural boundaries explicit.
If the canonical article says that a certain structure is internal, the developer and the AI tool should not use it directly.
If interaction is allowed only through a specific interface, it becomes part of the current knowledge of the system.
Thus, the canon does not just serve as documentation.
It protects modular architecture from gradual erosion.
LLM and the Parnassus principle
Modern LLMs can create code quickly.
But the generation speed does not guarantee a good architecture.
On the contrary, the model can easily use any available part if it does not know that this part should be hidden.
Therefore, it is not enough for AI to show the repository.
He needs to be given architectural rules.
Which parts of the system are public?
Which solutions belong to a particular module?
What are other modules forbidden to know?
Which interfaces are considered stable?
When does the LLM receive such canonical For example, it can work within architectural boundaries, rather than just generate technically valid code.
In this sense, Noda becomes the connecting layer between the principles of classical modular engineering and modern AI tools.
Modularity of knowledge
The ideas of Parnas are applicable not only to program code.
The knowledge itself can also be organized modularly.
One canonical article should be responsible for one architectural area.
For example:
- the Reganta OS execution model;
- the life cycle of the Sekura JS module;
- linking modules;
- Firmware Kernel Interfaces;
- addressing Memora8 devices;
- memory management rules.
Each article reveals the public meaning of the decision, but does not force the reader to explore the entire platform at once.
This reduces the cognitive load.
The developer gets exactly the amount of knowledge that is needed to work with a specific module.
Parnas was right
The work of David Parnas was published more than half a century ago.
Since then, object-oriented programming, microservices, containers, cloud platforms, and large language models have emerged.
But the problem remains the same.
Systems become fragile when too many components know too much about each other.
True modularity does not occur when the code is organized into folders.
It occurs when every changeable solution is localized and hidden behind a stable boundary.
Instead of output
When developing Memora8, Reganta OS, and Sekura JS, modularity is not just a convenient way for us to organize a project.
It underlies the entire computing platform.
The Sekura JS module is a unit of execution.
The Firmware Kernel prepares the modules and manages their launch.
Modules communicate with each other through certain contracts.
Each level must hide its internal decisions from the rest.
But code alone is not enough to maintain these boundaries.
The team should equally understand what is a public contract and what is an internal architectural decision.
That is why we use Noda as a canonical knowledge base.
It helps to preserve not only the description of the modules, but also the reasons for their separation, acceptable dependencies, and solutions that each module must hide.
The main lesson of David Parnas remains relevant today.:
A complex system does not become stable when all its parts know as much about each other as possible.
It becomes stable when each part knows only what it really needs.
Когда говорят о модульной архитектуре, обычно имеют в виду разделение большой системы на части.
Один модуль отвечает за память.
Другой — за ввод и вывод.
Третий — за выполнение команд.
Кажется, что главное — правильно распределить функции.
Но еще в 1972 году Давид Парнас предложил гораздо более глубокий критерий.
Он утверждал, что систему следует делить на модули не по последовательности выполняемых операций, а по решениям, которые могут измениться.
Каждый модуль должен скрывать от остальных одну важную часть архитектуры.
Именно эта идея получила название сокрытия информации.
Модуль — это не просто отдельный файл
На практике словом «модуль» называют почти все что угодно.
Файл.
Библиотеку.
Папку в проекте.
Сервис.
Пакет.
Но физическое разделение кода еще не создает модульную архитектуру.
Если изменение внутреннего решения одного компонента требует правок по всей системе, настоящее разделение отсутствует.
По Парнасу хороший модуль должен скрывать свое внутреннее устройство за стабильным интерфейсом.
Остальная система должна знать, что модуль делает.
Но не должна зависеть от того, как именно он это делает.
В этом и заключается смысл сокрытия информации.
Что именно должен скрывать модуль
Представим компонент, который управляет памятью.
Можно разделить его код на функции выделения, освобождения и проверки памяти.
Но это еще не гарантирует модульность.
Если другие компоненты напрямую знают формат таблиц, расположение служебных областей и внутренний алгоритм распределения, любое изменение менеджера памяти затронет всю систему.
Парнас предложил другой подход.
Модуль памяти должен скрывать:
- внутреннее представление данных;
- алгоритмы управления памятью;
- изменяемые архитектурные решения;
- ограничения конкретной реализации;
- детали, которые не нужны другим частям системы.
Снаружи остается только контракт.
Внутреннюю реализацию можно менять, пока контракт сохраняется.
Именно это позволяет сложной системе развиваться без постоянного разрушения уже написанного кода.
Мы столкнулись с этим при разработке Memora8
Мы разрабатываем собственный 32-разрядный процессор Memora8, операционную систему Reganta OS, язык программирования Sekura JS и инструменты вокруг них.
Все эти компоненты образуют единую вычислительную платформу.
При этом каждый уровень должен иметь возможность развиваться независимо.
Архитектура процессора не должна раскрывать языку программирования все детали аппаратной реализации.
Sekura JS не должен зависеть от внутреннего устройства планировщика.
Прикладной модуль не должен знать, как Firmware Kernel подготавливает его к исполнению.
Reganta OS не должна заставлять каждый модуль понимать внутреннюю структуру всей системы.
Иначе любое изменение Memora8, Firmware Kernel или среды исполнения потребует переписывать всю платформу.
Модульная модель Reganta OS
В традиционной операционной системе единицей исполнения обычно считается процесс.
В архитектуре Reganta OS единицей исполнения является модуль Sekura JS.
Модули могут связываться динамически либо статически объединяться при запуске.
Firmware Kernel получает готовые к исполнению модули, подготавливает их и запускает.
Такая модель дает большую гибкость.
Но она требует очень четких границ.
Модуль должен понимать доступный ему интерфейс.
Он не должен зависеть от внутренних механизмов Firmware Kernel.
Firmware Kernel, в свою очередь, не должен знать бизнес-логику каждого модуля.
Связи между модулями должны строиться через известные контракты, а не через знание чужой внутренней реализации.
По сути, идеи Парнаса становятся не просто принципом организации исходного кода.
Они становятся основой модели исполнения.
Sekura JS как модульная среда
Sekura JS создается не только как язык программирования.
Это среда для создания исполняемых модулей, которые могут подключаться, объединяться и взаимодействовать друг с другом.
В такой архитектуре особенно важно различать интерфейс и реализацию.
Например, один модуль может предоставлять работу с устройством.
Другой — использовать это устройство.
Пользовательский модуль должен знать доступные команды и формат обмена.
Но он не должен зависеть от того, как устройство физически реализовано внутри Memora8 или Firmware Kernel.
Если внутренняя реализация изменится, внешний контракт должен по возможности остаться прежним.
Именно это делает модуль заменяемым.
Без сокрытия информации модульность быстро превращается в формальность.
Почему документации недостаточно
Даже если интерфейсы хорошо спроектированы, со временем возникает другая проблема.
Команда перестает помнить, какие решения модуль обязан скрывать.
В документации может быть описан API.
Но не всегда зафиксировано:
Почему граница проведена именно здесь?
Какие детали считаются внутренними?
Что может изменяться независимо?
Какие зависимости запрещены?
Какая часть контракта является стабильной?
Какие архитектурные решения модуль защищает от распространения по системе?
Без этих знаний разработчик может случайно создать новую зависимость от внутренней реализации.
Формально код продолжит работать.
Но архитектурная граница будет разрушена.
Так появилась Noda
Изначально Noda создавалась как внутренний инструмент для разработки Memora8, Reganta OS и Sekura JS.
Нам было недостаточно хранить только описание функций и интерфейсов.
Нужно было сохранять смысл архитектурных границ.
Поэтому в Noda фиксируются канонические знания:
- назначение модуля;
- его публичный контракт;
- скрываемые архитектурные решения;
- допустимые зависимости;
- запрещенные зависимости;
- причины выбранного разделения;
- условия, при которых канон должен быть пересмотрен.
Такая статья объясняет не только то, как использовать модуль.
Она объясняет, почему он существует именно в такой форме.
Канонические знания как защита границ
В сложной системе интерфейс можно нарушить не только кодом.
Его можно нарушить неправильным пониманием.
Разработчик может начать использовать внутреннюю структуру данных.
AI-инструмент может сгенерировать код, который обходит публичный контракт.
Новый компонент может получить лишние знания о соседнем модуле.
Каждое такое решение создает скрытую связанность.
Noda помогает сделать архитектурные границы явными.
Если каноническая статья говорит, что определенная структура является внутренней, разработчик и AI-инструмент не должны использовать ее напрямую.
Если взаимодействие допускается только через определенный интерфейс, это становится частью действующего знания системы.
Таким образом канон выполняет роль не просто документации.
Он защищает модульную архитектуру от постепенного размывания.
LLM и принцип Парнаса
Современные LLM умеют быстро создавать код.
Но скорость генерации не гарантирует хорошую архитектуру.
Наоборот, модель может легко использовать любую доступную деталь, если не знает, что эта деталь должна быть скрыта.
Поэтому AI недостаточно показать репозиторий.
Ему нужно дать архитектурные правила.
Какие части системы являются публичными?
Какие решения принадлежат конкретному модулю?
Что другим модулям запрещено знать?
Какие интерфейсы считаются стабильными?
Когда LLM получает такие канонические знания, она может работать внутри архитектурных границ, а не просто генерировать технически допустимый код.
В этом смысле Noda становится связующим слоем между принципами классической модульной инженерии и современными AI-инструментами.
Модульность знаний
Идеи Парнаса применимы не только к программному коду.
Сами знания тоже можно организовывать модульно.
Одна каноническая статья должна отвечать за одну архитектурную область.
Например:
- модель исполнения Reganta OS;
- жизненный цикл модуля Sekura JS;
- связывание модулей;
- интерфейсы Firmware Kernel;
- адресация устройств Memora8;
- правила управления памятью.
Каждая статья раскрывает публичный смысл решения, но не заставляет читателя изучать всю платформу сразу.
Это уменьшает когнитивную нагрузку.
Разработчик получает именно тот объем знаний, который необходим для работы с конкретным модулем.
Парнас оказался прав
Работа Давида Парнаса была опубликована более полувека назад.
С тех пор появились объектно-ориентированное программирование, микросервисы, контейнеры, облачные платформы и большие языковые модели.
Но проблема осталась прежней.
Системы становятся хрупкими, когда слишком много компонентов знают слишком много друг о друге.
Настоящая модульность возникает не тогда, когда код разложен по папкам.
Она возникает тогда, когда каждое изменяемое решение локализовано и скрыто за стабильной границей.
Вместо вывода
При разработке Memora8, Reganta OS и Sekura JS модульность для нас является не просто удобным способом организации проекта.
Она лежит в основе всей вычислительной платформы.
Модуль Sekura JS является единицей исполнения.
Firmware Kernel подготавливает модули и управляет их запуском.
Модули связываются между собой через определенные контракты.
Каждый уровень должен скрывать свои внутренние решения от остальных.
Но для сохранения этих границ недостаточно одного кода.
Команда должна одинаково понимать, что является публичным контрактом, а что — внутренним архитектурным решением.
Именно поэтому мы используем Noda как каноническую базу знаний.
Она помогает сохранять не только описание модулей, но и причины их разделения, допустимые зависимости и решения, которые каждый модуль обязан скрывать.
Главный урок Давида Парнаса остается актуальным и сегодня:
сложная система становится устойчивой не тогда, когда все ее части знают друг о друге как можно больше.
Она становится устойчивой тогда, когда каждая часть знает только то, что ей действительно необходимо.