Documentation-Driven Development with ADR Governance and Reference Architectures: A Complete Integration Tutorial¶
Objective: Build a comprehensive documentation-driven development platform that integrates documentation best practices, ADR (Architecture Decision Records) governance, reference architecture diagrams, and cognitive load management. This tutorial demonstrates how to build self-documenting systems that minimize cognitive load and maximize developer productivity.
This tutorial combines: - Documentation Best Practices - Writing and organizing useful documentation - ADR and Technical Decision Governance - Architecture Decision Records - Reference Architecture Diagrams - System documentation - Cognitive Load Management and Developer Experience - Developer experience optimization
1) Prerequisites¶
# Required tools
python --version # >= 3.10
node --version # >= 18.0
git --version # >= 2.30
mkdocs --version # Documentation generator
mermaid-cli --version # Diagram generation
# Python packages
pip install mkdocs mkdocs-material \
mkdocs-mermaid2-plugin \
mkdocs-git-revision-date-localized-plugin \
mkdocs-macros-plugin \
pymdown-extensions \
markdown-include
# Node packages
npm install -g @mermaid-js/mermaid-cli \
d3-graphviz \
graphviz
Why: Documentation-driven development requires documentation generators (MkDocs), diagram tools (Mermaid), ADR management, and cognitive load optimization to create maintainable, discoverable documentation.
2) Architecture Overview¶
We'll build a Documentation Platform with comprehensive governance:
graph TB
subgraph "Documentation Sources"
MARKDOWN[Markdown Files]
ADR[ADR Records]
DIAGRAMS[Architecture Diagrams]
CODE[Code Comments]
end
subgraph "Documentation Engine"
MKDOCS[MkDocs Generator]
ADR_PARSER[ADR Parser]
DIAGRAM_GEN[Diagram Generator]
LINK_CHECKER[Link Checker]
end
subgraph "ADR Governance"
ADR_REPO[ADR Repository]
ADR_TEMPLATE[ADR Templates]
ADR_VALIDATOR[ADR Validator]
ADR_INDEX[ADR Index]
end
subgraph "Reference Architectures"
ARCH_DIAGRAMS[Architecture Diagrams]
SYSTEM_TOPOLOGY[System Topology]
DEPLOYMENT[Deployment Diagrams]
DATA_FLOW[Data Flow Diagrams]
end
subgraph "Cognitive Load Management"
DX_ANALYZER[DX Analyzer]
COMPLEXITY[Complexity Metrics]
ONBOARDING[Onboarding Guides]
GOLDEN_PATH[Golden Paths]
end
subgraph "Documentation Site"
STATIC_SITE[Static Site]
SEARCH[Search Index]
NAVIGATION[Navigation]
VERSIONING[Versioning]
end
MARKDOWN --> MKDOCS
ADR --> ADR_PARSER
DIAGRAMS --> DIAGRAM_GEN
CODE --> MKDOCS
ADR_PARSER --> ADR_REPO
ADR_REPO --> ADR_TEMPLATE
ADR_TEMPLATE --> ADR_VALIDATOR
ADR_VALIDATOR --> ADR_INDEX
DIAGRAM_GEN --> ARCH_DIAGRAMS
ARCH_DIAGRAMS --> SYSTEM_TOPOLOGY
SYSTEM_TOPOLOGY --> DEPLOYMENT
DEPLOYMENT --> DATA_FLOW
MKDOCS --> DX_ANALYZER
DX_ANALYZER --> COMPLEXITY
COMPLEXITY --> ONBOARDING
ONBOARDING --> GOLDEN_PATH
MKDOCS --> STATIC_SITE
ADR_INDEX --> STATIC_SITE
ARCH_DIAGRAMS --> STATIC_SITE
STATIC_SITE --> SEARCH
STATIC_SITE --> NAVIGATION
STATIC_SITE --> VERSIONING Platform Capabilities: 1. Documentation Generation: Automated documentation from multiple sources 2. ADR Governance: Structured decision records with validation 3. Reference Architectures: Visual system documentation 4. Cognitive Load Management: Optimized developer experience
3) Repository Layout¶
documentation-platform/
├── mkdocs.yml
├── docs/
│ ├── index.md
│ ├── getting-started.md
│ ├── architecture/
│ │ ├── overview.md
│ │ ├── system-topology.md
│ │ └── deployment.md
│ ├── adr/
│ │ ├── index.md
│ │ ├── 0001-template-standard.md
│ │ ├── 0002-use-protobuf.md
│ │ └── 0003-adopt-kafka.md
│ └── guides/
│ ├── onboarding.md
│ └── golden-paths.md
├── scripts/
│ ├── generate_docs.sh
│ ├── validate_adr.py
│ ├── generate_diagrams.py
│ └── check_links.py
├── templates/
│ ├── adr-template.md
│ └── architecture-template.md
└── tools/
├── adr_manager.py
├── diagram_generator.py
└── dx_analyzer.py
4) MkDocs Configuration¶
Create mkdocs.yml:
site_name: Documentation Platform
site_description: Comprehensive documentation with ADR governance
site_author: Engineering Team
site_url: https://docs.example.com
repo_name: company/documentation-platform
repo_url: https://github.com/company/documentation-platform
edit_uri: edit/main/docs/
theme:
name: material
palette:
- scheme: default
primary: blue
accent: blue
toggle:
icon: material/brightness-7
name: Switch to dark mode
- scheme: slate
primary: blue
accent: blue
toggle:
icon: material/brightness-4
name: Switch to light mode
features:
- navigation.tabs
- navigation.sections
- navigation.expand
- navigation.top
- search.suggest
- search.highlight
- content.code.annotate
- content.code.copy
markdown_extensions:
- pymdownx.highlight:
anchor_linenums: true
- pymdownx.inlinehilite
- pymdownx.snippets:
auto_append:
- docs/macros.md
- pymdownx.superfences:
custom_fences:
- name: mermaid
class: mermaid
format: !!python/name:pymdownx.superfences.fence_code_format
- pymdownx.tabbed:
alternate_style: true
- pymdownx.tasklist:
custom_checkbox: true
- admonition
- pymdownx.details
- attr_list
- md_in_html
- tables
- toc:
permalink: true
plugins:
- search
- git-revision-date-localized:
enable_creation_date: true
- macros:
include_dir: docs
- mermaid2:
arguments:
theme: default
themeVariables:
primaryColor: '#2196F3'
primaryTextColor: '#fff'
primaryBorderColor: '#1976D2'
lineColor: '#1976D2'
secondaryColor: '#006100'
tertiaryColor: '#fff'
nav:
- Home: index.md
- Getting Started: getting-started.md
- Architecture:
- Overview: architecture/overview.md
- System Topology: architecture/system-topology.md
- Deployment: architecture/deployment.md
- ADRs:
- ADR Index: adr/index.md
- ADR-0001: Template Standard: adr/0001-template-standard.md
- ADR-0002: Use Protocol Buffers: adr/0002-use-protobuf.md
- ADR-0003: Adopt Kafka: adr/0003-adopt-kafka.md
- Guides:
- Onboarding: guides/onboarding.md
- Golden Paths: guides/golden-paths.md
5) ADR Manager¶
Create tools/adr_manager.py:
"""ADR (Architecture Decision Record) manager."""
from typing import List, Dict, Optional, Any
from dataclasses import dataclass, field
from datetime import datetime
from pathlib import Path
import re
import yaml
import frontmatter
from prometheus_client import Counter, Gauge
adr_metrics = {
"adr_created": Counter("adr_created_total", "ADRs created", ["status"]),
"adr_updated": Counter("adr_updated_total", "ADRs updated"),
"adr_superseded": Counter("adr_superseded_total", "ADRs superseded"),
"active_adrs": Gauge("active_adrs_count", "Active ADRs"),
}
@dataclass
class ADR:
"""Architecture Decision Record."""
number: int
title: str
status: str # proposed, accepted, rejected, deprecated, superseded
date: datetime
deciders: List[str]
context: str
decision: str
consequences: str
alternatives: List[Dict[str, str]] = field(default_factory=list)
related_adrs: List[int] = field(default_factory=list)
superseded_by: Optional[int] = None
tags: List[str] = field(default_factory=list)
content: str = ""
class ADRManager:
"""Manages Architecture Decision Records."""
def __init__(self, adr_directory: Path):
self.adr_directory = Path(adr_directory)
self.adr_directory.mkdir(parents=True, exist_ok=True)
self.adrs: Dict[int, ADR] = {}
self._load_adrs()
def _load_adrs(self):
"""Load all ADRs from directory."""
for file_path in self.adr_directory.glob("*.md"):
if file_path.stem.startswith("0000") or file_path.stem == "index":
continue
try:
adr = self._parse_adr_file(file_path)
if adr:
self.adrs[adr.number] = adr
except Exception as e:
logging.error(f"Failed to parse ADR {file_path}: {e}")
adr_metrics["active_adrs"].set(len([a for a in self.adrs.values() if a.status == "accepted"]))
def _parse_adr_file(self, file_path: Path) -> Optional[ADR]:
"""Parse ADR from markdown file."""
with open(file_path, 'r') as f:
post = frontmatter.load(f)
metadata = post.metadata
content = post.content
# Extract ADR number from filename
match = re.match(r'(\d+)-', file_path.stem)
if not match:
return None
number = int(match.group(1))
# Parse content sections
context = self._extract_section(content, "Context")
decision = self._extract_section(content, "Decision")
consequences = self._extract_section(content, "Consequences")
alternatives = self._extract_alternatives(content)
return ADR(
number=number,
title=metadata.get("title", file_path.stem),
status=metadata.get("status", "proposed"),
date=datetime.fromisoformat(metadata.get("date", datetime.utcnow().isoformat())),
deciders=metadata.get("deciders", []),
context=context,
decision=decision,
consequences=consequences,
alternatives=alternatives,
related_adrs=metadata.get("related_adrs", []),
superseded_by=metadata.get("superseded_by"),
tags=metadata.get("tags", []),
content=content
)
def _extract_section(self, content: str, section_name: str) -> str:
"""Extract section from markdown content."""
pattern = rf"## {section_name}\s*\n(.*?)(?=\n## |\Z)"
match = re.search(pattern, content, re.DOTALL)
return match.group(1).strip() if match else ""
def _extract_alternatives(self, content: str) -> List[Dict[str, str]]:
"""Extract alternatives from content."""
alternatives = []
# Look for alternatives section
pattern = r"## Alternatives\s*\n(.*?)(?=\n## |\Z)"
match = re.search(pattern, content, re.DOTALL)
if match:
alt_section = match.group(1)
# Extract each alternative
alt_pattern = r"### (.*?)\s*\n(.*?)(?=\n### |\Z)"
for alt_match in re.finditer(alt_pattern, alt_section, re.DOTALL):
alternatives.append({
"name": alt_match.group(1).strip(),
"description": alt_match.group(2).strip()
})
return alternatives
def create_adr(
self,
title: str,
context: str,
decision: str,
consequences: str,
deciders: List[str],
alternatives: Optional[List[Dict[str, str]]] = None,
tags: Optional[List[str]] = None
) -> ADR:
"""Create a new ADR."""
# Get next ADR number
next_number = max(self.adrs.keys(), default=0) + 1
adr = ADR(
number=next_number,
title=title,
status="proposed",
date=datetime.utcnow(),
deciders=deciders,
context=context,
decision=decision,
consequences=consequences,
alternatives=alternatives or [],
tags=tags or []
)
# Write ADR file
self._write_adr_file(adr)
self.adrs[adr.number] = adr
adr_metrics["adr_created"].labels(status="proposed").inc()
return adr
def _write_adr_file(self, adr: ADR):
"""Write ADR to file."""
file_path = self.adr_directory / f"{adr.number:04d}-{self._slugify(adr.title)}.md"
# Generate frontmatter
frontmatter_data = {
"title": adr.title,
"status": adr.status,
"date": adr.date.isoformat(),
"deciders": adr.deciders,
"tags": adr.tags
}
if adr.related_adrs:
frontmatter_data["related_adrs"] = adr.related_adrs
if adr.superseded_by:
frontmatter_data["superseded_by"] = adr.superseded_by
# Generate content
content = f"""# {adr.number:04d}. {adr.title}
**Status**: {adr.status.title()}
**Date**: {adr.date.strftime('%Y-%m-%d')}
**Deciders**: {', '.join(adr.deciders)}
## Context
{adr.context}
## Decision
{adr.decision}
## Consequences
{adr.consequences}
"""
if adr.alternatives:
content += "\n## Alternatives\n\n"
for alt in adr.alternatives:
content += f"### {alt['name']}\n\n{alt['description']}\n\n"
if adr.related_adrs:
content += "\n## Related ADRs\n\n"
for related_num in adr.related_adrs:
related_adr = self.adrs.get(related_num)
if related_adr:
content += f"- [{related_num:04d}. {related_adr.title}]({related_num:04d}-{self._slugify(related_adr.title)}.md)\n"
# Write file with frontmatter
post = frontmatter.Post(content, **frontmatter_data)
with open(file_path, 'wb') as f:
frontmatter.dump(post, f)
def _slugify(self, text: str) -> str:
"""Convert text to URL-friendly slug."""
text = text.lower()
text = re.sub(r'[^\w\s-]', '', text)
text = re.sub(r'[-\s]+', '-', text)
return text.strip('-')
def update_adr_status(self, adr_number: int, new_status: str):
"""Update ADR status."""
adr = self.adrs.get(adr_number)
if not adr:
raise ValueError(f"ADR {adr_number} not found")
adr.status = new_status
self._write_adr_file(adr)
adr_metrics["adr_updated"].inc()
def supersede_adr(self, old_adr_number: int, new_adr_number: int):
"""Mark ADR as superseded by another."""
old_adr = self.adrs.get(old_adr_number)
if not old_adr:
raise ValueError(f"ADR {old_adr_number} not found")
old_adr.status = "superseded"
old_adr.superseded_by = new_adr_number
new_adr = self.adrs.get(new_adr_number)
if new_adr:
new_adr.related_adrs.append(old_adr_number)
self._write_adr_file(old_adr)
if new_adr:
self._write_adr_file(new_adr)
adr_metrics["adr_superseded"].inc()
def generate_index(self) -> str:
"""Generate ADR index markdown."""
index_lines = [
"# Architecture Decision Records (ADRs)",
"",
"This directory contains Architecture Decision Records (ADRs) for this project.",
"",
"## ADR Index",
"",
"| Number | Title | Status | Date | Deciders |",
"|--------|-------|--------|------|----------|"
]
for adr in sorted(self.adrs.values(), key=lambda a: a.number):
status_badge = self._status_badge(adr.status)
date_str = adr.date.strftime('%Y-%m-%d')
deciders_str = ', '.join(adr.deciders[:2]) # Limit display
if len(adr.deciders) > 2:
deciders_str += f" +{len(adr.deciders) - 2}"
index_lines.append(
f"| [{adr.number:04d}]({adr.number:04d}-{self._slugify(adr.title)}.md) | "
f"{adr.title} | {status_badge} | {date_str} | {deciders_str} |"
)
return "\n".join(index_lines)
def _status_badge(self, status: str) -> str:
"""Generate status badge."""
badges = {
"proposed": "🟡 Proposed",
"accepted": "✅ Accepted",
"rejected": "❌ Rejected",
"deprecated": "⚠️ Deprecated",
"superseded": "🔄 Superseded"
}
return badges.get(status, status)
def validate_adr(self, adr_number: int) -> List[str]:
"""Validate ADR structure and content."""
errors = []
adr = self.adrs.get(adr_number)
if not adr:
errors.append(f"ADR {adr_number} not found")
return errors
# Check required fields
if not adr.context:
errors.append("Context section is required")
if not adr.decision:
errors.append("Decision section is required")
if not adr.consequences:
errors.append("Consequences section is required")
if not adr.deciders:
errors.append("At least one decider is required")
# Check status
if adr.status not in ["proposed", "accepted", "rejected", "deprecated", "superseded"]:
errors.append(f"Invalid status: {adr.status}")
return errors
6) Diagram Generator¶
Create tools/diagram_generator.py:
"""Diagram generator for reference architectures."""
from typing import Dict, List, Optional
from pathlib import Path
import subprocess
import json
from prometheus_client import Counter
diagram_metrics = {
"diagrams_generated": Counter("diagrams_generated_total", "Diagrams generated", ["diagram_type"]),
}
class DiagramGenerator:
"""Generates architecture diagrams."""
def __init__(self, output_directory: Path):
self.output_directory = Path(output_directory)
self.output_directory.mkdir(parents=True, exist_ok=True)
def generate_system_topology(
self,
services: List[Dict[str, Any]],
connections: List[Dict[str, str]],
output_file: str = "system-topology"
) -> str:
"""Generate system topology diagram."""
mermaid_code = """graph TB
"""
# Add services
for service in services:
service_id = service["id"].replace("-", "_")
service_name = service["name"]
service_type = service.get("type", "service")
mermaid_code += f' {service_id}["{service_name}<br/>{service_type}"]\n'
# Add connections
for conn in connections:
from_id = conn["from"].replace("-", "_")
to_id = conn["to"].replace("-", "_")
label = conn.get("label", "")
mermaid_code += f' {from_id} -->|"{label}"| {to_id}\n'
# Write mermaid file
mermaid_file = self.output_directory / f"{output_file}.mmd"
with open(mermaid_file, 'w') as f:
f.write(mermaid_code)
# Generate PNG/SVG
self._render_mermaid(mermaid_file, output_file)
diagram_metrics["diagrams_generated"].labels(diagram_type="system_topology").inc()
return mermaid_code
def generate_deployment_diagram(
self,
environments: List[Dict[str, Any]],
output_file: str = "deployment"
) -> str:
"""Generate deployment diagram."""
mermaid_code = """graph TB
subgraph "Production"
"""
for env in environments:
env_name = env["name"]
services = env.get("services", [])
mermaid_code += f' subgraph "{env_name}"\n'
for service in services:
service_id = service["id"].replace("-", "_")
mermaid_code += f' {service_id}["{service["name"]}"]\n'
mermaid_code += " end\n"
mermaid_code += "```"
# Write and render
mermaid_file = self.output_directory / f"{output_file}.mmd"
with open(mermaid_file, 'w') as f:
f.write(mermaid_code)
self._render_mermaid(mermaid_file, output_file)
diagram_metrics["diagrams_generated"].labels(diagram_type="deployment").inc()
return mermaid_code
def generate_data_flow(
self,
data_sources: List[Dict[str, Any]],
transformations: List[Dict[str, Any]],
data_sinks: List[Dict[str, Any]],
output_file: str = "data-flow"
) -> str:
"""Generate data flow diagram."""
mermaid_code = """flowchart LR
"""
# Add sources
for source in data_sources:
source_id = source["id"].replace("-", "_")
mermaid_code += f' {source_id}["{source["name"]}<br/>Source"]\n'
# Add transformations
for trans in transformations:
trans_id = trans["id"].replace("-", "_")
mermaid_code += f' {trans_id}["{trans["name"]}<br/>Transform"]\n'
# Add sinks
for sink in data_sinks:
sink_id = sink["id"].replace("-", "_")
mermaid_code += f' {sink_id}["{sink["name"]}<br/>Sink"]\n'
# Add connections (simplified)
for source in data_sources:
for trans in transformations:
if trans.get("input") == source["id"]:
source_id = source["id"].replace("-", "_")
trans_id = trans["id"].replace("-", "_")
mermaid_code += f' {source_id} --> {trans_id}\n'
for trans in transformations:
for sink in data_sinks:
if sink.get("input") == trans["id"]:
trans_id = trans["id"].replace("-", "_")
sink_id = sink["id"].replace("-", "_")
mermaid_code += f' {trans_id} --> {sink_id}\n'
# Write and render
mermaid_file = self.output_directory / f"{output_file}.mmd"
with open(mermaid_file, 'w') as f:
f.write(mermaid_code)
self._render_mermaid(mermaid_file, output_file)
diagram_metrics["diagrams_generated"].labels(diagram_type="data_flow").inc()
return mermaid_code
def _render_mermaid(self, mermaid_file: Path, output_name: str):
"""Render Mermaid diagram to PNG/SVG."""
try:
# Generate PNG
subprocess.run([
"mmdc",
"-i", str(mermaid_file),
"-o", str(self.output_directory / f"{output_name}.png"),
"-w", "1920",
"-H", "1080"
], check=True)
# Generate SVG
subprocess.run([
"mmdc",
"-i", str(mermaid_file),
"-o", str(self.output_directory / f"{output_name}.svg"),
"-t", "default"
], check=True)
except subprocess.CalledProcessError as e:
logging.error(f"Failed to render diagram {mermaid_file}: {e}")
except FileNotFoundError:
logging.warning("mmdc (mermaid-cli) not found. Install with: npm install -g @mermaid-js/mermaid-cli")
7) DX Analyzer¶
Create tools/dx_analyzer.py:
"""Developer Experience (DX) analyzer for cognitive load management."""
from typing import Dict, List, Optional, Any
from pathlib import Path
import ast
import re
from dataclasses import dataclass
from prometheus_client import Gauge
dx_metrics = {
"documentation_completeness": Gauge("documentation_completeness_percent", "Documentation completeness", ["component"]),
"onboarding_time_minutes": Gauge("onboarding_time_minutes", "Estimated onboarding time", ["role"]),
"cognitive_load_score": Gauge("cognitive_load_score", "Cognitive load score", ["component"]),
}
@dataclass
class DXMetrics:
"""Developer Experience metrics."""
component: str
documentation_completeness: float
code_complexity: float
onboarding_time_minutes: float
cognitive_load_score: float
issues: List[str]
class DXAnalyzer:
"""Analyzes developer experience and cognitive load."""
def __init__(self, code_directory: Path, docs_directory: Path):
self.code_directory = Path(code_directory)
self.docs_directory = Path(docs_directory)
self.metrics: Dict[str, DXMetrics] = {}
def analyze_component(self, component_name: str) -> DXMetrics:
"""Analyze DX for a component."""
# Check documentation
doc_completeness = self._check_documentation(component_name)
# Check code complexity
code_complexity = self._check_code_complexity(component_name)
# Estimate onboarding time
onboarding_time = self._estimate_onboarding_time(component_name, doc_completeness, code_complexity)
# Calculate cognitive load score
cognitive_load = self._calculate_cognitive_load(doc_completeness, code_complexity)
# Find issues
issues = self._find_issues(component_name, doc_completeness, code_complexity)
metrics = DXMetrics(
component=component_name,
documentation_completeness=doc_completeness,
code_complexity=code_complexity,
onboarding_time_minutes=onboarding_time,
cognitive_load_score=cognitive_load,
issues=issues
)
self.metrics[component_name] = metrics
# Update Prometheus metrics
dx_metrics["documentation_completeness"].labels(component=component_name).set(doc_completeness)
dx_metrics["onboarding_time_minutes"].labels(role="developer").set(onboarding_time)
dx_metrics["cognitive_load_score"].labels(component=component_name).set(cognitive_load)
return metrics
def _check_documentation(self, component_name: str) -> float:
"""Check documentation completeness (0-100)."""
doc_file = self.docs_directory / f"{component_name}.md"
if not doc_file.exists():
return 0.0
with open(doc_file, 'r') as f:
content = f.read()
# Check for required sections
required_sections = ["Overview", "Getting Started", "API Reference", "Examples"]
found_sections = sum(1 for section in required_sections if f"## {section}" in content)
completeness = (found_sections / len(required_sections)) * 100
# Check for code examples
if "```" in content:
completeness += 10
# Check for diagrams
if "```mermaid" in content or "![diagram]" in content:
completeness += 10
return min(100.0, completeness)
def _check_code_complexity(self, component_name: str) -> float:
"""Check code complexity (0-100, higher = more complex)."""
component_path = self.code_directory / component_name
if not component_path.exists():
return 0.0
total_complexity = 0
file_count = 0
for py_file in component_path.rglob("*.py"):
try:
with open(py_file, 'r') as f:
tree = ast.parse(f.read())
# Count complexity metrics
functions = [node for node in ast.walk(tree) if isinstance(node, ast.FunctionDef)]
classes = [node for node in ast.walk(tree) if isinstance(node, ast.ClassDef)]
# Simple complexity: functions + classes + nesting
complexity = len(functions) + len(classes) * 2
for func in functions:
# Count nested structures
for node in ast.walk(func):
if isinstance(node, (ast.If, ast.For, ast.While, ast.Try)):
complexity += 1
total_complexity += complexity
file_count += 1
except:
pass
if file_count == 0:
return 0.0
avg_complexity = total_complexity / file_count
# Normalize to 0-100 scale (arbitrary threshold)
normalized = min(100.0, (avg_complexity / 50.0) * 100)
return normalized
def _estimate_onboarding_time(
self,
component_name: str,
doc_completeness: float,
code_complexity: float
) -> float:
"""Estimate onboarding time in minutes."""
base_time = 60.0 # 1 hour base
# Adjust for documentation
if doc_completeness < 50:
base_time += 120 # +2 hours if poor docs
elif doc_completeness < 80:
base_time += 60 # +1 hour if moderate docs
# Adjust for complexity
complexity_multiplier = 1 + (code_complexity / 100)
base_time *= complexity_multiplier
return base_time
def _calculate_cognitive_load(
self,
doc_completeness: float,
code_complexity: float
) -> float:
"""Calculate cognitive load score (0-100, higher = more load)."""
# Inverse documentation completeness (lower docs = higher load)
doc_load = 100 - doc_completeness
# Direct complexity (higher complexity = higher load)
complexity_load = code_complexity
# Weighted average
cognitive_load = (doc_load * 0.4) + (complexity_load * 0.6)
return cognitive_load
def _find_issues(
self,
component_name: str,
doc_completeness: float,
code_complexity: float
) -> List[str]:
"""Find DX issues."""
issues = []
if doc_completeness < 50:
issues.append("Documentation is incomplete (< 50%)")
if code_complexity > 70:
issues.append("Code complexity is high (> 70)")
if doc_completeness < 50 and code_complexity > 70:
issues.append("High cognitive load: poor documentation and high complexity")
return issues
def generate_golden_path(self, component_name: str) -> str:
"""Generate golden path guide for component."""
metrics = self.metrics.get(component_name)
if not metrics:
metrics = self.analyze_component(component_name)
golden_path = f"""# Golden Path: {component_name}
## Quick Start
This is the recommended path for getting started with {component_name}.
### Prerequisites
- [ ] Prerequisite 1
- [ ] Prerequisite 2
### Steps
1. **Step 1**: Description
2. **Step 2**: Description
3. **Step 3**: Description
## Common Tasks
### Task 1
Description and steps.
### Task 2
Description and steps.
## Troubleshooting
Common issues and solutions.
## Next Steps
- Link to detailed documentation
- Link to examples
- Link to API reference
"""
return golden_path
8) ADR Template¶
Create templates/adr-template.md:
# {ADR_NUMBER}. {TITLE}
**Status**: {STATUS}
**Date**: {DATE}
**Deciders**: {DECIDERS}
**Tags**: {TAGS}
## Context
{CONTEXT}
## Decision
We will {DECISION}.
## Consequences
### Positive
- {POSITIVE_CONSEQUENCE_1}
- {POSITIVE_CONSEQUENCE_2}
### Negative
- {NEGATIVE_CONSEQUENCE_1}
- {NEGATIVE_CONSEQUENCE_2}
## Alternatives Considered
### Alternative 1: {NAME}
{DESCRIPTION}
**Pros:**
- {PRO}
**Cons:**
- {CON}
### Alternative 2: {NAME}
{DESCRIPTION}
**Pros:**
- {PRO}
**Cons:**
- {CON}
## Related ADRs
- [ADR-XXXX](link) - Related decision
9) Example ADR¶
Create docs/adr/0002-use-protobuf.md:
---
title: Use Protocol Buffers for API Serialization
status: accepted
date: 2024-01-15
deciders:
- Engineering Team
- API Team
tags:
- api
- serialization
- polyglot
related_adrs:
- 0001
---
# 0002. Use Protocol Buffers for API Serialization
**Status**: Accepted
**Date**: 2024-01-15
**Deciders**: Engineering Team, API Team
## Context
We need to choose a serialization format for our polyglot microservices architecture. Services are written in Python, Go, and Rust, and need to communicate efficiently with type safety.
## Decision
We will use Protocol Buffers (protobuf) for all inter-service communication and API serialization.
## Consequences
### Positive
- **Type Safety**: Strong typing across languages prevents serialization errors
- **Performance**: Binary format is more efficient than JSON
- **Schema Evolution**: Backward compatibility built-in
- **Code Generation**: Automatic code generation for all languages
- **Polyglot Support**: Native support for Python, Go, Rust, and more
### Negative
- **Learning Curve**: Team needs to learn protobuf syntax and tooling
- **Tooling**: Requires protoc compiler and language-specific plugins
- **Debugging**: Binary format is harder to debug than JSON
- **Human Readability**: Not human-readable like JSON
## Alternatives Considered
### Alternative 1: JSON
Use JSON for all API communication.
**Pros:**
- Human-readable
- Easy to debug
- Universal support
**Cons:**
- No type safety
- Larger payload size
- No schema enforcement
- Manual serialization/deserialization
### Alternative 2: Avro
Use Apache Avro for serialization.
**Pros:**
- Schema evolution support
- Efficient binary format
**Cons:**
- Less mature tooling
- Limited language support compared to protobuf
- More complex schema definition
## Related ADRs
- [ADR-0001](0001-template-standard.md) - ADR Template Standard
10) Documentation Generation Script¶
Create scripts/generate_docs.sh:
#!/bin/bash
set -e
echo "Generating documentation..."
# Generate ADR index
python tools/adr_manager.py generate-index > docs/adr/index.md
# Generate diagrams
python tools/diagram_generator.py \
--system-topology \
--deployment \
--data-flow
# Analyze DX
python tools/dx_analyzer.py \
--analyze-all \
--output docs/dx-analysis.md
# Build MkDocs site
mkdocs build
# Validate links
python scripts/check_links.py
echo "Documentation generated successfully!"
11) Testing the Platform¶
11.1) Create ADR¶
from tools.adr_manager import ADRManager
manager = ADRManager("docs/adr")
adr = manager.create_adr(
title="Use Protocol Buffers for API Serialization",
context="We need to choose a serialization format for our polyglot microservices...",
decision="We will use Protocol Buffers (protobuf) for all inter-service communication.",
consequences="Positive: Type safety, performance, schema evolution. Negative: Learning curve, tooling requirements.",
deciders=["Engineering Team", "API Team"],
alternatives=[
{
"name": "JSON",
"description": "Use JSON for all API communication."
},
{
"name": "Avro",
"description": "Use Apache Avro for serialization."
}
],
tags=["api", "serialization", "polyglot"]
)
print(f"Created ADR {adr.number}: {adr.title}")
11.2) Generate Diagrams¶
from tools.diagram_generator import DiagramGenerator
generator = DiagramGenerator("docs/images")
# Generate system topology
services = [
{"id": "order-service", "name": "Order Service", "type": "gRPC"},
{"id": "payment-service", "name": "Payment Service", "type": "gRPC"},
{"id": "inventory-service", "name": "Inventory Service", "type": "gRPC"}
]
connections = [
{"from": "order-service", "to": "payment-service", "label": "Process Payment"},
{"from": "order-service", "to": "inventory-service", "label": "Reserve Items"}
]
generator.generate_system_topology(services, connections)
11.3) Build Documentation¶
# Generate all documentation
./scripts/generate_docs.sh
# Serve locally
mkdocs serve
# Build for production
mkdocs build --clean
12) Best Practices Integration Summary¶
This tutorial demonstrates:
- Documentation Best Practices: Comprehensive, discoverable documentation with MkDocs
- ADR Governance: Structured decision records with validation and indexing
- Reference Architecture Diagrams: Visual system documentation with Mermaid
- Cognitive Load Management: DX analysis and golden paths for onboarding
Key Integration Points: - ADR manager creates structured decision records - Diagram generator creates visual architecture documentation - DX analyzer identifies cognitive load issues - MkDocs integrates all documentation sources - Golden paths provide optimized onboarding
13) CI/CD Integration¶
13.1) Automated ADR Validation¶
Create .github/workflows/validate-adr.yml:
name: Validate ADRs
on:
pull_request:
paths:
- 'docs/adr/**'
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.10'
- name: Install dependencies
run: |
pip install frontmatter pyyaml
- name: Validate ADRs
run: |
python scripts/validate_adr.py
- name: Check ADR index
run: |
python tools/adr_manager.py generate-index > /tmp/index.md
diff docs/adr/index.md /tmp/index.md || echo "ADR index needs update"
13.2) Documentation Build Check¶
Create .github/workflows/build-docs.yml:
name: Build Documentation
on:
push:
branches: [main]
pull_request:
paths:
- 'docs/**'
- 'mkdocs.yml'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.10'
- name: Install dependencies
run: |
pip install mkdocs mkdocs-material
npm install -g @mermaid-js/mermaid-cli
- name: Build documentation
run: |
./scripts/generate_docs.sh
mkdocs build
- name: Check for broken links
run: |
python scripts/check_links.py
14) Advanced Features¶
14.1) Documentation Analytics¶
Track documentation usage and effectiveness:
"""Documentation analytics."""
from typing import Dict, List
from datetime import datetime
import json
class DocumentationAnalytics:
"""Tracks documentation usage and effectiveness."""
def __init__(self):
self.page_views: Dict[str, int] = {}
self.search_queries: List[str] = []
self.feedback: Dict[str, List[str]] = {}
def track_page_view(self, page: str):
"""Track page view."""
self.page_views[page] = self.page_views.get(page, 0) + 1
def track_search(self, query: str):
"""Track search query."""
self.search_queries.append({
"query": query,
"timestamp": datetime.utcnow().isoformat()
})
def get_popular_pages(self, limit: int = 10) -> List[Dict[str, Any]]:
"""Get most popular pages."""
sorted_pages = sorted(
self.page_views.items(),
key=lambda x: x[1],
reverse=True
)
return [
{"page": page, "views": views}
for page, views in sorted_pages[:limit]
]
14.2) Interactive Architecture Diagrams¶
Create interactive diagrams with D3.js:
// Interactive system topology diagram
function createInteractiveDiagram(data) {
const svg = d3.select("#diagram-container")
.append("svg")
.attr("width", 1200)
.attr("height", 800);
// Add nodes
const nodes = svg.selectAll("circle")
.data(data.services)
.enter()
.append("circle")
.attr("r", 20)
.attr("cx", d => d.x)
.attr("cy", d => d.y)
.on("click", function(d) {
showServiceDetails(d);
});
// Add links
const links = svg.selectAll("line")
.data(data.connections)
.enter()
.append("line")
.attr("stroke", "#999")
.attr("stroke-width", 2);
}
15) Best Practices Integration Summary¶
This tutorial demonstrates:
- Documentation Best Practices: Comprehensive, discoverable documentation with MkDocs
- ADR Governance: Structured decision records with validation and indexing
- Reference Architecture Diagrams: Visual system documentation with Mermaid
- Cognitive Load Management: DX analysis and golden paths for onboarding
Key Integration Points: - ADR manager creates structured decision records - Diagram generator creates visual architecture documentation - DX analyzer identifies cognitive load issues - MkDocs integrates all documentation sources - Golden paths provide optimized onboarding - CI/CD validates documentation quality
16) Next Steps¶
- Add automated ADR validation in CI/CD
- Integrate code documentation extraction
- Add interactive architecture diagrams
- Implement documentation versioning
- Add search and discovery features
- Integrate documentation analytics
- Add automated diagram generation from code
- Implement documentation review workflows
This tutorial demonstrates how multiple best practices integrate to create a comprehensive documentation platform that minimizes cognitive load and maximizes developer productivity.