- Added comprehensive course structure with 11 modules - Included theoretical content, practical examples, and tasks - Multi-language code examples (Java, Rust, Node.js, Python) - Detailed analysis of architectural approaches and trade-offs - Complete caching strategies documentation - Architecture examples from real-world systems (Twitter, Netflix, Uber) - Structured navigation and learning path - Ready for educational use
2613 lines
87 KiB
Markdown
2613 lines
87 KiB
Markdown
# Модуль 7: Документирование архитектуры
|
||
|
||
## Содержание модуля
|
||
1. [Основы архитектурной документации](#основы-архитектурной-документации)
|
||
2. [C4 Model](#c4-model)
|
||
3. [PlantUML](#plantuml)
|
||
4. [Architecture Decision Records (ADR)](#architecture-decision-records-adr)
|
||
5. [Техническая документация](#техническая-документация)
|
||
6. [Коммуникация архитектуры](#коммуникация-архитектуры)
|
||
|
||
---
|
||
|
||
## Основы архитектурной документации
|
||
|
||
### Зачем нужна архитектурная документация
|
||
|
||
#### 1. Коммуникация
|
||
- Общий язык для команды
|
||
- Понимание системы новыми участниками
|
||
- Обсуждение архитектурных решений
|
||
|
||
#### 2. Планирование
|
||
- Основа для принятия решений
|
||
- Анализ влияния изменений
|
||
- Планирование миграций
|
||
|
||
#### 3. Поддержка
|
||
- Быстрое понимание системы
|
||
- Отладка проблем
|
||
- Обучение команды
|
||
|
||
### Принципы архитектурной документации
|
||
|
||
#### 1. Living Documentation
|
||
**Принцип:** Документация должна быть живой и актуальной.
|
||
|
||
**Технический пример - Living Documentation с автоматизацией:**
|
||
|
||
```markdown
|
||
# Архитектура системы E-Commerce Platform
|
||
|
||
> **Последнее обновление:** 2024-01-15
|
||
> **Версия:** 2.1
|
||
> **Ответственный:** Архитектор команды
|
||
> **Статус:** Активная разработка
|
||
> **Следующий обзор:** 2024-02-15
|
||
|
||
## Изменения в версии 2.1
|
||
- ✅ Добавлен новый микросервис Payment Service
|
||
- ✅ Обновлена схема базы данных (добавлены индексы для производительности)
|
||
- ✅ Изменена стратегия кэширования (Redis Cluster)
|
||
- 🔄 В процессе: Миграция на Kubernetes
|
||
- 📋 Планируется: Внедрение Event Sourcing
|
||
|
||
## Архитектурные решения
|
||
- [ADR-001: Выбор микросервисной архитектуры](./adr/001-microservices-architecture.md)
|
||
- [ADR-002: Использование PostgreSQL](./adr/002-postgresql-database.md)
|
||
- [ADR-003: Redis для кэширования](./adr/003-redis-caching.md)
|
||
- [ADR-004: Kubernetes для оркестрации](./adr/004-kubernetes-orchestration.md)
|
||
|
||
## Метрики системы
|
||
- **Uptime:** 99.9% (последние 30 дней)
|
||
- **Response Time:** P95 < 200ms
|
||
- **Throughput:** 10,000 RPS
|
||
- **Error Rate:** < 0.1%
|
||
```
|
||
|
||
**Технический пример - Automated Documentation Generation:**
|
||
|
||
```java
|
||
// Architecture Documentation Generator
|
||
@Component
|
||
public class ArchitectureDocumentationGenerator {
|
||
|
||
@Autowired
|
||
private ServiceRegistry serviceRegistry;
|
||
|
||
@Autowired
|
||
private DatabaseSchemaAnalyzer schemaAnalyzer;
|
||
|
||
@Autowired
|
||
private ApiSpecificationGenerator apiGenerator;
|
||
|
||
@Scheduled(cron = "0 0 2 * * ?") // Каждый день в 2:00
|
||
public void generateDocumentation() {
|
||
log.info("Starting architecture documentation generation");
|
||
|
||
try {
|
||
// Генерируем обновленную документацию
|
||
generateSystemOverview();
|
||
generateServiceCatalog();
|
||
generateDatabaseSchema();
|
||
generateApiDocumentation();
|
||
generateDeploymentDiagrams();
|
||
|
||
// Обновляем метрики
|
||
updateSystemMetrics();
|
||
|
||
// Отправляем уведомления
|
||
notifyTeamAboutUpdates();
|
||
|
||
log.info("Architecture documentation generation completed");
|
||
|
||
} catch (Exception e) {
|
||
log.error("Failed to generate architecture documentation", e);
|
||
}
|
||
}
|
||
|
||
private void generateSystemOverview() {
|
||
SystemOverview overview = SystemOverview.builder()
|
||
.systemName("E-Commerce Platform")
|
||
.version(getCurrentVersion())
|
||
.lastUpdated(LocalDateTime.now())
|
||
.services(serviceRegistry.getAllServices())
|
||
.databases(schemaAnalyzer.getDatabaseInfo())
|
||
.build();
|
||
|
||
String markdown = convertToMarkdown(overview);
|
||
writeToFile("docs/system-overview.md", markdown);
|
||
}
|
||
|
||
private void generateServiceCatalog() {
|
||
List<ServiceInfo> services = serviceRegistry.getAllServices();
|
||
|
||
StringBuilder catalog = new StringBuilder();
|
||
catalog.append("# Service Catalog\n\n");
|
||
catalog.append("| Service | Version | Status | Health | Dependencies |\n");
|
||
catalog.append("|---------|---------|--------|--------|-------------|\n");
|
||
|
||
for (ServiceInfo service : services) {
|
||
catalog.append(String.format("| %s | %s | %s | %s | %s |\n",
|
||
service.getName(),
|
||
service.getVersion(),
|
||
service.getStatus(),
|
||
service.getHealthStatus(),
|
||
String.join(", ", service.getDependencies())
|
||
));
|
||
}
|
||
|
||
writeToFile("docs/service-catalog.md", catalog.toString());
|
||
}
|
||
|
||
private void generateDatabaseSchema() {
|
||
DatabaseSchema schema = schemaAnalyzer.analyzeSchema();
|
||
|
||
StringBuilder schemaDoc = new StringBuilder();
|
||
schemaDoc.append("# Database Schema\n\n");
|
||
|
||
for (TableInfo table : schema.getTables()) {
|
||
schemaDoc.append(String.format("## %s\n\n", table.getName()));
|
||
schemaDoc.append(String.format("**Description:** %s\n\n", table.getDescription()));
|
||
schemaDoc.append("| Column | Type | Nullable | Key | Description |\n");
|
||
schemaDoc.append("|--------|------|----------|-----|-------------|\n");
|
||
|
||
for (ColumnInfo column : table.getColumns()) {
|
||
schemaDoc.append(String.format("| %s | %s | %s | %s | %s |\n",
|
||
column.getName(),
|
||
column.getType(),
|
||
column.isNullable() ? "Yes" : "No",
|
||
column.getKeyType(),
|
||
column.getDescription()
|
||
));
|
||
}
|
||
schemaDoc.append("\n");
|
||
}
|
||
|
||
writeToFile("docs/database-schema.md", schemaDoc.toString());
|
||
}
|
||
|
||
private void generateApiDocumentation() {
|
||
List<ApiSpecification> apiSpecs = apiGenerator.generateSpecifications();
|
||
|
||
for (ApiSpecification spec : apiSpecs) {
|
||
String openApiSpec = convertToOpenApi(spec);
|
||
writeToFile(String.format("docs/api/%s-openapi.yaml", spec.getServiceName()), openApiSpec);
|
||
|
||
String markdownDoc = convertToMarkdown(spec);
|
||
writeToFile(String.format("docs/api/%s.md", spec.getServiceName()), markdownDoc);
|
||
}
|
||
}
|
||
|
||
private void generateDeploymentDiagrams() {
|
||
// Генерируем PlantUML диаграммы
|
||
String deploymentDiagram = generatePlantUMLDeploymentDiagram();
|
||
writeToFile("docs/diagrams/deployment.puml", deploymentDiagram);
|
||
|
||
// Конвертируем в изображения
|
||
convertPlantUMLToImages("docs/diagrams/deployment.puml");
|
||
}
|
||
|
||
private void updateSystemMetrics() {
|
||
SystemMetrics metrics = collectSystemMetrics();
|
||
|
||
StringBuilder metricsDoc = new StringBuilder();
|
||
metricsDoc.append("# System Metrics\n\n");
|
||
metricsDoc.append(String.format("**Last Updated:** %s\n\n", LocalDateTime.now()));
|
||
metricsDoc.append("## Performance Metrics\n");
|
||
metricsDoc.append(String.format("- **Uptime:** %.2f%%\n", metrics.getUptime()));
|
||
metricsDoc.append(String.format("- **Response Time P95:** %dms\n", metrics.getResponseTimeP95()));
|
||
metricsDoc.append(String.format("- **Throughput:** %d RPS\n", metrics.getThroughput()));
|
||
metricsDoc.append(String.format("- **Error Rate:** %.2f%%\n", metrics.getErrorRate()));
|
||
|
||
writeToFile("docs/system-metrics.md", metricsDoc.toString());
|
||
}
|
||
|
||
private void notifyTeamAboutUpdates() {
|
||
// Отправляем уведомления в Slack/Teams
|
||
notificationService.sendNotification(
|
||
"Architecture documentation has been updated",
|
||
"docs/system-overview.md"
|
||
);
|
||
}
|
||
}
|
||
|
||
// Service Registry для автоматического обнаружения сервисов
|
||
@Component
|
||
public class ServiceRegistry {
|
||
|
||
@Autowired
|
||
private DiscoveryClient discoveryClient;
|
||
|
||
@Autowired
|
||
private RestTemplate restTemplate;
|
||
|
||
public List<ServiceInfo> getAllServices() {
|
||
List<ServiceInfo> services = new ArrayList<>();
|
||
|
||
List<String> serviceNames = discoveryClient.getServices();
|
||
|
||
for (String serviceName : serviceNames) {
|
||
List<ServiceInstance> instances = discoveryClient.getInstances(serviceName);
|
||
|
||
if (!instances.isEmpty()) {
|
||
ServiceInstance instance = instances.get(0);
|
||
|
||
try {
|
||
ServiceInfo serviceInfo = getServiceInfo(instance);
|
||
services.add(serviceInfo);
|
||
} catch (Exception e) {
|
||
log.warn("Failed to get info for service: {}", serviceName, e);
|
||
}
|
||
}
|
||
}
|
||
|
||
return services;
|
||
}
|
||
|
||
private ServiceInfo getServiceInfo(ServiceInstance instance) {
|
||
String healthUrl = String.format("http://%s:%d/actuator/health",
|
||
instance.getHost(), instance.getPort());
|
||
|
||
ResponseEntity<Map> healthResponse = restTemplate.getForEntity(healthUrl, Map.class);
|
||
|
||
String infoUrl = String.format("http://%s:%d/actuator/info",
|
||
instance.getHost(), instance.getPort());
|
||
|
||
ResponseEntity<Map> infoResponse = restTemplate.getForEntity(infoUrl, Map.class);
|
||
|
||
return ServiceInfo.builder()
|
||
.name(instance.getServiceId())
|
||
.version(getVersionFromInfo(infoResponse.getBody()))
|
||
.status(healthResponse.getStatusCode().is2xxSuccessful() ? "UP" : "DOWN")
|
||
.healthStatus(healthResponse.getBody().get("status").toString())
|
||
.dependencies(getDependenciesFromInfo(infoResponse.getBody()))
|
||
.build();
|
||
}
|
||
|
||
private String getVersionFromInfo(Map<String, Object> info) {
|
||
Map<String, Object> build = (Map<String, Object>) info.get("build");
|
||
return build != null ? build.get("version").toString() : "unknown";
|
||
}
|
||
|
||
private List<String> getDependenciesFromInfo(Map<String, Object> info) {
|
||
// Логика извлечения зависимостей из actuator/info
|
||
return new ArrayList<>();
|
||
}
|
||
}
|
||
|
||
// Database Schema Analyzer
|
||
@Component
|
||
public class DatabaseSchemaAnalyzer {
|
||
|
||
@Autowired
|
||
private JdbcTemplate jdbcTemplate;
|
||
|
||
public DatabaseSchema analyzeSchema() {
|
||
List<TableInfo> tables = new ArrayList<>();
|
||
|
||
// Получаем список таблиц
|
||
List<String> tableNames = getTableNames();
|
||
|
||
for (String tableName : tableNames) {
|
||
TableInfo table = analyzeTable(tableName);
|
||
tables.add(table);
|
||
}
|
||
|
||
return new DatabaseSchema(tables);
|
||
}
|
||
|
||
private List<String> getTableNames() {
|
||
String sql = "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'";
|
||
return jdbcTemplate.queryForList(sql, String.class);
|
||
}
|
||
|
||
private TableInfo analyzeTable(String tableName) {
|
||
// Получаем информацию о колонках
|
||
String columnSql = """
|
||
SELECT
|
||
column_name,
|
||
data_type,
|
||
is_nullable,
|
||
column_key,
|
||
column_comment
|
||
FROM information_schema.columns
|
||
WHERE table_name = ? AND table_schema = 'public'
|
||
""";
|
||
|
||
List<ColumnInfo> columns = jdbcTemplate.query(columnSql,
|
||
new Object[]{tableName},
|
||
(rs, rowNum) -> new ColumnInfo(
|
||
rs.getString("column_name"),
|
||
rs.getString("data_type"),
|
||
"YES".equals(rs.getString("is_nullable")),
|
||
rs.getString("column_key"),
|
||
rs.getString("column_comment")
|
||
)
|
||
);
|
||
|
||
// Получаем информацию о таблице
|
||
String tableSql = """
|
||
SELECT
|
||
table_comment,
|
||
table_rows,
|
||
data_length
|
||
FROM information_schema.tables
|
||
WHERE table_name = ? AND table_schema = 'public'
|
||
""";
|
||
|
||
Map<String, Object> tableInfo = jdbcTemplate.queryForMap(tableSql, tableName);
|
||
|
||
return new TableInfo(
|
||
tableName,
|
||
(String) tableInfo.get("table_comment"),
|
||
columns
|
||
);
|
||
}
|
||
}
|
||
|
||
// API Specification Generator
|
||
@Component
|
||
public class ApiSpecificationGenerator {
|
||
|
||
@Autowired
|
||
private RestTemplate restTemplate;
|
||
|
||
public List<ApiSpecification> generateSpecifications() {
|
||
List<ApiSpecification> specifications = new ArrayList<>();
|
||
|
||
List<ServiceInfo> services = serviceRegistry.getAllServices();
|
||
|
||
for (ServiceInfo service : services) {
|
||
try {
|
||
ApiSpecification spec = generateSpecificationForService(service);
|
||
specifications.add(spec);
|
||
} catch (Exception e) {
|
||
log.warn("Failed to generate API spec for service: {}", service.getName(), e);
|
||
}
|
||
}
|
||
|
||
return specifications;
|
||
}
|
||
|
||
private ApiSpecification generateSpecificationForService(ServiceInfo service) {
|
||
// Получаем OpenAPI спецификацию от сервиса
|
||
String openApiUrl = String.format("http://%s:%d/v3/api-docs",
|
||
service.getHost(), service.getPort());
|
||
|
||
ResponseEntity<Map> response = restTemplate.getForEntity(openApiUrl, Map.class);
|
||
Map<String, Object> openApiSpec = response.getBody();
|
||
|
||
return ApiSpecification.builder()
|
||
.serviceName(service.getName())
|
||
.version(service.getVersion())
|
||
.openApiSpec(openApiSpec)
|
||
.endpoints(extractEndpoints(openApiSpec))
|
||
.build();
|
||
}
|
||
|
||
private List<EndpointInfo> extractEndpoints(Map<String, Object> openApiSpec) {
|
||
// Логика извлечения endpoints из OpenAPI спецификации
|
||
return new ArrayList<>();
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 2. Just Enough Documentation
|
||
**Принцип:** Документируйте достаточно, но не слишком много.
|
||
|
||
#### 3. Audience-Driven
|
||
**Принцип:** Документация должна быть ориентирована на аудиторию.
|
||
|
||
### Типы архитектурной документации
|
||
|
||
#### 1. High-Level Architecture
|
||
- Общий обзор системы
|
||
- Основные компоненты
|
||
- Ключевые взаимодействия
|
||
|
||
#### 2. Detailed Design
|
||
- Детальное описание компонентов
|
||
- API спецификации
|
||
- Схемы баз данных
|
||
|
||
#### 3. Operational Documentation
|
||
- Процедуры развертывания
|
||
- Мониторинг и алертинг
|
||
- Troubleshooting guides
|
||
|
||
---
|
||
|
||
## C4 Model
|
||
|
||
### Что такое C4 Model
|
||
|
||
**C4 Model** — методология для документирования архитектуры программного обеспечения через четыре уровня абстракции.
|
||
|
||
### Уровни C4 Model
|
||
|
||
#### 1. Context (Контекст)
|
||
**Цель:** Показать систему в контексте её окружения.
|
||
|
||
```plantuml
|
||
@startuml
|
||
!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Context.puml
|
||
|
||
title System Context diagram for Internet Banking System
|
||
|
||
Person(customer, "Customer", "A customer of the bank, with personal bank accounts.")
|
||
Person(customerService, "Customer Service", "Customer service representative of the bank.")
|
||
|
||
System(bankingSystem, "Internet Banking System", "Allows customers to view information about their bank accounts, and make payments.")
|
||
|
||
System_Ext(emailSystem, "E-mail System", "The internal Microsoft Exchange e-mail system.")
|
||
System_Ext(mainframeBankingSystem, "Mainframe Banking System", "Stores all of the core banking information about customers, accounts, transactions, etc.")
|
||
|
||
Rel(customer, bankingSystem, "Uses", "HTTPS")
|
||
Rel(customerService, bankingSystem, "Uses", "HTTPS")
|
||
Rel(bankingSystem, emailSystem, "Sends e-mails using", "SMTP")
|
||
Rel(bankingSystem, mainframeBankingSystem, "Gets account information from, and makes payments using", "XML/HTTPS")
|
||
|
||
@enduml
|
||
```
|
||
|
||
#### 2. Container (Контейнеры)
|
||
**Цель:** Показать высокоуровневую форму системы.
|
||
|
||
```plantuml
|
||
@startuml
|
||
!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Container.puml
|
||
|
||
title Container diagram for Internet Banking System
|
||
|
||
Person(customer, "Customer", "A customer of the bank, with personal bank accounts.")
|
||
Person(customerService, "Customer Service", "Customer service representative of the bank.")
|
||
|
||
System_Boundary(c1, "Internet Banking System") {
|
||
Container(webApp, "Web Application", "Java, Spring MVC", "Delivers the static content and the Internet banking single page application.")
|
||
Container(api, "API Application", "Java, Spring Boot", "Provides Internet banking functionality via a JSON/HTTPS API.")
|
||
ContainerDb(database, "Database", "Oracle Database", "Stores user login information, hashed authentication credentials, access logs, etc.")
|
||
}
|
||
|
||
System_Ext(emailSystem, "E-mail System", "The internal Microsoft Exchange e-mail system.")
|
||
System_Ext(mainframeBankingSystem, "Mainframe Banking System", "Stores all of the core banking information about customers, accounts, transactions, etc.")
|
||
|
||
Rel(customer, webApp, "Uses", "HTTPS")
|
||
Rel(customerService, webApp, "Uses", "HTTPS")
|
||
Rel(webApp, api, "Makes API calls to", "JSON/HTTPS")
|
||
Rel(api, database, "Reads from and writes to", "JDBC")
|
||
Rel(api, emailSystem, "Sends e-mails using", "SMTP")
|
||
Rel(api, mainframeBankingSystem, "Makes API calls to", "XML/HTTPS")
|
||
|
||
@enduml
|
||
```
|
||
|
||
#### 3. Component (Компоненты)
|
||
**Цель:** Показать как контейнер состоит из компонентов.
|
||
|
||
```plantuml
|
||
@startuml
|
||
!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Component.puml
|
||
|
||
title Component diagram for Internet Banking System - API Application
|
||
|
||
Container_Boundary(api, "API Application") {
|
||
Component(security, "Security Component", "Spring Security", "Provides authentication and authorisation functionality.")
|
||
Component(controllers, "Controllers", "Spring MVC", "Handles HTTP requests and responses.")
|
||
Component(services, "Services", "Spring", "Contains the business logic.")
|
||
Component(repositories, "Repositories", "Spring Data", "Handles data access.")
|
||
}
|
||
|
||
ContainerDb(database, "Database", "Oracle Database", "Stores user login information, hashed authentication credentials, access logs, etc.")
|
||
System_Ext(mainframeBankingSystem, "Mainframe Banking System", "Stores all of the core banking information about customers, accounts, transactions, etc.")
|
||
|
||
Rel(controllers, security, "Uses")
|
||
Rel(controllers, services, "Uses")
|
||
Rel(services, repositories, "Uses")
|
||
Rel(repositories, database, "Reads from and writes to", "JDBC")
|
||
Rel(services, mainframeBankingSystem, "Makes API calls to", "XML/HTTPS")
|
||
|
||
@enduml
|
||
```
|
||
|
||
#### 4. Code (Код)
|
||
**Цель:** Показать детали реализации компонентов.
|
||
|
||
```plantuml
|
||
@startuml
|
||
!include https://raw.githubusercontent.com/plantuml-stdlib/C4-PlantUML/master/C4_Component.puml
|
||
|
||
title Component diagram for Internet Banking System - Services
|
||
|
||
Component_Boundary(services, "Services") {
|
||
Component(accountService, "AccountService", "Java Class", "Provides account related operations.")
|
||
Component(securityService, "SecurityService", "Java Class", "Provides security related operations.")
|
||
Component(emailService, "EmailService", "Java Class", "Provides email related operations.")
|
||
}
|
||
|
||
ComponentDb(database, "Database", "Oracle Database", "Stores user login information, hashed authentication credentials, access logs, etc.")
|
||
System_Ext(mainframeBankingSystem, "Mainframe Banking System", "Stores all of the core banking information about customers, accounts, transactions, etc.")
|
||
|
||
Rel(accountService, database, "Reads from and writes to", "JDBC")
|
||
Rel(accountService, mainframeBankingSystem, "Makes API calls to", "XML/HTTPS")
|
||
Rel(securityService, database, "Reads from and writes to", "JDBC")
|
||
Rel(emailService, mainframeBankingSystem, "Makes API calls to", "XML/HTTPS")
|
||
|
||
@enduml
|
||
```
|
||
|
||
### Лучшие практики C4 Model
|
||
|
||
#### 1. Используйте правильный уровень детализации
|
||
- Context: для стейкхолдеров
|
||
- Container: для разработчиков
|
||
- Component: для архитекторов
|
||
- Code: для разработчиков
|
||
|
||
#### 2. Фокусируйтесь на взаимодействиях
|
||
- Показывайте потоки данных
|
||
- Указывайте протоколы
|
||
- Описывайте интерфейсы
|
||
|
||
#### 3. Обновляйте документацию
|
||
- Синхронизируйте с кодом
|
||
- Версионируйте изменения
|
||
- Отслеживайте устаревшую документацию
|
||
|
||
---
|
||
|
||
## PlantUML
|
||
|
||
### Что такое PlantUML
|
||
|
||
**PlantUML** — инструмент для создания диаграмм из текстового описания.
|
||
|
||
### Типы диаграмм
|
||
|
||
#### 1. Sequence Diagrams (Диаграммы последовательности)
|
||
```plantuml
|
||
@startuml
|
||
title Order Processing Sequence
|
||
|
||
actor Customer
|
||
participant "Web App" as Web
|
||
participant "API Gateway" as Gateway
|
||
participant "Order Service" as Order
|
||
participant "Payment Service" as Payment
|
||
participant "Inventory Service" as Inventory
|
||
database "Database" as DB
|
||
|
||
Customer -> Web: Create Order
|
||
Web -> Gateway: POST /api/orders
|
||
Gateway -> Order: Create Order Request
|
||
Order -> Inventory: Check Availability
|
||
Inventory --> Order: Availability Status
|
||
Order -> Payment: Process Payment
|
||
Payment --> Order: Payment Result
|
||
Order -> DB: Save Order
|
||
DB --> Order: Order Saved
|
||
Order --> Gateway: Order Created
|
||
Gateway --> Web: Order Response
|
||
Web --> Customer: Order Confirmation
|
||
@enduml
|
||
```
|
||
|
||
#### 2. Class Diagrams (Диаграммы классов)
|
||
```plantuml
|
||
@startuml
|
||
title Order Domain Model
|
||
|
||
class Order {
|
||
- id: OrderId
|
||
- customerId: CustomerId
|
||
- items: List<OrderItem>
|
||
- status: OrderStatus
|
||
- totalAmount: Money
|
||
- createdAt: LocalDateTime
|
||
+ addItem(item: OrderItem): void
|
||
+ removeItem(itemId: ItemId): void
|
||
+ confirm(): void
|
||
+ cancel(): void
|
||
}
|
||
|
||
class OrderItem {
|
||
- id: ItemId
|
||
- productId: ProductId
|
||
- quantity: int
|
||
- unitPrice: Money
|
||
+ getTotalPrice(): Money
|
||
}
|
||
|
||
class Money {
|
||
- amount: BigDecimal
|
||
- currency: Currency
|
||
+ add(other: Money): Money
|
||
+ multiply(factor: BigDecimal): Money
|
||
}
|
||
|
||
Order ||--o{ OrderItem : contains
|
||
OrderItem ||--|| Money : unitPrice
|
||
Order ||--|| Money : totalAmount
|
||
|
||
@enduml
|
||
```
|
||
|
||
#### 3. Activity Diagrams (Диаграммы активности)
|
||
```plantuml
|
||
@startuml
|
||
title Order Processing Flow
|
||
|
||
start
|
||
:Receive Order Request;
|
||
:Validate Order Data;
|
||
if (Data Valid?) then (yes)
|
||
:Check Inventory;
|
||
if (Items Available?) then (yes)
|
||
:Reserve Items;
|
||
:Process Payment;
|
||
if (Payment Successful?) then (yes)
|
||
:Create Order;
|
||
:Send Confirmation;
|
||
stop
|
||
else (no)
|
||
:Release Reserved Items;
|
||
:Return Payment Error;
|
||
stop
|
||
endif
|
||
else (no)
|
||
:Return Inventory Error;
|
||
stop
|
||
endif
|
||
else (no)
|
||
:Return Validation Error;
|
||
stop
|
||
endif
|
||
@enduml
|
||
```
|
||
|
||
#### 4. Deployment Diagrams (Диаграммы развертывания)
|
||
```plantuml
|
||
@startuml
|
||
title System Deployment
|
||
|
||
node "Load Balancer" {
|
||
component "NGINX" as nginx
|
||
}
|
||
|
||
node "Application Servers" {
|
||
component "API Gateway" as gateway
|
||
component "Order Service" as order
|
||
component "Payment Service" as payment
|
||
component "User Service" as user
|
||
}
|
||
|
||
node "Database Servers" {
|
||
database "PostgreSQL" as db
|
||
database "Redis" as cache
|
||
}
|
||
|
||
node "External Services" {
|
||
component "Payment Gateway" as ext_payment
|
||
component "Email Service" as ext_email
|
||
}
|
||
|
||
nginx --> gateway
|
||
gateway --> order
|
||
gateway --> payment
|
||
gateway --> user
|
||
order --> db
|
||
payment --> db
|
||
user --> db
|
||
order --> cache
|
||
payment --> cache
|
||
user --> cache
|
||
payment --> ext_payment
|
||
user --> ext_email
|
||
|
||
@enduml
|
||
```
|
||
|
||
### Лучшие практики PlantUML
|
||
|
||
#### 1. Используйте семантические имена
|
||
```plantuml
|
||
# Хорошо
|
||
participant "Order Service" as OrderService
|
||
participant "Payment Service" as PaymentService
|
||
|
||
# Плохо
|
||
participant "Service1" as S1
|
||
participant "Service2" as S2
|
||
```
|
||
|
||
#### 2. Группируйте связанные элементы
|
||
```plantuml
|
||
box "Order Processing" #LightBlue
|
||
participant "Order Service" as Order
|
||
participant "Inventory Service" as Inventory
|
||
end box
|
||
|
||
box "Payment Processing" #LightGreen
|
||
participant "Payment Service" as Payment
|
||
participant "Payment Gateway" as Gateway
|
||
end box
|
||
```
|
||
|
||
#### 3. Используйте цвета для выделения
|
||
```plantuml
|
||
actor Customer #LightBlue
|
||
participant "Web App" as Web #LightGreen
|
||
participant "API Gateway" as Gateway #LightYellow
|
||
database "Database" as DB #LightCoral
|
||
```
|
||
|
||
---
|
||
|
||
## Architecture Decision Records (ADR)
|
||
|
||
### Что такое ADR
|
||
|
||
**Architecture Decision Record (ADR)** — документ, который фиксирует важное архитектурное решение, принятое в проекте.
|
||
|
||
### Структура ADR
|
||
|
||
#### 1. Статус
|
||
- **Proposed** — предложено
|
||
- **Accepted** — принято
|
||
- **Deprecated** — устарело
|
||
- **Superseded** — заменено
|
||
|
||
#### 2. Контекст
|
||
- Описание ситуации
|
||
- Проблема, которую нужно решить
|
||
- Ограничения и требования
|
||
|
||
#### 3. Решение
|
||
- Описание принятого решения
|
||
- Обоснование выбора
|
||
- Альтернативы, которые рассматривались
|
||
|
||
#### 4. Последствия
|
||
- Положительные последствия
|
||
- Отрицательные последствия
|
||
- Риски и митигации
|
||
|
||
### Пример ADR
|
||
|
||
**Технический пример - Advanced ADR Management:**
|
||
|
||
```markdown
|
||
# ADR-001: Использование микросервисной архитектуры
|
||
|
||
## Статус
|
||
Принято
|
||
|
||
## Дата
|
||
2024-01-15
|
||
|
||
## Контекст
|
||
Наша система электронной коммерции растет и становится сложнее. Монолитная архитектура создает следующие проблемы:
|
||
|
||
- Сложность развертывания изменений
|
||
- Технологическая привязка к одному стеку
|
||
- Сложность масштабирования отдельных компонентов
|
||
- Риск каскадных отказов
|
||
|
||
Команда разработки увеличилась до 15 человек, и мы планируем дальнейший рост.
|
||
|
||
## Решение
|
||
Мы решили перейти к микросервисной архитектуре со следующими принципами:
|
||
|
||
- Каждый сервис отвечает за один бизнес-домен
|
||
- Сервисы независимо развертываются
|
||
- Сервисы имеют собственные базы данных
|
||
- Коммуникация через REST API и события
|
||
|
||
## Альтернативы
|
||
1. **Модульный монолит** — разделение на модули внутри одного приложения
|
||
2. **SOA** — сервисно-ориентированная архитектура
|
||
3. **Оставить монолит** — продолжить развитие текущей архитектуры
|
||
|
||
## Последствия
|
||
|
||
### Положительные
|
||
- Независимое развертывание сервисов
|
||
- Возможность использования разных технологий
|
||
- Лучшая масштабируемость
|
||
- Изоляция отказов
|
||
|
||
### Отрицательные
|
||
- Увеличение сложности системы
|
||
- Проблемы с распределенными транзакциями
|
||
- Сложность мониторинга
|
||
- Overhead коммуникации
|
||
|
||
### Риски
|
||
- **Сложность отладки** — митигация через distributed tracing
|
||
- **Проблемы с данными** — митигация через event sourcing
|
||
- **Сетевые задержки** — митигация через кэширование
|
||
|
||
## Реализация
|
||
- Начать с выделения User Service
|
||
- Использовать API Gateway для маршрутизации
|
||
- Настроить мониторинг и логирование
|
||
- Постепенно выделять остальные сервисы
|
||
|
||
## Метрики успеха
|
||
- Время развертывания: < 5 минут
|
||
- Время восстановления: < 30 минут
|
||
- Доступность: > 99.9%
|
||
- Производительность: P95 < 200ms
|
||
```
|
||
|
||
**Технический пример - ADR Management System:**
|
||
|
||
```java
|
||
// ADR Management Service
|
||
@Service
|
||
public class ADRManagementService {
|
||
|
||
@Autowired
|
||
private ADRRepository adrRepository;
|
||
|
||
@Autowired
|
||
private NotificationService notificationService;
|
||
|
||
@Autowired
|
||
private VersionControlService versionControlService;
|
||
|
||
public ADR createADR(CreateADRRequest request) {
|
||
// Валидация
|
||
validateADRRequest(request);
|
||
|
||
// Создание ADR
|
||
ADR adr = ADR.builder()
|
||
.id(generateADRId())
|
||
.title(request.getTitle())
|
||
.status(ADRStatus.PROPOSED)
|
||
.context(request.getContext())
|
||
.decision(request.getDecision())
|
||
.alternatives(request.getAlternatives())
|
||
.consequences(request.getConsequences())
|
||
.author(request.getAuthor())
|
||
.createdDate(LocalDateTime.now())
|
||
.build();
|
||
|
||
// Сохранение в БД
|
||
adr = adrRepository.save(adr);
|
||
|
||
// Создание файла в Git
|
||
createADRFile(adr);
|
||
|
||
// Уведомление команды
|
||
notifyTeamAboutNewADR(adr);
|
||
|
||
return adr;
|
||
}
|
||
|
||
public ADR updateADRStatus(Long adrId, ADRStatus newStatus, String comment) {
|
||
ADR adr = adrRepository.findById(adrId)
|
||
.orElseThrow(() -> new ADRNotFoundException("ADR not found"));
|
||
|
||
// Проверяем права на изменение статуса
|
||
validateStatusChangePermission(adr, newStatus);
|
||
|
||
// Обновляем статус
|
||
adr.setStatus(newStatus);
|
||
adr.setLastModified(LocalDateTime.now());
|
||
adr.setStatusComment(comment);
|
||
|
||
adr = adrRepository.save(adr);
|
||
|
||
// Обновляем файл в Git
|
||
updateADRFile(adr);
|
||
|
||
// Уведомляем команду об изменении
|
||
notifyTeamAboutStatusChange(adr);
|
||
|
||
return adr;
|
||
}
|
||
|
||
public List<ADR> getADRsByStatus(ADRStatus status) {
|
||
return adrRepository.findByStatus(status);
|
||
}
|
||
|
||
public List<ADR> getADRsByDomain(String domain) {
|
||
return adrRepository.findByDomain(domain);
|
||
}
|
||
|
||
public ADRImpactAnalysis analyzeImpact(Long adrId) {
|
||
ADR adr = adrRepository.findById(adrId)
|
||
.orElseThrow(() -> new ADRNotFoundException("ADR not found"));
|
||
|
||
return ADRImpactAnalysis.builder()
|
||
.affectedServices(analyzeAffectedServices(adr))
|
||
.requiredChanges(analyzeRequiredChanges(adr))
|
||
.estimatedEffort(estimateEffort(adr))
|
||
.risks(identifyRisks(adr))
|
||
.build();
|
||
}
|
||
|
||
@Scheduled(cron = "0 0 9 * * MON") // Каждый понедельник в 9:00
|
||
public void reviewADRs() {
|
||
List<ADR> proposedADRs = getADRsByStatus(ADRStatus.PROPOSED);
|
||
|
||
for (ADR adr : proposedADRs) {
|
||
if (isADRStale(adr)) {
|
||
notifyTeamAboutStaleADR(adr);
|
||
}
|
||
}
|
||
|
||
// Генерируем отчет по ADR
|
||
generateADRReport();
|
||
}
|
||
|
||
private void createADRFile(ADR adr) {
|
||
String fileName = String.format("adr/%03d-%s.md", adr.getId(),
|
||
adr.getTitle().toLowerCase().replaceAll("\\s+", "-"));
|
||
|
||
String content = generateADRMarkdown(adr);
|
||
|
||
versionControlService.createFile(fileName, content,
|
||
String.format("Add ADR-%d: %s", adr.getId(), adr.getTitle()));
|
||
}
|
||
|
||
private String generateADRMarkdown(ADR adr) {
|
||
StringBuilder markdown = new StringBuilder();
|
||
|
||
markdown.append(String.format("# ADR-%03d: %s\n\n", adr.getId(), adr.getTitle()));
|
||
markdown.append("## Статус\n");
|
||
markdown.append(adr.getStatus().getDisplayName()).append("\n\n");
|
||
markdown.append("## Дата\n");
|
||
markdown.append(adr.getCreatedDate().toLocalDate()).append("\n\n");
|
||
markdown.append("## Автор\n");
|
||
markdown.append(adr.getAuthor()).append("\n\n");
|
||
markdown.append("## Контекст\n");
|
||
markdown.append(adr.getContext()).append("\n\n");
|
||
markdown.append("## Решение\n");
|
||
markdown.append(adr.getDecision()).append("\n\n");
|
||
|
||
if (adr.getAlternatives() != null && !adr.getAlternatives().isEmpty()) {
|
||
markdown.append("## Альтернативы\n");
|
||
for (String alternative : adr.getAlternatives()) {
|
||
markdown.append("- ").append(alternative).append("\n");
|
||
}
|
||
markdown.append("\n");
|
||
}
|
||
|
||
markdown.append("## Последствия\n");
|
||
markdown.append(adr.getConsequences()).append("\n\n");
|
||
|
||
if (adr.getStatusComment() != null) {
|
||
markdown.append("## Комментарии\n");
|
||
markdown.append(adr.getStatusComment()).append("\n\n");
|
||
}
|
||
|
||
return markdown.toString();
|
||
}
|
||
|
||
private boolean isADRStale(ADR adr) {
|
||
return adr.getCreatedDate().isBefore(LocalDateTime.now().minusWeeks(2));
|
||
}
|
||
|
||
private void generateADRReport() {
|
||
List<ADR> allADRs = adrRepository.findAll();
|
||
|
||
ADRReport report = ADRReport.builder()
|
||
.totalADRs(allADRs.size())
|
||
.proposedADRs(countByStatus(allADRs, ADRStatus.PROPOSED))
|
||
.acceptedADRs(countByStatus(allADRs, ADRStatus.ACCEPTED))
|
||
.deprecatedADRs(countByStatus(allADRs, ADRStatus.DEPRECATED))
|
||
.supersededADRs(countByStatus(allADRs, ADRStatus.SUPERSEDED))
|
||
.recentADRs(getRecentADRs(allADRs, 30))
|
||
.staleADRs(getStaleADRs(allADRs))
|
||
.build();
|
||
|
||
String reportContent = generateReportMarkdown(report);
|
||
writeToFile("docs/adr-report.md", reportContent);
|
||
|
||
// Отправляем отчет команде
|
||
notificationService.sendReport("ADR Report", reportContent);
|
||
}
|
||
}
|
||
|
||
// ADR Entity
|
||
@Entity
|
||
@Table(name = "architecture_decision_records")
|
||
public class ADR {
|
||
|
||
@Id
|
||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||
private Long id;
|
||
|
||
@Column(nullable = false)
|
||
private String title;
|
||
|
||
@Enumerated(EnumType.STRING)
|
||
@Column(nullable = false)
|
||
private ADRStatus status;
|
||
|
||
@Column(columnDefinition = "TEXT")
|
||
private String context;
|
||
|
||
@Column(columnDefinition = "TEXT")
|
||
private String decision;
|
||
|
||
@ElementCollection
|
||
@CollectionTable(name = "adr_alternatives", joinColumns = @JoinColumn(name = "adr_id"))
|
||
@Column(name = "alternative", columnDefinition = "TEXT")
|
||
private List<String> alternatives;
|
||
|
||
@Column(columnDefinition = "TEXT")
|
||
private String consequences;
|
||
|
||
@Column(nullable = false)
|
||
private String author;
|
||
|
||
@Column(nullable = false)
|
||
private LocalDateTime createdDate;
|
||
|
||
private LocalDateTime lastModified;
|
||
|
||
private String statusComment;
|
||
|
||
private String domain;
|
||
|
||
// Getters and setters
|
||
}
|
||
|
||
// ADR Status Enum
|
||
public enum ADRStatus {
|
||
PROPOSED("Предложено"),
|
||
ACCEPTED("Принято"),
|
||
DEPRECATED("Устарело"),
|
||
SUPERSEDED("Заменено");
|
||
|
||
private final String displayName;
|
||
|
||
ADRStatus(String displayName) {
|
||
this.displayName = displayName;
|
||
}
|
||
|
||
public String getDisplayName() {
|
||
return displayName;
|
||
}
|
||
}
|
||
|
||
// ADR Impact Analysis
|
||
public class ADRImpactAnalysis {
|
||
private List<String> affectedServices;
|
||
private List<String> requiredChanges;
|
||
private String estimatedEffort;
|
||
private List<String> risks;
|
||
|
||
// Builder pattern implementation
|
||
public static Builder builder() {
|
||
return new Builder();
|
||
}
|
||
|
||
public static class Builder {
|
||
private List<String> affectedServices = new ArrayList<>();
|
||
private List<String> requiredChanges = new ArrayList<>();
|
||
private String estimatedEffort;
|
||
private List<String> risks = new ArrayList<>();
|
||
|
||
public Builder affectedServices(List<String> affectedServices) {
|
||
this.affectedServices = affectedServices;
|
||
return this;
|
||
}
|
||
|
||
public Builder requiredChanges(List<String> requiredChanges) {
|
||
this.requiredChanges = requiredChanges;
|
||
return this;
|
||
}
|
||
|
||
public Builder estimatedEffort(String estimatedEffort) {
|
||
this.estimatedEffort = estimatedEffort;
|
||
return this;
|
||
}
|
||
|
||
public Builder risks(List<String> risks) {
|
||
this.risks = risks;
|
||
return this;
|
||
}
|
||
|
||
public ADRImpactAnalysis build() {
|
||
ADRImpactAnalysis analysis = new ADRImpactAnalysis();
|
||
analysis.affectedServices = this.affectedServices;
|
||
analysis.requiredChanges = this.requiredChanges;
|
||
analysis.estimatedEffort = this.estimatedEffort;
|
||
analysis.risks = this.risks;
|
||
return analysis;
|
||
}
|
||
}
|
||
}
|
||
|
||
// ADR Repository
|
||
@Repository
|
||
public interface ADRRepository extends JpaRepository<ADR, Long> {
|
||
|
||
List<ADR> findByStatus(ADRStatus status);
|
||
|
||
List<ADR> findByDomain(String domain);
|
||
|
||
List<ADR> findByAuthor(String author);
|
||
|
||
List<ADR> findByCreatedDateBetween(LocalDateTime start, LocalDateTime end);
|
||
|
||
@Query("SELECT a FROM ADR a WHERE a.status = 'PROPOSED' AND a.createdDate < :cutoffDate")
|
||
List<ADR> findStaleProposedADRs(@Param("cutoffDate") LocalDateTime cutoffDate);
|
||
|
||
@Query("SELECT a FROM ADR a ORDER BY a.createdDate DESC")
|
||
List<ADR> findRecentADRs(Pageable pageable);
|
||
}
|
||
```
|
||
|
||
**Node.js примеры - ADR Management System:**
|
||
|
||
```typescript
|
||
import express, { Request, Response, NextFunction } from 'express';
|
||
import { PrismaClient } from '@prisma/client';
|
||
import fs from 'fs/promises';
|
||
import path from 'path';
|
||
import { z } from 'zod';
|
||
|
||
const app = express();
|
||
const prisma = new PrismaClient();
|
||
|
||
// ADR Management Service
|
||
class ADRManagementService {
|
||
constructor(
|
||
private adrRepository: ADRRepository,
|
||
private notificationService: NotificationService,
|
||
private versionControlService: VersionControlService
|
||
) {}
|
||
|
||
async createADR(request: CreateADRRequest): Promise<ADR> {
|
||
this.validateADRRequest(request);
|
||
|
||
// Создание ADR
|
||
const adr = await this.adrRepository.save({
|
||
id: await this.generateADRId(),
|
||
title: request.title,
|
||
domain: request.domain,
|
||
author: request.author,
|
||
status: ADRStatus.PROPOSED,
|
||
context: request.context,
|
||
decision: request.decision,
|
||
alternatives: request.alternatives,
|
||
consequences: request.consequences,
|
||
implementation: request.implementation,
|
||
createdDate: new Date(),
|
||
updatedDate: new Date()
|
||
});
|
||
|
||
// Создание файла ADR
|
||
await this.createADRFile(adr);
|
||
|
||
// Интеграция с системой контроля версий
|
||
await this.versionControlService.commitADR(adr);
|
||
|
||
// Уведомление команды
|
||
await this.notificationService.notifyTeamAboutNewADR(adr);
|
||
|
||
return adr;
|
||
}
|
||
|
||
async updateADRStatus(adrId: number, newStatus: ADRStatus, comment: string): Promise<ADR> {
|
||
const adr = await this.adrRepository.findById(adrId);
|
||
if (!adr) {
|
||
throw new Error('ADR not found');
|
||
}
|
||
|
||
// Обновление статуса
|
||
adr.status = newStatus;
|
||
adr.updatedDate = new Date();
|
||
if (comment) {
|
||
adr.comments = adr.comments || [];
|
||
adr.comments.push({
|
||
author: 'System',
|
||
content: comment,
|
||
timestamp: new Date()
|
||
});
|
||
}
|
||
|
||
const updatedADR = await this.adrRepository.save(adr);
|
||
|
||
// Обновление файла ADR
|
||
await this.updateADRFile(updatedADR);
|
||
|
||
// Уведомление команды об изменении
|
||
await this.notificationService.notifyTeamAboutADRUpdate(updatedADR);
|
||
|
||
return updatedADR;
|
||
}
|
||
|
||
async getADRsByStatus(status: ADRStatus): Promise<ADR[]> {
|
||
return await this.adrRepository.findByStatus(status);
|
||
}
|
||
|
||
async getADRsByDomain(domain: string): Promise<ADR[]> {
|
||
return await this.adrRepository.findByDomain(domain);
|
||
}
|
||
|
||
async analyzeImpact(adrId: number): Promise<ADRImpactAnalysis> {
|
||
const adr = await this.adrRepository.findById(adrId);
|
||
if (!adr) {
|
||
throw new Error('ADR not found');
|
||
}
|
||
|
||
return {
|
||
adrId: adr.id,
|
||
affectedServices: await this.getAffectedServices(adr),
|
||
estimatedEffort: await this.estimateEffort(adr),
|
||
risks: await this.identifyRisks(adr),
|
||
dependencies: await this.getDependencies(adr),
|
||
timeline: await this.estimateTimeline(adr)
|
||
};
|
||
}
|
||
|
||
@Scheduled('0 0 9 * * MON') // Каждый понедельник в 9:00
|
||
async reviewADRs(): Promise<void> {
|
||
const proposedADRs = await this.getADRsByStatus(ADRStatus.PROPOSED);
|
||
|
||
for (const adr of proposedADRs) {
|
||
if (this.isADRStale(adr)) {
|
||
await this.notificationService.notifyTeamAboutStaleADR(adr);
|
||
}
|
||
}
|
||
|
||
// Генерируем отчет по ADR
|
||
await this.generateADRReport();
|
||
}
|
||
|
||
private async createADRFile(adr: ADR): Promise<void> {
|
||
const adrDir = path.join(process.cwd(), 'docs', 'adr');
|
||
await fs.mkdir(adrDir, { recursive: true });
|
||
|
||
const content = this.generateADRMarkdown(adr);
|
||
const filePath = path.join(adrDir, `adr-${adr.id.toString().padStart(3, '0')}.md`);
|
||
|
||
await fs.writeFile(filePath, content, 'utf8');
|
||
}
|
||
|
||
private generateADRMarkdown(adr: ADR): string {
|
||
const markdown: string[] = [];
|
||
|
||
markdown.push(`# ADR-${adr.id.toString().padStart(3, '0')}: ${adr.title}\n`);
|
||
markdown.push(`## Статус\n${adr.status}\n`);
|
||
markdown.push(`## Дата\n${adr.createdDate.toISOString().split('T')[0]}\n`);
|
||
markdown.push(`## Автор\n${adr.author}\n`);
|
||
markdown.push(`## Домен\n${adr.domain}\n`);
|
||
markdown.push(`## Контекст\n${adr.context}\n`);
|
||
markdown.push(`## Решение\n${adr.decision}\n`);
|
||
|
||
if (adr.alternatives) {
|
||
markdown.push(`## Альтернативы\n${adr.alternatives}\n`);
|
||
}
|
||
|
||
if (adr.consequences) {
|
||
markdown.push(`## Последствия\n${adr.consequences}\n`);
|
||
}
|
||
|
||
if (adr.implementation) {
|
||
markdown.push(`## Реализация\n${adr.implementation}\n`);
|
||
}
|
||
|
||
return markdown.join('');
|
||
}
|
||
|
||
private async updateADRFile(adr: ADR): Promise<void> {
|
||
const filePath = path.join(process.cwd(), 'docs', 'adr', `adr-${adr.id.toString().padStart(3, '0')}.md`);
|
||
const content = this.generateADRMarkdown(adr);
|
||
await fs.writeFile(filePath, content, 'utf8');
|
||
}
|
||
|
||
private isADRStale(adr: ADR): boolean {
|
||
const cutoffDate = new Date();
|
||
cutoffDate.setDate(cutoffDate.getDate() - 14); // 2 недели
|
||
return adr.createdDate < cutoffDate;
|
||
}
|
||
|
||
private async generateADRReport(): Promise<void> {
|
||
const allADRs = await this.adrRepository.findAll();
|
||
|
||
const report = {
|
||
totalADRs: allADRs.length,
|
||
proposedADRs: allADRs.filter(adr => adr.status === ADRStatus.PROPOSED).length,
|
||
acceptedADRs: allADRs.filter(adr => adr.status === ADRStatus.ACCEPTED).length,
|
||
deprecatedADRs: allADRs.filter(adr => adr.status === ADRStatus.DEPRECATED).length,
|
||
supersededADRs: allADRs.filter(adr => adr.status === ADRStatus.SUPERSEDED).length,
|
||
recentADRs: this.getRecentADRs(allADRs, 30),
|
||
staleADRs: this.getStaleADRs(allADRs)
|
||
};
|
||
|
||
const reportContent = this.formatADRReport(report);
|
||
await this.notificationService.sendReport('ADR Report', reportContent);
|
||
}
|
||
|
||
private getRecentADRs(adrs: ADR[], days: number): ADR[] {
|
||
const cutoffDate = new Date();
|
||
cutoffDate.setDate(cutoffDate.getDate() - days);
|
||
return adrs.filter(adr => adr.createdDate >= cutoffDate);
|
||
}
|
||
|
||
private getStaleADRs(adrs: ADR[]): ADR[] {
|
||
return adrs.filter(adr => this.isADRStale(adr));
|
||
}
|
||
|
||
private formatADRReport(report: any): string {
|
||
return `
|
||
# ADR Report - ${new Date().toISOString().split('T')[0]}
|
||
|
||
## Summary
|
||
- Total ADRs: ${report.totalADRs}
|
||
- Proposed: ${report.proposedADRs}
|
||
- Accepted: ${report.acceptedADRs}
|
||
- Deprecated: ${report.deprecatedADRs}
|
||
- Superseded: ${report.supersededADRs}
|
||
|
||
## Recent ADRs (last 30 days)
|
||
${report.recentADRs.map(adr => `- ADR-${adr.id}: ${adr.title}`).join('\n')}
|
||
|
||
## Stale ADRs (older than 14 days)
|
||
${report.staleADRs.map(adr => `- ADR-${adr.id}: ${adr.title}`).join('\n')}
|
||
`;
|
||
}
|
||
|
||
private validateADRRequest(request: CreateADRRequest): void {
|
||
if (!request.title || !request.title.trim()) {
|
||
throw new Error('Title is required');
|
||
}
|
||
if (!request.domain || !request.domain.trim()) {
|
||
throw new Error('Domain is required');
|
||
}
|
||
if (!request.author || !request.author.trim()) {
|
||
throw new Error('Author is required');
|
||
}
|
||
if (!request.context || !request.context.trim()) {
|
||
throw new Error('Context is required');
|
||
}
|
||
if (!request.decision || !request.decision.trim()) {
|
||
throw new Error('Decision is required');
|
||
}
|
||
}
|
||
|
||
private async generateADRId(): Promise<number> {
|
||
const lastADR = await this.adrRepository.findLast();
|
||
return (lastADR?.id || 0) + 1;
|
||
}
|
||
|
||
private async getAffectedServices(adr: ADR): Promise<string[]> {
|
||
// Логика определения затронутых сервисов
|
||
return ['user-service', 'order-service', 'payment-service'];
|
||
}
|
||
|
||
private async estimateEffort(adr: ADR): Promise<string> {
|
||
// Логика оценки усилий
|
||
return 'Medium (2-4 weeks)';
|
||
}
|
||
|
||
private async identifyRisks(adr: ADR): Promise<string[]> {
|
||
// Логика идентификации рисков
|
||
return ['Breaking changes', 'Performance impact', 'Data migration'];
|
||
}
|
||
|
||
private async getDependencies(adr: ADR): Promise<string[]> {
|
||
// Логика определения зависимостей
|
||
return ['Database schema changes', 'API updates', 'Infrastructure changes'];
|
||
}
|
||
|
||
private async estimateTimeline(adr: ADR): Promise<string> {
|
||
// Логика оценки временных рамок
|
||
return 'Q2 2024';
|
||
}
|
||
}
|
||
|
||
// ADR Entity
|
||
interface ADR {
|
||
id: number;
|
||
title: string;
|
||
domain: string;
|
||
author: string;
|
||
status: ADRStatus;
|
||
context: string;
|
||
decision: string;
|
||
alternatives?: string;
|
||
consequences?: string;
|
||
implementation?: string;
|
||
comments?: ADRComment[];
|
||
createdDate: Date;
|
||
updatedDate: Date;
|
||
}
|
||
|
||
interface ADRComment {
|
||
author: string;
|
||
content: string;
|
||
timestamp: Date;
|
||
}
|
||
|
||
// ADR Status Enum
|
||
enum ADRStatus {
|
||
PROPOSED = 'PROPOSED',
|
||
ACCEPTED = 'ACCEPTED',
|
||
DEPRECATED = 'DEPRECATED',
|
||
SUPERSEDED = 'SUPERSEDED'
|
||
}
|
||
|
||
// ADR Impact Analysis
|
||
interface ADRImpactAnalysis {
|
||
adrId: number;
|
||
affectedServices: string[];
|
||
estimatedEffort: string;
|
||
risks: string[];
|
||
dependencies: string[];
|
||
timeline: string;
|
||
}
|
||
|
||
// Repository
|
||
interface ADRRepository {
|
||
save(adr: Omit<ADR, 'id'>): Promise<ADR>;
|
||
findById(id: number): Promise<ADR | null>;
|
||
findByStatus(status: ADRStatus): Promise<ADR[]>;
|
||
findByDomain(domain: string): Promise<ADR[]>;
|
||
findAll(): Promise<ADR[]>;
|
||
findLast(): Promise<ADR | null>;
|
||
}
|
||
|
||
class PrismaADRRepository implements ADRRepository {
|
||
async save(adr: Omit<ADR, 'id'>): Promise<ADR> {
|
||
return await prisma.adr.create({ data: adr });
|
||
}
|
||
|
||
async findById(id: number): Promise<ADR | null> {
|
||
return await prisma.adr.findUnique({ where: { id } });
|
||
}
|
||
|
||
async findByStatus(status: ADRStatus): Promise<ADR[]> {
|
||
return await prisma.adr.findMany({ where: { status } });
|
||
}
|
||
|
||
async findByDomain(domain: string): Promise<ADR[]> {
|
||
return await prisma.adr.findMany({ where: { domain } });
|
||
}
|
||
|
||
async findAll(): Promise<ADR[]> {
|
||
return await prisma.adr.findMany();
|
||
}
|
||
|
||
async findLast(): Promise<ADR | null> {
|
||
return await prisma.adr.findFirst({
|
||
orderBy: { id: 'desc' }
|
||
});
|
||
}
|
||
}
|
||
|
||
// Services
|
||
class NotificationService {
|
||
async notifyTeamAboutNewADR(adr: ADR): Promise<void> {
|
||
console.log(`New ADR created: ADR-${adr.id} - ${adr.title}`);
|
||
// Реализация уведомлений (email, Slack, etc.)
|
||
}
|
||
|
||
async notifyTeamAboutADRUpdate(adr: ADR): Promise<void> {
|
||
console.log(`ADR updated: ADR-${adr.id} - Status: ${adr.status}`);
|
||
}
|
||
|
||
async notifyTeamAboutStaleADR(adr: ADR): Promise<void> {
|
||
console.log(`Stale ADR detected: ADR-${adr.id} - ${adr.title}`);
|
||
}
|
||
|
||
async sendReport(title: string, content: string): Promise<void> {
|
||
console.log(`Report: ${title}\n${content}`);
|
||
}
|
||
}
|
||
|
||
class VersionControlService {
|
||
async commitADR(adr: ADR): Promise<void> {
|
||
console.log(`Committing ADR-${adr.id} to version control`);
|
||
// Интеграция с Git
|
||
}
|
||
}
|
||
|
||
// Validation schemas
|
||
const createADRSchema = z.object({
|
||
title: z.string().min(1, 'Title is required'),
|
||
domain: z.string().min(1, 'Domain is required'),
|
||
author: z.string().min(1, 'Author is required'),
|
||
context: z.string().min(1, 'Context is required'),
|
||
decision: z.string().min(1, 'Decision is required'),
|
||
alternatives: z.string().optional(),
|
||
consequences: z.string().optional(),
|
||
implementation: z.string().optional()
|
||
});
|
||
|
||
// Dependency injection
|
||
const adrRepository = new PrismaADRRepository();
|
||
const notificationService = new NotificationService();
|
||
const versionControlService = new VersionControlService();
|
||
const adrManagementService = new ADRManagementService(
|
||
adrRepository,
|
||
notificationService,
|
||
versionControlService
|
||
);
|
||
|
||
// API endpoints
|
||
app.post('/api/adr', async (req: Request, res: Response, next: NextFunction) => {
|
||
try {
|
||
const validatedData = createADRSchema.parse(req.body);
|
||
const adr = await adrManagementService.createADR(validatedData);
|
||
res.json(adr);
|
||
} catch (error) {
|
||
if (error instanceof z.ZodError) {
|
||
res.status(400).json({ error: error.errors });
|
||
} else {
|
||
next(error);
|
||
}
|
||
}
|
||
});
|
||
|
||
app.put('/api/adr/:id/status', async (req: Request, res: Response, next: NextFunction) => {
|
||
try {
|
||
const id = parseInt(req.params.id);
|
||
const { status, comment } = req.body;
|
||
const adr = await adrManagementService.updateADRStatus(id, status, comment);
|
||
res.json(adr);
|
||
} catch (error) {
|
||
next(error);
|
||
}
|
||
});
|
||
|
||
app.get('/api/adr/status/:status', async (req: Request, res: Response, next: NextFunction) => {
|
||
try {
|
||
const status = req.params.status as ADRStatus;
|
||
const adrs = await adrManagementService.getADRsByStatus(status);
|
||
res.json(adrs);
|
||
} catch (error) {
|
||
next(error);
|
||
}
|
||
});
|
||
|
||
app.get('/api/adr/domain/:domain', async (req: Request, res: Response, next: NextFunction) => {
|
||
try {
|
||
const domain = req.params.domain;
|
||
const adrs = await adrManagementService.getADRsByDomain(domain);
|
||
res.json(adrs);
|
||
} catch (error) {
|
||
next(error);
|
||
}
|
||
});
|
||
|
||
app.get('/api/adr/:id/impact', async (req: Request, res: Response, next: NextFunction) => {
|
||
try {
|
||
const id = parseInt(req.params.id);
|
||
const analysis = await adrManagementService.analyzeImpact(id);
|
||
res.json(analysis);
|
||
} catch (error) {
|
||
next(error);
|
||
}
|
||
});
|
||
|
||
// Error handling middleware
|
||
app.use((error: Error, req: Request, res: Response, next: NextFunction) => {
|
||
console.error(error);
|
||
res.status(500).json({ error: 'Internal server error' });
|
||
});
|
||
|
||
app.listen(3000, () => {
|
||
console.log('ADR Management Service running on port 3000');
|
||
});
|
||
```
|
||
|
||
### Шаблон ADR
|
||
|
||
```markdown
|
||
# ADR-XXX: [Название решения]
|
||
|
||
## Статус
|
||
[Proposed/Accepted/Deprecated/Superseded]
|
||
|
||
## Дата
|
||
[YYYY-MM-DD]
|
||
|
||
## Контекст
|
||
[Описание ситуации и проблемы]
|
||
|
||
## Решение
|
||
[Описание принятого решения]
|
||
|
||
## Альтернативы
|
||
[Список рассмотренных альтернатив]
|
||
|
||
## Последствия
|
||
|
||
### Положительные
|
||
[Список положительных последствий]
|
||
|
||
### Отрицательные
|
||
[Список отрицательных последствий]
|
||
|
||
### Риски
|
||
[Список рисков и митигаций]
|
||
|
||
## Реализация
|
||
[План реализации решения]
|
||
```
|
||
|
||
---
|
||
|
||
## Техническая документация
|
||
|
||
### Документация API
|
||
|
||
#### 1. OpenAPI Specification
|
||
```yaml
|
||
openapi: 3.0.0
|
||
info:
|
||
title: Order Service API
|
||
version: 1.0.0
|
||
description: API for managing orders
|
||
|
||
servers:
|
||
- url: https://api.example.com/v1
|
||
description: Production server
|
||
- url: https://staging-api.example.com/v1
|
||
description: Staging server
|
||
|
||
paths:
|
||
/orders:
|
||
post:
|
||
summary: Create a new order
|
||
requestBody:
|
||
required: true
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/CreateOrderRequest'
|
||
responses:
|
||
'201':
|
||
description: Order created successfully
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/Order'
|
||
'400':
|
||
description: Bad request
|
||
content:
|
||
application/json:
|
||
schema:
|
||
$ref: '#/components/schemas/Error'
|
||
|
||
components:
|
||
schemas:
|
||
CreateOrderRequest:
|
||
type: object
|
||
required:
|
||
- customerId
|
||
- items
|
||
properties:
|
||
customerId:
|
||
type: string
|
||
format: uuid
|
||
items:
|
||
type: array
|
||
items:
|
||
$ref: '#/components/schemas/OrderItem'
|
||
|
||
Order:
|
||
type: object
|
||
properties:
|
||
id:
|
||
type: string
|
||
format: uuid
|
||
customerId:
|
||
type: string
|
||
format: uuid
|
||
status:
|
||
type: string
|
||
enum: [CREATED, CONFIRMED, SHIPPED, DELIVERED, CANCELLED]
|
||
totalAmount:
|
||
type: number
|
||
format: decimal
|
||
createdAt:
|
||
type: string
|
||
format: date-time
|
||
```
|
||
|
||
#### 2. API Documentation Tools
|
||
- **Swagger UI** — интерактивная документация
|
||
- **ReDoc** — альтернативный интерфейс
|
||
- **Postman** — коллекции и тесты
|
||
|
||
### Документация базы данных
|
||
|
||
#### 1. Entity Relationship Diagrams
|
||
```plantuml
|
||
@startuml
|
||
title Order Database Schema
|
||
|
||
entity "users" {
|
||
* id : UUID
|
||
--
|
||
* username : VARCHAR(50)
|
||
* email : VARCHAR(100)
|
||
* password_hash : VARCHAR(255)
|
||
* created_at : TIMESTAMP
|
||
* updated_at : TIMESTAMP
|
||
}
|
||
|
||
entity "orders" {
|
||
* id : UUID
|
||
--
|
||
* user_id : UUID
|
||
* status : VARCHAR(20)
|
||
* total_amount : DECIMAL(10,2)
|
||
* created_at : TIMESTAMP
|
||
* updated_at : TIMESTAMP
|
||
}
|
||
|
||
entity "order_items" {
|
||
* id : UUID
|
||
--
|
||
* order_id : UUID
|
||
* product_id : UUID
|
||
* quantity : INTEGER
|
||
* unit_price : DECIMAL(10,2)
|
||
}
|
||
|
||
entity "products" {
|
||
* id : UUID
|
||
--
|
||
* name : VARCHAR(100)
|
||
* description : TEXT
|
||
* price : DECIMAL(10,2)
|
||
* stock_quantity : INTEGER
|
||
* created_at : TIMESTAMP
|
||
* updated_at : TIMESTAMP
|
||
}
|
||
|
||
users ||--o{ orders : "places"
|
||
orders ||--o{ order_items : "contains"
|
||
products ||--o{ order_items : "referenced in"
|
||
|
||
@enduml
|
||
```
|
||
|
||
#### 2. Data Dictionary
|
||
```markdown
|
||
# Data Dictionary
|
||
|
||
## Table: users
|
||
|
||
| Column | Type | Constraints | Description |
|
||
|--------|------|-------------|-------------|
|
||
| id | UUID | PRIMARY KEY | Unique identifier for user |
|
||
| username | VARCHAR(50) | NOT NULL, UNIQUE | User's login name |
|
||
| email | VARCHAR(100) | NOT NULL, UNIQUE | User's email address |
|
||
| password_hash | VARCHAR(255) | NOT NULL | Hashed password |
|
||
| created_at | TIMESTAMP | NOT NULL, DEFAULT NOW() | Record creation timestamp |
|
||
| updated_at | TIMESTAMP | NOT NULL, DEFAULT NOW() | Record update timestamp |
|
||
|
||
## Table: orders
|
||
|
||
| Column | Type | Constraints | Description |
|
||
|--------|------|-------------|-------------|
|
||
| id | UUID | PRIMARY KEY | Unique identifier for order |
|
||
| user_id | UUID | NOT NULL, FOREIGN KEY | Reference to users.id |
|
||
| status | VARCHAR(20) | NOT NULL | Order status (CREATED, CONFIRMED, etc.) |
|
||
| total_amount | DECIMAL(10,2) | NOT NULL | Total order amount |
|
||
| created_at | TIMESTAMP | NOT NULL, DEFAULT NOW() | Record creation timestamp |
|
||
| updated_at | TIMESTAMP | NOT NULL, DEFAULT NOW() | Record update timestamp |
|
||
```
|
||
|
||
---
|
||
|
||
## Коммуникация архитектуры
|
||
|
||
### Эффективное взаимодействие со стейкхолдерами
|
||
|
||
#### 1. Типы стейкхолдеров и их потребности
|
||
|
||
**C-Level (CEO, CTO, CFO)**
|
||
- **Потребности:** Бизнес-ценность, ROI, риски, временные рамки
|
||
- **Язык:** Бизнес-метрики, финансовые показатели, стратегические цели
|
||
- **Формат:** Executive Summary, высокоуровневые диаграммы
|
||
|
||
```markdown
|
||
# Executive Summary - Миграция в облако
|
||
|
||
## Бизнес-обоснование
|
||
- **Экономия затрат**: 40% снижение TCO за 3 года
|
||
- **Ускорение разработки**: 60% быстрее time-to-market
|
||
- **Повышение надежности**: 99.9% uptime vs 95% сейчас
|
||
|
||
## Инвестиции
|
||
- **Начальные затраты**: $150,000 (миграция + обучение)
|
||
- **Ежемесячные затраты**: $25,000 (vs $45,000 сейчас)
|
||
- **ROI**: 200% за 18 месяцев
|
||
|
||
## Риски и митигации
|
||
- **Риск**: Зависимость от облачного провайдера
|
||
- **Митигация**: Multi-cloud стратегия, exit plan
|
||
- **Вероятность**: Низкая (5%)
|
||
```
|
||
|
||
**Product Managers**
|
||
- **Потребности:** Функциональность, пользовательский опыт, временные рамки
|
||
- **Язык:** Пользовательские сценарии, фичи, метрики продукта
|
||
- **Формат:** User stories, wireframes, API спецификации
|
||
|
||
```markdown
|
||
# Product Requirements - User Management System
|
||
|
||
## Пользовательские сценарии
|
||
1. **Регистрация пользователя**
|
||
- Пользователь вводит email и пароль
|
||
- Система отправляет подтверждение
|
||
- Пользователь активирует аккаунт
|
||
|
||
2. **Вход в систему**
|
||
- Пользователь вводит credentials
|
||
- Система аутентифицирует пользователя
|
||
- Пользователь получает доступ к системе
|
||
|
||
## API Endpoints
|
||
- POST /api/users/register
|
||
- POST /api/users/login
|
||
- GET /api/users/profile
|
||
- PUT /api/users/profile
|
||
|
||
## Метрики успеха
|
||
- Время регистрации < 30 секунд
|
||
- Успешность входа > 99%
|
||
- Время отклика API < 200ms
|
||
```
|
||
|
||
**Development Teams**
|
||
- **Потребности:** Технические детали, API, архитектурные паттерны
|
||
- **Язык:** Код, диаграммы, технические спецификации
|
||
- **Формат:** ADR, техническая документация, диаграммы
|
||
|
||
```markdown
|
||
# Technical Specification - User Service
|
||
|
||
## Архитектурные решения
|
||
|
||
### ADR-001: Выбор базы данных
|
||
**Статус**: Принято
|
||
**Дата**: 2024-01-15
|
||
**Участники**: Архитектор, Lead Developer, DBA
|
||
|
||
**Контекст**
|
||
Нужно выбрать базу данных для хранения пользовательских данных.
|
||
|
||
**Варианты**
|
||
1. PostgreSQL - реляционная БД
|
||
2. MongoDB - документная БД
|
||
3. DynamoDB - NoSQL БД
|
||
|
||
**Решение**
|
||
Выбрана PostgreSQL по следующим причинам:
|
||
- ACID транзакции для критических данных
|
||
- Зрелая экосистема и инструменты
|
||
- Опыт команды с SQL
|
||
- Хорошая производительность для наших объемов
|
||
|
||
**Последствия**
|
||
- Нужно спроектировать схему БД
|
||
- Настроить репликацию для отказоустойчивости
|
||
- Реализовать миграции данных
|
||
```
|
||
|
||
**DevOps/Infrastructure Teams**
|
||
- **Потребности:** Инфраструктура, мониторинг, безопасность, производительность
|
||
- **Язык:** Infrastructure as Code, метрики, алерты
|
||
- **Формат:** Terraform, Kubernetes manifests, мониторинг дашборды
|
||
|
||
```yaml
|
||
# Infrastructure Configuration
|
||
apiVersion: apps/v1
|
||
kind: Deployment
|
||
metadata:
|
||
name: user-service
|
||
labels:
|
||
app: user-service
|
||
version: v1.0.0
|
||
spec:
|
||
replicas: 3
|
||
selector:
|
||
matchLabels:
|
||
app: user-service
|
||
template:
|
||
metadata:
|
||
labels:
|
||
app: user-service
|
||
version: v1.0.0
|
||
spec:
|
||
containers:
|
||
- name: user-service
|
||
image: user-service:v1.0.0
|
||
ports:
|
||
- containerPort: 8080
|
||
env:
|
||
- name: DATABASE_URL
|
||
valueFrom:
|
||
secretKeyRef:
|
||
name: user-service-secrets
|
||
key: database-url
|
||
resources:
|
||
requests:
|
||
memory: "256Mi"
|
||
cpu: "250m"
|
||
limits:
|
||
memory: "512Mi"
|
||
cpu: "500m"
|
||
livenessProbe:
|
||
httpGet:
|
||
path: /health
|
||
port: 8080
|
||
initialDelaySeconds: 30
|
||
periodSeconds: 10
|
||
readinessProbe:
|
||
httpGet:
|
||
path: /ready
|
||
port: 8080
|
||
initialDelaySeconds: 5
|
||
periodSeconds: 5
|
||
```
|
||
|
||
**QA Teams**
|
||
- **Потребности:** Тестируемость, качество, покрытие тестами
|
||
- **Язык:** Тест-кейсы, сценарии тестирования, метрики качества
|
||
- **Формат:** Test plans, API тесты, performance тесты
|
||
|
||
```markdown
|
||
# Test Plan - User Service
|
||
|
||
## Функциональное тестирование
|
||
|
||
### Тест-кейсы
|
||
1. **TC001: Успешная регистрация пользователя**
|
||
- **Предусловия**: Система доступна
|
||
- **Шаги**:
|
||
1. Отправить POST /api/users/register с валидными данными
|
||
2. Проверить статус 201
|
||
3. Проверить создание пользователя в БД
|
||
- **Ожидаемый результат**: Пользователь создан, отправлен email
|
||
|
||
2. **TC002: Регистрация с существующим email**
|
||
- **Предусловия**: Пользователь с email уже существует
|
||
- **Шаги**:
|
||
1. Отправить POST /api/users/register с существующим email
|
||
- **Ожидаемый результат**: Статус 409, сообщение об ошибке
|
||
|
||
## Нагрузочное тестирование
|
||
- **Цель**: Проверить производительность при 1000 RPS
|
||
- **Инструменты**: JMeter, K6
|
||
- **Метрики**: Response time < 200ms, Error rate < 0.1%
|
||
```
|
||
|
||
#### 2. Стратегии коммуникации
|
||
|
||
**1. Адаптация сообщения под аудиторию**
|
||
|
||
```python
|
||
# Пример адаптации сообщения
|
||
class StakeholderCommunication:
|
||
def __init__(self):
|
||
self.audiences = {
|
||
'executive': {
|
||
'focus': ['business_value', 'roi', 'risks'],
|
||
'language': 'business',
|
||
'detail_level': 'high_level'
|
||
},
|
||
'technical': {
|
||
'focus': ['implementation', 'architecture', 'performance'],
|
||
'language': 'technical',
|
||
'detail_level': 'detailed'
|
||
},
|
||
'product': {
|
||
'focus': ['features', 'user_experience', 'timeline'],
|
||
'language': 'product',
|
||
'detail_level': 'medium'
|
||
}
|
||
}
|
||
|
||
def create_message(self, audience, topic, data):
|
||
"""Создание сообщения для конкретной аудитории"""
|
||
audience_config = self.audiences[audience]
|
||
|
||
if audience_config['language'] == 'business':
|
||
return self._create_business_message(topic, data)
|
||
elif audience_config['language'] == 'technical':
|
||
return self._create_technical_message(topic, data)
|
||
else:
|
||
return self._create_product_message(topic, data)
|
||
|
||
def _create_business_message(self, topic, data):
|
||
"""Создание бизнес-сообщения"""
|
||
return f"""
|
||
{topic} - Бизнес-обоснование
|
||
|
||
Ключевые преимущества:
|
||
- Экономия затрат: {data.get('cost_savings', 'N/A')}
|
||
- Ускорение разработки: {data.get('time_savings', 'N/A')}
|
||
- Повышение качества: {data.get('quality_improvement', 'N/A')}
|
||
|
||
Инвестиции: {data.get('investment', 'N/A')}
|
||
ROI: {data.get('roi', 'N/A')}
|
||
"""
|
||
|
||
def _create_technical_message(self, topic, data):
|
||
"""Создание технического сообщения"""
|
||
return f"""
|
||
{topic} - Техническая спецификация
|
||
|
||
Архитектурные решения:
|
||
- Технологии: {data.get('technologies', 'N/A')}
|
||
- Паттерны: {data.get('patterns', 'N/A')}
|
||
- Производительность: {data.get('performance', 'N/A')}
|
||
|
||
Реализация:
|
||
- Временные рамки: {data.get('timeline', 'N/A')}
|
||
- Ресурсы: {data.get('resources', 'N/A')}
|
||
- Риски: {data.get('risks', 'N/A')}
|
||
"""
|
||
```
|
||
|
||
**2. Визуализация для разных аудиторий**
|
||
|
||
```mermaid
|
||
graph TB
|
||
subgraph "Executive View"
|
||
A[Business Goals] --> B[Technical Solution]
|
||
B --> C[Business Value]
|
||
C --> D[ROI]
|
||
end
|
||
|
||
subgraph "Technical View"
|
||
E[Microservices] --> F[API Gateway]
|
||
F --> G[Database]
|
||
G --> H[Monitoring]
|
||
end
|
||
|
||
subgraph "Product View"
|
||
I[User Stories] --> J[Features]
|
||
J --> K[User Experience]
|
||
K --> L[Success Metrics]
|
||
end
|
||
```
|
||
|
||
**3. Управление ожиданиями**
|
||
|
||
```markdown
|
||
# Template: Управление ожиданиями
|
||
|
||
## Проект: Миграция в микросервисы
|
||
|
||
### Что мы ДЕЛАЕМ:
|
||
- ✅ Разбиваем монолит на микросервисы
|
||
- ✅ Внедряем API Gateway
|
||
- ✅ Настраиваем мониторинг
|
||
- ✅ Обеспечиваем отказоустойчивость
|
||
|
||
### Что мы НЕ ДЕЛАЕМ:
|
||
- ❌ Полная переписывание с нуля
|
||
- ❌ Изменение бизнес-логики
|
||
- ❌ Замена всех технологий
|
||
|
||
### Ограничения:
|
||
- Временные рамки: 6 месяцев
|
||
- Бюджет: $500,000
|
||
- Команда: 8 разработчиков
|
||
- Технологии: Текущий стек + новые инструменты
|
||
|
||
### Риски:
|
||
- Высокий: Сложность миграции данных
|
||
- Средний: Производительность в переходный период
|
||
- Низкий: Обучение команды новым технологиям
|
||
```
|
||
|
||
#### 3. Инструменты коммуникации
|
||
|
||
**1. Архитектурные диаграммы**
|
||
|
||
```plantuml
|
||
@startuml
|
||
!theme plain
|
||
skinparam backgroundColor #FFFFFF
|
||
skinparam componentStyle rectangle
|
||
|
||
package "E-commerce Platform" {
|
||
[Web Application] as Web
|
||
[Mobile App] as Mobile
|
||
[Admin Panel] as Admin
|
||
|
||
package "API Gateway" {
|
||
[Load Balancer] as LB
|
||
[Rate Limiter] as RL
|
||
[Authentication] as Auth
|
||
}
|
||
|
||
package "Microservices" {
|
||
[User Service] as US
|
||
[Product Service] as PS
|
||
[Order Service] as OS
|
||
[Payment Service] as PayS
|
||
}
|
||
|
||
package "Data Layer" {
|
||
[User DB] as UDB
|
||
[Product DB] as PDB
|
||
[Order DB] as ODB
|
||
[Payment DB] as PayDB
|
||
}
|
||
|
||
package "External Services" {
|
||
[Payment Gateway] as PG
|
||
[Email Service] as ES
|
||
[SMS Service] as SMS
|
||
}
|
||
}
|
||
|
||
Web --> LB
|
||
Mobile --> LB
|
||
Admin --> LB
|
||
|
||
LB --> RL
|
||
RL --> Auth
|
||
Auth --> US
|
||
Auth --> PS
|
||
Auth --> OS
|
||
Auth --> PayS
|
||
|
||
US --> UDB
|
||
PS --> PDB
|
||
OS --> ODB
|
||
PayS --> PayDB
|
||
|
||
PayS --> PG
|
||
US --> ES
|
||
US --> SMS
|
||
|
||
@enduml
|
||
```
|
||
|
||
**2. Презентационные шаблоны**
|
||
|
||
```markdown
|
||
# Шаблон презентации для C-Level
|
||
|
||
## Slide 1: Executive Summary
|
||
- **Проблема**: Текущая система не масштабируется
|
||
- **Решение**: Миграция в облако + микросервисы
|
||
- **Результат**: 40% экономия + 60% ускорение разработки
|
||
- **Инвестиции**: $150K, ROI за 18 месяцев
|
||
|
||
## Slide 2: Текущее состояние
|
||
- Монолитная архитектура
|
||
- Высокие затраты на инфраструктуру
|
||
- Медленная разработка новых фич
|
||
- Проблемы с масштабированием
|
||
|
||
## Slide 3: Предлагаемое решение
|
||
- Микросервисная архитектура
|
||
- Облачная инфраструктура
|
||
- Автоматизация DevOps
|
||
- Современные технологии
|
||
|
||
## Slide 4: Бизнес-ценность
|
||
- **Экономия**: $200K/год
|
||
- **Скорость**: 3x быстрее разработка
|
||
- **Надежность**: 99.9% uptime
|
||
- **Гибкость**: Независимые команды
|
||
|
||
## Slide 5: План реализации
|
||
- **Фаза 1** (2 мес): Подготовка инфраструктуры
|
||
- **Фаза 2** (3 мес): Миграция сервисов
|
||
- **Фаза 3** (1 мес): Тестирование и оптимизация
|
||
- **Фаза 4** (ongoing): Поддержка и развитие
|
||
```
|
||
|
||
**3. Интерактивные демонстрации**
|
||
|
||
```html
|
||
<!-- Интерактивная демонстрация архитектуры -->
|
||
<!DOCTYPE html>
|
||
<html>
|
||
<head>
|
||
<title>Architecture Demo</title>
|
||
<script src="https://d3js.org/d3.v7.min.js"></script>
|
||
<style>
|
||
.node { cursor: pointer; }
|
||
.link { stroke: #999; stroke-opacity: 0.6; }
|
||
.tooltip { position: absolute; padding: 10px; background: rgba(0,0,0,0.8); color: white; border-radius: 5px; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div id="architecture-diagram"></div>
|
||
<div id="tooltip" class="tooltip" style="display: none;"></div>
|
||
|
||
<script>
|
||
// Интерактивная диаграмма архитектуры
|
||
const data = {
|
||
nodes: [
|
||
{id: "web", name: "Web App", type: "frontend"},
|
||
{id: "api", name: "API Gateway", type: "gateway"},
|
||
{id: "user", name: "User Service", type: "microservice"},
|
||
{id: "product", name: "Product Service", type: "microservice"},
|
||
{id: "order", name: "Order Service", type: "microservice"},
|
||
{id: "db", name: "Database", type: "database"}
|
||
],
|
||
links: [
|
||
{source: "web", target: "api"},
|
||
{source: "api", target: "user"},
|
||
{source: "api", target: "product"},
|
||
{source: "api", target: "order"},
|
||
{source: "user", target: "db"},
|
||
{source: "product", target: "db"},
|
||
{source: "order", target: "db"}
|
||
]
|
||
};
|
||
|
||
// Создание интерактивной диаграммы
|
||
const svg = d3.select("#architecture-diagram")
|
||
.append("svg")
|
||
.attr("width", 800)
|
||
.attr("height", 600);
|
||
|
||
const simulation = d3.forceSimulation(data.nodes)
|
||
.force("link", d3.forceLink(data.links).id(d => d.id))
|
||
.force("charge", d3.forceManyBody().strength(-300))
|
||
.force("center", d3.forceCenter(400, 300));
|
||
|
||
const link = svg.append("g")
|
||
.selectAll("line")
|
||
.data(data.links)
|
||
.enter().append("line")
|
||
.attr("class", "link");
|
||
|
||
const node = svg.append("g")
|
||
.selectAll("circle")
|
||
.data(data.nodes)
|
||
.enter().append("circle")
|
||
.attr("class", "node")
|
||
.attr("r", 20)
|
||
.style("fill", d => {
|
||
const colors = {
|
||
frontend: "#ff6b6b",
|
||
gateway: "#4ecdc4",
|
||
microservice: "#45b7d1",
|
||
database: "#96ceb4"
|
||
};
|
||
return colors[d.type] || "#ddd";
|
||
})
|
||
.on("mouseover", function(event, d) {
|
||
d3.select("#tooltip")
|
||
.style("display", "block")
|
||
.html(`<strong>${d.name}</strong><br/>Type: ${d.type}`)
|
||
.style("left", (event.pageX + 10) + "px")
|
||
.style("top", (event.pageY - 10) + "px");
|
||
})
|
||
.on("mouseout", function() {
|
||
d3.select("#tooltip").style("display", "none");
|
||
});
|
||
|
||
simulation.on("tick", () => {
|
||
link
|
||
.attr("x1", d => d.source.x)
|
||
.attr("y1", d => d.source.y)
|
||
.attr("x2", d => d.target.x)
|
||
.attr("y2", d => d.target.y);
|
||
|
||
node
|
||
.attr("cx", d => d.x)
|
||
.attr("cy", d => d.y);
|
||
});
|
||
</script>
|
||
</body>
|
||
</html>
|
||
```
|
||
|
||
#### 4. Управление конфликтами и компромиссами
|
||
|
||
**1. Framework для принятия решений**
|
||
|
||
```markdown
|
||
# Decision Framework
|
||
|
||
## 1. Определение критериев
|
||
- **Функциональность**: Соответствие требованиям
|
||
- **Производительность**: Скорость и масштабируемость
|
||
- **Безопасность**: Защита данных и системы
|
||
- **Стоимость**: Бюджет и ROI
|
||
- **Время**: Временные рамки реализации
|
||
- **Риски**: Технические и бизнес-риски
|
||
|
||
## 2. Оценка вариантов
|
||
| Критерий | Вариант A | Вариант B | Вариант C | Вес |
|
||
|----------|-----------|-----------|-----------|-----|
|
||
| Функциональность | 9/10 | 8/10 | 7/10 | 30% |
|
||
| Производительность | 7/10 | 9/10 | 8/10 | 25% |
|
||
| Безопасность | 8/10 | 7/10 | 9/10 | 20% |
|
||
| Стоимость | 6/10 | 8/10 | 9/10 | 15% |
|
||
| Время | 8/10 | 6/10 | 7/10 | 10% |
|
||
| **Итого** | **7.6** | **7.8** | **7.9** | **100%** |
|
||
|
||
## 3. Анализ компромиссов
|
||
- **Вариант A**: Высокая функциональность, но дорогой
|
||
- **Вариант B**: Хорошая производительность, но долгая реализация
|
||
- **Вариант C**: Лучшая безопасность, но ограниченная функциональность
|
||
|
||
## 4. Рекомендация
|
||
Выбран **Вариант C** как оптимальный баланс всех критериев.
|
||
```
|
||
|
||
**2. Техники разрешения конфликтов**
|
||
|
||
```python
|
||
# Conflict Resolution Framework
|
||
class ConflictResolver:
|
||
def __init__(self):
|
||
self.stakeholders = {}
|
||
self.conflicts = []
|
||
|
||
def identify_conflict(self, stakeholders, issue, positions):
|
||
"""Идентификация конфликта"""
|
||
conflict = {
|
||
'id': len(self.conflicts) + 1,
|
||
'stakeholders': stakeholders,
|
||
'issue': issue,
|
||
'positions': positions,
|
||
'status': 'open',
|
||
'resolution': None
|
||
}
|
||
self.conflicts.append(conflict)
|
||
return conflict
|
||
|
||
def analyze_conflict(self, conflict_id):
|
||
"""Анализ конфликта"""
|
||
conflict = self.conflicts[conflict_id - 1]
|
||
|
||
# Анализ интересов vs позиций
|
||
interests = self._extract_interests(conflict['positions'])
|
||
|
||
# Поиск общих интересов
|
||
common_interests = self._find_common_interests(interests)
|
||
|
||
# Генерация вариантов решения
|
||
solutions = self._generate_solutions(conflict, common_interests)
|
||
|
||
return {
|
||
'interests': interests,
|
||
'common_interests': common_interests,
|
||
'solutions': solutions
|
||
}
|
||
|
||
def resolve_conflict(self, conflict_id, solution, rationale):
|
||
"""Разрешение конфликта"""
|
||
conflict = self.conflicts[conflict_id - 1]
|
||
conflict['resolution'] = {
|
||
'solution': solution,
|
||
'rationale': rationale,
|
||
'status': 'resolved',
|
||
'date': datetime.now()
|
||
}
|
||
return conflict
|
||
|
||
def _extract_interests(self, positions):
|
||
"""Извлечение интересов из позиций"""
|
||
interests = {}
|
||
for stakeholder, position in positions.items():
|
||
interests[stakeholder] = {
|
||
'stated_position': position,
|
||
'underlying_interests': self._analyze_underlying_interests(position)
|
||
}
|
||
return interests
|
||
|
||
def _find_common_interests(self, interests):
|
||
"""Поиск общих интересов"""
|
||
common = []
|
||
stakeholder_interests = list(interests.values())
|
||
|
||
for i, stakeholder1 in enumerate(stakeholder_interests):
|
||
for j, stakeholder2 in enumerate(stakeholder_interests[i+1:], i+1):
|
||
common_interest = self._compare_interests(
|
||
stakeholder1['underlying_interests'],
|
||
stakeholder2['underlying_interests']
|
||
)
|
||
if common_interest:
|
||
common.append(common_interest)
|
||
|
||
return common
|
||
|
||
def _generate_solutions(self, conflict, common_interests):
|
||
"""Генерация вариантов решения"""
|
||
solutions = []
|
||
|
||
# Win-Win решения
|
||
for common_interest in common_interests:
|
||
solutions.append({
|
||
'type': 'win_win',
|
||
'description': f"Решение на основе общего интереса: {common_interest}",
|
||
'benefits': "Удовлетворяет интересы всех сторон",
|
||
'drawbacks': "Может потребовать дополнительных ресурсов"
|
||
})
|
||
|
||
# Компромиссные решения
|
||
solutions.append({
|
||
'type': 'compromise',
|
||
'description': "Частичное удовлетворение интересов всех сторон",
|
||
'benefits': "Быстрое решение",
|
||
'drawbacks': "Никто не получает полного удовлетворения"
|
||
})
|
||
|
||
# Альтернативные решения
|
||
solutions.append({
|
||
'type': 'alternative',
|
||
'description': "Поиск третьего варианта",
|
||
'benefits': "Может привести к лучшему решению",
|
||
'drawbacks': "Требует времени и креативности"
|
||
})
|
||
|
||
return solutions
|
||
|
||
# Использование
|
||
resolver = ConflictResolver()
|
||
|
||
# Идентификация конфликта
|
||
conflict = resolver.identify_conflict(
|
||
stakeholders=['CTO', 'Product Manager', 'Lead Developer'],
|
||
issue='Выбор технологии для нового сервиса',
|
||
positions={
|
||
'CTO': 'Java Spring Boot',
|
||
'Product Manager': 'Node.js для быстрой разработки',
|
||
'Lead Developer': 'Python Django для простоты'
|
||
}
|
||
)
|
||
|
||
# Анализ и разрешение
|
||
analysis = resolver.analyze_conflict(conflict['id'])
|
||
resolution = resolver.resolve_conflict(
|
||
conflict['id'],
|
||
solution='Использовать Java Spring Boot с быстрыми итерациями',
|
||
rationale='Баланс между производительностью, скоростью разработки и опытом команды'
|
||
)
|
||
```
|
||
|
||
### Презентация архитектуры
|
||
|
||
#### 1. Executive Summary
|
||
```markdown
|
||
# Архитектура системы электронной коммерции
|
||
|
||
## Ключевые метрики
|
||
- **Доступность**: 99.9% uptime
|
||
- **Производительность**: < 200ms response time
|
||
- **Масштабируемость**: до 10,000 RPS
|
||
- **Безопасность**: PCI DSS compliant
|
||
|
||
## Бизнес-ценность
|
||
- Быстрое время выхода на рынок
|
||
- Независимое развитие команд
|
||
- Высокая отказоустойчивость
|
||
- Гибкость в выборе технологий
|
||
```
|
||
|
||
#### 2. Technical Deep Dive
|
||
```markdown
|
||
# Техническая архитектура
|
||
|
||
## Архитектурные принципы
|
||
1. **Микросервисы** — независимые сервисы
|
||
2. **API-First** — все взаимодействия через API
|
||
3. **Event-Driven** — асинхронная обработка
|
||
4. **Cloud-Native** — оптимизация для облака
|
||
|
||
## Технологический стек
|
||
- **Backend**: Java, Spring Boot, PostgreSQL
|
||
- **Frontend**: React, TypeScript
|
||
- **Infrastructure**: Kubernetes, Docker
|
||
- **Monitoring**: Prometheus, Grafana
|
||
```
|
||
|
||
### Архитектурные ревью
|
||
|
||
#### 1. Review Process
|
||
```markdown
|
||
# Процесс архитектурного ревью
|
||
|
||
## Подготовка
|
||
1. Создание архитектурной диаграммы
|
||
2. Подготовка ADR
|
||
3. Анализ рисков и митигаций
|
||
4. Планирование миграции
|
||
|
||
## Проведение ревью
|
||
1. Презентация архитектуры (30 мин)
|
||
2. Обсуждение и вопросы (30 мин)
|
||
3. Анализ рисков (15 мин)
|
||
4. Принятие решения (15 мин)
|
||
|
||
## Результат
|
||
- Одобрение или отклонение
|
||
- Рекомендации по улучшению
|
||
- План реализации
|
||
- Следующие шаги
|
||
```
|
||
|
||
#### 2. Review Checklist
|
||
```markdown
|
||
# Чек-лист архитектурного ревью
|
||
|
||
## Функциональные требования
|
||
- [ ] Все требования покрыты
|
||
- [ ] Пользовательские сценарии реализуемы
|
||
- [ ] Интеграции с внешними системами
|
||
|
||
## Нефункциональные требования
|
||
- [ ] Производительность соответствует требованиям
|
||
- [ ] Масштабируемость обеспечена
|
||
- [ ] Безопасность учтена
|
||
- [ ] Надежность гарантирована
|
||
|
||
## Техническая реализуемость
|
||
- [ ] Технологии подходят для задачи
|
||
- [ ] Команда имеет необходимые навыки
|
||
- [ ] Инфраструктура поддерживает решение
|
||
- [ ] Временные рамки реалистичны
|
||
|
||
## Риски и митигации
|
||
- [ ] Основные риски идентифицированы
|
||
- [ ] План митигации разработан
|
||
- [ ] Fallback стратегии определены
|
||
- [ ] Мониторинг рисков настроен
|
||
```
|
||
|
||
---
|
||
|
||
## Практические задания
|
||
|
||
### Задание 1: C4 Model
|
||
1. Создайте Context диаграмму для системы интернет-магазина
|
||
2. Создайте Container диаграмму
|
||
3. Создайте Component диаграмму для одного сервиса
|
||
|
||
### Задание 2: PlantUML
|
||
1. Создайте Sequence диаграмму для процесса заказа
|
||
2. Создайте Class диаграмму для доменной модели
|
||
3. Создайте Deployment диаграмму
|
||
|
||
### Задание 3: ADR
|
||
1. Создайте ADR для выбора базы данных
|
||
2. Создайте ADR для выбора архитектурного стиля
|
||
3. Создайте ADR для стратегии кэширования
|
||
|
||
---
|
||
|
||
## Дополнительные ресурсы
|
||
|
||
### Инструменты
|
||
- **C4 Model**: C4-PlantUML, Structurizr
|
||
- **PlantUML**: PlantUML Online, VS Code Extension
|
||
- **Documentation**: GitBook, Confluence, Notion
|
||
- **API Docs**: Swagger, Postman, Insomnia
|
||
|
||
### Книги
|
||
- "Documenting Software Architectures" by Paul Clements
|
||
- "Architecture Documentation" by Rozanski & Woods
|
||
- "The C4 Model" by Simon Brown
|
||
|
||
---
|
||
|
||
## Контрольные вопросы
|
||
|
||
1. Зачем нужна архитектурная документация?
|
||
2. Что такое C4 Model и как его использовать?
|
||
3. Какие типы диаграмм можно создать в PlantUML?
|
||
4. Что такое ADR и как его структурировать?
|
||
5. Как документировать API?
|
||
6. Как проводить архитектурные ревью?
|
||
7. Какие принципы архитектурной документации вы знаете?
|
||
8. Как поддерживать актуальность документации?
|