Некоторое время назад вышел первый релиз ветки 5.x, а потом несколько меньших патч-версий, так что опять есть чего рассказать.

Предыдущие изменения: часть 1, часть 2, часть 3, часть 4.

Комментарии под предыдущей статьей и в чатиках были весьма полезными, отдельное спасибо fesor, который хоть и не согласен, но предоставляет конструктивную критику, которая имеет позитивные последствия.

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

Прощай jQuery


В версиях 4.x jQuery сначала была объявлена устаревшей, позже в 5.x она была полностью выпилена из фреймворка.

Множество рутинных операций с использованием Polymer становятся декларативными и тянуть jQuery ради небольшого количества методов не хотелось.

Ещё jQuery активно использовалась для XHR запросов, но с широкой поддержкой XHR2 исчезла необходимость писать несколько реализаций, и была написана функция cs.api(), которая:

  • обеспечивает 95% потребностей с гораздо более удобным синтаксисом
  • поддерживает отправку форм и файлов
  • интерфейс базируется на ES2015 Promise
  • поддерживает обработку ошибок с выводом пользователю красивых всплывающих сообщений

Некоторые модули использовали jQuery плагины, они теперь используют NPM версию jQuery.

Так же один из jQuery плагинов что использовался в ядре был отрефакторен без использования jQuery, патч принят upstream, так что фреймворк включает чистую оригинальную версию: github.com/voidberg/html5sortable/pull/204

Больше нет плагинов и шаблонов блоков


Раньше отдельно были модули и плагины. Плагины были похожи на модули, но без собственных страниц, админок, настроек, API и прочего.

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

Так же components/blocks и components/modules впоследствии перенеслись в blocks и modules по аналогии с themes.

Шаблоны блоков были достаточно кривой штукой изначально и по факту были слишком привязаны к конкретному проекту. Если добавить к этому невозможность установки блоков из пакетов — польза и вовсе сводилась к нулю.
Поэтому шаблоны блоков тоже были выпилены, теперь блоки сами беспокоятся о собственном внешнем виде.

Консистентность интерфейсов


Вместе с чисткой второстепенных фич было проведено много работы по консистентности внутреннего API фреймворка.

К примеру, cs\CRUD при работе с разными движками БД теперь приводит числовые типы к одному виду (MySQL даже для числовых столбцов возвращал строки, а SQLite возвращал числа).

Ещё один пример — удаление поддержки вызовов cs\DB::instance()->$db_id и cs\DB::instance()->$db_id() вместо cs\DB::instance()->db($db_id) и cs\DB::instance()->db_prime($db_id). Когда-то давно это использовалось, но это не очень удобно и усложняет вывод типов для IDE.

Множество подобных мелочей было исправлено и приведено к общему виду.

Тестирование


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

В результате общее покрытие фреймворка тестами 66%+ на момент написания статьи, в покрытие системных классов 96%+ (папка modules практически полностью состоит из контроллеров, поэтому приоритет ниже):





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

Фронтенд ещё легче, опциональное отключение поддержки веб-компонентов


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

Второе важное улучшение — переход на асинхронные интерфейсы работы с переводами на фронтенде.

Раньше было так:

cs.Language.system_profile_hello('Username')
// или
cs.Language('system_profile_').hello('Username')

Теперь же нужно подождать:

cs.Language.ready().then(function (L) {
    L.system_profile_hello('Username');
});
// или
cs.Language('system_profile_').ready().then(function (L) {
    L.hello('Username');
});

На самом деле под капотом до 6.x оно всё ещё синхронно, но в итоге переводы будут загружаться только когда нужны.

Все эти изменения позволят уменьшить объем обязательно загружаемого JS кода с 312 КиБ до меньше чем 30 КиБ (это всё без учета gzip), а объем HTML импортов из 107 КиБ до 0 КиБ (0 файлов).

По сути, будет загружаться Alameda (RequireJS) и несколько вспомогательных системных функций/объектов.

Производительность серверной части


Очередная порция изменений привела к ещё большей скорости работы (хотя низко висящих фруктов уже нет).

Особенно полезным является оптимизация API запросов. Существенными (относительно) накладными расходами обладала работа с переводами, большой JSON занимал как время на парсинг, так и память. Теперь же во время API запроса, не работающего с многоязычностью, переводы вообще не будут загружены в память.

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

Скрытый текст

В текстовом виде:

|framework          |requests per second|relative|peak memory|relative|
|-------------------|------------------:|-------:|----------:|-------:|
|silex-1.3          |           3,029.75|     8.8|       0.59|     1.0|
|symfony-2.7        |           1,423.83|     4.2|       1.41|     2.4|
|symfony-3.0        |             995.79|     2.9|       1.64|     2.8|
|laravel-5.2        |             342.99|     1.0|       1.98|     3.4|
|zf-2.5             |             671.20|     2.0|       1.36|     2.3|
|cleverstyle-5.15   |           1,939.17|     5.7|       0.66|     1.1|


Так же выросла пиковая производительность под встроенным Http сервером вместе с отличной масштабируемостью (HHVM, 16 процессов, Core i7 4900MQ):
Скрытый текст
nazar-pc@nazar-pc /w/t/www> ab -c500 -n100000 -k http://test.com:9990/api/System/blank
This is ApacheBench, Version 2.3 <$Revision: 1706008 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/

Benchmarking test.com (be patient)
Completed 10000 requests
Completed 20000 requests
Completed 30000 requests
Completed 40000 requests
Completed 50000 requests
Completed 60000 requests
Completed 70000 requests
Completed 80000 requests
Completed 90000 requests
Completed 100000 requests
Finished 100000 requests


Server Software:        nginx/1.10.1
Server Hostname:        test.com
Server Port:            9990

Document Path:          /api/System/blank
Document Length:        4 bytes

Concurrency Level:      500
Time taken for tests:   9.375 seconds
Complete requests:      100000
Failed requests:        0
Keep-Alive requests:    0
Total transferred:      24400000 bytes
HTML transferred:       400000 bytes
Requests per second:    10666.64 [#/sec] (mean)
Time per request:       46.875 [ms] (mean)
Time per request:       0.094 [ms] (mean, across all concurrent requests)
Transfer rate:          2541.66 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0    8  69.2      1    1013
Processing:     0   39  30.9     31     272
Waiting:        0   37  30.5     29     272
Total:          0   47  75.3     35    1199

Percentage of the requests served within a certain time (ms)
  50%     35
  66%     48
  75%     57
  80%     63
  90%     83
  95%    104
  98%    137
  99%    166
 100%   1199 (longest request)

А в однопоточном режиме запросы отрабатывают начиная с 0.6 мс (0.0006 секунд), что очень достойный результат, хотя и хотелось бы забраться под 0.5 миллисекунды.

Напоследок


Как обычно, буду благодарен за конструктивные комментарии.
Репозиторий на GitHub: github.com/nazar-pc/CleverStyle-Framework
Поделиться с друзьями
-->

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


  1. MetaDone
    11.08.2016 21:01

    Хотелось бы увидеть сравнение производительности с Phalcon на php7


    1. nazarpc
      11.08.2016 21:28
      +1

      Только что запустил на PHP 7.0.8, Ubuntu 16.10 x64

      Скрытый текст


      1. MetaDone
        11.08.2016 21:32

        ок, спасибо


        1. nazarpc
          11.08.2016 21:34

          Решил сделать более полный, результаты опять слегка отличаются, но в целом Phalcon не переплюнуть по производительности

          Скрытый текст


          1. Fesor
            11.08.2016 23:50

            а никто symfony 2.8+ в режиме микроядра не добавлял?


  1. wispoz
    12.08.2016 11:35

    А Yii2? Можете добавить?


    1. nazarpc
      12.08.2016 13:33

      Я хотел, но он сломан в бенчмарке. Если кто-то исправит, с радостью сравню.


  1. Miraage
    12.08.2016 14:02

    Когда планируете использовать паттерны проектирования, PSR, DI?


    1. nazarpc
      12.08.2016 15:49
      -1

      PSR поддерживается на уровне совместимости, к примеру, прослойка для PSR7, автозагрузчик аналогичен PSR4 по сути, просто есть несколько директорий с разными префиксами (cs\modules это модули в modules, а cs это системные классы в core\classes), тот же Composer можете использовать.


      На счёт DI — нет и не планируется. Во фреймворке он не нужен, ибо он не компонентный и не предполагает свободной замены своих частей. А если вам нужен DI — вон сколько их готовых на GitHub, подключайте чего вам нравится, не вижу пока смысла что-то в этом плане навязывать.


      На счёт паттернов — что конкретно вам не нравится? Буду благодарен если ответите конкретно с примером, а то выглядит как вопрос на собеседовании)


      1. Miraage
        12.08.2016 17:58
        +2

        no offence, но все на синглтонах — это, на мой взгляд, не комильфо.


        1. nazarpc
          12.08.2016 18:45
          -1

          Это я часто слышу, попытался ответить на этот вопрос здесь


          1. Fesor
            12.08.2016 22:03

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

            1) что такое "ручное внедрение зависимостей"? Фабрики? Что там нечитабельного?
            2) внедрение зависимостей, которые на самом деле не используются означают что у модуля низкая внутренняя связанность и его нужно разделить на отдельные модули. А еще есть lazy инициализация с проксями. Опять же вас никто не заставляет писать свой контейнер — юзаем готовые которые все это умеют и учитывают.


            либо локатор сервисов (Service Locator)

            какое отношение это вообще к Dependency Injection имеет?


            Контейнер внедрения зависимостей

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

            неверно. Основная суть что у вас есть один объект в системе, чья задача разруливание зависимостей. Никакой чуши про интерфейсы => реализации.


            в противном случае какой толк от конфигурации? Так же этот подход не решает проблемы с ленивым созданием сервисов.

            Толк в том что она есть и мы не пишем кучу фабрик. Они генерятся сами. А если вы посмотрите на популярные контейнеры — там в принципе в 80% случаев вообще конфигурацию описывать не нужно.


            Сервис локатор

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

            ленивая загрузка это не решение проблемы а ее симптом. Ну и про конфигурацию — тупой autowiring делается весьма просто.


            очень маловероятно что мы будем писать многопоточное и/или полностью асинхронное приложение

            справедливости ради, при использовании pthreads статика не шарится по потокам. Как раз таки как защита от дурака. Расшаривание данных происходит максимально явно и случайно расшарить что-то не выйдет.


            То есть по сути, в CleverStyle Framework тот же локатор сервисов, который, тем не менее, не требует никакой конфигурации, поскольку система не предполагает свободной замены системных классов.

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


            Резюмирую. Сингелтон сам по себе — нормальный паттерн. В PHP мире на нем реализуют сервис локатор, и как бэ вот это и есть "плохо". Вообще любое глобальное — не очень хорошо и давать такое использовать кому попало не стоит.


            1. nazarpc
              12.08.2016 22:51

              1) что такое "ручное внедрение зависимостей"? Фабрики? Что там нечитабельного?

              Я имел ввиду подобное:


              $obj = new SuperClass(new Dependency1(new Dependency2()), new Dependency3());

              Когда вы создаете все зависимости явно и явно передаете их в конструктор в самом начале, то есть без использования контейнеров и чего либо ещё.


              какое отношение это вообще к Dependency Injection имеет?

              Достаточно прямое, позволяет вместо кода выше писать подобное:


              $obj = $locator->get('SuperService');

              А под капотом будут внедрены нужные зависимости.


              2) внедрение зависимостей, которые на самом деле не используются означают что у модуля низкая внутренняя связанность и его нужно разделить на отдельные модули. А еще есть lazy инициализация с проксями. Опять же вас никто не заставляет писать свой контейнер — юзаем готовые которые все это умеют и учитывают.

              Согласен, но всегда есть граница здравого смысла. Городить прокси просто для того чтобы следовать паттерну, при том что сервис никогда не будет изменен? По-моему овчинка выделки не стоит. По крайней мере я предпочту повышение связности до определённой границы.


              неверно. Основная суть что у вас есть один объект в системе, чья задача разруливание зависимостей. Никакой чуши про интерфейсы => реализации.

              Это грубо, имеется ввиду что зависимости не черной магией разруливаются, а по определённым образом составленным правилам.


              Толк в том что она есть и мы не пишем кучу фабрик. Они генерятся сами. А если вы посмотрите на популярные контейнеры — там в принципе в 80% случаев вообще конфигурацию описывать не нужно.

              То есть решаем проблему, которой изначально не было? Сейчас во фреймворке вообще нет фабрик, они просто не нужны. И есть лишь один метод во всём фреймворке, который занимается созданием подобных объектов. autowiring это круто, но это работает не в 100% случаев, а в данном случае решает то, что не является проблемой изначально.


              ленивая загрузка это не решение проблемы а ее симптом. Ну и про конфигурацию — тупой autowiring делается весьма просто.

              Опять таки да, но проблемы может вообще не быть при альтернативном подходе.


              Я пытаюсь донести, что при наличии единственной реализации чего либо в принципе, не вижу смысла в интерфейсах, DI, тащить что-то калибра PHP-DI (который, несомненно, крут, но по объему как 15% всего CleverStyle Framework) и так далее, поскольку это не имеет практического смысла. Гораздо удобнее иметь прямой вызов и автоматический вывод типов в IDE без каких-либо сторонних плагинов, да и производительность получится самая высокая. Если это можно в итоге протестировать и вместе оно хорошо работает — то почему нет?


              Не хочется закончить написанием https://github.com/Herzult/SimplePHPEasyPlus либо https://github.com/EnterpriseQualityCoding/FizzBuzzEnterpriseEdition в реальной жизни.


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


              1. Fesor
                13.08.2016 00:26
                +1

                Достаточно прямое, позволяет вместо кода выше писать подобное:

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


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


                Все сводится к принципу "работает не трогай", или как я его трактую — "делай так, что бы не пришлось потом менять, только удаляй/добавляй классы".


                Городить прокси просто для того чтобы следовать паттерну, при том что сервис никогда не будет изменен?

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


                А зачем и когда нужно следовать "паттерну" — я описал выше.


                Это грубо, имеется ввиду что зависимости не черной магией разруливаются, а по определённым образом составленным правилам.

                разруливание по правилам = магия. Она такая же "черная" но чуть другая.


                То есть решаем проблему, которой изначально не было?

                проблема управления зависимостями и управление связанностью существует… ну скажем так, она появилась раньше чем ООП. В каком-то смысле ООП является решением этой проблемы.


                Я пытаюсь донести, что при наличии единственной реализации чего либо в принципе, не вижу смысла в интерфейсах, DI, тащить что-то калибра PHP-DI

                вы этим как бы говорите "у меня всегда будет только одна реализация, и не только у меня — у всех кто будет использовать мою систему!". То есть вы не даете никакой возможности для кастомизации.


                Не хочется закончить написанием

                Вы из одной крайности (все жестко) бросаетесь в другую (отсутствие здравого смысла). Вы же понимаете что… все должно быть в балансе.