Category: it

cartoon

NestedTypes 2.0 RC: прямая поддержка агрегации и ОО в JS

Че-та сюда написать забыл.

Есть набор весьма неприятных проблем, которые возникают в JS в случае, если слой данных SPA действительно сложен. Знаете, например, как на раз делается memory leak в JavaScript? Для мемори лика в языке с GC нам нужны две вещи. Синглтон, и подписка на события. И иногда, утечки добиться настолько легко, что это выглядит как фокус. Я объясню на примере BackboneJS.

Пусть у нас есть глобальная коллекция-синглон каких-то объектов - неважно каких, скажем, users. И где-то на одной из страничек нашего SPA мы выбираем юзеров из списка, и выбранные элементы лежат в другой коллекции. Временной. Которая живет столько же, сколько живет страница (пусть у нас SPA состоит из разных "страниц", которые он переключает меняя рауты).

Ну все, у вас уже утечка памяти. Откуда она берется? Коллекция в Бэкбон подписывается на событие change от своих элементов. Подписка на событие на самом деле означает, что объект, который источник события, держит обратную ссылку на получателя. Чтобы его уведомлять.

Так вот, ваши users лежат в синглтоне, и держат ссылку на каждую из коллекций, в которых они лежат. А это означает, что каждая такая коллекция, в которой лежит хоть один юзер из users, не будет подобрана GC.

Ну конечно, если при смене страницы, которая ее создала (в случае SPA это не настоящее закрытие страницы, а лишь видимость, с точки зрения браузера страница одна), не удалить из всех этих коллекций все элементы, лежащие в синглоне. То есть, если у вас нет деструктора - у вас утечка памяти. И не важно, что у нас GC. Добро пожаловать в увлекательный мир, в котором живут программисты на С++.

Бэкбон - это был просто пример. Механизм этой проблемы таков, что ему все равно, бэкбон у вас или не бэкбон.

Так вот. Не знаю, как вы, но не хочу обратно в увлекательный мир С++, частью которого я уже был примерно 6 лет. Поэтому, NestedTypes 2.0 RC, который мы в Volicon/Verizon сейчас готовим к выпуску, и который сейчас лежит в основе наших приложений для мониторинга потоковых видео-трансляций, управляет памятью полностью автоматически. При условии, что вы отличаете агрегацию от ассоциации, и напишете об этом в декларации атрибута модели.

А вот о том, что такое агрегация в ОО - вот вам статья. С веселыми картинками и доступными примерами.

Статья с веселыми картинками

cartoon

NestedReact 1.0 beta. Hierarchical Checklist Demo.

Пример из NestedReact 1.0 beta, которая сейчас в бранче develop.

Это вот такой иерархический чекист с правилом - группа выбрана тогда и только тогда, когда все дерево под ней выбрано. 79 строк кода за вычетом комментариев и пустых. В основе - новый движок транзакционных моделей, написанный на TypeScript (NestedTypes 2.0 beta). Кругом ES6 классы. Ни одного стора нет. Просто составное состояние в корневом элементе.



Так вот, здесь, не смотря на data binding и классическое ОО в слое данных - unidirectional data flow, и вовсю работает pure render оптимизация. Очень рекомендую на код посмотреть - я его закомментировал в литературном стиле. Там смотреть то почти не на что - кода тьфу.

https://github.com/Volicon/NestedReact/tree/develop/examples/checklistTree

И то и другое возможно на классических моделях без каких-либо проблем. Не верите - запустите и посмотрите в React Tools как там рендер происходит. :) Оно уже собрано, надо просто сделать клон репозитория, и открыть index.html. Да, эти линии - в них можно кликать мышкой и писать там. Это input на самом деле.

UPD: Теперь оно сохраняет и восстанавливает состояние работая с localstorage. В качестве демонстрации, что любое состояние сериализуемо.

ЗЫ: Все время слышу последнее время - mobx, mobx. Было бы клево, если бы кто-нибудь сделал аналогичный пример на mobx.

TypeScript: https://github.com/mikhail-aksenov/mobxChecklist
ES6: https://github.com/kvasdopil/checklistTree

Спасибо, ребята. Выглядит действительно очень похоже. Оба примера по 90 строк. Пример с NestedReact занимает столько же, только в отличии от примеров mobx он сохраняется в LocalStorage. Бесплатная сериализация - это самое основное отличие. Ну, могу еще для демонстрации отличий валидацию на текстовое поле навесить. :) У меня это делается в модели - "name : String" -> "name : String.has.check( x => x )", и поле с именем получит стиль error, если значение пустое. Красным станет, например. Само.

Полный список возможеностей сверх mobx - two-way data binding, декларативная валидация, миксины, коллекции с индексами по id вместо простых массивов, сериализация (с id-ссылками и без), REST.

ЗЗЫ: На всякий случай напоминаю, что этим волшебным моделям NestedTypes уже два с половиной года. Они как бэ пораньше mobx появились, у нас в Verizon поверх них уже 100К строк наколбашено. Я считаю, это освобождает меня от дурацких объяснений "а зачем оно написано если есть mobx". И если честно - я сильно сомневаюсь, что mobx сравним с NestedTypes по производительности на сериализации и синхронизации с сервером коллекций сложных объектов по десять тыщ элементов. У нас на таком сейчас в 2.0 beta субсекундный отклик, например (часть требований). А вот, например, Backbone на таком - сдохнет, он заблокирует тред браузера секунд на 10. И не только бэкбон :).

ЗЗЗЫ: Это вообще довольно глупый вопрос - "а зачем". Затем, что можем. Не хотим страдать с редуксами, как все, и не собираемся дожидаться, когда кто-то что-нибудь сделает с этим.
cartoon

Введение в NestedTypes

React ValueLinks, это конечно, хорошо, но пришло время рассказать о нашей основной технологии, которая лежит в основе продуктов Volicon/Verizon.

https://medium.com/@gaperton/introduction-to-nestedtypes-data-framework-69aff986287e#.nn1a3qsc7

Это универсальная технология управления состоянием приложения. Динамическая сериализуемая система типов для JS.

Мы используем ее в сочетании вот с этим:
https://github.com/Volicon/NestedReact

Которое замещает стандартный React State нашими моделями, и мы получаем сквозную технику управления состоянием. С двухсторонним databinding, естественно - для этого используются наши линки, о которых я писал раньше.

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

TypeScript: Static or Dynamic?

https://medium.com/@gaperton/typescript-static-or-dynamic-64bceb50b93e#.mbkvfp8hh

Про TypeScript, и немного про теорию языков программирования вообще.

В связи с ростом популярности TypeScript, оживились флеймы static vs dynamic languages. Стороны опять точат копья.

А дело в том, что война окончена. В статье я, взрывая мозг примерами, популярно объясняю, что такое soft type system. На примере TypeScript. Ну и заодно делая небольшой экскурс в терминологию и понятия теории языков программирования.

Руководство по two-way data binding в React.

Во-первых, он таки в React есть. Называется value link. Это такой дизайн паттерн. И не смотря на то, что Фейсбук убирает свою кривую реализацию оного из React, он не может вам запретить его использовать.

Понимаем, что это такое, и учимся делать формы на React просто и приятно.

https://medium.com/@gaperton/managing-state-and-forms-with-react-part-1-12eacb647112#.m4awqrpf2

Во-вторых, этот паттерн способен на куда большее, чем примитивная его реализация в React. Например, он помогает весьма изящно делать валидацию ввода в реальном времени.

Учимся делать валидацию форм красиво, без традиционной для таких случаев боли:

https://medium.com/@gaperton/react-forms-with-value-links-part-2-validation-9d1ba78f8e49#.nbf6s3zee

Ну и в третьих. Самое интересное в этом паттерне вовсе не тот очевидный факт, что он устраняет ненужные коллбэки. А то, что он позволяет полностью отделить (и инкапсулировать) способ, которым выполняется data binding, как от слоя view, так и от слоя данных. Чего вообще ни один из популярных фреймворков не может.

Что, например, позволяет нам легко и непринужденно работать со сложным состоянием в React State, без какой-либо черной магии и кучи сторонних библиотек.

Учимся работать со сложным состоянием:

https://medium.com/@gaperton/state-and-forms-in-react-part-3-handling-the-complex-state-acf369244d37#.9f9rp5mwi

Помимо того, что это просто красиво - эта техника сокращает объем кода в JSX примерно вдвое.

https://medium.com/@gaperton/software-managing-the-complexity-caff5c4964cf#.3zdeyc03t

Такие дела.

5. Backbone Sucks

Кому-то могло показаться после прочтении предыдущей статьи, что мне "просто нравится бэкбон", или что я считаю его лучшей в мире технологией. Это не так. Мы начали с backbone по двум причинам: (1) многие с него начинают, и (2) это, пожалуй, минимально возможный фреймворк для браузерных приложений.

Так как мы приверженцы "бритвы Оккама" - сравниваться мы будем именно с ним, это как точка ноля. Теперь мы разберемся, с какими именно сложностями сталкиваются люди, использующие Backbone, и умеющие готовить его хорошо. Эти сложности однозначно надо исправлять, и по этим причинам можно выбрать другой фреймворк. А вот то, что в минимально возможном Backbone сложностей не вызывает - для этого нужна очень хорошая аргументация, чтобы это исправлять.

Вообще-то это черновик, написанный в один проход. Но мне сегодня влом вычитывать.

Collapse )

4. BackboneJS.

Допустим, у нас есть группа разработчиков на PHP, которые знают чуть чуть JS и jQuery. Что самое простое мы можем сделать, чтобы начали писать браузерное приложение, и были продуктивны немедленно?

Мы можем попробовать использовать тот же принцип, и те же архитектурны правила, к которым они привыкли в PHP. У них были шаблоны с встроенным PHP? Отлично, мы будем использовать такие же шаблоны со встроенным JS, которые будут разворачиваться в браузере. Они использовали jQuery? Отлично, мы сохраним jQuery.

Нам остается задать этому какую-то структуру. Элемент UI - это будет объект View с присоединенным DOM-элементом, и методом 'render', который должен разворачивать шаблон в присоединенный элемент. Помимо этого, View должен уметь перехватывать события UI, и вызывать свои методы.

Помимо этого, нелишне добавить к этому простые средства для работы с REST API. Класс для кусочка JSON, который умеет создаваться/читаться/сохраняться/удаляться (CRUD) в каком-нибудь стандартном варианте REST. Назовем его Model. Раз у нас REST, то надо еще уметь получать их список. Значит, нужен еще Collection.

У нас сейчас получилось что? Правильно, самый популярный фреймворк для разработки в браузере - backbonejs (http://backbonejs.org), который, в сочетании с модульной системой позволяет из говна и палок собрать браузерное приложение.

Collapse )

3. Рекурсивный паттерн проектирования

Теперь поговорим об архитектуре так называемых "одностраничных приложений" более детально.
Это будет непросто - сотни фреймворков, предлагающих десятки подходов - какой из них выбрать? У человека, решившего написать одностраничное приложение, будут большие проблемы. Такое многообразие выбора - это почти что то же самое, что и его отсутствие.

К счастью, основной критерий "хорошести" или "плохости" архитектуры очень простой. Коль скоро архитектура - это правила проектирования, то в первую очередь эти правила должны выполнять свою главную функцию - помогать проектировать:


Хорошая архитектура снабжает вас простыми и понятными правилами, которые помогают легко разбить что-то большое и сложное на набор менее сложных, слабо связанных между собой кусочков. А потом - эти кусочки разбить на более мелкие. И так далее.


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


FYI - есть два основных типа "рекурсивного паттерна".

Первый - "слои". Суть его в том, что система организована в "слои сверху вниз", таким образом, что верхний знает про нижние, а нижние про верхние ничего не знают. Слои отделены друг от друга строгим API, а за этим API вполне может царить Адъ. Ну и что.

Этот паттерн теоретически рекурсивен потому, что часто один слой при большом желании можно разбить на два слоя. Или он может быть слоистым внутри. Но, как правило, у каждого "слоя" есть определенный смысл, и бить его дальше некуда.

Второй - "иерархическая декомпозиция". Его суть в том, что сложный модуль состоит из более мелких, они - еще из более мелких, и т. д., таким образом, образуя "дерево". Все модуля отделены API.

"Иерархическая декомпозиция" является архитектурным паттерном только тогда, когда интерфейс "узлов" не уникален, а стандартизован. Например, если мы говорим о UI виджетах - то архитектурное правило говорит нам, как выглядит интерфейс виджета.

В реальной системе они всегда встречаются в сочетании. Например, если есть какой-то фрагмент используется в двух "деревьях" сразу - это означает, что он принадлежит "нижнему слою". Явно отделять слои, и наделять их смыслом - критически важно для архитектуры. Задавая осмысленные слои, с внятными критериями принадлежности им, вы задаете правила.


Надо отметить, что у "голой" браузерной платформы с этим исходно большие проблемы. Для описания UI требется целый трех языка: HTML, CSS, и JavaScript. И каждый более мелкий кусочек должен каким-то образом включать в себя все три. Но все, что может дать нам голый браузер - это один файл HTML, включающий в себя много файлов JS и CSS. А из JS файла сказать, что ему нужен другой JS файл нельзя вообще.

Прежде чем разбираться с фреймворками, надо разобраться с этим. Без нормальных модулей JS с зависимостями, этого элементарнейшего "рекурсивного паттерна", очевидно, ничего сложного в бразуре сделать в принципе невозможно.

Хорошая новость в том, что на JS модули есть открытая публичная спецификация CommonJS/Modules. Именно с такими модулями работает серверный JavaScript, например - nodejs.

Выглядит это так. В каждом файлике .js пишется что-то вот такое:
    var iamimporting = require( 'path/to/js/file' );
    ...
    module.exports = iamexporting;


В node это работает прекрасно, потому, что загрузка модуля с локального диска быстра, и может происходить синхронно. Чтобы это работало в браузере, модуль потребует дополнительной обработки (сборки). Например, мы можем собрать все модули, которые надо загрузить, в один большой файл, и включить его один раз тегом script.

Именно так работают browserify и webpack. При этом, (1) у нас в проекте появляется шаг сборки, и (2) возникает куча нюансов в случае, если приложение очень большое, и мы не хотим грузить все сразу. Мы хотим сразу загрузить необходимый минимум, а далее подгружать то, что нужно, по необходимости.

Это можно сделать, разбив приложение на крупные куски (единицы загрузки), и собрать эти куски
раздельно (по сути очень похоже на DLL). Это особенно удобно делается в webpack.

Или же, можно придумать что-нибудь еще. Это что-нибудь еще называется CommonJS/AMD (Asynchronous Module Definition), позволяет грузить модули без сборки, и в тот момент, когда они действительно нужны. Выглядит это так:

    define( [ 'path/to/js/file', ... ], function( iamimporting, ...){

        return iamexporting;
    });


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

Но у этого модуля есть недостаток, который сразу бросается в глаза. Им невозможно пользоваться. Поэтому, помучавшись несколько лет, разработчики придумали формат асинхронного модуля "Simplified CommonJS Wrapper", который допускает асинхронную загрузку, но выглядит по человечески:

     define( function( require, exports, module ){
        var iamimporting = require( 'path/to/js/file' );
        ...
        module.exports = iamexporting;
    });


requirejs также позволяет выполнять сборку проекта в один или несколько больших файлов js, но в данном случае это не необходимость, а оптимизация.

Глядя на это, возникает простая идея. Раз импорт модуля - это не часть языка, и все равно делается неким фреймворком, может ли он быть чуть поумнее, и загрузить прочие нужные нам ресурсы - html-шаблоны, css?

Конечно же может. Например, в requirejs это будет выглядеть так:

    define( function( require, exports, module ){
        require( 'css!./widget');
        var template = require( 'ejs!./widget' );
        ...
        module.exports = mywidget;
    });


В requirejs и webpack это далется при помощи контент-плагинов, они могут грузить практически все, что угодно, выполняя при этом над загружаемым объектом необходимые преобразования. Например, шаблон может на лету компилироваться в JS. И если они не умеют грузить то, что нужно вам - вы можете легко научить их это делать.

Что касается директивы import из ES6, я не думаю, что когда она будет поддерживаться браузерами, она быстро вытеснит модули CommonJS. Во-первых, синтаксис CommonJS совсем не плох, и никакого выигрыша сама по себе замена 'require' на 'import' не даст. Во-вторых, возможность тонко контролировать процесс загрузки, и выполнять преобразования над загружаемыми объектами - это очень большое преимущество для браузерных приложений. Вряд-ли от него кто-то просто так откажется.

Вообще, я бы не советовал уделять слишком большое внимание системе модулей. Она должна быть CоmmonJS, и выбирайте любой загрузчик. Его потом будет не так сложно заменить. Я бы советовал выбирать между requirejs и webpack. Первый - если не хотите вводить в проект шаг сборки. Сел, и поехал. Второй - готовы делать сборку, озабочены тем, чтобы "быть на острие технологий", и не хотите потом горько жалеть о том, что выбрали какую-то фигню.

Но уверяю вас, система сборки и загрузки модулей - это совсем не то острие, которое изменит
всю игру.

С модулями CommonJS и контент-плагинами уже вполне понятно, как в принципе бить приложение на кусочки, разложив их по файлам. Это необходимо, но не достаточно для того, чтобы построить наше приложение - нам надо знать, как примерно будут выглядеть эти кусочки. "UI виджет - это js модуль" - это слишком общо.

Не всякий модуль экспортирует виджет, в конце концов.

Часть 2. Возможности и ограничения современных веб-платформ

SPA (Single-Page Application) весьма сильно изменили архитектуру веб-приложений. Если говорить кратко - архитектура SPA гораздо проще традиционных, и по характеру ограничений практически ничем не отличается от традиционных клиент-серверных приложений. С поправками на веб-технологии, конечно. Остановимся поподробнее на принципиальных возможностях и ограничениях современных веб-платформ - что поменялось с архитектурной точки зрения.
Collapse )

Часть 1. Об архитектуре и фреймворках

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




Архитектура* - это набор формальных и неформальных *правил*, руководствуясь которыми люди проектируют систему.


Результат проектирования - это как правило некоторая высокоуровневая структура системы, которую тут же снова называют словом "архитектура" (ну вот мало у нас слов, и те все иностранные), а потом начинают ломать голову, чем же это отличается от "дизайна", и пр. И накидывать при этом кучу умняка. Мы не будем. Вместо этого:


  • Мы будем стараться использовать для обозначения нашего понимания термин "Common Design Principles".

  • Постараемся разобраться в определении, и как оно соотносится с фреймворками и структурами. Увидете, как только вы начнете думать об архитектуре как о правилах много встанет на свои места.

Collapse )