Но самый проблемный момент — это поддерживать эту документацию в актуальном состоянии. И ладно бы текст, но схемы… Т.к. вся документация онлайн, т.е. в формате html, то к тексту прилагаются картинки gif/jpeg/png, на которых собственно изображены схемы. А схемы рисуются в различных программах типа Visio или онлайн-сервисах а-ля draw.io. Затем экспортируешь схему в графический формат и прилагаешь к html. Все просто.
В чем проблема?
Схемы обычно простые. Точнее, не сильно сложные. Да, количество объектов десяток-два, количество связей примерно столько же. Плюс подписи, какие-то обозначения. Простые схемы и на словах описать можно, а слишком сложные, кх-м… (с) «не поймут-с». Схем много, изменения в них нужно вносить периодически-эпизодически, т.е. постоянно, т.к. они идут вслед за разработкой наших продуктов.
Можно же встраивать html сервиса. Пробовал?
Да, конечно. Мне, например, нравятся графики gliffy.com. Но для изменений надо идти в сторонний сервис, там править. И сложнее делегировать сделать поправки коллеге.
Что делать?
Недавно на гитхабе мне попался в рекомендациях репозиторий github.com/RaoulMeyer/diagram-as-code. Диаграмма как код. Т.е. мы описываем на js нужную нам схему. Этот js мы пишем прямо в том же html, где и прочий текст документации.
К слову сказать, но я пишу документацию не совсем в html. Обычно документация — это набор файлов с markdown-текстом, который затем конвертируется в полноценный сайт документации каким-нибудь движком, например wintersmith. Или wiki-система.
Получается очень удобно: вот мы написали текст, затем открывается тег script и в нем описан js код схемы.
Что опять не так?
Этот репозиторий мне понравился, но это не единственный пример когда диаграмму рисуют с помощью кода или текстового представления. (В конце статьи будут ссылки проектов и статей, которые нагуглил по теме diagram as code.)
И ведь я не один правлю документацию. Иногда свою лепту вносят и коллеги — слово поправить, описание изменить, новые картинки вставить.
Поэтому хотелось бы диаграмму видеть в читаемом понятном текстовом формате, которому бы не пришлось долго обучаться. А местами даже просто copy-paste сделать для ускорения добавления новой схемы.
А еще один коллега заметил, что код это, конечно, хорошо, но если использовать структуру, все может быть очень строго и выразительно.
Поэтому я попробовал представить схему как набор нескольких небольших массивов, которые описывают узлы, связи, группы узлов, а также расположение узлов. Получилось на мой скромный взгляд достаточно удобно, хотя, конечно, на вкус и цвет…
Как это диаграмма в массиве?
- Каждый узел описывается идентификатором, который однозначно определяет узел.
- Также к узлу можно добавить иконку, добавить надпись.
- Между двумя узлами можно указать связь.
- Для связи на схеме можно задать цвет, надпись.
- Направление связи определяется как от источника к цели. А источник и цель указываются идентификаторами узла.
- Один и более узлов можно добавить в группу.
- Связь также можно указать и от группы, и к группе.
Пользуясь этими простыми правилами получается вот такая схема. Просто? Вполне.
А описывается она следующим js-кодом. Основное здесь — это объект elements. В котором указаны nodes — узлы, edges — связи.
const elements = {
nodes: [ // описываем узлы
{ id: 'client', type: 'smartphone', label: 'Mobile App'},
{ id: 'server', type: 'server', label: 'Main Server'},
{ id: 'db1', type: 'database', label: 'DB 1'},
{ id: 'db2', type: 'database', label: 'DB 2'},
],
edges: [ // указываем связи
{ source: 'client', target: 'server', label: 'request' },
{ source: 'server', target: 'db1', label: 'request' },
{ source: 'server', target: 'db2', label: 'request' },
],
};
Diagram('scheme1', elements);
Конечно, отрисовку схемы я придумал не сам, а воспользовался библиотекой cytoscape.js — очень мощный инструмент визуализации. Толику возможностей которой в своем решении использую лишь.
Понятно, это простой пример. Можно посложнее?
Да, пожалуйста. Для указания позиций — мы используем positions, для указания групп — указываем список групп в groups, а у самих элементов атрибут group.
А это код:
<div id="scheme5" style="height:500px;width:800px;"></div>
<script>
const elements5 = {
groups: [
{ id: 'g1', label: 'Группа сервисов 1'},
{ id: 'g2', label: 'Группа сервисов 2'},
],
nodes: [
{ id: 'man1', type: 'person', label: 'Человек'},
{ id: 'client', type: 'smartphone', label: 'Смартфон'},
{ id: 'agent-backend', type: 'server', group: 'g1', label: 'agent-backend'},
{ id: 'web', type: 'server', group: 'g1', label: 'Приложение admin'},
{ id: 'www', type: 'server', group: 'g1', label: 'страница загрузки'},
{ id: 'mongodb1', type: 'database', group: 'g1', label: 'Mongo DB 1'},
{ id: 'mongodb2', type: 'database', group: 'g1', label: 'Mongo DB 2'},
{ id: 'runner-integration1', type: 'worker', group: 'g1', label: 'отправка'},
{ id: 'runner-integration2', type: 'worker', group: 'g1', label: 'отправка'},
{ id: 'api', type: 'server', group: 'g1', label: 'API'},
{ id: 'server2', type: 'server', group:'g2', label: 'сервер'},
{ id: 'otherServer', type: 'server', group:'g2', label: 'сервер'},
{ id: 'firebase', type: 'cloud', label: 'Google Firebase'},
],
edges: [
{ source: 'client', target: 'agent-backend', label: 'json', color: 'red' },
{ source: 'agent-backend', target: 'mongodb1', color: 'red' },
{ source: 'agent-backend', target: 'mongodb2', color: 'red' },
{ source: 'mongodb1', target: 'runner-integration1', label: 'данные' },
{ source: 'mongodb2', target: 'runner-integration2', label: 'данные' },
{ source: 'mongodb1', target: 'web', label: 'данные для отображения' },
{ source: 'runner-integration1', target: 'server2', label: 'данные' },
{ source: 'runner-integration2', target: 'otherServer', label: 'данные' },
{ source: 'api', target: 'firebase', label: 'запросы', color: 'blue', },
{ source: 'firebase', target: 'client', label: 'push', color: 'blue'},
{ source: 'server2', target: 'api', label: 'уведомления', color: 'blue'},
{ source: 'man1', target: 'client', },
],
positions: [
{ id: 'client', row: 2, col: 1,},
{ id: 'agent-backend', row: 2, col: 3,},
{ id: 'web', row: 6, col: 3,},
{ id: 'www', row: 1, col: 3,},
{ id: 'mongodb1', row: 1, col: 4,},
{ id: 'mongodb2', row: 2, col: 5,},
{ id: 'runner-integration1', row: 3, col: 3,},
{ id: 'runner-integration2', row: 4, col: 3,},
{ id: 'api', row: 5, col: 3,},
{ id: 'server2', row: 6, col: 7,},
{ id: 'otherServer', row: 4, col: 7,},
{ id: 'firebase', row: 5, col: 1,},
{ id: 'logger', row: 2, col: 7,},
{ id: 'crm', row: 5, col: 8,},
],
};
Diagram('scheme5', elements5, {layout: 'grid'});
</script>
Такая схема с одной стороны — это почти пара экранов кода на ноуте, с другой структура а-ля json позволяет заполнять все данные по аналогии, быстро и можно copy-paste.
А почему positions вынесены отдельно от узлов?
Так удобнее. Сначала мы указываем nodes. Затем можем указать пару-тройку групп и указать их в узлах. Затем обозначаем связи. А уж затем, когда основные объекты и связи между ними есть, беремся за расположение этих объектов на схеме. Или наоборот.
А можно без positions?
Можно и без positions. Но это будет немного скомкано, в примерах можно посмотреть такой вариант. Это обусловлено тем, что для cytoscape есть алгоритм расположения узлов fcose, который также учитывает наличие групп. Указание positions делает схему более контролируемой, но на стадии первого наброска схемы можно и без positions.
Также positions можно указывать в стиле Морского боя. Т.е. один узел располагается в a1, а другой в d5. Особенно помогает, что cytoscape формирует объекты на canvas подвижными, т.е. мы можем их подвигать, посмотреть разные варианты расположения, а затем зафиксировать в коде понравившееся расположение элементов.
В целом, понятно. Можно попробовать?
Конечно, для быстрого создания схем сделал себе небольшой редактор, который сам обновляет схему и в браузере хранит последний вариант (в localStorage).
Попробовали? Можно теперь и к себе на страницу добавить.
Тогда еще раз:
1. Подключаем скрипт
<script src="https://unpkg.com/@antirek/network-diagram@0.1.4/dist/code-full.min.js"></script>
2. Добавляем в html код
<div id="scheme1" style="height:300px;width:800px;"></div>
<script>
const elements = {
nodes: [
{ id: 'client', type: 'smartphone', label: 'Mobile App'},
{ id: 'server', type: 'server', label: 'Main Server'},
{ id: 'db1', type: 'database', label: 'DB 1'},
{ id: 'db2', type: 'database', label: 'DB 2'},
],
edges: [
{ source: 'client', target: 'server', label: 'request' },
{ source: 'server', target: 'db1', label: 'request' },
{ source: 'server', target: 'db2', label: 'request' },
],
};
Diagram('scheme1', elements);
</script>
3. правим код до нужной нам схемы (думаю, это проще чем нарисовать сову :)
Еще подробнее на странице проекта на гитхабе.
Что в итоге?
Своих целей я достиг — сделать добавление схем inline в документацию, формат достаточно простой и понятный. Для суперсхем не подойдет, а для небольших схем, поясняющих структуру связей — очень даже ничего. Всегда можно быстро подправить и что-то с течением времени поменять. Да, и коллеги могут в доке сами что-то подправить, как минимум подписи к объектам без особого обучения ))
Что можно улучшить?
Тут вариантов, конечно, масса. Сделать добавление дополнительных иконок (все имеющиеся добавлены inline в скрипт). Подобрать более выразительный набор иконок. Сделать возможность указания стиля линии связей. Добавить фоновое изображение.
А что думаете вы?
У меня уже есть несколько идей на реализацию в issues, вы также добавьте свои в комментарии.
Мое решение определенно применимо в узком спектре задач, и возможно вы найдете более удобный инструмент для рисования диаграмм, просто закодировав их — как говорится 'show me your diagram as code'
- Хорошая подборка
- Шикарный сервис (9 типов графиков онлайн-редактор)
- Конечно, mermaid.js
- И если вам любы супер детальные и сложные схемы — то вас определенно восхитит этот проект: go.drawthe.net
maksim_ms
Спасибо за статью.
Есть похожий инструмент для на python diagrams.mingrammer.com
antirek Автор
О, да, я его видел, забыл добавить в свой список )) Думаю, он вдохновил Raoul Meyer написать github.com/RaoulMeyer/diagram-as-code. К сожалению, он на python и более сервер-сайд решение.