Термин «модуль» (module) взят из статьи Modules vs. microservices. Так же для описания чего-то среднего между микросервисами и монолитами иногда используют термины «микролит» (microlith) или «моносервис» (monoservice). Но, не смотря на то, что термин «модуль» и так уже нагружен общеизвестным смыслом, на мой взгляд он подходит лучше других вариантов. Update: В комментарии lega использовал термин «встроенный микросервис» — он лучше описывает суть подхода, чем «модуль».
Монолит и микросервисы это очень разные подходы, поэтому в любой попытке взять лучшее от обоих критически важен баланс — что взять, а что нет. Иначе получится монстр вроде OSGi.
Я пишу микросервисы с 2009 года, но применять модули вместо микросервисов в реальных проектах пока не пробовал — всё описанное далее это моё предположение о том, каким должен быть вышеупомянутый баланс, и оно нуждается как в теоретической критике так и в проверке практикой.
20. Программные модули. Основы программирования.
Что такое модуль
Модуль — это что-то вроде микросервиса, только реализованного внутри единого приложения, состоящего из группы таких модулей и очень небольшой части, которая занимается инициализацией и запуском всех этих модулей.
Хотя формально такое приложение можно назвать монолитом, у этого подхода намного больше общего с микросервисами, поэтому и сравнивать его имеет смысл именно с микросервисным подходом. Как и микросервис, каждый модуль:
- Должен иметь тщательно спроектированное и документированное API, и только оно должно быть доступно другому коду, выполняющемся в этом же приложении (контроль этого ограничения требует поддержки на уровне языка и/или инструментов разработки).
- Должен выполняться независимо от кода других модулей и иметь возможность эффективно взаимодействовать с API других модулей без использования сетевых протоколов (это требует поддержки на уровне языка: нити/горутины/акторы, каналы/сообщения, etc.).
- Должен использовать собственное хранилище данных (если оно ему нужно).
- Может находиться в отдельном репо или монорепо приложения.
- Может использоваться разными приложениями.
- Может предоставлять доступ к своему API по сети (только модулю это делать не обязательно, да и доступ может быть не полным, как внутри приложения, а частичным).
В отличие от микросервисов, модуль:
- Обычно нельзя деплоить независимо (если только язык не поддерживает горячую замену части кода в запущенном приложении).
- Нельзя перезапустить независимо от всего приложения.
- Нельзя масштабировать независимо от других модулей.
- Не может помешать доступу к «своим» файлам из других модулей, если они решат нарушить изоляцию между модулями. Ни язык ни инструменты такое нарушение предотвратить не смогут, так что здесь придётся полагаться исключительно на дисциплину самих разработчиков и ревью кода.
- Возможно, может без ограничений использовать библиотеки, общие с другими модулями — но тут я не уверен. Негативные последствия этого будут не такие значительные, как для обычных микросервисов, но всё-таки будут. Особенно если модуль будет в будущем запущен как полноценный микросервис (а этому, по большому счёту, пока что ничего не мешает — модули довольно легко превращаются в отдельные микросервисы при необходимости).
В отличие от обычных библиотек, модуль:
Что такое «Модуль» программы
- Не должен разделять с использующим его API кодом никаких общих данных — все данные должны передаваться через API либо в виде копии, либо, если данных очень много и к ним нужен доступ только на чтение, в виде неизменяемой (immutable) структуры данных (требует поддержки на уровне языка).
- Не предоставляет функций, которые могут вызывать другие модули — т.е. не выполняет никакого своего кода в чужом потоке выполнения (нити, горутине, etc.), таким образом изолируя от вызывающего кода даже исключения, которые могут возникать в коде модуля.
- У модуля есть публичная функция инициализации и запуска модуля, которая вызывается при запуске приложения, но она не предназначена для (повторного) вызова из других модулей.
- Конфигурацию (передаваемую ему при запуске приложения).
• Она может включать общие для всех модулей настройки логирования. - Хранилище данных (если оно ему нужно).
• Поддержка версионирования схемы этих данных и миграций при обновлениях — так же ответственность модуля, хотя запуск миграций данных каждого модуля может быть частью общего для всех модулей процесса деплоя приложения.
В большинстве случаев модули не нуждаются в каком-либо реестре сервисов — вместо регистрации себя и поиска других модулей они получают необходимые им интерфейсы других модулей при запуске, когда запускающееся приложение вызывает функцию инициализации каждого модуля (в порядке, определяемом зависимостями между модулями). В качестве побочного эффекта это позволит сразу обнаружить циклические зависимости между модулями, если они появятся (и, по возможности, изменить архитектуру так, чтобы от них избавиться).
Где нужны модули
Есть константная добавленная сложность (accidental complexity), одинаковая в каждом микросервисе (регистрация/обнаружение сервисов, подключение и переподключение к ним, авторизация между сервисами, (де)маршалинг и шифрование трафика, использование прерывателей зацикленных запросов, реализация трассировки запросов, etc.). Есть аналогичная константная добавленная операционная сложность (необходимость автоматизации тестирования и выката, реализация детального мониторинга, агрегация логов, использование служебных сервисов для регистрации и поиска сервисов, для хранения конфигурации сервисов, для аудита, etc.). С этим можно смириться, потому что реализовать всё это можно один раз, по мере роста количества микросервисов эта сложность не растёт, а преимущества микросервисов с лихвой компенсируют эти затраты.
Но есть сложность, зависящая от бизнес-логики конкретного приложения и увеличивающаяся по мере развития приложения, от которой хотелось бы избавиться хотя бы в тех приложениях, которым не требуется возможность масштабирования и высокая доступность (или хотя бы той части кода таких приложений, в которой нет явной необходимости взаимодействовать с внешними сервисами):
- Необходимость разделять идемпотентные и не идемпотентные запросы.
- Для всех запросов на клиенте:
- Необходимость использовать таймауты (которые могут быть разными для разных запросов).
- Необходимость использовать больше асинхронности для ускорения работы.
- Больше ситуаций eventual consistency и вызванных этим сложностей.
• В т.ч. необходимость иногда кешировать данные других сервисов. - Необходимость повторять некоторые запросы:
• Используя ограничение по количеству или времени повторов.
• Используя задержки между повторами.
- Необходимость реализовать возможность определения дубликатов при повторе запросов.
- Необходимость определять и обрабатывать дубликаты запросов (по-разному, в зависимости от бизнес-логики каждого запроса).
Правильный модульный подход позволяет сохранить многие преимущества микросервисов (при наличии необходимой поддержки на уровне языка и/или инструментов разработки), но помимо потери ненужных в данном приложении возможностей масштабирования и высокой доступности есть и другие:
- Падение модуля приводит к падению всего приложения.
- Уменьшается скорость сборки и тестирования.
- Не уверен, что скорость сборки это реальная проблема — есть много способов её ускорения.
- В принципе возможно при тестировании ветки/PR запускать только тесты изменившегося модуля, а тесты всего проекта выполнять только перед деплоем — это в достаточной степени должно нивелировать эту проблему.
- Здесь проблема не столько в реальном замедлении, сколько в том, что когда сервисов много время запуска «размазано» по нескольким сервисам, которые (пере)запускаются независимо друг от друга. Тем не менее, видимый эффект более медленного запуска этот факт не отменяет.
Так же у модульного подхода появляются новые достоинства:
- Увеличивается скорость взаимодействия между модулями-сервисами, что и позволяет значительно уменьшить необходимость в асинхронности, eventual consistency и кешировании.
- Пока у модуля нет внешнего (сетевого) API его API намного проще изменять и деплоить эти изменения атомарно с деплоем использующих этот модуль клиентов (других модулей этого же приложения).
- Совместную работу модулей проще тестировать, чем группу микросервисов.
- Проще использовать монорепо (если хочется), хоть это и не обязательно.
- Монорепо упрощает рефакторинг и изменение API, по крайней мере пока есть гарантия что у модуля нет сетевого интерфейса и внешних клиентов.
Резюме
В общем и целом подход выглядит достаточно соблазнительным — мы получаем возможность писать монолитное приложение в виде кучки по-настоящему хорошо изолированных частей (причём контролировать это будет по большей части язык и/или инструменты, а не внутренняя дисциплина), разрабатываемых в микросервисном стиле (в т.ч. разными командами в разных репо), которые «лёгким движением руки» могут превратиться в настоящие микросервисы если в этом возникнет реальная необходимость… А пока её нет — мы можем использовать обмен сообщениями между модулями внутри приложения как простую и очень быструю замену настоящего RPC, избегая сложностей асинхронности, eventual consistency и обработки сетевых ошибок.
Необходимая поддержка этого подхода в данный момент есть далеко не во всех языках, но в некоторых есть: автор статьи «Modules vs. microservices» писал о поддержке модульности в Java 9, в Go уже пару лет есть поддержка internal-пакетов, в Erlang судя по статье на эту же тему Dawn of the Microlith — Monoservices with Elixir всё хорошо, …. Я не уверен, насколько на скриптовых языках возможно обеспечить реальную изоляцию модулей, но попытки есть: micromono на NodeJS, в комментарии lega ссылка на подход для Python, …
Если у вас есть соображения по теме (а ещё лучше — опыт реального проекта на похожих принципах) или дополнительные ссылки на статьи/проекты по теме — пишите в комментариях, я постараюсь дополнять ими статью.
- microservices
- microservice architecture
- monolith
- modules
- микросервисы
- микросервисная архитектура
- монолит
- модули
Источник: habr.com
Что такое модульное программирование и кому оно нужно
В любой профессии, не только в программировании, вы переживаете разные эмоциональные состояния по ходу выполнения проекта:
- Сначала есть энтузиазм от перспектив и возможностей.
- Затем приходит азарт. Первые ошибки и трудности вас только раззадоривают, заставляя мозг и фантазию работать на полную катушку.
- Следом проседает концентрация. В какой-то момент вы перестаёте обращать внимание на предупреждения и мелкие ошибки, откладывая решение этих проблем на потом.
- В итоге вы теряете мотивацию. Вы исправляете одну ошибку – появляется три. Вы пытаетесь добавить новую функцию, но выкидываете идею в мусорное ведро из-за нежелания тратить на это много времени.
Некоторые думают, что это нормально: стоит смириться и каждый раз проживать этот цикл. На деле же всё немного проще, и решение лежит не в области психологии, а в подходе к созданию кода.
Классическая проблема программирования
В западной литературе существует термин «big ball of mud» для описания архитектуры программы. Давайте переведём его дословно. Графически «большой шар грязи» можно представить в виде точек на окружности, символизирующих функциональные элементы, и прямых – связей между ними:
Похоже на ваши глаза перед сдачей проекта, не так ли?
Это иллюстрация той сложности, с которой вам надо работать, какое количество связей учитывать, если возникает ошибка.
Программирование не уникальная дисциплина: здесь можно и нужно применять опыт из других областей. Возьмём, к примеру, компьютер. Их производители не задумываются над многообразием задач, которые решает пользователь, и уж тем более не выделяют под каждую маленький процессор и память. Компьютер – это просто набор независимых сложных объектов, объединённых в одном корпусе при помощи разъёмов и проводов. Объекты не уникальны, не оптимизированы конкретно под вас, и тем не менее блестяще справляются со своей задачей.
В программировании есть точно такие же решения. Например, библиотеки. Они помогают не тратить драгоценное время на изобретение велосипеда. Однако для частных задач библиотеки не эффективны – создание отнимет уйму времени, а при единичной повторяемости эффективность стремится к нулю.
В этом случае полезнее обратиться к модулям. Модуль – логически завершённый фрагмент кода, имеющий конкретное функциональное назначение. Для взаимодействия модулей используются способы, не позволяющие изменять параметры и функциональность. Плюсы модульного программирования очевидны:
- Ускорение разработки.
- Повышение надёжности.
- Упрощение тестирования.
- Взаимозаменяемость.
Модульное программирование крайне эффективно при групповых разработках, где каждый сотрудник может сконцентрироваться только на своём фронте работ и не оглядываться на решения коллег. Однако и в индивидуальном подходе вы получаете, как минимум, вышеописанные преимущества.
Но не всё так просто.
Проблемы модульного программирования
Сама по себе идея использования модулей не сильно упрощает код, важно минимизировать количество прямых связей между ними. Здесь мы подходим к понятию «инверсия управления» (IoC). Упрощённо – это принцип программирования, при котором отдельные компоненты кода максимально изолированы друг от друга. То есть детали одного модуля не должны влиять на реализацию другого. Достигается это при помощи интерфейсов или других видов представления, не обеспечивающих прямого доступа к модульному коду.
В повседневной жизни таких примеров множество. Чтобы купить билет на самолёт или узнать время вылета, вам не надо звонить пилоту. Чтобы выпить молока, не надо ехать в деревню или на завод и стоять над душой у коровы. Для этого всегда есть посредники.
В модульном программировании существует три основные реализации:
- Внедрение зависимостей. Способ, при котором каждый элемент имеет свой интерфейс, взаимодействие модулей происходит через интерфейсы.
- Фабричный метод. Основывается на существовании некого объекта, предназначенного для создания других объектов. Иначе говоря, введение в программу прототипа, объединяющего общие черты для большинства объектов. Прямого взаимодействия между модулями нет, все параметры наследуются от «завода».
- Сервисный метод. Создаётся один общий интерфейс, являющийся буфером для взаимодействия объектов. Похожую функцию в реальной жизни выполняют колл-центры, магазины, площадки для объявлений и т.д.
Несмотря на то, что первая реализация IoC используется чаще всего, для первых шагов в модульном программировании лучше использовать другие два. Причина – простое создание интерфейсов лишь ограничивает доступ к модулям, а для снижения сложности кода необходимо также уменьшить количество связей. Интерфейсы, хаотично ссылающиеся на другие интерфейсы, код только усложняют.
Для решения этой проблемы необходимо разработать архитектуру кода. Как правило, она схожа с файловой структурой любого приложения:
Таким образом, поддержка принципов модульного программирования, инверсии управления и четкой архитектуры приложения поможет убить сразу трёх зайцев:
- Обеспечить чёткое функциональное разделение кода. При возникновении ошибок можно быстро определить источник, а исправления не приведут к появлению новых сбоев.
- Минимизировать количество связей. Это позволит упростить разработку, отдав на откуп нескольким разработчикам разные модули. Или вы сможете самостоятельно разрабатывать каждый блок без оглядки на другие, что тоже экономит время и силы.
- Создать иерархию с чёткой вертикалью наследования. Это повышает надёжность кода, так как тестирование провести проще, а результаты информативнее.
Соблюдение принципа модульности в больших проектах позволит сэкономить время и не расплескать стартовый задор. Более того, у вас получится наконец сосредоточиться на самом интересном – реализации оригинальных задумок в коде. А ведь это именно то, что каждый из нас ищет в программировании.
Источник: gb.ru
Модульное программирование
Существуют различные методы создания программ. Каждый предусматривает свои особенности, а также принципы организации работы. Пример – объектно-ориентированное программирование. В данном случае код строится на взаимоотношениях и связях различных компонентов, а не только на логике и абстракциях.
Огромную роль для разработки играет архитектурный подход, носящий название «модульное программирование». Рассмотрим его подробнее.
Определение
Модуль в информатике и программировании – блок программы. Он имеет конкретное функциональное значение. Характеризуется своей полной логической завершенностью.
Так принято называть метод создания программного обеспечения через объединение имеющихся модулей (блоков) в единую общую структуру.
Такое программирование нацелено на:
- повышение скорости создания ПО;
- обеспечение надежности приложения;
- упрощение процедуры тестирования.
Программирование «по модулям» особо удобно для ситуаций, когда в команде разработчиков много участников. Там, где в основе заложен командный труд. Каждый человек сможет сконцентрироваться на собственной задаче, не переключаясь к проблемам остальных.
Для чего нужен
Каждый проект в программировании – это специально составленный код. Его может быть очень много. Даже небольшой объем исходного кода иногда приводит к проблемам обновления, поддержки и исправления ошибок. Запутаться в нем легко, если нет строгой структуризации.
Разбиение будущего приложения на модули – наиболее рациональный подход. Он помогает решать определенные проблемы программирования:
- Код становится более читаемым, понятным и прозрачным. Для этого требуется грамотно классифицировать модули.
- Разрешение конфликтов имен. Такая ситуация неизбежна, если в коде присутствуют десятки идентификаторов. Все они обычно расположены в одной области видимости. Путем деления на модули можно «отсекать» лишние блоки кода. Этот прием устраняется конфликты идентификаторов.
- Повышение надежности. Рассматриваемая концепция способствует осуществлению инкапсуляции программного кода. Каждый блок будет изолирован от другого.
В информатике и программировании модуль – важный компонент. Далее соответствующий процесс создания программ будет рассмотрен более подробно.
Ключевые принципы
Метод модулей в разработке появился благодаря канадскому программисту – Дэвиду Парнасу. Он предположил, что для создания «отдельного блока кода» достаточно минимального набора знаний о содержании других. На подобном высказывании базируется концепция сокрытия информации и программировании.
Сокрытие – это метод проектирования, который предусматривает разграничение доступа разных частей (модулей) продукта к внутренним элементам друг друга.
Процесс написания контента подобным образом будет выглядеть так:
- Описание информации/данных.
- Проектирование. Осуществляется по нисходящему принципу.
- Модульное программирование.
- Описание и создание главного программного обеспечения.
- Сборка готовой утилиты.
Структурными единицами здесь выступают совершенно разные компоненты – от сервисов до фреймворков. Главное, чтобы они отвечали за конкретные функции. А еще – могли предоставить к ней непосредственный доступ.
Виды
Если говорить об информатике, то она предусматривает еще несколько определений модуля:
- команды, которые обладают собственным обозначением и возможностью вызова по имени;
- совокупность программных операторов с идентификаторами и граничными элементами.
Существуют разные виды модулей:
- Малоразмерные. На реализацию таких модулей отдается единственная заданная функция. Большинство языков программирования определяют в качестве своего элементарного компонента процедуру или заданную операцию.
- Среднеразмерные. Представлены небольшим набором операций или функций.
- Крупные. Они предусматривают включение в свой состав нескольких малоразмерных и среднеразмерных модулей.
Примеры последних – это наборы пакетов в Java или Ada.
Ключевые проблемы
Несмотря на то, что рассматриваемый вариант к созданию программного обеспечения является удобным и эффективным, он имеет некоторые недостатки. К таковым относят следующие моменты:
- требования к памяти устройства – она должна быть увеличенной;
- более долгая компиляция и загрузка софта;
- медленное исполнение исходного кода – особо актуально для крупных приложений;
- риски создания слишком сложных алгоритмов взаимодействия.
Лишь грамотный и основательный подход к ТЗ и составлению структуры утилиты поможет устранить последний недостаток. В умелых руках модульное программирование дает одни только плюсы.
Где реализуется
Для того, чтобы пользоваться рассматриваемой парадигмой, разработчику предстоит выучить конкретные языки. Концепция модулей поддерживается в:
- Кобол;
- Ада;
- Фортран;
- Паскаль;
- Оберон;
- Модула-2;
- Zonnon;
- Erlang;
- Perl;
- Ruby.
Еще один довольно интересный вариант – это Python. Данный язык является одним из наиболее популярных в 21 веке. Поэтому модульное программирование нельзя назвать устаревшим. Оно весьма активно применяется на практике.
Отличие от микросервисов и библиотек
Модуль – это не микросервис и не библиотека, хотя определения напоминают соответствующие компоненты. Вот ключевые различия с предложенными элементами кода:
- отсутствие горячей замены части кода в работающей утилите;
- невозможность запуска независимо от программы;
- отсутствие масштабируемости отдельно от остальных модулей;
- модуль не предоставляет функции, которые могут вызывать другие блоки кода;
- обладает собственными изолированными хранилищами информации и параметрами.
Дистанционные онлайн курсы помогут быстрее разобраться в выбранном направлении с нуля.
Хотите освоить современную IT-специальность? Огромный выбор курсов по востребованным IT-направлениям есть в Otus!
Источник: otus.ru