Пользователь
0,0
рейтинг
26 февраля 2014 в 12:08

Разработка → Продвинутая работа с подписками в еvent-driven архитектуре в JavaScript из песочницы

Одним из самых распространенных методов построения JavaScript приложений является событийно-ориентированный подход, который предоставляет большую гибкость и позволяет сделать модули системы максимально независимыми друг от друга. Эта парадигма набирает все большую популярность и не редко становится частью многих фреймворков. Но, как правило, с ростом приложения увеличивается и количество событий/подписок, управлять и следить за которыми самому разработчику становится все сложнее, не говоря уже об адаптации новых сотрудников на проекте.

Справиться со всеми этими проблемами, призван помочь Capo – модуль, который служит для управления событиями в event-driven js архитектуре и решает один из самых больших недостатков шаблона Mediator – неопределенность триггеров и подписчиков.

Столкнувшись с подобной проблемой на реальном проекте, мы поначалу пользовались стандартным средством разработки – поиском. Но со временем количество событий возрастало, находить «источники» событий становилось всё сложнее, а управлять «одиночками» (событиями, которые никто не слушает или не посылает) стало просто невозможно. Процесс разработки становился сложнее с каждым днем, а с приходом на проект новых людей, и вовсе стал обременять.

Как вы успели заметить, мы не стали уходить далеко от «гитарной» темы (привет mediator.js) и назвали наше детище Capo (см. каподастр). Capo это не метод или подход, а скорее — готовый помощник, который воплотился в виде npm модуля и плагина для Sublime Text. Что же он умеет? Вернемся к проблеме паттерна Mediator.

Представим ситуацию – вы приходите на новый проект, где используется архитектура, построенная на событиях. Сколько времени вам понадобится, чтобы полностью отследить все цепочки ивентов и подписок на них? Как показывает практика, занятие это занимает уйму времени и не приносит массу удовольствия.

Чтобы упростить этот процесс мы решили унифицировать поиск всех событий и предоставить результат разработчику в виде интерактивного отчета. Npm модуль Capo осуществляет поиск по всем файлам из указанной папки, находит все связи типа триггер-подписчик и отслеживает их. После этого формирует (по-умолчанию) html отчет следующего вида:


Live Demo

Слева находится меню для навигации по найденным событиям, с правой стороны триггеры и подписчики с указанием файла, строки и контекста. В секции Strange events – ивенты, которые не имеют подписчиков или триггеров и могут стать причиной больших бед. Модуль подходит для всех проектов, где используется mediator.js, Backbone.js, node.js EventEmmiter или любой другой объект, предоставляющий publish-subscribe интерфейс. Capo также обладает рядом дополнительных возможностей, которые могут стать очень полезными. Так, к примеру, присутствует возможность отправить отчет на печать или вывести непосредственно в консоль (capo-cli), сам Capo может быть добавлен как build step в grunt (grunt-capo), а также «шпионить» за событиями, сгенерировав подписки-заглушки для всех ивентов найденных в проекте(spy-generator).

Для еще большего удобства следовало интегрировать эти возможности и в среду разработки. Так Sublime Text 3 пополнился еще одним плагином, который призван облегчить жизнь при непосредственной разработке приложений с использованием event-driven js архитектуры.
После установки, достаточно воспользоваться комбинацией клавиш для запуска поиска (по-умолчанию Alt+D) и, используя быструю панель, выбрать необходимый файл. Плагин откроет его в новом окне и выделит строчку с найденным событием.



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

Исходный код и описание модулей Capo можно найти по ссылкам:
Кирилл Буга @confa
карма
4,0
рейтинг 0,0

Похожие публикации

Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (18)

  • 0
    Круто конечно, но мне кажется тему раскрыл Миша Давыдов в феврале 2012 на Субботнике. Он говорил о песочнице для модулей. Вместо одного хранилища всех событий, возможно стоит создавать экземпляр песочницы для каждой группы модулей. К тому же, насколько я понял, у вас модули могут слушать события друг-друга, что не верно с точки зрения архитектуры.

    1. Каждый модуль может только публиковать/слушать свои события
    2. Есть песочница которая управляет группой модулей (она же устанавливает связи между их событиями)
    3. Песочниц может быть много, в зависимости от масштабов приложения.

    Получается что если у меня есть модуль Message, и он публикует Message_show, то это событие может быть только внутри файла Controller_Message, который разруливает все события модулей сообщения, всплывающих окон и т.п. Ну и для других соответственно. Например Controller_Data — точно не должен работать с модулями сообщений и связывает только события связанные с данными.
    • 0
      Действительно, локальные события в модулях не должны выходить за их пределы, и это правильно с точки зрения архитектуры.
      Но, во-первых, наш модуль никоим образом не навязывает какой бы то ни было стиль программирования, а всего лишь
      является инструментом для анализа уже существующих событий. Т.е. глядя на чужой проект, написанный в таком стиле,
      знание о «песочницах» не поможет вам разобраться.
      Во-вторых, в приложениях с единой точках входа информации единого медиатора избежать практически нереально.
      Примером таких точек входа является общение между встроенными приложениями через их API, принятие информации
      по WebSocket'ам и много другое.
      • 0
        Штука крутая! Только заголовок статьи очень смущает :) Читаешь его и думашь, что статья будет про кодописание, а не инструмент/документацию.
    • 0
      Кстати, с одной стороны песочница это крутая штука, но она может превратиться в большой слой бюрократии если постоянно все везде ограничивать. Те когда нужно получать какие-то новые данные приложения приходится добавить этот модуль в песочницу, а потом вызвать его в Модуле. Похоже на антипаттерн Павлик Морозов.

      В свою очередь интерфейс CommonJS модулей так же является своеобразной песочницей и эта песочница положительно сказывается на качестве продукта и не вставляет палок в колеса.
      • 0
        В моей реализации все события коммуникации между модулями идут через песочницу (sandbox.listen, sandbox.notify,… — внутри модулей). Ядро контролирует какие события кому и от кого разрешены (appSandbox.allow, appSandbox.deny). Т.е. модули могут смело подписываться на события других модулей (внутри себя события доставляются по умолчанию), но только ядро решает кто и от кого получит сообщение. Что вы думаете про такой подход?
        • 0
          Насколько сложно поддерживать и модифицировать такой проект? Как выглядит код, где ядро определяет кто какие события получает?
          • 0
            Достаточно просто вроде бы — через логгер выводится что происходит и кто кому отправил сообщение, когда подписался и когда получил, так что если вдруг кто-нибудь запутается, то всегда можно отследить что происходит.

            Код примерно такой в ядре:
            Core.initModule(a, {
            	moduleName: 'a'
            });
            //...
            
            //allow a to get 'ccc' notifications from core - shortcut
            appSandbox.allow({
            	a: ['ccc']
            });
            
            //allow a to get 'bbb' notifications from b module
            appSandbox.allow('a', {b: ['bbb']});
            


            Ну и соответственно 'a' внутри своего .init подписывается на события 'ccc' & 'bbb', а ядро и 'b' уже шлют информацию когда им вздумается.
  • 0
    не навязывает какой бы то ни было стиль программирования

    Тогда ещё вопрос. Если у меня синтаксис отличается, например слушатели и события объявляются как-нибудь вот так:
    event.listen("something", function(data) { ... }); event.trigger("something", data);
    Есть какой-нибудь функционал, чтобы без гемора и копания в коде начать искать вместо mediator.on / off -> event.listen / trigger?
    • 0
      Модуль capo поддерживает следующие методы для подписки: «on|listen|listenTo|listenToOnce|once|subscribe»,
      для триггеров — «trigger|publish|emit». Поиск по этим шаблонам будет происходить по умолчанию, без
      дополнительной конфигурации.
      Имя же объекта-медиатора указывается опцией (по дефолту «mediator»). Пример для
      консоли -o event, для работы через API — метод event:
      capo(path).event('event')...
      Плагин поддерживает расширение методов и триггеров через файл конфигурации.
  • 0
    Больше об event-driven архитектуре 6 Марта на Вебинаре от Binary Studio.
  • 0
    По-моему, наследником событий является FRP. Это, по сути, те же самые подписки, но «в другую сторону». Плюс в том, что FRP в коде как раз и выражает (практически дословно) кто на кого подписан.

    А описанный вами минус — неопределенность триггеров и подписчиков — по мне вообще является плюсом событийной модели, потому что как раз независимость источника от приёмников события (да и наоборот) как раз постулируется как преимущество подписки в противовес прямому вызову.
    • 0
      С моей точки зрения и да и нет, поскольку взамен преимуществ слабосвязанной системы мы получаем некоторые сложности при разработке или адаптации на проекте.
      • 0
        Похоже, я вас не понял. Мой поинт в том, что если зависимость есть, то в FRP она будет выражена явно, в виде формулы. В событийной схеме — нет. А зависимости есть всегда. Если в нашей модели компонент Б зависит от компонента А, то в FRP, в коде, мы получим ровно эту зависимость. Это не является сильной связанностью.
        мы получаем некоторые сложности при разработке или адаптации на проекте
        О каких сложностях идёт речь?
        • 0
          FRP это немного другая парадигма, и я не затрагивал ее в своей статье. Она также имеет свои преимущества и недостатки.

          Я имел в виду сложность отслеживания ивентов с ростом проекта в событийной модели. Событие триггерится и слушается сразу в нескольких компонентах. Следовательно части кода, отвечающие за это, находятся в разных файлах или и того хуже — проектах. Можно еще представить ситуацию, когда подписка на ивент влечет за собой триггер другого ивента. Исходя из личного опыта, такого рода «цепочки» порой очень трудно отслеживать.
  • 0
    Скорее даже так: мы получаем сложности, когда система становиться очень большой. Иногда появляются магические баги и трудно понять кто сгенерировал лишнее событие. Автор спасибо, подтолкнул к некоторым идеям по оптимизации. В данный момент, например у меня, очень много такого трудно-читаемого кода:
        utils.event.listen("difficulty__update", function (index) {
            global.sudoku.difficulty = index;
        });
    
        utils.event.listen("helper__click", function () {
            utils.event.trigger("board__select_values");
        });
    
        utils.event.listen("popup_window__hidden", function () {
            utils.event.trigger("main_menu__show");
        });\\
    
        utils.event.listen("board__create", function (nodes) {
            utils.event.trigger("board_animation__init", nodes);
            utils.event.trigger("memory__load");
        });
    
        utils.event.listen("splash_screen__hidden", function () {
            utils.event.trigger("main_menu__show");
        });
    
  • 0
    Ай красота какая, написано людьми для людей. Спасибо за труд!
  • 0
    на одном проекте юзал похожую библиотеку. называлась PubSub.js. правда, она была без такого инструментария, как в топике.
  • 0
    А для Webstorm такое можете сделать?

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