EDA подход в Angular

    image

    EDA – event driven architecture или событийно-ориентированная архитектура. Довольно известный подход к проектированию веб-приложений, который сильно облегчает разработку, когда связанные компоненты находятся на разных ветвях иерархии, делая их связь более прозрачной.



    К слову о компонентах. Angular лишь недавно обзавелся своими настоящими компонентами, но реализация данного подхода была возможна и ранее, при помощи простых и вложенных директив.

    Про EDA


    Как нам говорит википедия, событие в EDA можно определить как «существенное изменение состояния». Известно, что компоненты в большинстве Фреймворков, находящиеся в прямом родстве, спокойно могут наследовать и передавать состояние, определяющее их поведение. Тем более с этим не возникает проблем у Angular, который реализует всеми нелюбимый two-way binding. Но все посылы команды разработчиков говорят о том, что это не хорошо и нужно привыкать к мысли, что компоненты — это чистые функции и возвращение состояния от дочерних компонентов оправдано лишь в крайних случаях.

    Про Angular


    Я некоторое время занимаюсь разработкой на Angular и использование EDA подхода сильно помогает в создании приложений. Ниже я бы хотел поделиться простой реализацией основных возможностей, описанной выше архитектуры, на примере Angular 1.x

    Как обычно, все начинается с разработки сервиса, который определяет некоторые возможности. В нашем случае – генерирование и отлов событий.

    Листинг нашего сервиса.
    'use strict';
    
      function EventService($rootScope) {
    
        /* ----- api нашего сервиса ------*/ 
        this.on = on;
        this.broadcast = invoke;
    
        /* ----- функции для внутреннего использования --------*/ 
        function _on(scope, event, func) {
          /* ------- создание watcher для scope необходимого компонента ------*/
          var off = scope.$on(event, func);
    
          /* -------- подчищаем за собой -------*/      
          scope.$on("$destroy", off);
        };
    
        function _broadcast(event, params) {
          if (!params) params = [];
    
          params = angular.copy(params);
    
          /*------ каррируем исходные параметры ------*/
          params = [event].concat(params);
    
          $rootScope.$broadcast.apply($rootScope, params);
        };
    
        /* ------- используя замыкания, создаем методы генерирования и отлова событий -------*/
        function on(handler) {
          return function(scope, func) {
            _on(scope, handler, func);
          };
        };
    
        function invoke(handler) {
          return function() {
            _broadcast(handler, arguments);
          };
        };
      };
    
      EventService.$inject = ['$rootScope'];
    

    Некоторые высказываются, что Angular не приспособлен для реализации подобного рода вещей, мол циклы дайджеста съедают все рабочее время и если необходимо реализовать большое количество возможных событий, то количество watchers, которое понадобится для этого, будет избыточным. Но это все в прошлом и начиная с версии 1.4.х магическое число 2000 давно перевалило за 100к.

    Далее, имея такой сервис, можно легко создавать собственный event-manager для отдельно взятого компонента либо группы компонентов.

    'use strict';
    
      function EventManager(EventService, EVENTS) {
        /* ----- набор событий определенного компонента -----*/
        var handlers = {
          onScroll: EVENTS.scroll
        };
    
        /* ------ установка генератора и листенера событий ------*/
        this.onScroll = EventService.on(handlers.onScroll);
        this.scroll = EventService.broadcast(handlers.onScroll);
      };
    
      /* ------ централизованное или распределенное хранение событий -------*/
      EventManager.$inject = ['EventService', 'EVENTS'];
    

    И собственно примеры использования данного event-manager'a

    'use strict';
    
      function ComponentOne(EventManager) {
        /*----- some code before -----*/
        EventManager.scroll();
        /*----- some code after -----*/
      };
      ComponentOne.$inject = ['EventManager'];
    
      function ComponentTwo($scope, EventManager) {
        /*----- some code before -----*/
        EventManager.onScroll($scope, cb);
    
        function cb(event, params) {
           /* ---- some code -----*/
        };
        /*----- some code after -----*/
      };
      ComponentTwo.$inject = ['$scope', 'EventManager'];
    


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

    Спасибо за внимание.

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

    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 10

        0
        Использование $rootScope.$broadcast — не лучший пример, такое событие будет спускаться по иерархии скоупов и возникать на каждом.
        Ну и конечно неизбежно возникают гонки, когда событие сгенерировано, а получатель еще не подписался на него.
          0
          Не во все скоупы, к счастью, а только в те, которые содержат слушателя для данного события (внутри самого скоупа или его потомков). По крайней мере, в свежих версиях angular-а.

          Scope.$broadcast() Is Surprisingly Efficient In AngularJS
            0
            Оптимизация $broadcast ценой тормознутого $on и отписки. Окупится только если событие бросается из scopes, приближенных к корневому. Ближе к веткам порождает больше накладных расходов, чем приносит пользы. Отличный пример того, как Angular скрывает проблему архитектуры, не решая ее.

            Во всех нормальных фреймворках родительский компонент напрямую вызывает методы дочернего компонента, а дочерний сообщает родителю об изменениях через события (omit). Такой подход удобен, в меру расширяем и не имеет штрафов производительности. Так же предпочитаю организовывать код и в Angular через ряд костылей.
          +6
          Довольно известный подход к проектированию веб-приложений, который сильно облегчает разработку, когда связанные компоненты находятся на разных ветвях иерархии, делая их связь более прозрачной.
          Облегчает? Да. Прозрачной? Серьёзно? Да код превращается в макароны, где не видно, что откуда растёт и где заканчивается. А если продебажить? Попробуйте. А лучше в проекте, который вы видите впервые.
            +1
            Хотя, справедливости ради, стоит отметить, что при правильном подходе именования ивентов с использованием NS, вполне можно проследить код. Не дебаггером, конечно, но глобальным поиском по коду можно.
            +1
            [описываемый подход] сильно облегчает разработку, когда связанные компоненты находятся на разных ветвях иерархии

            Надо просто не усложнять разработку изначально — не связывать компоненты на разных уровнях иерархии.
            Для этого есть простая совокупность правил проектирования, не зависящих от фреймворка:

            • Компонента (часть UI) может знать только о своих непосредственных потомках (которых она так или иначе и создала)
            • Компонента-родитель при создании передает непосредственному потомку данные, нужные ему для работы (+ опционально дергает его методы интерфейса), и слушает его события — тупо передает ему обработчик, который надо дернуть
            • Компонента-потомок не знает о родителе — только генерирует события, а точнее, просто дергает полученные обработчики
            • Все дополнительное взаимодействие организуется через сервисы («вечные» единицы бизнес-логики): сервисы уже генерируют полноценные события, на которые может подписываться множество слушателей (других сервисов или компонент), с учетом жизненного цикла слушателей (для Angular это — автоотписка компоненты при уничтожении ее scope, как в примере в статье
              0
              Я некоторое время занимаюсь разработкой на Angular и использование EDA подхода сильно помогает в создании приложений.

              А вы другие подходы пробовали? EDA далеко не для всех задач будет удобен. По своему опыту, EDA подходит для сильно связанных систем, например игрушки. А web приложеньки — там в 99% слуачев больше проблем.

              Далее, использование событий $scope вне жизненного цикла директив/компонентов — антипаттерн, не для того эта штука там нужна. Я даже правило в eslint добавить вот планирую, что бы не пропускало коммиты где $scope используется вне link директив.

              большое количество возможных событий, то количество watchers

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

              магическое число 2000 давно перевалило за 100к.

              Оно изначально было ~100К. Фраза про 2000 была о том, что если у вас 2000 ватчеров на скрин, значит вы что-то делаете неправильно. Да и производительность ватчеров меряется не их количеством, а временем их работы. Простые ватчеры, которые втупую сравнивают все по ссылке — их может быть и 100К, а добавить один дип ватч на жирный объект который часто меняется, и он один уже убьет всю производительность.

              В целом — ватчеры в коде самого приложения вообще не нужны. Они нужны только самому ангуляру для работы биндингов, но не более того. Ну и еще директивы-примитивы для работы с DOM например, которые отслеживают изменение состояния и мутируют DOM.

              Лично для меня, достоинства такого подхода заключаются в сохранении модульности приложения.

              А вы когда-нибудь дебажили приложение построенное на этом подходе? Это ж маленький ад. А как это тестировать? Мокать везде менеджер событий? EDA используют в геймдеве, потому что там это единственный адекватный вариант как-то снизить связанность между компонентами без потерь производительности. В разработке приложений — это так себе подход.
                0
                Спасибо за комментарий,
                ни в коем случае не призывал использовать данный подход повсеместно.
                На счет отладки могу сказать, что если грамотно спроектировать модульность приложения, как уже писали в комментах, то отлдака не вызывать боли в глазах. Так как, если так можно сказать, модули получают небольшое расширение.
                  0
                  если грамотно спроектировать

                  С этим нынче проблемы. Именно по этому использование таких подходов легко превращаются в боль и страдания.

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

              Самое читаемое