Merge branch 'docs/regulation-html' into 'main'
docs: HTML-вариант регламента (standalone, brand-styled) See merge request 2-6/2-6-4/terravault/ordinis!249
This commit is contained in:
@@ -0,0 +1,588 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="ru">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Регламент · Что заводить в Ordinis НСИ</title>
|
||||||
|
<style>
|
||||||
|
/* === Warm Terracotta palette — brand-consistent с PPTX === */
|
||||||
|
:root {
|
||||||
|
--ink: #1a1714;
|
||||||
|
--ink-2: #52483d;
|
||||||
|
--mute: #8c8276;
|
||||||
|
--accent: #b05a2e;
|
||||||
|
--accent-bg: #f4d9cd;
|
||||||
|
--navy: #3a2818;
|
||||||
|
--bg: #faf9f5;
|
||||||
|
--surface: #ffffff;
|
||||||
|
--surface-2: #f5f3ec;
|
||||||
|
--green: #4f7038;
|
||||||
|
--green-bg: #e4ead0;
|
||||||
|
--warn: #a8762a;
|
||||||
|
--warn-bg: #f6e8bc;
|
||||||
|
--pink: #a64636;
|
||||||
|
--pink-bg: #f4d9cd;
|
||||||
|
--line: #e6e1d4;
|
||||||
|
}
|
||||||
|
|
||||||
|
* { box-sizing: border-box; }
|
||||||
|
|
||||||
|
html { -webkit-text-size-adjust: 100%; }
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
background: var(--bg);
|
||||||
|
color: var(--ink);
|
||||||
|
font: 15px/1.55 -apple-system, BlinkMacSystemFont, "Inter", "Segoe UI", Roboto, sans-serif;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wrap {
|
||||||
|
max-width: 880px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 40px 32px 80px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Header / hero === */
|
||||||
|
.hero {
|
||||||
|
background: var(--navy);
|
||||||
|
color: #fff;
|
||||||
|
margin: -40px -32px 32px;
|
||||||
|
padding: 48px 32px 40px;
|
||||||
|
border-left: 6px solid var(--accent);
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.hero .eyebrow {
|
||||||
|
color: var(--accent-bg);
|
||||||
|
font: 600 11px/1 ui-monospace, SFMono-Regular, Menlo, monospace;
|
||||||
|
letter-spacing: 0.18em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin: 0 0 12px;
|
||||||
|
}
|
||||||
|
.hero h1 {
|
||||||
|
font: 700 34px/1.15 Georgia, "Times New Roman", serif;
|
||||||
|
margin: 0 0 12px;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.hero .lead {
|
||||||
|
color: #cadcfc;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 1.6;
|
||||||
|
max-width: 680px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.hero .tldr {
|
||||||
|
margin-top: 24px;
|
||||||
|
padding: 14px 18px;
|
||||||
|
background: rgba(176, 90, 46, 0.18);
|
||||||
|
border-left: 3px solid var(--accent);
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.hero .tldr strong { color: var(--accent-bg); }
|
||||||
|
|
||||||
|
/* === Section headings === */
|
||||||
|
h2 {
|
||||||
|
font: 700 24px/1.25 Georgia, "Times New Roman", serif;
|
||||||
|
color: var(--ink);
|
||||||
|
margin: 56px 0 16px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
border-bottom: 1px solid var(--line);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
h2 .dot {
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--accent);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
h3 {
|
||||||
|
font: 600 18px/1.3 Georgia, "Times New Roman", serif;
|
||||||
|
color: var(--ink);
|
||||||
|
margin: 32px 0 12px;
|
||||||
|
}
|
||||||
|
h4 {
|
||||||
|
font: 600 15px/1.3 -apple-system, sans-serif;
|
||||||
|
color: var(--ink-2);
|
||||||
|
margin: 20px 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
p { margin: 0 0 12px; }
|
||||||
|
p.muted { color: var(--ink-2); font-size: 14px; }
|
||||||
|
|
||||||
|
ul, ol { margin: 0 0 16px; padding-left: 24px; }
|
||||||
|
li { margin: 4px 0; }
|
||||||
|
|
||||||
|
/* === Criterion cards (positive — green accent) === */
|
||||||
|
.criterion {
|
||||||
|
background: var(--surface);
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-left: 4px solid var(--green);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 18px 20px;
|
||||||
|
margin: 16px 0;
|
||||||
|
}
|
||||||
|
.criterion h3 {
|
||||||
|
margin: 0 0 8px;
|
||||||
|
color: var(--ink);
|
||||||
|
}
|
||||||
|
.criterion h3::before {
|
||||||
|
content: "✓";
|
||||||
|
color: var(--green);
|
||||||
|
font-weight: 700;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
.criterion .hint {
|
||||||
|
margin-top: 10px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
background: var(--green-bg);
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--ink-2);
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
.criterion .hint::before { content: "💡 "; font-style: normal; }
|
||||||
|
|
||||||
|
/* === Anti-pattern cards (negative — pink accent) === */
|
||||||
|
.anti {
|
||||||
|
background: var(--surface);
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-left: 4px solid var(--pink);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 16px 20px;
|
||||||
|
margin: 12px 0;
|
||||||
|
}
|
||||||
|
.anti h3 {
|
||||||
|
margin: 0 0 6px;
|
||||||
|
color: var(--pink);
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
.anti h3::before {
|
||||||
|
content: "✗";
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
.anti .symptom {
|
||||||
|
margin-top: 8px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--ink-2);
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
.anti .symptom::before { content: "Признак: "; font-style: normal; font-weight: 600; }
|
||||||
|
|
||||||
|
/* === Tables === */
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin: 16px 0 20px;
|
||||||
|
background: var(--surface);
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 6px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
th, td {
|
||||||
|
text-align: left;
|
||||||
|
padding: 10px 14px;
|
||||||
|
border-bottom: 1px solid var(--line);
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
th {
|
||||||
|
background: var(--surface-2);
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--ink-2);
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 11px;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
}
|
||||||
|
tr:last-child td { border-bottom: none; }
|
||||||
|
td.yes { color: var(--green); font-weight: 600; }
|
||||||
|
td.no { color: var(--pink); font-weight: 600; }
|
||||||
|
|
||||||
|
/* === Checklist === */
|
||||||
|
.checklist {
|
||||||
|
background: var(--accent-bg);
|
||||||
|
border: 1px solid var(--accent);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 24px;
|
||||||
|
margin: 24px 0;
|
||||||
|
}
|
||||||
|
.checklist h3 {
|
||||||
|
margin: 0 0 16px;
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
.checklist h3::before {
|
||||||
|
content: "📋 ";
|
||||||
|
}
|
||||||
|
.checklist ol {
|
||||||
|
counter-reset: cl-item;
|
||||||
|
list-style: none;
|
||||||
|
padding-left: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.checklist li {
|
||||||
|
counter-increment: cl-item;
|
||||||
|
padding: 8px 0 8px 36px;
|
||||||
|
position: relative;
|
||||||
|
border-bottom: 1px dashed rgba(176, 90, 46, 0.3);
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.checklist li:last-child { border-bottom: none; }
|
||||||
|
.checklist li::before {
|
||||||
|
content: counter(cl-item);
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 8px;
|
||||||
|
width: 26px;
|
||||||
|
height: 26px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--accent);
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-family: ui-monospace, monospace;
|
||||||
|
}
|
||||||
|
.checklist .verdict {
|
||||||
|
margin-top: 16px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
background: rgba(58, 40, 24, 0.06);
|
||||||
|
border-left: 3px solid var(--navy);
|
||||||
|
font-size: 13px;
|
||||||
|
color: var(--ink-2);
|
||||||
|
}
|
||||||
|
.checklist .verdict strong { color: var(--ink); }
|
||||||
|
|
||||||
|
/* === Process steps === */
|
||||||
|
.process {
|
||||||
|
counter-reset: step;
|
||||||
|
margin: 16px 0 24px;
|
||||||
|
}
|
||||||
|
.process > div {
|
||||||
|
counter-increment: step;
|
||||||
|
background: var(--surface);
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 14px 18px 14px 56px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
position: relative;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.process > div::before {
|
||||||
|
content: counter(step);
|
||||||
|
position: absolute;
|
||||||
|
left: 12px;
|
||||||
|
top: 12px;
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--accent);
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 14px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-family: Georgia, serif;
|
||||||
|
}
|
||||||
|
.process strong { color: var(--ink); }
|
||||||
|
|
||||||
|
/* === Code / monospaced === */
|
||||||
|
code {
|
||||||
|
font: 13px/1.4 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||||
|
background: var(--surface-2);
|
||||||
|
color: var(--accent);
|
||||||
|
padding: 1px 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
background: var(--navy);
|
||||||
|
color: #fff;
|
||||||
|
padding: 14px 16px;
|
||||||
|
border-radius: 6px;
|
||||||
|
overflow-x: auto;
|
||||||
|
font: 12.5px/1.5 ui-monospace, SFMono-Regular, Menlo, monospace;
|
||||||
|
margin: 12px 0;
|
||||||
|
}
|
||||||
|
pre code {
|
||||||
|
background: none;
|
||||||
|
color: inherit;
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Strong inline === */
|
||||||
|
strong { color: var(--ink); font-weight: 600; }
|
||||||
|
em { color: var(--ink-2); font-style: italic; }
|
||||||
|
|
||||||
|
/* === Convention list (technical bullets) === */
|
||||||
|
.conventions li {
|
||||||
|
padding: 6px 0;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.conventions code { font-size: 12.5px; }
|
||||||
|
|
||||||
|
/* === Anti-pattern callouts (final section) === */
|
||||||
|
.callouts > div {
|
||||||
|
background: var(--surface);
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
margin: 8px 0;
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
.callouts > div::before {
|
||||||
|
content: "❌";
|
||||||
|
flex-shrink: 0;
|
||||||
|
font-size: 16px;
|
||||||
|
margin-top: 1px;
|
||||||
|
}
|
||||||
|
.callouts > div p { margin: 0; font-size: 14px; }
|
||||||
|
|
||||||
|
/* === Footer === */
|
||||||
|
.foot {
|
||||||
|
margin-top: 60px;
|
||||||
|
padding: 24px;
|
||||||
|
background: var(--surface-2);
|
||||||
|
border: 1px solid var(--line);
|
||||||
|
border-left: 4px solid var(--accent);
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.foot strong { display: block; margin-bottom: 8px; color: var(--ink); font-size: 15px; }
|
||||||
|
.foot ul { margin: 0; padding-left: 20px; color: var(--ink-2); }
|
||||||
|
.foot li { margin: 4px 0; }
|
||||||
|
|
||||||
|
.meta {
|
||||||
|
margin-top: 40px;
|
||||||
|
padding-top: 16px;
|
||||||
|
border-top: 1px solid var(--line);
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--mute);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Print === */
|
||||||
|
@media print {
|
||||||
|
body { background: white; }
|
||||||
|
.hero { background: white; color: var(--ink); border-left-color: var(--accent); }
|
||||||
|
.hero .eyebrow, .hero .lead, .hero .tldr { color: var(--ink-2); }
|
||||||
|
.hero h1 { color: var(--ink); }
|
||||||
|
.hero .tldr { background: var(--accent-bg); }
|
||||||
|
.hero .tldr strong { color: var(--accent); }
|
||||||
|
.criterion, .anti, table, .checklist, .process > div, .foot { page-break-inside: avoid; }
|
||||||
|
h2 { page-break-after: avoid; }
|
||||||
|
pre { white-space: pre-wrap; word-wrap: break-word; }
|
||||||
|
a { color: var(--accent); text-decoration: none; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* === Mobile === */
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.wrap { padding: 20px 16px 40px; }
|
||||||
|
.hero { margin: -20px -16px 24px; padding: 32px 16px 28px; }
|
||||||
|
.hero h1 { font-size: 26px; }
|
||||||
|
h2 { font-size: 20px; margin-top: 40px; }
|
||||||
|
table { font-size: 13px; }
|
||||||
|
th, td { padding: 8px 10px; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="wrap">
|
||||||
|
|
||||||
|
<header class="hero">
|
||||||
|
<p class="eyebrow">Регламент · Ordinis MDM · v1.0</p>
|
||||||
|
<h1>Что заводить в Ordinis НСИ</h1>
|
||||||
|
<p class="lead">
|
||||||
|
Decision framework для владельцев данных: какие справочники стоит выносить в централизованную НСИ Ordinis MDM, а какие держать локально в своём сервисе.
|
||||||
|
</p>
|
||||||
|
<div class="tldr">
|
||||||
|
<strong>TL;DR:</strong> НСИ — для <strong>shared reference data со slow change rate</strong>. Если данные нужны только одному сервису, меняются 100 раз в секунду или это транзакции — это <strong>не</strong> НСИ.
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<h2><span class="dot"></span>Что <em>стоит</em> заводить в НСИ</h2>
|
||||||
|
<p>Справочник — кандидат, если он удовлетворяет <strong>минимум 3 из 5</strong> критериев ниже.</p>
|
||||||
|
|
||||||
|
<div class="criterion">
|
||||||
|
<h3>1. Shared между ≥ 2 потребителями</h3>
|
||||||
|
<p>Данные читают/используют как минимум два разных сервиса (или будут в обозримом roadmap).</p>
|
||||||
|
<p><strong>Примеры:</strong> Космические аппараты (планировщик миссий + геопортал + биллинг), наземные станции (планировщик + телеметрия + Альтум), классы разрешения (геопортал + ML pipeline).</p>
|
||||||
|
<div class="hint">Если данные нужны только одному сервису — храни локально. Не плоди интеграционные точки на пустом месте.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="criterion">
|
||||||
|
<h3>2. Slow change rate (часы → недели, а не секунды)</h3>
|
||||||
|
<p>Изменяется редко — раз в неделю / месяц / квартал. Не транзакционные данные.</p>
|
||||||
|
<table>
|
||||||
|
<thead><tr><th>Частота изменений</th><th>Это НСИ?</th></tr></thead>
|
||||||
|
<tbody>
|
||||||
|
<tr><td>Раз в год — справочник стран</td><td class="yes">✓ да</td></tr>
|
||||||
|
<tr><td>Раз в квартал — справочник миссий ДЗЗ</td><td class="yes">✓ да</td></tr>
|
||||||
|
<tr><td>Раз в неделю — справочник антенн (новые станции)</td><td class="yes">✓ да</td></tr>
|
||||||
|
<tr><td>Раз в час — текущая температура спутника</td><td class="no">✗ нет (телеметрия)</td></tr>
|
||||||
|
<tr><td>100×/сек — статус заявки на съёмку</td><td class="no">✗ нет (state machine)</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="criterion">
|
||||||
|
<h3>3. Нужна validity-window (validFrom / validTo)</h3>
|
||||||
|
<p>Данные <strong>актуальны на определённый период</strong>. Закрытие/замена записи через bitemporal versioning, не через <code>DELETE</code>.</p>
|
||||||
|
<p><strong>Примеры:</strong> Тарифы (действуют с 01.01.2026), орбитальные параметры (TLE на конкретную epoch), составы flight crew (период).</p>
|
||||||
|
<div class="hint">Если данные «текущее состояние и забыли» — может быть просто config или KV store.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="criterion">
|
||||||
|
<h3>4. Требуется audit-trail (compliance / governance)</h3>
|
||||||
|
<p>Нужно знать <strong>кто, когда, почему</strong> изменил запись — для compliance, forensics, отката.</p>
|
||||||
|
<p>Все справочники в Ordinis по умолчанию имеют <strong>Hibernate Envers</strong> audit log с full history.</p>
|
||||||
|
<p><strong>Примеры:</strong> Регуляторные классификации, security clearances, тарифные планы (для финансового audit'а).</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="criterion">
|
||||||
|
<h3>5. Есть схема (structured, validated)</h3>
|
||||||
|
<p>Данные имеют <strong>стабильную JSON-Schema</strong> с required-полями и валидацией. Не «свободный мешок ключей».</p>
|
||||||
|
<div class="hint">Если структура каждую неделю меняется и нет SLA на стабильность — может быть это ещё не НСИ, а Notion-like wiki.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<h2><span class="dot" style="background:var(--pink)"></span>Что <em>не стоит</em> заводить в НСИ</h2>
|
||||||
|
|
||||||
|
<div class="anti">
|
||||||
|
<h3>A. Транзакционные данные</h3>
|
||||||
|
<p>Заявки, заказы, события, измерения, журналы. Это transactional store (PostgreSQL/Kafka topic application'а), не НСИ.</p>
|
||||||
|
<p class="symptom">растёт линейно со временем работы системы</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="anti">
|
||||||
|
<h3>B. Высокочастотные мутации</h3>
|
||||||
|
<p>> 100 updates/sec — не лезет в audit, забивает Kafka outbox. Для счётчиков и telemetry — Redis / time-series DB / Prometheus.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="anti">
|
||||||
|
<h3>C. Производные / вычисляемые данные</h3>
|
||||||
|
<p>Если значение <strong>вычисляется</strong> из других данных — храни источник, не результат. Кэш производных — в Redis с invalidation.</p>
|
||||||
|
<p class="symptom">«активные миссии за месяц» — это query, не справочник</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="anti">
|
||||||
|
<h3>D. PII / sensitive без access control</h3>
|
||||||
|
<p>Персональные данные физлиц, payment data, security secrets. Ordinis сейчас поддерживает scope (<code>PUBLIC</code> / <code>INTERNAL</code> / <code>RESTRICTED</code>), но <strong>per-record ACL пока в roadmap</strong> (Q4 2026).</p>
|
||||||
|
<p>Для PII — выделенный сервис с шифрованием и compliance audit, не общий НСИ.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="anti">
|
||||||
|
<h3>E. Большие BLOB'ы</h3>
|
||||||
|
<p>Изображения, PDF, сырые телеметрические дампы. → MinIO/S3 + ссылка в справочнике, но не контент в <code>data</code>.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="anti">
|
||||||
|
<h3>F. Данные, нужные только одному сервису</h3>
|
||||||
|
<p>Если справочник никогда не покинет границу сервиса X — это его внутренний config. Не выноси наружу.</p>
|
||||||
|
<p class="symptom">boundary test — «может ли потребитель Y разумно захотеть читать это?». Нет → не НСИ</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<h2><span class="dot"></span>Чек-лист перед созданием справочника</h2>
|
||||||
|
|
||||||
|
<div class="checklist">
|
||||||
|
<h3>10 вопросов · ответь "да" / "нет"</h3>
|
||||||
|
<ol>
|
||||||
|
<li>Минимум один реальный потребитель кроме создателя? (named system)</li>
|
||||||
|
<li>Согласованная schema (фиксированный набор полей)?</li>
|
||||||
|
<li>Change rate < 10/мин в среднем?</li>
|
||||||
|
<li>Validity windows имеют смысл (или достаточно "current state")?</li>
|
||||||
|
<li>Audit-trail нужен для compliance / forensics?</li>
|
||||||
|
<li>Owner назначен? Reviewer pool назначен (для approval-required dicts)?</li>
|
||||||
|
<li>Bundle подобран? (cuod / geo / comms / delivery — см. existing)</li>
|
||||||
|
<li>Локализация — нужна? Какие locale supported?</li>
|
||||||
|
<li>Scope: PUBLIC / INTERNAL / RESTRICTED?</li>
|
||||||
|
<li>Onboarding plan — кто и когда мигрирует существующие consumers?</li>
|
||||||
|
</ol>
|
||||||
|
<div class="verdict">
|
||||||
|
<strong>≥ 7 yes</strong> — заводи. Если меньше — обсуди с Ordinis team, возможно паттерн не НСИ.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<h2><span class="dot"></span>Процесс создания справочника</h2>
|
||||||
|
|
||||||
|
<h3>Простой случай (PUBLIC scope, owner = создающий)</h3>
|
||||||
|
<div class="process">
|
||||||
|
<div>Owner открывает <code>/dictionaries</code> admin UI → <strong>Создать</strong></div>
|
||||||
|
<div>Заполняет name, displayName, schema (через UI builder или импорт JSON)</div>
|
||||||
|
<div>Тестит в <strong>staging</strong> (<code>ordinis.k8s.265.nstart.cloud</code>)</div>
|
||||||
|
<div>После validation — publish; данные иммутабельно зашиваются в Kafka outbox</div>
|
||||||
|
<div>Сообщение в <code>#ordinis</code> Slack — потребители подхватывают через <code>/dictionaries</code> listing</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Approval-required (для критичных)</h3>
|
||||||
|
<div class="process">
|
||||||
|
<div>Owner создаёт <strong>schema draft</strong> (не публикует сразу)</div>
|
||||||
|
<div>Reviewer pool получает email уведомление</div>
|
||||||
|
<div>Reviewer открывает <code>/reviews</code> → diff → approve/reject</div>
|
||||||
|
<div>После approval — auto-publish через workflow</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Onboarding существующих данных (миграция)</h3>
|
||||||
|
<div class="process">
|
||||||
|
<div>Owner экспортирует существующий справочник из source-системы в CSV/JSON</div>
|
||||||
|
<div>Создаёт schema в Ordinis (UI builder)</div>
|
||||||
|
<div><strong>Bulk import API</strong> (Q4 2026) или manual seed через bundle YAML</div>
|
||||||
|
<div>Запускает <strong>параллельное чтение</strong> — consumers читают и из source, и из Ordinis, сверяют diff</div>
|
||||||
|
<div>После недели зелёного diff — переключают consumers на Ordinis exclusive</div>
|
||||||
|
<div>Декомиссия source: справочник удаляется в source, остаётся в Ordinis</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<h2><span class="dot"></span>Conventions для schema</h2>
|
||||||
|
<p>Чтобы интеграторам было проще читать твой справочник:</p>
|
||||||
|
<ul class="conventions">
|
||||||
|
<li><strong><code>businessKey</code></strong> должен быть human-readable стабильный идентификатор. Не UUID — <code>GS_MOSCOW_A1</code> лучше чем <code>b284f74e-60c8-…</code>.</li>
|
||||||
|
<li><strong>Required fields</strong> — минимум что нужно для валидности. Не объявляй required то, что может быть пустым в legacy data.</li>
|
||||||
|
<li><strong><code>x-localized: true</code></strong> на текстовых полях, которые должны переводиться (<code>name</code>, <code>displayName</code>, <code>description</code>).</li>
|
||||||
|
<li><strong><code>x-references: target_dict.field</code></strong> для FK — Ordinis валидирует и строит граф.</li>
|
||||||
|
<li><strong><code>x-section</code></strong> — группировка полей в UI editor'е (<code>Основное</code>, <code>Геометрия</code>, <code>Метаданные</code>).</li>
|
||||||
|
<li><strong><code>enum</code></strong> для closed lists вместо свободной строки (тип КА, статус миссии).</li>
|
||||||
|
<li><strong><code>format: date-time</code></strong> для timestamps (Ordinis парсит как ISO-8601).</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
|
||||||
|
<h2><span class="dot" style="background:var(--pink)"></span>Anti-patterns (видели на практике)</h2>
|
||||||
|
<div class="callouts">
|
||||||
|
<div><p><strong>Справочник = state machine состояний заявки.</strong> <code>application_status: NEW/APPROVED/REJECTED</code> — это enum в схеме заявки, а не отдельный справочник.</p></div>
|
||||||
|
<div><p><strong>Справочник под per-user preferences.</strong> Предпочтения юзера X — это его profile в auth-сервисе.</p></div>
|
||||||
|
<div><p><strong>Справочник вместо feature flag.</strong> Если читается на каждом запросе для decisions — config service (Spring Cloud Config + LaunchDarkly-like).</p></div>
|
||||||
|
<div><p><strong>«Логирование» через справочник.</strong> Лог событий — Kafka topic + Loki, не НСИ.</p></div>
|
||||||
|
<div><p><strong>Справочник, чтобы передать данные между двумя своими микросервисами.</strong> Используй внутреннюю DB / message broker, не НСИ.</p></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<h2><span class="dot"></span>Когда сомневаешься — спроси</h2>
|
||||||
|
<div class="foot">
|
||||||
|
<strong>Контакты Ordinis team</strong>
|
||||||
|
<ul>
|
||||||
|
<li>Slack <code>#ordinis</code></li>
|
||||||
|
<li>Email: <a href="mailto:zimin.an@nstart.space">zimin.an@nstart.space</a></li>
|
||||||
|
<li>Office-hours: четверг 15:00 МСК (ad-hoc на DM)</li>
|
||||||
|
</ul>
|
||||||
|
<p style="margin-top:12px; color:var(--ink-2); font-style:italic;">Лучше 30 минут на консультацию, чем месяц на отрыв справочника обратно в local store.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="meta">
|
||||||
|
Ordinis MDM · v1.0 · 2026-05 · NStart
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user