Итоги разработки прототипа менеджера конфигурации.

Материал переведен отсюда

На прошлой неделе, перед DrupalCamp Colorado, Greg "heyrocker" Dunlap, David Strauss, Larry "Crell" Garfield, and Karoly "chx" Negyesi, встретились, чтобы определить основные моменты кода Drupal 8, сделанные на скорую руку Группой Управления Конфигурацией(Configuration Management Initiative).

Какие проблемы мы пытаемся решить? 

  • На сегодняшний день нет способа переноса информации о "Друпаловской" конфигурации между различными средами разработки, потому что эти данные разбросаны по всей базе данных и часто пересекается с контентом;
  • Это также делает невозможным управление информацией по версиям, хранения истории и нет возможности отката изменений;
  • Каждый модуль хранит свои данные о конфигурации в различных форматах, даже в пределах ядра нет никакой стандартизации;
  • Также нет стандартов API для хранения этой информации (кроме простой таблицы переменных), поэтому  разработчики часто применяют собственные решения;
  • Вся таблица переменных загружается, на каждую запрашиваемую страницу, даже для редко запрашиваемых данных, что приводит к заполнению памяти;
  • Очень громоздкое управление информацией, которая отличается на разных серверных окружениях одного и того же проекта. (база данных, api keys, и пр. )

Мы пока не решаем проблему контекстной конфигурации; только нужного для него API для чтения/записи этой конфигурации и перемещения его для нужных сайтов.

Код прототипа доступен по следующей ссылке http://drupal.org/sandbox/heyrocker/1145636 . Основные изменения находятся в файлах includes/config.inc и modules/config/config.test

Ниже приведены результаты разработанного как прототип кода. 

Общее строение 

 

 

Уровень 1: Хранилище файлов с цифровой подписью.

На самом низком уровне, все данные конфигурации хранятся на диске, используя подписанные JSON файлы, находящиеся в sites/$sitename/config.  Так же смотрите "Вопросы безопасности", изложенные ниже.  

Пример того, как может выглядеть такой файл prefix.example.json.php

<?php die(); 723fd490de3fb7203c3a408abee8c0bf3c2d302392[snip]
{
  "string_value": "string",
  "integer_value": 1,
  "array_or_object_value": [
    "value1",
    "value2"],
  "boolean_value": false,
}

Первая строка предотвращает непосредственный просмотр файлов, а также обеспечивает цифровую подпись для проверки целостности файлов.

Предполагается, что в файле подобном module.$modulename.json.php будет хранится информация о всей конфигурации модуля. Ядро может включать в себя  такие файлы как  core.site_information.json.php, а модуль типа Flag может хранить свои глобальные настройки как в файле  module.flag.json.php так и в  flag.$flagname.json.php

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

Уровень 2: Активная обертка конфигуратора

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

В конфигурации по умолчанию, эти данные будут передаваться в главную таблицу называемой "config" 

CREATE TABLE config (
   name varchar(255) NOT NULL DEFAULT '' COMMENT 'The identifier for the  configuration entry, such as module.example (the name of the file,  minus .json.php).',
  data longtext NOT NULL COMMENT 'The raw JSON data for this configuration entry.',
  PRIMARY KEY (name),
);

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

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

  1. Изменения пользовательского интерфейса (автоматически): Когда вы нажимаете кнопку "Сохранить" на странице администрирования, данные записываются в базу и в файл .json.php (цифровая подпись для измененного файла тоже обновляется)  
  2. Изменения кода (в ручном режиме): Например: при миграции с площадки разработки на боевой сайт, конфигурационные файлы будут изменены, но эти данные не попадут в таблицу обертки конфигуратора. Данные будут по-пержнему считываться из таблицы, и сайт не сломается. Администратор сможет поместить в таблицу актуальные данные из файлов при помощи административного интерфейса или командой drush.

Уровень 3: API конфигурации

На этом уровне находятся функции, через которые, по сути, разработчик и будет работать: конкретно, замена функций variable_get()/varriable_set().

<?php
// Load a set of configuration out of the active store.
// 'prefix.name' refers to the filename of the .json.php file, without the extension.
$config = config('prefix.name');
// Access a single value out of the store.
echo $config->value;
// Change a value and save it back to both the active store and the filesystem.
$config->value = 'new value';
$config->save();
?>

Я разработчик модулей. Как предлагаемые изменения повлияют на меня?

variable_get()/varriable_set()  

В версиях Drupal 7 и ниже, все переменные являются глобальными, поэтому доступ и сохранение переменных делается так: 

<?php
// Load the site name out of configuration.
$site_name = variable_get('site_name', 'Drupal');
// Change the site name to something else.
variable_set('site_name', 'This is the dev site.');
?>

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

<?php
// Load the site name out of configuration.
$site_name = config('core.site_information')->name;
// Change the site name to something else.
$config = config('core.site_information');
$config->name = 'My Awesome Site';
$config->save();
?>

Для загрузки таких как объектов как entities или views, вы можете загрузить все "вложенные" конфигурационные файлы (в том числе, для списка вьюс, списка всех типов материалов...) следующим вызовом: config_get_names_with_prefix('entity.node.'); Это вернет что-то вроде  array('entity.node.page', 'entity.node.article'); Таким образом, чтобы получить все настройки для типа материала page запустите config('entity.node.page');Это означает, что config('entity.node')не будет ничего возвращать.

Объявление собственных настроек

Раньше конфигурация объявлялась разными путями, system_settings_form() автоматически сохраняла все элементы как переменные в таблице переменных. Модули такие как Flag и Views  использовали hook_X_default() как шаблон.

В новой системе объявление собственной "переменной" происходит в файле module.$modulename.json.php, который поставляется вместе с модулем. Вы определяете эти переменные по умолчанию только в одном месте, а не каждый раз когда вам их надо получить.

Например, файл может выглядеть так:

module.book.json.php

<?php die();
{
    "book_child_type": "book",
    "book_block_mode": "all pages",
    "book_allowed_types": [
        "book"
    ]
}

Во время установки модуля файл конфигурации скопируется в sites/$sitename/config и все настройки будут храниться здесь.

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

 Но мои конфигурационные данные намного сложнее, чем эти!

Так как мы используем JSON как формат хранения, то конфигурационные объекты могут быть произвольной сложности. Однако есть здесь одно затруднение. JSON не делает различий между объектом и ключом, поэтому мы поддерживаем только массивы "ключ/значение" как в PHP. После некоторого обсуждения мы решили, что отображение всех таких данных в ассоциативном массиве обеспечивает надежную структуру для дискретного набора свойств, а данные вроде $object->1 являются недействительными. 

<?php
$config
= config('module.mymodule');
$config->alternate_name; // A simple property
$config->instances[0]['name']; // An array
// You can't do this...
$config->foo->bar->baz = 1;
// But you can do this instead...
$config->foo['bar']['baz'] = 1;
?>

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

Я супер-пупер разработчик и мне надо что-то мощнее, чем это. Как я могу изменить это?

Объект $config будет реализацией базового класса, который реализует логику ->save(). Для продвинутого использования его можно будет переопределять 

<?php
class MyAdvancedConfig extends DrupalConfig {
  function
someHelperMethod() {
   
$this->pants = 'fancy';
    echo
"Ma'am, you have my compliments on your fancy pants.";
  }
}
$config = config('core.site', 'MyAdvancedConfig');
$config->someHelperMethod();
$config->save();
?>

Перемещение конфигурации с разработческой площадки на боевой сайт

Общий процесс работы выглядит следующим образом:

  1. На вашем рабочем сервере любые изменения конфигурации надо выполнять через пользовательский интерфейс: создание вьюшек, чекбоксов и т.д. Эти изменения записываются в таблицу базы данных и файловую систему, поэтому они синхронизированы (также будут обновляться цифровые подписи изменяемых файлов конфигурации)
  2. Когда закончите ваши изменения, посмотрите их в sites/$sitename/config при помощи команды $vcs diff (или в выбранном вами инструменте), убедитесь что они верны.
  3. Если все сделано так, как вы хотите, переместите измененные файлы на боевой сайт используя удобный для вас способ (SFTP, $vcs add/commit/push, $vcs update/pull). Ничего не будет изменено пока сайт не будет готов к запуску, т.к. все данные будут считываться из базы данных.
  4. Напоследок, зайдите в admin/configuration/system/config и запустите команду drush config-update (Примечание: эта команда запускается на данный момент). Будут определены различия между тем, что было и что стало. Если все правильно подтвердите перезапись. Командная строка будет спрашивать по необходимости подтверждение на применение изменений.

Обработка локальных изменений

 Обычно удобно для API  keys или $db_url иметь настройки специфичные для каждого сайта, которые не находятся под версионным контролем. В Drupal 7 и ниже это решается при помощи settings.php (или settings.local.php)

<?php
$conf
['site_name'] = 'This is the dev site';
?>

В новой системе будет специальный файл, в котором будут храниться все локальные изменения (этот файл не будет проверяться системой контроля версии): /sites/default/config/local.json.php. В этом файле будут храниться все переопределенные параметры в виде ключ/значение.

<?php die(); 723fd490de3fb7203c3a408abee8c0bf3c2d302392[snip]
{
  "core.site_information": {"site_name": "This is the dev site"}
}

Правила безопасности

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

  1. В корне /sites/sitename/config будет файл .htaccess, который будет содержать параметр "Deny from all"  (или его аналог в web.config для IIS) который полностью запретит чтение этих файлов и будет выдавать ошибку 403 (Отказано в доступе)
  2. Однако, на всякий случай все конечные конфигурационные файлы имеют расширение .php. Поэтому, если к ним будет осуществлен доступ выдасться ошибка в php синтаксисе. Сообщение об ошибке будет в журнале наблюденний.
  3. В корне /sites/sitename/ будет файл key.php доступный только для чтения, который будет записан во время установки, и состоит из специфичного для сайта ключа.Этот ключ будет использоваться для цифровой подписи, которая будет проверять не изменяли ли конфигурацию непосредственно в файле. Например,
    <?php die(); c3a408abee8c0bf3c2d302392
  4. Первая строка в конфигурационном файле состоит из автоматически сгенерированного ключа
    <?php die(); 723fd490de3fb7203c3a408abee8c0bf3c2d302392[snip]
    // All JSON data follows here.
    Если есть несоответствие между хэшом и файловым содержимым, конфигурация системы не будет загружаться. Это приведет к тому, что файл не сможет быть перезаписан кем-то другим. Целью является глубокая защита файла, чтобы сломать ее взломщику надо будет повредить несколько слоев и при этом остаться не замеченным, что мало вероятно.