• Событийная архитектура веб-приложения

    5a64a4b8882d7events

    Одной из самых плохо расширяемых частей любого веб-приложения является его клиентский код, как правило, написанный на javascript. Во многих проектах он представляет собой джунгли из функций, принимающих коллбеки — и это в лучшем случае. Многие склонны винить в таком положении дел непосредственно сам язык, припоминая его «низкое» происхождение, странное поведение и отсутствие синтаксического сахара. Несомненно, в этом есть своя правда. Но я полагаю, что основная причина такой запутанности заключается в том, что построить для взаимодействия с интерфейсом стройную и расширяемую архитектуру, руководствуясь только принципами императивного программирования — невозможно. И хотя модель реализации событий в браузере сама подводит к идее организации кода декларативно, почему-то немногие на это отваживаются.

    Приведу пример простейшего API корзины интернет-магазина, а для событий используем уже имеющуюся «шину» — элемент body.

    function add(item_id, quantity) {
      $.ajax({
        type: "POST",
        url: "/api/cart/add",
        dataType: 'json',
        data: {item_id: item_id, quantity: quantity}
      }).done(function (data) {
        $('body').trigger('QuantityChanged', [data.item_id, data.quantity]);
        $('body').trigger('PriceChanged', [data.item_id, data.sum]);
      });
    };

    Теперь после добавления товара отправляется два события: изменилось количество, изменилась цена. Можно было бы реализовать это и одним событием с большим количеством параметров, но так лучше не делать: если слушателю события интересно только изменение цены, нет нужды передавать ему и все остальные тридцать три параметра. Это повышает шанс ошибки и ведет к неразберихе в коде. События должны быть предельно атомарными, чтобы и слушателей можно было делать небольшими, выполняющими лишь одну роль — но хорошо. Можно также расширить количество событий, добавив, например, своё событие для ошибки, начала запроса и т.д.

    /**
     * Обновить количество товара в корзине
     */
    $('body').on('QuantityChanged', function (e, item_id, quantity) {
      $('[data-item-quantity="' + item_id + '"]').html(quantity);
    });
    
    /**
     * Обновить цену товара в корзине
     */
    $('body').on('PriceChanged', function (e, item_id, sum) {
      $('[data-item-sum="' + item_id + '"]').html(sum);
    });

    Никто теперь не мешает нам расширить систему, добавив на какой-то отдельной странице дополнительное действие при добавлении товара в корзину. И это всё без изменения API, без изменения старых событий и без условных операторов.