Как стать автором
Обновить
Veeam Software
Продукты для резервного копирования информации

Как документировать базы данных на MediaWiki и не свихнуться

Время на прочтение22 мин
Количество просмотров8.8K

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

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

Зачем ты в это полез и кто ты такой вообще

Ahoj! Меня зовут Коля, я QA тимлидер.

QA в Veeam включает в себя не только/не столько тестирование, сколько обеспечение качества в широком смысле. И среди прочих значений этого самого широкого смысла затесалась такая штука, как управление знаниями. По этой теме довольно много абстрактных статей и книг, повествующих о принципах Knowledge management, но на удивление мало конкретных рекомендаций и практически применимых идей, во всяком случае сколько-нибудь свежих. Это заставляет меня думать, что или все пользуются тем, что дают известные компании и радуются, или не пользуются ничем и страдают, или пилят свой тайный велосипед, о котором неловко рассказывать в приличной компании. Мне тоже неловко, но я все-таки расскажу.

О проблеме

Не томи, зачем конкретно мы тут все собрались?

Дано:

  • Продукты с многолетней историей разработки;

  • Полное отсутствие документации БД при наличии большого запроса на нее (особенно в разрезе описания таблиц, столбцов и типов хранимых данных);

  • Вагон страданий QA, Support & Dev;

  • База знаний на движке MediaWiki, к которой RnD привык и с которой все более-менее освоились;

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

Найти:

  • Способ организовать документацию БД на той же MediaWiki, где лежит вся остальная документация. Заставлять коллег пользоваться N+1-ым сайтом для документации чего-то специфичного — бессмысленно и бесчеловечно.

Решение:

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

  • Вытягиваем описания таблиц из кода на TFS

  • Закидываем на MediaWiki информацию о таблицах

  • Силами сотрудников добавляем к этому описания уже на самой вики

Звучит просто, но дьявол, как всегда, кроется в деталях.

Немного о внутренней базе знаний

А можно немножко больше контекста перед началом?

У нас в компании для работы со внутренней документацией используется большое число разных порталов: и несколько Sharepoint-ов, и пачка Confluence, и файлы на сетевой папке, и, конечно, единственный и неповторимый MediaWiki портал. Он же и самый популярный. На этой вики хранятся описания реестровых ключей, описания различных внутренних и внешних API, статьи по продукту, контакты, описания БД наших продуктов и многое-многое другое. Чуть больше про эту платформу — в моей прошлой статье.

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

Homepage sweet homepage
Homepage sweet homepage

Конечный результат для нетерпеливых (spoilers!)

Я не из тех, кто в каждой статье сразу в технические дебри лезет!
Мне б сперва с результатами познакомиться...

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

Здесь есть описание самой таблицы, описание ее столбцов (имя, тип, комментарий), ссылки на типы данных, информация о ссылках между таблицами...

И это все?
А разговору-то было...

Разумеется, это не все! Этот подход предоставляет целый ворох дополнительного функционала, который куда более интересен, чем просто описание таблиц:

Автоматическое обновление описаний таблиц

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

Автоматическое обновление описаний типов

При помощи того же подхода на вики были добавлены описания enum-ов и прочих типов данных, хранимых в таблицах. Это позволяет, например, не заглядывая в код понять, что такое job_type=14000.

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

Динамическое добавление информации о таблицах на другие страницы

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

Категоризация

Для удобства навигации и общего понимания связанной с таблицей функциональности таблицу можно добавить в одну или несколько категорий. А сами категории можно вывести в виде дерева при помощи специального расширения.

У категорий есть приятная особенность: они не обязаны формировать дерево, они могут формировать граф. Иными словами, дочерняя категория может быть включена в две и более родительские. Это чертовски упрощает навигацию: вам не приходится гадать, где находится категория "Agent Logging" — в категории "Agent" или в категории "Logging": "Agent logging" находится и там, и там.

Фильтрация поиска

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

Кроме этого, семантический поиск позволит искать также по всем полям таблицы. Например, найти все таблицы без описаний или все таблицы, добавленные в версии продукта 1.0.

Семантический поиск — ищем все таблицы, которые ссылаются на таблицу BJobs
Семантический поиск — ищем все таблицы, которые ссылаются на таблицу BJobs
Формы для редактирования описаний таблиц

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

Предоставление информации о таблицах БД через API

Кроме прочего, информацию о таблицах можно получать через API. Это может быть полезным в самых разных ситуациях: от автоматизации документации до миграции документации на другие порталы (если вдруг вы решите отказаться от MediaWiki, это не станет катастрофой: вы сможете сравнительно безболезненно смигрировать данные).

Пример запроса информации по таблице (в нем я запросил только имя и описание)

Get https://brainstorage.amust.local/api.php?action=ask&query=%5b%5b:Table:VBR/BJobs%5d%5d%7C?TableName=Name%7C?TableDescription=Description
Response body:
{
    "query": {
        "printrequests": [
            {
                "label": "",
                "key": "",
                "redi": "",
                "typeid": "_wpg",
                "mode": 2
            },
            {
                "label": "Name",
                "key": "TableName",
                "redi": "",
                "typeid": "_txt",
                "mode": 1,
                "format": ""
            },
            {
                "label": "Description",
                "key": "TableDescription",
                "redi": "",
                "typeid": "_txt",
                "mode": 1,
                "format": ""
            }
        ],
        "results": {
            "Table:VBR/BJobs": {
                "printouts": {
                    "Name": [
                        "BJobs"
                    ],
                    "Description": [
                        "Table for jobs of all types"
                    ]
                },
                "fulltext": "Table:VBR/BJobs",
                "fullurl": "https://brainstorage.amust.local/index.php/Table:VBR/BJobs",
                "namespace": 3010,
                "exists": "1",
                "displaytitle": ""
            }
        },
        "serializer": "SMW\\Serializers\\QueryResultSerializer",
        "version": 2,
        "meta": {
            "hash": "a03b120c8ef46c5fa8b8b940978fd422",
            "count": 1,
            "offset": 0,
            "source": "",
            "time": "0.002156"
        }
    }
}
Появление всех правок в новостях портала

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

  • Это позволяет информировать заинтересованных пользователей обо всех изменениях схемы;

  • Появление твоего ника в новостях — маленькое удовольствие, мотивирующее вносить правки.

И многое-многое другое. Функционал ограничен только мерой испорченности вашей фантазии и списком расширений MediaWiki.

Подготавливаем почву

Выглядит интересно.
Как это завести у себя? С чего начать?

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

Правим LocalSettings.php
<?php
<...>
// Здесь и далее я предполагаю, что у вас уже установлен экземпляр MediaWiki 
//   Если нет, то см: https://www.mediawiki.org/wiki/Manual:Installation_guide
// И что Semantic MediaWiki тоже установлен и работает
//   Если нет, то см: https://www.semantic-mediawiki.org/wiki/Help:Installation/Quick_guide
  
// Определяем пространства имен: одно для таблиц, 
// второе для страниц с обсуждениями. 

// Сами страницы с обсуждениями я не использую, 
// но для консистентности создаю и их.
define("NS_DB", 3010); // Число ДОЛЖНО быть четным
define("NS_DB_TALK", 3011); // Число ДОЛЖНО следовать сразу за предыдущим


// Назовем наши пространства имен как "Table" и "Table_talk"
// Странички будут иметь вид "Table:%yourPageName%"
$wgExtraNamespaces[NS_DB] = "Table";
$wgExtraNamespaces[NS_DB_TALK] = "Table_talk";


// Определяем вес в поисковой выдаче CirrusSearch
// 1 - вес основного пространства имен (вес обычных статей)
$wgCirrusSearchNamespaceWeights[NS_DB] => 0.5; 


// Говорим поиску, что по умолчанию нужно включать страницы таблиц в выдачу наравне с прочими
$wgNamespacesToBeSearchedDefault[NS_DB] = true;


// Эта настройка влияет как на статистику, так и на некоторые приложения:
// https://www.mediawiki.org/wiki/Manual:$wgContentNamespaces

// Нам она в первую очередь интересна тем, что заставляет работать 
// расширение Popups (превью страницы по наведению курсора)
// https://www.mediawiki.org/wiki/Extension:Popups
$wgContentNamespaces[] = NS_DB;


// Включаем работу подстраниц для нашего пространства имен
// Т.е., например, страница Table:Product/Table будет считаться 
// подстраницей страницы Table:Product
$wgNamespacesWithSubpages[NS_DB] = true;


// Включаем поддержку семантики для нашего пространства имен
// https://www.semantic-mediawiki.org/wiki/Help:$smwgNamespacesWithSemanticLinks
// Немного подробнее про семантику будет ниже, где мы начнем ее использовать.
$smwgNamespacesWithSemanticLinks[NS_DB] = true;

// Включим возможности продвинутой работы со строками
// Подробнее: https://www.mediawiki.org/wiki/Extension:StringFunctions
$wgPFEnableStringFunctions = true;

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

Создаем шаблоны описаний

Окей, погнали!
Как будут выглядеть страницы с описаниями? Хочется их как-то унифицировать.

Действительно, страницы с описаниями таблиц имеет смысл делать как можно более одинаковыми: это упростит как чтение, так и создание/редактирование. Для этого определим т.н. шаблон MediaWiki — страницу, которая будет определять, как нам выводить информацию. Сама же информация будет храниться в предельно упрощенном формате.

Определяем шаблоны таблицы

Будет трудно, крепитесь.

На самом деле нам потребуется не один шаблон, а два: один для общего описания таблицы и другой для описания столбцов. Что бы мы хотели видеть в описании таблицы?

Описание таблицы будет содержать:
* Имя
* Версия продукта, в котором эта таблица впервые был добавлена
* Описание таблицы
* Категории, к которым относится данная таблица
* Описания столбцов таблицы

Описание столбца таблицы будет содержать:
* Имя
* Версия продукта, в которой этот столбец был впервые было добавлен
* Тип данных
* Описание столбца
* Флаги Primary/Forign key
* Если Forign key, то возможность указать, на какой столбец 
  какой таблицы мы ссылаемся 

Переведем это в формат Mediaiwki синтаксиса шаблонов. Описание страницы должно выглядеть примерно так:

<!--
 Здесь не заданы поля "Имя" и "Продукт", поскольку для удобства 
 они берутся из имени страницы. Иными словами, описание таблицы 
 будет лежать на странице Table:ProductName/TableName.

 Вытащить из имени страницы имя продукта и таблицы можно будет 
 при помощи ключевых слов
 {{BASEPAGENAME}} и {{SUBPAGENAME}}соответственно
-->
{{Table
|Version=1.0
|Categories=MyCategory1, MyCategory2
|Description=Table description
}}

{{TableColumn
|Name=id
|KeyType=PK
|Description=My description 1
}}

{{TableColumn
|Name=links_to
|KeyType=FK
|Version=2.0
|LinksTo=Table:MyProduct/AnotherTable
|LinksToColumn=id
|Description=My description 2
}}

И из этого описания мы будем собирать приятное глазу представление.

Начнем с описания столбца. Можно было бы здесь определить то, в каком виде будет выводиться информация по каждому столбцу, но мы поступим хитрее и воспользуемся Semantic MediaWiki. Это позволит нам в шаблоне TableColumn только сохранить скрытые описания столбцов, а уже в шаблоне Table вывести пачкой все скрытые свойства. Это в разы уменьшит нагрузку на сервер, да и просто приятнее выглядит. А бонусом это позволит нам воспользоваться всеми остальными плюшками Semantic MediaWiki. Впрочем, обо все по порядку.

Чуть больше о Semantic Mediawiki

Semantic Mediaiwiki — расширение для MediaWiki, добавляющее поддержку Linked Data на MediaWiki сайт. С семантическими данными впоследствии можно делать много всего интересного: строить запросы, выводить их в различных форматах и работать с ними по API.

Концепция linked data не новая и пик ее популярности прошел лет 5-10 назад. Впрочем, отдельные области применения у него остаются. Тот же Google Knowledge Graph аккумулирует информацию из множества источника связанных данных и использует это для улучшения поиска.

Semantic MediaWIki предоставляет множество приятных вещей, из-за которых я продолжаю ее использовать:

  • Нативный API для всех определенных свойств и сущностей

  • Множество встроенных форматов вывода хранимых данных. При этом за счет предыдущего пункта довольно легко писать и свои представления для данных

  • В дополнение к предыдущему пункту семантика весьма быстро строит эти представления. В огромном количестве мест я менял шаблоны mediawiki на представления от semantic mediawiki, тем самым ускоряя загрузку страницы в десятки и сотни раз.

Начнем с создания страницы Template:TableColumn

<noinclude>
Этот шаблон будет служить для добавления полей в форму создания таблицы базы данных одного из наших продуктов.
[[Category:Database_tables_forms]]
</noinclude>
<!-- 
Класс "noexcerpt" используется для игнорирования контента расширением TextExtracts. 
Подробнее тут:https://www.mediawiki.org/wiki/Extension:TextExtracts#Configuration_settings
-->
<div class="noexcerpt">
<!-- 
Определяем подобъект -- набор сгруппированных свойств. 
Для удобства создаем отдельные подобъекты для столбца и Foreign Key ссылки.
Подробнее тут: https://www.semantic-mediawiki.org/wiki/Subobject

Для каждого свойства ColumnName, ColumnDescription, ... необходимо будет создать страницу
Property:%name%
С текстом
[[Has type::Text]]
-->
{{#subobject:column#{{{Name}}}
 |ColumnName={{{Name}}} 
<!-- 
В определении шаблона часто фигурирует функция парсера #if. 
Подробнее про нее можно почитать тут: https://www.mediawiki.org/wiki/Help:Extension:ParserFunctions##if
Кратко идея работы выглядит следующим образом:
{{#if: {{{ParameterName|}}} | Текст, который будет выведен, если параметр задан | Текст, который будет выведен, если параметр НЕ задан }}
-->
 |ColumnDescription={{#if:{{{Description|}}}|{{{Description}}}}}
 |ColumnLinksToTable={{#if:{{{LinksTo|}}}|{{{LinksTo}}}}}
 |ColumnLinksToColumn={{#if:{{{LinksToColumn|}}}|{{{LinksToColumn}}}}}
 |ColumnLinksToText={{#if:{{{LinksTo|}}}|[[{{{LinksTo|}}}|{{#explode:{{{LinksTo}}}|/|-1}}{{#if:{{{LinksToColumn|}}}|&#35;{{{LinksToColumn}}}}}]] }} <!-- Эта магическая конструкция сооружает ссылку вида [[Table:ProductName/TableName|TableName#columnName]] -->
 |ColumnKeyType={{#if:{{{KeyType|}}}|{{{KeyType}}}}}
 |ColumnTypeLink={{#if:{{{EnumName|}}}|[[Code:{{BASEPAGENAME}}/DB types/{{{EnumName}}}|{{{EnumName}}}]]}}{{#if:{{{CanBeNull|}}}|&nbsp;({{{CanBeNull}}})}}
 |ColumnTypeFullPageName={{#if:{{{EnumName|}}}|Code:{{BASEPAGENAME}}/DB types/{{{EnumName}}}}}
 |ColumnAddedInVersion = {{#if:{{{Version|}}}|{{{Version}}}}}
}}
</div>

Шаблон определяет скрытые из UI свойства. Т.е. если мы создадим страницу Test с таким описанием и сохраним:

{{TableColumn
|Name=myColumnName
|Description=My column description
}}

То сохраненная страница окажется совершенно пустой. Никакой контент не будет выведен, но в базу данных будет записан объект. С такими объектами довольно удобно работать при помощи метода #ask, но до этого мы еще доберемся.

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

Создаем страницу Template:Table

<noinclude>
Шаблон таблицы
[[Category:Database tables forms]]
</noinclude><includeonly>
<!-- 
 Определяем, какая форма будет использоваться для редактирования.
 Что еще за формы - поговорим несколько позднее.
 
 Также мы хотим, чтобы этот контент не индексировался поиском, 
 для чего помещаем описание формы по умолчанию в navigation-not-searchable div
 Подробнее: https://www.mediawiki.org/wiki/Help:CirrusSearch#Exclude_content_from_the_search_index
-->
<div class="searchaux navigation-not-searchable">{{#default_form:{{{DefaultForm}}}}}
  {{#if:{{{Version|}}}|:''Product/version: '''{{{Version}}}'''''
}}</div>
<!-- 
 Немного поможем поиску по вики, чтобы dbo.Backups выдавал в топе 
 выдачи страницу Table:Product/Backups.
 Класс keywords можно задать на MediaWiki:Common.css странице примерно в таком стиле:
 .keywords {
    display: none;
 }
-->
<div class="keywords">dbo.{{SUBPAGENAME}}</div>
{{#if:{{{Description|}}}|{{{Description}}}}}

<!--
 Описание столбцов мы будем формировать не на основе шаблонов, 
 а на основе семантических подобъектов, про которые мы говорили чуть раньше.
 Это работает быстрее и выглядит лучше.
-->
== Columns  ==
<!-- 
 Подробное описание ask метода есть на сайте Semantic MediaWiki
 https://www.semantic-mediawiki.org/wiki/Help:Inline_queries#Parser_function_.23ask

 Здесь мы запрашиваем все объекты, хранящиеся на этой страние, 
 у которых непустое поле ColumnName и выводим первые 100
 описаний столбцов.
-->
{{#ask:
 [[ColumnName::+]]
 [[-Has subobject::{{FULLPAGENAME}}]] 
 |format=broadtable
 |mainlabel=-
 |?ColumnName=Name
 |?ColumnLinksToText=Links to
 |?ColumnKeyType=Key type
 |?ColumnTypeLink=Data type
 |?ColumnDescription=Description
 |class=datatable cell-border
 |limit=100
}}

== Relations between tables ==

=== Links from the table ===
<!-- 
 Здесь я вывожу информацию о ссылках между таблицами в виде простого списка.
 Это отличается от того, что вы видели на скриншотах ранее, 
 но я решил не усложнять и без того не самый понятный синтаксис MediaWiki в этом примере.
-->
{{#ask:
  [[ColumnLinksToText::+]]
  [[-Has subobject::{{FULLPAGENAME}}]]
  |format=list
  |?ColumnlinksToText
}}

=== Links to the table ===
{{#ask:
  [[ColumnLinksToTable::{{FULLPAGENAME}}]]
  [[-Has subobject::+]]
  |format=list
}}

<!-- убираем [edit] ссылки рядом с заголовками -->
  __NOEDITSECTION__ 
 
 [[Category:{{{ProductDbCategory}}}]]
<!--
 В поле {{{Categories}}} мы будем задавать через запятую категории.
 Arraymap поможет нам переформатировать этот список категорий в 
 корректный mediawiki формат указания категорий, т.е. превратить
 "Катгория1, Категория2" в 
 [[Category:Категория1]] [[Category:Категория2]]
-->
 {{#arraymap:{{{Categories|}}}|,|x|[[Category:x]]|&#32;}}
<!-- 
 И, наконец, зададим семантические свойства наших таблиц.
 Это позволит в дальнейшем запрашивать через #ask описания таблиц
 аналогично тому, как мы запрашивали описания столбцов выше.

 Все свойства нужно будет определить. Т.е. нужно будет создать 
 страницы Property:TableName, Property:TableNameLink, ...
 В описании страниц нужно будет добавить "[[Has type::Text]]"
 Чуть подробнее: https://www.semantic-mediawiki.org/wiki/Help:Special_property_Has_type
-->
{{#set:
  TableName={{SUBPAGENAME}}
 |TableNameLink=[[{{FULLPAGENAME}}|{{SUBPAGENAME}}]]
 |TableProduct={{BASEPAGENAME}}
 |TableDescription={{#if:{{{Description|}}}|{{{Description}}} }}
}}</includeonly>

Это было сложно и непонятно, но все позади. Шаблоны готовы!

Итак, мы отделили данные от их представления и унифицировали внешний вид наших страниц.

Лучше открыть в отдельной вкладке. Слева - исходный текст, справа - то, что увидит юзер
Лучше открыть в отдельной вкладке. Слева - исходный текст, справа - то, что увидит юзер

Создаем форму редактирования

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

Разумеется, и расширение MediaWiki Page Forms сделает за нас большую часть волшебства.

Создаем форму на вики

Как в Linux все представлено файлом, так в MediaWiki все представлено страницей. Как и шаблон, форма — это страница в специальном пространстве имен "Form".

Создаем страницу "Form:My Product Table"

<!-- 
     Информация в noinclude не будет видна 
     на форме в режиме редактирования 
-->
<noinclude>[[Category:Database tables forms]]</noinclude>
<!-- 
     Информация в includeonly не будет видна, 
     если открыть страницу "Form:My Product Table"
-->

<includeonly>
{{{info|add title=Add datdabase table|edit title=Edit database table|page name=Table:[name]}}}

<!-- 
	Страница состоит из одного шаблона таблицы (описание самой таблицы) 
    и нескольких шаблонов с описаниями столбцов таблицы.
-->

<!-- 
	Сначала мы определяем таблицу 
-->
{{{for template|Table}}}
{| class="formtable" style="width:100%;"
! style="width:1%;white-space:nowrap;word-break:normal;" | Product version:
| {{{field|Version|default=3.0|input type=tokens|values=1.0, 2.0, 3.0}}}
|-
! style="width:1%;white-space:nowrap;word-break:normal;" | Category tree:
<!-- 
	Для поля "Categories" шаблона таблицы будет использоваться дерево категорий 
	с чекбоксами для выбора связанных с таблицей категорий.
	Для корректной работы нужно создать категорию "Category:My Product (DB)" 
	и добавить в нее интересующие вас дочерние категории (например, Category:Settings (DB)).
	Суффикс (DB) я использовал для разделения категорий обычных страниц и категоирй таблиц.
-->
| {{{field|Categories|input type=tree|list|mandatory|top category=My Product (DB)|height=auto|depth=1}}}
|-
! style="width:1%;white-space:nowrap;word-break:normal;" | Description:
<!-- 
	Для поля "Description" будет использоваться визуальный редактор.
	Для корректной работы нужно установить два расширения:
	https://www.mediawiki.org/wiki/Extension:VisualEditor
	https://www.mediawiki.org/wiki/Extension:VEForAll
-->
| {{{field|Description|input type=textarea|editor=visualeditor}}}
|-
| 
| 
<!-- 
	Здесь мы определяем два скрытых элемента формы. 
	Они нужны только для того, чтобы не перезаписать поля, задающие форму редактирования и дополнительную категорию.
	Дополнительная категория "My Product Tables" будет содержать плоский список всех таблиц продукта. 
-->
  {{{field|ProductDbCategory|hidden|default=My Product Tables}}}
  {{{field|DefaultForm|hidden|default=My Product Table}}}
|}
{{{end template}}}

<!-- 
	Далее мы определим как будет выглядеть часть формы для добавления описаний столбцов таблицы.
	Столбцов может быть несколько, так что добавляем "|multiple|minimum instances=0" в определение формы 
-->
{{{for template|TableColumn|label=Columns of this table|multiple|minimum instances=0|add button text=Add column}}}
{| class="formtable"
! style="width:1%;white-space:nowrap;word-break:normal;" |'''Column:'''
| {{{field|Name|mandatory}}}
|-
! style="width:1%;white-space:nowrap;word-break:normal;" |'''Key type:'''
|{{{field|KeyType|input type=checkboxes|values=PK, FK}}}
|-
! style="width:1%;white-space:nowrap;word-break:normal;" |'''Links to:''' 
<!-- 
	После добавления автоматизации это поле можно будет и вовсе спрятать.
	Здесь я его оставил для того, чтобы показать, как можно ограничить область допустимых значений.
	В форме этот кусок будет представлен комбобоксом, заполненным семантическими полями  "TableName" всех страниц из категории "My Product DB Tables"
	Т.е. вместо значений "Table:Product/TableName1" там будут значения "TableName2, TableName2, ..."
-->
|{{{field|LinksTo|input type=combobox|values from category=My Product Tables|mapping property=TableName}}}
|-
! '''Links to column:''' 
| {{{field|LinksToColumn}}} 
|-
! style="width:1%;white-space:nowrap;word-break:normal;" |'''Version:''' 
|{{{field|Version|input type=tokens|values=1.0, 2.0, 3.0}}}
|-
! style="width:1%;white-space:nowrap;word-break:normal;" |'''Data type:''' 
| {{{field|EnumName|input type=combobox|values from property=MyProductColumnType}}}
|-
! style="width:1%;white-space:nowrap;word-break:normal;" |'''Description:''' 
|{{{field|Description|input type=textarea|editor=visualeditor}}}
|}
{{{end template}}}

{{{standard input|minor edit}}} {{{standard input|watch}}}

{{{standard input|save}}} {{{standard input|preview}}} {{{standard input|changes}}} {{{standard input|cancel}}}
</includeonly>

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

Please specify table name without "dbo." prefix and without any braces. For example: '''Settings'''.
{{#forminput:form=My Product Table|size=40|placeholder=Settings|super_page=Table:My Product|button text=Create / Edit|namespace=Table}}

Итак, можно тестировать

Получится что-то такое — на странице создания статьи о таблице БД мы задаем имя таблицы и жмем кнопку:

Первый шаг добавления таблицы на вики
Первый шаг добавления таблицы на вики

После чего появляется сама форма:

То, как в итоге будет выглядет форма создания/редактирования таблицы
То, как в итоге будет выглядет форма создания/редактирования таблицы

Встраиваем документацию БД в другие страницы

А что еще эта ваша семантика умеет?
Можно ли в странице о фиче вывести список таблиц, относящихся к этой фиче?

У Semantic MediaWiki есть т.н. "ask" запросы со своим языком запросов и пачкой встроенных представлений данных.

К примеру, так можно вывести список таблиц из категории "HPE Lefthand" вместе с их описаниями:

=== HPE Lefthand ===
Additional tables in the database:
{{#ask:
[[Category:HPE Lefthand (VBR-DB)]]
[[TableName::+]]
|?TableNameLink=Name
|?TableDescription=Description
|mainlabel=-
|format=broadtable
|link=all
|headers=plain
|class=datatable cell-border dbtable
|sort=TableName
|order=asc
|limit=50
}}

После сохранения будет выведена таблица с описаниями:

Оттопыриваем API

Я прочитал заголовок, и у меня появился вопрос: зачем документации БД нужен API?
Ты там таблетки не забыл принять?

Во-первых, API можно использовать для автоматизации процесса документирования.

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

В-третьих... Я просто люблю API. ¯\_(ツ)_/¯

На самом деле API для чтения нам достается даром, поскольку ранее мы воспользовались Semantic MediaWiki.

API запросы очень похожи на #ask запросы, которые мы обсуждали выше

Пример запроса имени и описания таблицы
Get https://brainstorage.amust.local/api.php?action=ask&query=%5b%5b:Table:VBR/BJobs%5d%5d%7C?TableName=Name%7C?TableDescription=Description
Response body:
{
    "query": {
        "printrequests": [
            {
                "label": "",
                "key": "",
                "redi": "",
                "typeid": "_wpg",
                "mode": 2
            },
            {
                "label": "Name",
                "key": "TableName",
                "redi": "",
                "typeid": "_txt",
                "mode": 1,
                "format": ""
            },
            {
                "label": "Description",
                "key": "TableDescription",
                "redi": "",
                "typeid": "_txt",
                "mode": 1,
                "format": ""
            }
        ],
        "results": {
            "Table:VBR/BJobs": {
                "printouts": {
                    "Name": [
                        "BJobs"
                    ],
                    "Description": [
                        "Table for jobs of all types"
                    ]
                },
                "fulltext": "Table:VBR/BJobs",
                "fullurl": "https://brainstorage.amust.local/index.php/Table:VBR/BJobs",
                "namespace": 3010,
                "exists": "1",
                "displaytitle": ""
            }
        },
        "serializer": "SMW\\Serializers\\QueryResultSerializer",
        "version": 2,
        "meta": {
            "hash": "a03b120c8ef46c5fa8b8b940978fd422",
            "count": 1,
            "offset": 0,
            "source": "",
            "time": "0.002156"
        }
    }
}

Что же касается API для редактирования... С этим немножко сложнее, но давайте и об этом поговорим в рамках задачи автоматизации обновления информации о таблицах.

Автоматизируем

Итак, у нас получилось что-то рабочее. Но добавление описания каждой таблицы вручную — это боль.
Давай автоматизируем все, что автоматизируется?

Попробуем автоматизировать добавление новых таблиц и новых столбцов у старых таблиц. У меня есть: пачка sql скриптов для создания базы продукта в TFS. На их основании мы можем создать описания всех таблиц, имеющихся в разрабатываемой версии продукта.

Как именно можно превратить скрипты создания БД в пачку статей на вики? Как будем вытаскивать описания таблиц?

Первой мыслью было парсить SQL скрипты самостоятельно (я не очень умный), от чего в итоге пришлось отказаться из-за высокой сложности и несовместимости найденных мной питоновских токенайзеров с Microsoft SQL Server.

Вторая, более здравая мысль — автоматически накатывать на БД скрипты для создания полной БД на отдельном инстансе SQL Server, откуда после тащить информацию через odbc (pyodbc).

В качестве языка с самого начала был выбран Python по той простой причине, что именно на питоне пишется большинство ботов для MediaWiki. Есть даже пара замечательных библиотек: Pywikibot для взаимодействия с вики и mwparserfromhell для удобства работы с шаблонами MediaWiki. Вместе эта сладкая парочка дает что-то похожее на API для редактирования страниц.

Примерный принцип работы скрипта

Скрипт обновления информации на вики выглядит примерно так (приведен не полностью):

# Логинимся на MediaWiki при помощи Pywikibot
# Параметры подключения хранятся в виде отдельных файлов
# Больше информации тут: https://www.mediawiki.org/wiki/Manual:Pywikibot/Use_on_third-party_wikis
site = pywikibot.Site()
site.login(False, autocreate=True)

# Создаем описание БД на основании исходного кода
vbr_db = Database(tfs_base_path="$/MyTfsPath", product="VBR")

# Получаем по API список уже задокументированных таблиц
tables_list_request = pywikibot.data.api.Request(parameters={'action': 'ask', 'query': '[[TableProduct::VBR]]|?TableName|?TableProduct|limit=500', 'api_version': 3})
tables_list = tables_list_request.submit()

# Обновляем документацию для тех таблиц, которые уже существуют на вики
for table in tables_list['query']['results']:
   
    # Получаем полное имя (Table:ProductName/TableName) 
    # и короткое имя (TableName)
    table_full_name = next(iter(table))
    table_short_name = table[next(iter(table))]['printouts']['TableName'][0].replace(' ','_')

	  # Вытаскиваем вики-код описания таблицы
    page = pywikibot.Page(site, table_full_name)
    text = page.text
    wikicode = mwparserfromhell.parse(text)

    # Объединяем информацию о таблице, полученную из исходного кода,
    # с информацией, полученной из вики.
	  table = vbr_db.get_table_by_name(short_table_name = table_short_name)
	  table.update_wikicode(source_wikicode = wikicode)
    
    # Сохраняем страницу с комментарием правки
    page.text = str(table.get_wikicode())
    page.save(u'Automatically updated from source code')

# Генерируем описания таблиц, для которых еще нет страниц на вики
new_tables = vbr_db.get_not_documented_tables()
for table in new_tables:
    page = pywikibot.Page(site, table.get_full_name())
    page.text = str(table.get_wikicode())
    page.save(u'Automatically generated from source code')

Для получения таблиц, как я и писал выше, сперва создаем реальную БД на основании исходного кода (накатываем на базу скрипт создания БД), после чего пользуемся магией odbc:

# Подключаемся к БД
conn = pyodbc.connect('Driver={SQL Server};'
	'server=' + self.sql_server + ';' 
	'database=' + self.main_db + ';' 
	'trusted_connection=true;'
	'UID=' + self.sql_user + ';'
	'PWD=' + self.sql_password,
	autocommit=True)
cursor = conn.cursor()

# Получаем список всех таблиц
for t in cursor.tables(tableType = 'TABLE', schema = 'dbo'):
    new_table = Table(table_name = t.table_name)
    self.add_table(table = new_table)

# Обновляем описания всех таблиц
for table in self.get_all_tables():
  
  # Получаем список столбцов таблицы
	for col in cursor.columns(table = table.get_short_name()):
		table.add_column(
			name = col.column_name,
			type = col.type_name,
      # то же самое, что и type_name, но в виде enum-a
			type_enum = col.data_type, 
			size = col.column_size )
   
  # Ищем все Primary Key
  for pk in cursor.primaryKeys(table = table.get_short_name()):
    table.update_column(column_name = pk.column_name, is_pk = True)
  
  # Ищем все Foreign Key
  for fk in cursor.foreignKeys(table = table.get_short_name()):
    source_table = self.get_table_by_name(short_table_name = fk.fktable_name)
    source_table.update_column(
      column_name = fk.fkcolumn_name, 
      is_fk = True, 
      links_to_table = fk.pktable_name, 
      links_to_column = fk.pkcolumn_name)
    
conn.close()

Другая интересная часть автоматизации — объединение описаний таблиц, полученных из вики и из pyodbc. Парсинг mediaiwki синтаксиса — самая сложная часть этого процесса, но тут нам на помощь придет mwparserfromhell библиотека:

# Читаем контент страницы с описанием таблицы
table_ull_name = "Table:ProductName/TableName"
page = pywikibot.Page(site, table_full_name)
text = page.text

# mwparserfromhell умеет парсить шаблоны на странице 
# и предоставляет базовый набор функций по редактированию шаблонов.

wikicode = mwparserfromhell.parse(text)
templates = wikicode.filter_templates()

# Перебираем шаблоны один за одним
for template in templates:
	
  # Попался шаблон описания таблицы
	if (template.find('Table') != -1):
		for index, template_param in enumerate(template.params):
			# Здесь нам доступны все поля шаблона Table: 
      # Version, Categories, Description, etc
	
	if (template.find('TableColumn') != -1):
		for index, template_param in enumerate(template.params):
			# Здесь нам доступны все поля шаблона TableColumn: 
      # Name, KeyType, Description, etc

Voila!

Добавляем выполнение питон скрипта в дженкинс, и каждое утро на странице RecentChanges любуемся апдейтами документации:

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

Драматизируем

Как просто и удобно звучит! Наверно, это решение очень легко и быстро развернуть, а документация начнет сама себя писать, сиди только да читать успевай?

Well...

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

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

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

Во-вторых, у Semantic MediaWiki & Page Forms не очень понятные перспективы. Это, в целом, популярные расширения MediaWiki, но они не используются в Wikimedia Foundation проектах, а значит, не исключено, что они через несколько лет перестанут развиваться совсем. Темпы их развития и сейчас не поражают воображение. Конечно, удобный API для экспорта данных и наличие конвертеров mediawiki разметки в более популярные форматы делают этот риск менее существенным (в случае чего всегда можно переехать), но переезд между платформами — это неприятно и не очень быстро. Какими бы ни были эти платформы.

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

Фантазируем

Ишь, чего напридумывал, охальник!
А чего тебе не хватает для полного счастья?

На самом деле, основные потребности этот механизм документирования уже покрывает. Хотя есть еще пара нереализованных хотелок:

  • Хочется уметь автоматически строить схему БД по категориям на основе этих данных. Может, у вас есть мысли, как это реализовать проще всего? Раскидать квадратики со стрелочками проще простого, но хочется, чтобы после генерации связи между таблицами были не очень запутанными. Ну и писать свой код не очень хочется для этого.

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

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

Кажется, на этом все

Я читаю эту статью уже десять тысяч лет. Закругляемся?

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

P.s. Спустя несколько недель после публикации я собрал простейшие диаграммы связей таблиц при помощи Mermaid и семантики. Выглядит для больших данных страшненько, но, вообще, читаемо. Подробнее о том, как оно работает, я напишу как-нибудь в следующий раз. Если вы знакомы с синтаксисом Semantic MediaWiki и Mermaid, задача не будет такой уж сложной.

Диаграмма в уменьшенном масштабе
Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
А чем вы пользуетесь для документирования БД?
18.18% Ничем (и мне ок)4
50% Ничем (и мне не ок)11
9.09% Чем-то из готовых решений (расскажу в комментариях)2
9.09% Самодельным решением (тем более расскажу в комментариях)2
13.64% Документирую прямо в коде/SQL скриптах3
Проголосовали 22 пользователя. Воздержались 11 пользователей.
Теги:
Хабы:
+12
Комментарии1

Публикации

Информация

Сайт
veeam.com
Дата регистрации
Дата основания
Численность
1 001–5 000 человек
Местоположение
Швейцария

Истории