diff --git a/README.md b/README.md index 14ce834..0e0b4af 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ 1. [Пример создания пользовательской метамодели C4 Model](src/C4Model) 2. [Простой пример валидации сущностей в DocHub с выводом информации в меню](src/validator_example) 3. [Пример управления бизнес-сущностями](src/business_entity_management) +4. [Пример сущности для бизнес-процессов](src/sequences_entity_example) ## Разворачивание diff --git a/dochub.yaml b/dochub.yaml index 28fefa8..9cc2801 100644 --- a/dochub.yaml +++ b/dochub.yaml @@ -1,3 +1,6 @@ imports: # Пример создания метамодели для описания в нотации C4 Model - src/C4Model/dochub.yaml +# - src/validator_example/dochub.yaml +# - src/business_entity_management/dochub.yaml +# - src/sequences_entity_example/dochub.yaml \ No newline at end of file diff --git a/src/sequences_entity_example/README.md b/src/sequences_entity_example/README.md new file mode 100644 index 0000000..3f93cef --- /dev/null +++ b/src/sequences_entity_example/README.md @@ -0,0 +1,92 @@ +# Пример сущности для бизнес-процессов + +**Цель примера:** Показать возможности связки Dochub+plantuml для генерации диаграмм последовательности +(например, описание бизнес-процессов) в контексте имеющегося описания архитектуры предприятия. + +Пример является доработанной версией сущности interactions из [официальной документации](https://dochub.info/docs/dochub.entities), опубликованной на одном из воркшопов по Dochub. +Добавлена автоматическая линковка компонентов в качестве участников, а также поддержка ссылок в шагах. Это могут быть ссылки на другие диаграммы, swagger-контракты и тд. + +## Суть примера + +Показать, как с помощью сущностей можно "затащить" в dochub бизнес-документацию и связать ее с архитектурой компании. Как простой пример последовательности была выбрана сказка "Колобок". + +В результате аналитики и технические писатели получают возможность описать с помощью текста (а значит - и положить в git) бизнес-процессы: + +```yaml +sequences: + story.pigs: + title: Сказка "колобок" + location: Документы/Колобок/Диаграмма + icon: swap_horiz + groups: + - title: Рождение колобка + triggers: + - Жили-были дед и баба + steps: + - from: grandpa + to: grandma + value: Испеки, старуха, колобок! + - from: grandma + to: grandma + value: Поскрести по сусекам + - from: grandma + to: kolobok + value: Испекла баба колобок + contract: kolobok.receipt + results: + - Колобок полежал—полежал, да вдруг и покатился +... +``` + +Dochub сам превратит найденные упоминания компонентов из полей from/to в ссылки на их профили и подтянет названия, а поле contract позволит обеспечить перелинковку с другими документами, в том числе внешними. + +## Файловая структура примера +* components - данные архитектуры для рендеринга + * heroes.yaml - компоненты-участники сказки + * root.yaml - корневой манифест данных архитектуры +* docs - документы в формате markdown для демонстрации возможностей линковки через contract +* entities - метамодель; + * templates - шаблоны, используемые для рендеринга диаграмм + * tree.puml - Plantuml-шаблон для вывода всех диаграмм в виде дерева + * blank.puml - Plantuml-шаблон для просмотра конкретной диаграммы + * manifest.yaml - манифест пользовательских сущностей (там определена сущность sequences) + * sequences.yaml - пример сущности + * root.yaml - корневой манифест метамодели +* dochub.yaml - корневой манифест примера + +## Валидация + +Dochub имеет встроенные механизмы контроля корректности заполнения описания. Чтобы его активировать, для создаваемых сущностей мы описываем правила валидации: + +"Из коробки" это позволяет проверять соответствие сущностей при работе в IDE через плагин Dochub. Чтобы валидация выполнялась также в приложении Dochub, а ошибки выводились в блоке "Проблемы", необходимо добавить вызов валидатора: + +```yaml +rules: + validators: + sequences: + title: Валидатор последовательностей + source: > + ( + $validator := $jsonschema($.entities.sequences.schema.patternProperties.*); /* Передаем схему в части отдельного объекта, а не всех sequences */ + ([([ + $.sequences.$spread().( /* Сканируем все последовательности */ + $ID := $keys()[0]; + { + "id": $ID, /* Запоминаем идентификатор компонента */ + "isvalid": $validator($.*) /* Валидируем компонент по схеме */ + } + ) + ][isvalid != true]).isvalid.{ /* Генерируем отклонения по выявленным нарушениям */ + "uid": $.params.*[0] & "-sequence-" & %.id, /* Уникальный идентификатор выявленной ошибки */ + "location": "/entities/sequences/blank?id=" & %.id, /* Ссылка на расположение объекта ошибки */ + "correction": "Исправьте ошибку", + "description": $.message + }]) + + ) +``` + +## Задания для практики + +* Реализуйте документ со списком диаграмм и задействованных в них компонентов +* Добавьте поддержку [активации/деактивации](https://plantuml.com/ru/sequence-diagram#5cc0040514e70f7b) участников через дополнительный атрибут сущности \ No newline at end of file diff --git a/src/sequences_entity_example/components/heroes.yaml b/src/sequences_entity_example/components/heroes.yaml new file mode 100644 index 0000000..6188cd1 --- /dev/null +++ b/src/sequences_entity_example/components/heroes.yaml @@ -0,0 +1,22 @@ +components: + grandma: + entity: actor + title: Баба + grandpa: + entity: actor + title: Дед + kolobok: + entity: entity # Для демонстрации автоопределения entity + title: Колобок + animals.hair: + entity: actor + title: Заяц + animals.wolf: + entity: actor + title: Волк + animals.bear: + entity: actor + title: Медведь + animals.fox: + entity: actor + title: Лиса diff --git a/src/sequences_entity_example/components/root.yaml b/src/sequences_entity_example/components/root.yaml new file mode 100644 index 0000000..f394039 --- /dev/null +++ b/src/sequences_entity_example/components/root.yaml @@ -0,0 +1,2 @@ +imports: + - heroes.yaml \ No newline at end of file diff --git a/src/sequences_entity_example/dochub.yaml b/src/sequences_entity_example/dochub.yaml new file mode 100644 index 0000000..fc89d02 --- /dev/null +++ b/src/sequences_entity_example/dochub.yaml @@ -0,0 +1,4 @@ +imports: + - components/root.yaml + - entities/root.yaml + - docs/root.yaml \ No newline at end of file diff --git a/src/sequences_entity_example/docs/receipt.md b/src/sequences_entity_example/docs/receipt.md new file mode 100644 index 0000000..e2a7a99 --- /dev/null +++ b/src/sequences_entity_example/docs/receipt.md @@ -0,0 +1,3 @@ +# Рецепт колобка + +Взяла старуха крылышко, по коробу поскребла, по сусеку помела, и набралось муки пригоршни с две. Замесила на сметане, изжарила в масле и положила на окошечко постудить. \ No newline at end of file diff --git a/src/sequences_entity_example/docs/root.yaml b/src/sequences_entity_example/docs/root.yaml new file mode 100644 index 0000000..ef7942d --- /dev/null +++ b/src/sequences_entity_example/docs/root.yaml @@ -0,0 +1,21 @@ +docs: + kolobok.receipt: + type: markdown + location: Колобок/Рецепт + source: receipt.md + kolobok.song4hair: + type: markdown + location: Колобок/Песня для зайца + source: song4hair.md + kolobok.song4wolf: + type: markdown + location: Колобок/Песня для волка + source: song4wolf.md + kolobok.song4bear: + type: markdown + location: Колобок/Песня для медведя + source: song4bear.md + kolobok.song4fox: + type: markdown + location: Колобок/Песня для лисы + source: song4fox.md \ No newline at end of file diff --git a/src/sequences_entity_example/docs/song4bear.md b/src/sequences_entity_example/docs/song4bear.md new file mode 100644 index 0000000..8dc6020 --- /dev/null +++ b/src/sequences_entity_example/docs/song4bear.md @@ -0,0 +1,13 @@ +# Песня колобка для медведя + +Я Колобок, Колобок! +Я по коробу скребен, +По сусеку метен, +На сметане мешон, +Да в масле пряжон, +На окошке стужон; +Я от дедушки ушел, +Я от бабушки ушел, +Я от зайца ушел, +Я от волка ушел, +И от тебя, медведь, не хитро уйти! \ No newline at end of file diff --git a/src/sequences_entity_example/docs/song4fox.md b/src/sequences_entity_example/docs/song4fox.md new file mode 100644 index 0000000..8355ff2 --- /dev/null +++ b/src/sequences_entity_example/docs/song4fox.md @@ -0,0 +1,14 @@ +# Песня колобка для лисы + +Я Колобок, Колобок! +Я по коробу скребен, +По сусеку метен, +На сметане мешон, +Да в масле пряжон, +На окошке стужон; +Я от дедушки ушел, +Я от бабушки ушел, +Я от зайца ушел, +Я от волка ушел, +И от медведя ушел, +А от тебя, лиса, и подавно уйду! \ No newline at end of file diff --git a/src/sequences_entity_example/docs/song4hair.md b/src/sequences_entity_example/docs/song4hair.md new file mode 100644 index 0000000..c7af5f2 --- /dev/null +++ b/src/sequences_entity_example/docs/song4hair.md @@ -0,0 +1,11 @@ +# Песня колобка для зайца + +Я Колобок, Колобок! +Я по коробу скребен, +По сусеку метен, +На сметане мешон, +Да в масле пряжон, +На окошке стужон; +Я от дедушки ушел, +Я от бабушки ушел, +И от тебя, зайца, не хитро уйти! \ No newline at end of file diff --git a/src/sequences_entity_example/docs/song4wolf.md b/src/sequences_entity_example/docs/song4wolf.md new file mode 100644 index 0000000..29e7127 --- /dev/null +++ b/src/sequences_entity_example/docs/song4wolf.md @@ -0,0 +1,12 @@ +# Песня колобка для волка + +Я Колобок, Колобок! +Я по коробу скребен, +По сусеку метен, +На сметане мешон, +Да в масле пряжон, +На окошке стужон; +Я от дедушки ушел, +Я от бабушки ушел, +Я от зайца ушел, +И от тебя, волка, не хитро уйти! \ No newline at end of file diff --git a/src/sequences_entity_example/entities/manifest.yaml b/src/sequences_entity_example/entities/manifest.yaml new file mode 100644 index 0000000..24c898e --- /dev/null +++ b/src/sequences_entity_example/entities/manifest.yaml @@ -0,0 +1,233 @@ +entities: + sequences: + title: Диаграммы последовательности + description: Бизнес-сценарии в формате Sequence-диаграмм + menu: > + ( + $sequences := $.sequences; + /* Функция для вычисления пути к документу, с учетом возможной вложенности */ + $makeLocation := function($id) {( + $arrleft := function($arr, $count) { + $map($arr, function($v, $i) { + $i <= $count ? $v + }) + + }; + $domains := $split($id, "."); + "Документы/Sequence диаграммы/" & $join($map($domains, function($domain, $index) {( + $lookup($sequences, $join($arrleft($domains, $index), ".")).title + )}), "/"); + )}; + + $append([{ + "icon": "swap_horiz", + "link": "entities/sequences/tree", + "location": "Документы/Sequence диаграммы" + }], + [$.sequences.$spread().{ + "icon": *.icon, + "link": "entities/sequences/blank?id=" & $keys()[0], + /* Можно явно указать адрес схемы через location */ + "location": *.location ? *.location : $makeLocation($keys()[0]) + }][location] + ); + ) + presentations: + blank: + type: plantuml + template: templates/sequences/blank.puml + source: > # Для рендера шаблона собираем список задействованных в steps компонентов и данные самого описания, обогащенные ссылками на контракты + ( + $components := $.components; + $obj := $lookup($.sequences, $params.id); + $makeLink := function($entity) {( + /* В зависимости от контракта ссылка формируется разными способами */ + $entity.contract ? + $join([ + "[[", + $substring($entity.contract, 0, 4) = 'http' ? + "" : /* готовые ссылки не трогаем, выведем как есть */ + $contains($entity.contract, "/") ? + "/" : /* наличие слэша говорит, что это ссылка на документ докхаба, в том числе на другую диаграмму */ + "/docs/", /* по умолчанию считаем, что все прочее это id документа */ + $entity.contract, + " ", + $entity.value, + "]]" + ]) : + $entity.value; /* контракт не указан, ссылки не будет */ + )}; + + $components := $distinct($obj.groups.steps.$spread().($merge([{"id": $.from}, {"id": $.to}, $res]))).$spread().( + $component := $lookup($components, $.id); + $component ? + { /* компонент с таким id есть, линкуем */ + "id": $.id, + "entity": $component.entity in ['actor', 'database', 'queue', 'collections', 'entity'] ? $component.entity : "participant", + "value": "[[/architect/components/" & $.id & " " & $component.title & "]]" + } : + { /* покажем простой блок с текстом */ + "id": $.id, + "entity": "participant", + "value": $.id + } + ); + + $merge([{"components": $components, "title":$obj.title}, { + "groups": $obj.groups.( + { + "title": $.title, + "steps": $map($.steps, function($v, $i) { + { + "from": $components[id=$v.from].id, + "to": $components[id=$v.to].id, + /* Проверяем, нужна ли стрелка с возвратом управления. Если текст не указан, подставляем пробел */ + "return": $exists($v.return) ? ($v.return ? $v.return : " ") : false, + "value": $makeLink($v) + } + }), + "triggers": $.triggers, + "results": $.results + } + ) + }]) + + + ) + tree: # Идентификатор представления. Дерево объектов. + type: plantuml # Тип представления. Обязательно. + template: templates/sequences/tree.puml # Путь к шаблону. Обязательно. + source: > # Источник данных для рендера шаблона. Возвращает дерево объектов "sequences". Обязательно. + ( + $set("prev-id", undefined); + $arrleft := function($arr ,$count) { + $map($arr, function($v, $i) { + $i <= $count ? $v + }) + }; + $sequences := $.sequences; + [$sequences.$spread().($merge([{"id" : $keys()[0]}, $.*]))^(id).( + $prev_nodes := $split($get("prev-id"), "."); + $prev_level := $count($prev_nodes); + $curr_nodes := $split(id, "."); + $set("isdiff", false); + $result := $map($curr_nodes, function($v, $i) {( + $set("isdiff", $get("isdiff") or $prev_level = 0 or $prev_level <= $i or $v != $prev_nodes[$i]) ? ( + $id := $join($arrleft($curr_nodes, $i), "."); + $sequence := $lookup($sequences, $id); + { + "id": $id, + "level": $pad("", $i + 2, "*"), + "title": $sequence ? $sequence.title : $id, + "link": "/entities/sequences/blank?id=" & $id + } + ); + )}); + $set("prev-id", id); + $result + )]; + ) + schema: # JSON Schema контролирующая описание объекта сущности + type: object + patternProperties: + "[a-zA-Z0-9_\\.]*": + type: object + additionalProperties: false + properties: + icon: + title: Иконка + type: string + minLength: 2 + title: + title: Наименование + type: string + minLength: 5 + location: + title: Путь к документу + type: string + minLength: 2 + groups: + title: Блок действий + type: array + minLength: 1 + items: + type: object + properties: + title: + type: string + title: Заголовок блока + triggers: + title: Первоначальное состояние или активирующее действие + type: array + minItems: 1 + items: + type: string + minLength: 5 + steps: + title: Описание взаимодействия + type: array + minItems: 1 + items: + type: object + properties: + from: + title: От кого + type: string + minLength: 1 + to: + title: Кому + type: string + minLength: 1 + value: + title: Что + type: string + minLength: 1 + contract: + title: Ссылка на документ или внешний ресурс + type: string + minLength: 1 + return: + anyOf: + - type: 'null' + - type: string + title: Признак возврата управления + required: + - from + - to + - value + results: + title: Результаты взаимодействия + type: array + minItems: 1 + items: + type: string + minLength: 5 + required: + - steps + required: + - title + - groups + +rules: + validators: + sequences: + title: Валидатор последовательностей + source: > + ( + $validator := $jsonschema($.entities.sequences.schema.patternProperties.*); /* Передаем схему в части отдельного объекта, а не всех sequences */ + ([([ + $.sequences.$spread().( /* Сканируем все последовательности */ + $ID := $keys()[0]; + { + "id": $ID, /* Запоминаем идентификатор компонента */ + "isvalid": $validator($.*) /* Валидируем компонент по схеме */ + } + ) + ][isvalid != true]).isvalid.{ /* Генерируем отклонения по выявленным нарушениям */ + "uid": $.params.*[0] & "-sequence-" & %.id, /* Уникальный идентификатор выявленной ошибки */ + "location": "/entities/sequences/blank?id=" & %.id, /* Ссылка на расположение объекта ошибки */ + "correction": "Исправьте ошибку", + "description": $.message + }]) + + ) \ No newline at end of file diff --git a/src/sequences_entity_example/entities/root.yaml b/src/sequences_entity_example/entities/root.yaml new file mode 100644 index 0000000..56ffe76 --- /dev/null +++ b/src/sequences_entity_example/entities/root.yaml @@ -0,0 +1,3 @@ +imports: + - manifest.yaml # Файл с определениями видов сущностей + - sequences.yaml # Описание сущностей с типом sequences \ No newline at end of file diff --git a/src/sequences_entity_example/entities/sequences.yaml b/src/sequences_entity_example/entities/sequences.yaml new file mode 100644 index 0000000..687d5c8 --- /dev/null +++ b/src/sequences_entity_example/entities/sequences.yaml @@ -0,0 +1,67 @@ +sequences: + story.pigs: + title: Сказка "колобок" + location: Документы/Колобок/Диаграмма + icon: swap_horiz + groups: + - title: Рождение колобка + triggers: + - Жили-были дед и баба + steps: + - from: grandpa + to: grandma + value: Испеки, старуха, колобок! + - from: grandma + to: grandma + value: Поскрести по сусекам + - from: grandma + to: kolobok + value: Испекла баба колобок + contract: kolobok.receipt + results: + - Колобок полежал—полежал, да вдруг и покатился + - title: Путешествие колобка + steps: + - from: animals.hair + to: kolobok + value: Колобок-колобок, я тебя съем + - from: kolobok + to: animals.hair + value: Я тебе песенку спою + contract: kolobok.song4hair + return: + - from: animals.wolf + to: kolobok + value: Колобок-колобок, я тебя съем + - from: kolobok + to: animals.wolf + value: Я тебе песенку спою + contract: kolobok.song4wold + return: + - from: animals.bear + to: kolobok + value: Колобок-колобок, я тебя съем + - from: kolobok + to: animals.bear + value: Я тебе песенку спою + contract: kolobok.song4bear + return: + - from: animals.fox + to: kolobok + value: Колобок-колобок, я тебя съем + - from: kolobok + to: animals.fox + value: Я тебе песенку спою + contract: kolobok.song4fox + return: Сядь-ка на мою мордочку да пропой еще разок погромче + - from: kolobok + to: animals.fox + value: вскочил на мордочку, поет еще раз + contract: kolobok.song4fox + return: Сядь-ка на мой язычок да пропой в последний разок + - from: kolobok + to: animals.fox + value: прыгнул на язычок + return: R.I.P. + results: + - Тут должна быть мораль сказки - не убегайте от бабушки! \ No newline at end of file diff --git a/src/sequences_entity_example/entities/templates/sequences/blank.puml b/src/sequences_entity_example/entities/templates/sequences/blank.puml new file mode 100644 index 0000000..445a482 --- /dev/null +++ b/src/sequences_entity_example/entities/templates/sequences/blank.puml @@ -0,0 +1,28 @@ +@startuml + +{{#.}} +title {{{title}}} + +{{#components}} +{{entity}} "{{{value}}}" as {{id}} +{{/components}} + +{{#groups}} + group {{title}} + {{#triggers}} + note across: {{{.}}} + {{/triggers}} + {{#steps}} + {{from}} -> {{to}} : {{{value}}} + {{#return}} + return {{{return}}} + {{/return}} + {{/steps}} + {{#results}} + note across: {{{.}}} + {{/results}} + end +{{/groups}} +{{/.}} + +@enduml \ No newline at end of file diff --git a/src/sequences_entity_example/entities/templates/sequences/tree.puml b/src/sequences_entity_example/entities/templates/sequences/tree.puml new file mode 100644 index 0000000..38a85fd --- /dev/null +++ b/src/sequences_entity_example/entities/templates/sequences/tree.puml @@ -0,0 +1,6 @@ +@startwbs +* Sequence-диаграммы +{{#.}} +{{level}} [[{{&link}} {{{title}}}]] +{{/.}} +@endwbs \ No newline at end of file