Red Architecture — красная кнопка помощи для сложных и запутанных систем — часть 2 (пример с миллиардом ячеек)

    В первой части представлена концепция Red Architecture — подход, упрощающий взаимодействие между компонентами в сложных системах, и предназначенная в первую очередь для клиентских приложений. Для полного понимания текущей статьи необходимо познакомиться с данной концепцией здесь.



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

    У нас есть клиетское приложение — редактор таблиц, в нём отображается лист таблицы. Экран у пользователя настолько большой, что на нём помещается 1 000 000 000 (один миллиард) табличных ячеек. Всё усложняется тем, что наш табличный редактор подключен к облаку для возможности совместного редактирования таблицы, поэтому изменения в любой из одного миллиарда ячеек “где-то в облаке” должны быть сразу же отображены нашему пользователю.

    Паттерн Red Architecture позволяет реализовать данную функцию просто и с высокой производительностью.

    Прежде всего, нам нужно немного усовершенствовать класс v

    Вариант, когда каждая из миллиарда ячеек проверяет каждое полученное событие на соответствие самой себе — не подходит. Миллиард вызовов функций-обработчиков + миллиард сравнений guid’ов при каждом изменении какой-то одной ячейки — это слишком даже для высокопроизводительных пользовательских устройств.

    Рассмотрим решение этой проблемы.

    Теперь ключи (идентифицирующие логические цепочки) в классе v будут не элементами перечисления, а строками. Для краткости и лёгкого восприятия будем писать в псевдокоде:

    class v {
       // value got from this format string will look like OnCellUpdateForList_List1_Cell_D9
       public const string KeyOnCellUpdate = “OnCellUpdateForList_%s_Cell_%s”;
    }
    

    Мы объявляем ключ, который по факту является форматной строкой, для чего? Дело в том, что ячейка в любом случае должна быть некоторым образом идентифицирована на табличном листе. Мы предполагаем, что информация об обновлении ячейки, пришедшая “из облака”, содержит данные идентифицирующие ячейку (иначе как мы её найдём в листе чтобы проапдейтить?), такие как имя листа (List1) и адрес ячейки (D9). Также мы предполагаем, что каждая ячейка, отображённая на экране пользователя, тоже “знает” путь к себе, а именно те же имя листа и свой адрес (иначе как она оповестит систему о том, что изменения произошли именно в ней, а не в какой-то другой ячейке?).

    Далее, нам нужно добавить ещё один аргумент в метод h(). Теперь обработчики подписываются не на все ключи которые есть в системе, а на конкретный ключ, который передаётся первым аргументом:

    class v {
       // for instance, OnCellUpdateForList_List1_Cell_D9
       public const string KeyOnCellUpdate = “OnCellUpdateForList_%s_Cell_%s”;
    
        private var handlers = new HashMap<String, List<HandlerMethod> >();
    
        void h(string Key, HandlerMethod h) {
            handlers[Key] += h; 
       }
    
       void Add(string Key, data d) {
          for_each(handler in handlers[Key]) {
            handler(Key, d);
         }
      }
    }
    

    Для хранения обработчиков мы используем приватную коллекцию типа HashMap, содержащую пары “один ко многим” — один ключ, на который могут подписаться один и более обработчиков; а в методе Add(), “рассылающем” события по подписчикам, мы используем только функции-обработчики подписанные на данный ключ. Для контейнера с потенциальным миллиардом элементов стоит подыскать подходящую для такого объёма данных реализацию, поэтому мы используем HashMap — коллекцию, которая неявно конвертирует строковые ключи в числовые хеш значения. В случае с миллиардом элементов, HashMap позволит нам найти нужный элемент бинарным поиском за не более чем 30 операций сравнения чисел. Такая задача даже на низкопроизводительном оборудовании будет выполнена почти мгновенно.

    Вот и всё! На этом изменения “инфраструктуры” Red Architecutre, а именно класса v, закончены. И теперь мы можем приступить к рассмотрению логики приёма и отображения апдейта ячейки.

    Для начала нам нужно зарегистрировать ячейку для приёма апдейта. Код регистрации ячейки представлен в методе OnAppear():

    class TableCellView {
        // List and Address are components of identifier for this cell in system (i.e. GUID consisting of two strings)
        private const string List;
        private const string Address;
    
        handler void OnEvent(string key, object data) {
            string thisCellUpdateKey = string.Format( /* format string */ v.OnCellUpdate, /* arguments for format string */ this.List, this.Address); 
        if(key == thisCellUpdateKey)
            // update content of this cell
            this.CellContent = data.Content;
       }
    
        // constructor
        TableCellView(string list, string address) {
            this.List = list;
            this.Address = address;
       }
    
        // cell appears on user’s screen - register it for receiving events
        void OnAppear() {
            string thisCellUpdateKey = string.Format( /* format string */ v.OnCellUpdate, /* arguments for  format string */ this.List, this.Address); 
            v.Add(thisCellUpdateKey, OnEvent);
       }
    
        // don't forget to "switch off" the cell from receiving events when cell goes out of user's screen
        void OnDisappear() {
            string thisCellUpdateKey = string.Format( /* format string */ v.OnCellUpdate, /* arguments for  format string */ this.List, this.Address);
            v.m(thisCellUpdateKey, OnEvent);
        }
    }
    

    При появлении ячейки на экране в методе OnAppear() мы “регистрируем” её для получения событий с уникальным ключом thisCellUpdateKey, который формируется в конструкторе объекта и является производным от форматной строки-ключа v.OnCellUpdate, и который позволяет позже передать данные именно этой ячейке, не вызывая функции обработчики у других ячеек.

    А в методе обработчика OnEvent() мы проверяем ключ на соответствие текущей ячейке (на самом деле, в случае с OnCellUpdate эта проверка не обязательна, но поскольку в обработчике мы можем обрабатывать более одного ключа — всё же желательна) и в случае соответствия пришедшего ключа ключу текущей ячейки апдейтим отображаемые данные this.CellContent = data.Content;

    Теперь рассмотрим логику получения и передачи данных ячейке

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

    class SomeObjectWorkingWithSocket {
    
        void socket.OnData(data) {
           if(data.Action == SocketActions.UpdateCell) {
             string cellKey = string.Format( /* format string */ v.OnCellUpdate, /* arguments for format string */ data.List, data.Address);
             v.Add(cellKey, data);
    
             // This call for objects which process updates for any of the cells, for instance, caching data objects
             v.Add(v.OnCellUpdate, data);
           }
       }
    }
    

    Таким образом, мы всего одним вызовом (не считая логики внутри класса v, которая не относится к какой-либо конкретной логической цепочке) передали данные из места их получения — в место их использования — в одну конкретную ячейку из миллиарда. Во всей логической цепочке всего несколько строк кода, и один ключ OnCellUpdate, по которому находится весь код связанный с этой функцией.

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

    • Сколько времени займёт поиск кода, который нужно «патчить» для решения этой задачи? — Весь связанный код найдётся моментально, главное сказать разработчику, чтобы он поискал по коду v.OnCellUpdate.
    • Сколько времени такая задача займёт у нового человека в нашем случае? — Если удаётся обойтись уже существующим API для решения вопросов отображения и анимации, то 1-2 дня точно хватит.
    • Сколько шансов у нового разработчика сделать что-то не так? — Мало: код простой, разобраться в нём несложно.

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



    На этом можно было бы закончить, но… К нам пришла задача чтобы мы не только отображали, но и кешировали пришедшие данные. Неужели нам придётся что-то менять в уже написанном или, хуже того, всё переписывать? Нет! В Red Architecture объекты совершенно не связаны друг с другом. К нам пришла задача — добавить функцию, ровно в таком виде эта задача и будет отражена в коде — мы добавим код кеширующий данные без каких-либо изменений того, что уже написано. А именно:

    class db {
        handler void OnEvent(string key, object data) {
           if(key == v.OnUpdateCell)
               // cache updates in db
               db.Cells.update("content = data.content WHERE list = data.List AND address = data.Address");
       }
    
       // constructor
       db() {
          v.h(v.OnUpdateCell, OnEvent);
       }
    
       // destructor
       ~db() {
          v.m(v.OnUdateCell, OnEvent);
       }
    }
    

    Вся логика, связанная с апдейтом ячейки, будь то отображение или кеширование, по-прежнему проста и идентифицируется в коде ключом v.OnUpdateCell.

    Вы прочли вторую часть, первая часть здесь. В 3 части мы решим проблемы многопоточности.
    Share post
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 6

      0
      Вы серьезно?
        0
        А чем это отличается от классических EventSubscriber/EventListener-ов?
          0
          Дайте ссылку на описание конкретной технологии, я приведу отличия.
            0
            pub/sub, observer, event dispatching
          0
          Да, роутинг сообщений это именно то, чего не хватало первой реализации.
          Добавить приоритеты, отложенную доставку и получим фреймворк/сервис очередей, аналогичный RabbitMQ.
            0
            Без добавления приоритетов и отложенной доставки https://habrahabr.ru/post/334840/

          Only users with full accounts can post comments. Log in, please.