- BrainTools - https://www.braintools.ru -
Когнитивная сложность – это понятие, описывающее сложность процесса познания и мышления [1]. Оно используется в разных областях: в психологии оно характеризует индивидуальную способность к восприятию [2] и обработке информации. Более высокая когнитивная сложность означает, что система (будь то человек или программа) требует больше усилий для понимания и может быть трудной в поддержке.
Когнитивная сложность при проектировании приложения часто возникает из-за смешения архитектуры кода и архитектуры приложения. В большинстве случаев эти термина никак не разделены, а также эти термины не имеют однозначного толкования, как по содержанию так и по контексту использования. В практике и литературе эти ��онятия часто используются как синонимы или в пересекающихся контекстах, что приводит к неоднозначности. В зависимости от контекста (например, обсуждение микросервисов, монолитов, паттернов проектирования или рефакторинга), один и тот же термин может обозначать как уровень организации кода, так и более высокий уровень организации приложения или системы. В профессиональной литературе и стандартах (например, TOGAF, ArchiMate) архитектура программного обеспечения охватывает оба аспекта и организацию кода, и организацию приложения, что еще больше стирает границы между этими понятиями.
В рамках этой статьи термин “Архитектура приложения” рассматривается исключительно как процессно-ориентированная архитектура приложения.
Процессно-ориентированная архитектура – это подход, при котором элементами архитектуры являются физические процессы ОС, а не абстрактные программные модули.
В такой архитектуре компоненты приложения описываются как отдельные процессы, а не как абстрактные функциональные модули внутри одного процесса.
Каждый процесс выполняет свою задачу и взаимодействует с другими процессами через механизмы межпроцессного взаимодействия (IPC): каналы, сокеты, очереди сообщений, разделяемую память [3] и т.д..
Основное отличие процессно-ориентированной архитектуры (где элементы — физические процессы ОС) ��т сервис-ориентированной архитектуры (SOA) заключается в уровне абстракции, способе взаимодействия и области применения:
В процессно-ориентированной архитектуре элементы — это физические процессы ОС, которые выполняют конкретные задачи и взаимодействуют через механизмы IPC (межпроцессное взаимодействие).
В SOA элементы — это сервисы, которые могут быть реализованы как процессы, потоки, или даже функции, но главное — они предоставляют бизнес-функции через стандартизированные интерфейсы и не обязательно соответствуют отдельным процессам ОС.
В процессно-ориентированной архитектуре взаимодействие между компонентами обычно происходит через IPC, сокеты, очереди сообщений и т.д., что связано с особенностями ОС.
В SOA взаимодействие между сервисами осуществляется через стандартные протоколы (например, HTTP, SOAP, REST), и часто используется шина сервисов (ESB) для управления взаимодействием.
Процессно-ориентированная архитектура чаще применяется в системах, где требуется высокая изоляция, отказоустойчивость и управление ресурсами на уровне ОС (например, микроядерные ОС, распределённые системы).
SOA ориентирована на бизнес-процессы и интеграцию приложений, обеспечивая многократное использование сервисов, гибкость и масштабируемость в корпоративных средах.
В процессно-ориентированной архитектуре управление ресурсами и масштабированием происходит на уровне ОС, а сервисы могут быть жестко привязаны к конкретным процессам.
В SOA сервисы могут быть независимо развёрнуты, масштабированы и управлять��я централизованно через ESB или другие средства управления сервисами.
Таким образом, процессно-ориентированная архитектура фокусируется на физических процессах ОС и их взаимодействии, тогда как SOA — на бизнес-сервисах, стандартизированных интерфейсах и интеграции приложений на уровне бизнес-процессов.
Итак, используя процессно-оринетированную архитектуру в качестве архитектуры приложения, критически рассмотрим типичные высказывания, которые сложны для понимания и, к сожалению, часто воспринимаются как некий малосодержательный фон повествования.
Примеры высказываний, возникающих при смешении архитектуры кода и архитектуры приложения:
“Приложение использует микросервисную архитектуру, потому что у нас много классов.”
Правиль��о: “Приложение использует микросервисную архитектуру, потому что его компоненты развернуты как независимые сервисы, взаимодействующие по сети. Количество классов в коде не определяет архитектуру приложения.”
“Наше приложение построено по принципу Clean Architecture, потому что у нас есть слои Presentation, Business и Data.”
Правильно: “Код приложения организован по принципу Clean Architecture, если слои кода соответствуют принципам инверсии зависимостей и чёткого разделения ответственностей. Архитектура приложения – это способ развертывания и взаимодействия компонентов, а не только структура кода.”
“Мы используем архитектуру приложения MVC, потому что у нас есть контроллеры, сервисы и репозитории.”
Правильно: “Код приложения использует паттерн MVC, если разделение на контроллеры, представления и модели соответствует принципам MVC. Архитектура приложения – это способ организации компонентов приложения в среде выполнения, а не только структура классов.”
“Наше приложение имеет архитектуру микросервисов, потому что у нас много модулей.”
Правильно: “Приложение имеет архитектуру микросервисов, если компоненты развернуты как отдельные процессы, взаимодействующие по сети. Количество модулей в коде не определяет архитектуру приложения.”
“Мы используем архитектуру приложения N-Tier, потому что у нас есть папки Presentation, Business и Data.”
Правильно: “Код приложения организован по принципу N-Tier, если слои кода соответствуют принципам разделения ответственностей. Архитектура приложения – это способ развертывания и взаимодействия компонентов, а не только структура папок.”
Кроме проблем с коммуникацией существуют и другие проблемы.
Проблемы при смешении:
Разработчики могут применять архитектурные решения на уровне кода (например, сложные паттерны или декомпозицию классов), не учитывая общую архитектуру приложения, что приводит к излишней сложности и неочевидным зависимостям.
С другой стороны, принятие архитектурных решений без учета возможностей и ограничений кода может привести к непрактичным, трудно поддерживаемым решениям.
Смешение затрудняет понимание системы, увеличивает порог входа для новых участников команды и усложняет поддержку и развитие.
Как минимизировать когнитивную сложность:
Четко разделять уровни архитектурных р��шений: от глобальной структуры до деталей реализации.
Обеспечивать согласованность между архитектурой приложения и кода, чтобы изменения на одном уровне не нарушали целостность другого.
Таким образом, осознанное разделение архитектуры кода и архитектуры приложения – ключ к снижению когнитивной нагрузки и повышению качества архитектуры.
Далее разбираемся подробно.
Разница между архитектурой кода и архитектурой приложения заключается в уровне абстракции и контексте, в котором рассматриваются границы и структура системы.
Архитектура кода – это организация исходного кода на уровне логических границ, выраженных структурой файлов, модулей, классов, функций и других конструкций языка программирования. Эти границы определяются принципами проектирования, паттернами, стилем кода и стандартами организации проекта. Например, разделение на слои (presentation, business logic, data access), модульность, инкапсуляция, декомпозиция на микросервисы или компоненты – всё это проявляется на уровне исходного кода и не зависит от того, как код будет выполняться.
Архитектура приложения – это организация исполняемого процесса, то есть того, как компоненты системы взаимодействуют между собой в момент выполнения. В исполняемом процессе границы определяются технически: память (код, данные в стеке, данные в куче), процессы, потоки, ресурсы ОС и т.д. Эти границы не обязательно соответствуют логическим границам исходного кода – например, разные модули могут быть загружены в один процесс, а один логический компонент может быть разнесён по нескольким процессам или машинам.
Один и тот же код может дублироваться в разных процессах, даже если в исходной кодовой базе он описан один раз. Например, библиотека вспомогательных функций может быть подключена к нескольким процессам (например микросервисам), и каждый из них будет использовать её независимо. Это не означает, что код физически дублируется в исходниках – он может быть собран в виде отдельной библиотеки и подключаться как зависимость. Однако на уровне архитектуры приложения каждый процесс, использующий эту библиотеку, будет иметь свою копию в своём окружении.
Когда код запускается и становится исполняемым процессом, логические границы исходного кода теряют своё значение, если только они не реализованы через технические границы (например, разные процессы, контейнеры, сервисы). Внутри процесса все данные и код существуют в едином адресном пространстве, и разделение происходит только по типу памяти (стек, куча) и по способу доступа к данным. Исходный код сохраняет свою структуру только как логическая модель, но не как физическая граница выполнения.
При проектировании архитектуры приложения важно понимать, что один и тот же код может быть задействован в нескольких процессах, и это влияет на масштабируемость, обновление и отладку системы.
Архитектура кода помогает минимизировать дублирование и обеспечить переиспользование, но архитектура приложения определяет, где и как этот код будет использоваться.
Смешение этих уровней приводит к когнитивной сложности – разработчики могут не видеть, как изменения в одной части архитектуры повлияют на другую.
Сравнительная таблица
|
Критерий |
Архитектура кода |
Архитектура приложения |
|---|---|---|
|
Уровень абстракции |
Логический, исходный код |
Технический, исполняемый процесс |
|
Границы |
Файлы, модули, классы, функции |
Процессы, потоки, память, ресурсы |
|
Структура |
Документы, конструкции языка |
Объекты ядра ОС: память, процессы, ресурсы |
Таким образом, архитектура кода это логическая модель, а архитектура приложения – техническая реализация этой модели в исполняемой среде.
Архитектура приложения – это не просто набор всего исходного кода приложения, а организация исполняемых компонентов системы. Если архитектура кода строится из атомарных сущностей языка программирования, таких как операторы, функции и данные, то архитектура приложения строится из атомарных сущностей среды исполнения кода, объектов ядра операционной системы – процессов.
Процесс является минимальной единицей, внутри которой реализуется функциональность приложения, полностью или частично, и обеспечиваются изоляция, управление ресурсами и безопасность.
Архитектура кода фокусируется на логических границах: функции, классы, модули. Эти границы определяют, как организован исходный код, но не влияют напрямую на выполнение приложения – неважно на сколько методов в коде разложен алгоритм сортировки он будет выполнять свою работу. Архитектура приложения, напротив, определяет, как логические функции реализуются и взаимодействуют в реальной среде – через процессы, контейнеры, микросервисы.
Примеры:
В монолитной архитектуре вся логика [4] приложения может быть реализована в одном процессе, и хотя логические функции выделены через структуру кода, но технически работают в рамках одного адресного пространства.
В микросервисной архитектуре каждая функция, а чаще набор функций, реализуется в отдельном процессе, что обеспечивает независимость, масштабируемость и отказоустойчивость для приложения в целом.
Процесс – это экземпляр исполняемой программы, который получает выделенные ресурсы операционной системы: память, файловые дескрипторы, потоки выполнения. Каждый процесс изолирован от других, что обеспечивает независимость и устойчивость системы. Внутри процесса могут быть реализованы любые логические функции приложения, но сам процесс определяет техническую границу, за которой не может быть прямого доступа к ресурсам другого процесса без специальных механизмов межпроцессного взаимодействия.
На практике часто один процесс выполняет несколько логических функций, которые можно выделить на уровне архитектуры кода. Например, в монолитном приложении один процесс может содержать слои представления, бизнес-логики и доступа к данным. Такая организация кода позволяет быстро разрабатывать и запускать весь функционал приложения внутри одного процесса, но при масштабировании и необходимости независимого управления функциями возникает потребность [5] в разделении функционала приложения на отдельные процессы.
Проектируя архитектуру приложения, архитектор обязан рассчитывать его работу под пиковую нагрузку, определять допустимый уровень задержек, планировать масштабирование процессов – горизонтальное (увеличение числа процессов) или вертикальное (увеличение ресурсов на процесс).
Проектирование процессов – это ключевой этап архитектуры приложения, на котором определяются не только функции, но и технические характеристики каждого исполняемого компонента приложения.
При проектировании процесса учитываются четыре основных аспекта:
группа выполняемых функций
максимальная мощность процесса
способы взаимодействия с процессом
количество доступных ресурсов
Группа функций процесса
При проектировании процесса сначала определяется набор логических функций, которые он будет выполнять. Это могут быть задачи бизнес-логики, обработка запросов, работа с данными или взаимодействие с внешними системами. Функции должны быть сгруппированы логически: например, все операции с пользователями могут быть вынесены в отдельный процесс, а обработка платежей – в другой. Такое разделение упрощает масштабирование, тестирование и поддержку.
Максимальная мощность процесса
Важно заранее оценить, какую нагрузку будет испытывать процесс: сколько CPU, памяти, дискового пространства и других ресурсов ему потребуется.
Это позволяет:
избежать перегрузки системы и сбоев из-за нехватки ресурсов.
оптимизировать использование железа, особенно при работе в контейнерах или облаке.
планировать масштабирование: если нагрузка растёт, можно добавить больше процессов или перераспределить нагрузку между ними.
Способы взаимодействия с процессом
Процесс не существует изолированно – он должен взаимодействовать с другими процессами, внешними системами и пользователями.
Способы взаимодействия определяются:
интерфейсами (API, очереди сообщений, сокеты).
протоколами обмена данными.
механизмами синхронизации и обработки ошибок.
Количество доступных ресурсов
Основными ограничениями для любого процесса служат ресурсы, предоставляемые операционной системой:
CPU – количество операций, вычислительная мощность, доступная для процесса.
Память – объём оперативной памяти, необходимый для хранения данных и выполнения вычислений.
Диск – пространство для хранения файлов, журналов, временных данных; скорость доступа к данным.
Сеть – пропускная способность и доступность сетевых ресурсов для обмена с другими процессами и системами.
Ограничения ресурсов определяют, сколько полезной работы может выполнить процесс в единицу времени, то есть его производительность и какую нагрузку система способна выдержать без деградации производительности.
Ограничения по ресурсам и производительности влияют на то, как архитектура приложения делится на процессы, какие механизмы балансировки и мониторинга внедряются, как реализуются механизмы отказоустойчивости и защиты от перегрузки.
Проектирование процессов – это оптимизация использования ограниченных ресурсов для достижения максимальной полезной работы, с учётом требований к скорости и надёжности системы.
Архитектура кода определяет функционал решения, то есть набор задач, которые должна выполнять система. Она описывает, какие функции реализуются, как они объединяются в логические группы и как эти группы взаимодействуют между собой: например, через вызовы функций, интерфейсы, события или паттерны проектирования.
Группировка функций и их распределение по процессам
При проектировании архитектуры кода разработчики группируют функции по смыслу, ответственности или по принципу связности (например, все операции с пользователями – в один модуль, работа с базой – в другой). Эти группы функций могут быть реализованы в рамках одного процесса (например, в монолитном приложении) или распределены по нескольким процессам (например, в микросервисной архитектуре).
Архитектура кода сильно зависит от архитектуры приложения – разделение функционала на отдельные выполняемые процессы, напрямую влияет на архитектуру кода.
Особенно сильно архитектура приложения влияет на способы взаимодействия между группами функций:
внутрипроцессное взаимодействие
межпроцессное взаимодействие – требует дополнительного кода и использования специальных библиотек и компонентов
Таким образом, архитектура кода задаёт логическую структуру решения, а её реализация на уровне процессов определяет, как эта структура будет работать в реальной среде, с учётом ограничений и требований к производительности.
Архитектура кода строится с учётом ключевых ограничений, определяющих, как реализуется и поддерживается система. Эти ограничения включают выбранный технологический стек и квалификацию команды разработчиков.
Ограничения технологического стека
Выбранный стек технологий – языки программирования, библиотеки, фреймворки, системы хранения данных, кэширования, поиска и другие специализированные инструменты – задаёт рамки, в которых возможно проектирование и реализация кода.
Архитектура кода должна учитывать:
совместимость и ограничения используемых технологий.
возможности и ограничения библиотек и фреймворков.
ограничения на выбор решений для хранения и обработки данных.
Например, если выбрана микросервисная архитектура приложения, каждый сервис может использовать свой стек технологий, но при этом возрастает сложность координации и интеграции между сервисами. В монолите чаще требуется единый и согласованный стек для всех компонентов.
Ограничения квалификации команды
Архитектура кода также определяется уровнем знаний и навыков членов команды. Утверждение, что «код пишется для человека, а не для компьютера», означает, что важна читаемость, поддерживаемость и понятность кода.
Архитектура кода должна быть:
доступна для понимания и модификации текущей командой
соответствовать опыту [6] и навыкам разработчиков, чтобы избежать технического долга и ошибок
Если команда не владеет определённым языком или инструментом, использование его в архитектуре кода приведёт к проблемам с поддержкой и развитием системы. Поэтому архитектура кода всегда должна учитывать не только технические, но и человеческие ограничения.
Архитектура приложения и архитектура кода тесно связаны и оказывают взаимное влияние. Архитектура приложения определяет, как компоненты взаимодействуют в среде выполнения, а архитектура кода – как эти компоненты реализованы и организованы внутри.
Масштабируемость приложения и модульность кода
Если архитектура приложения предусматривает масштабирование (например, микросервисы), то код должен быть организован так, чтобы каждый сервис был автономным модулем с минимальной связностью и высокой когезией. Это влияет на выбор структуры кода: разделение на независимые пакеты, использование API, изоляция бизнес-логики и данных. Если код не модульный, масштабирование приложения становится сложным и дорогостоящим.
Производительность кода и состав приложения
Архитектура приложения может предусматривать отдельные сервисы кэширования, использование очередей сообщений и оптимизацию взаимодействия между процессами для снижения задержек, но если оптимизация кода привела к значительному увеличению производительности приложения, то появляется возможность полностью исключить указанные дополнительные сервисы или снизить количество ресурсов для их работы.
Автор: antonb73
Источник [7]
Сайт-источник BrainTools: https://www.braintools.ru
Путь до страницы источника: https://www.braintools.ru/article/22139
URLs in this post:
[1] мышления: http://www.braintools.ru/thinking
[2] восприятию: http://www.braintools.ru/article/7534
[3] память: http://www.braintools.ru/article/4140
[4] логика: http://www.braintools.ru/article/7640
[5] потребность: http://www.braintools.ru/article/9534
[6] опыту: http://www.braintools.ru/article/6952
[7] Источник: https://habr.com/ru/articles/968170/?utm_source=habrahabr&utm_medium=rss&utm_campaign=968170
Нажмите здесь для печати.