Files
sa-lessons/modules/module1/theory.md
T
Alexander Abakumov 15e744da49 Initial commit: Complete Software Architecture course with 11 modules
- 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
2025-09-09 22:08:07 +03:00

6366 lines
198 KiB
Markdown

# Модуль 1: Основы архитектуры программного обеспечения
## Содержание модуля
1. [Роль и обязанности Solution Architect](#роль-и-обязанности-solution-architect)
2. [Введение в архитектуру ПО](#введение-в-архитектуру-по)
3. [Доменная модель и Domain-Driven Design](#доменная-модель-и-domain-driven-design)
4. [Принципы проектирования](#принципы-проектирования)
5. [Архитектурные стили и паттерны](#архитектурные-стили-и-паттерны)
6. [Качественные атрибуты](#качественные-атрибуты)
7. [Роль архитектора в команде](#роль-архитектора-в-команде)
---
## Роль и обязанности Solution Architect
### Кто такой Solution Architect?
**Solution Architect** — это специалист, который проектирует комплексные технические решения для решения бизнес-задач. Он является мостом между бизнес-требованиями и технической реализацией.
### Ключевые обязанности Solution Architect
#### 1. Анализ и проектирование решений
- **Анализ бизнес-требований** и перевод их в технические решения
- **Проектирование архитектуры** с учетом всех ограничений и требований
- **Выбор технологий** и инструментов для реализации
- **Создание технических спецификаций** и архитектурной документации
#### 2. Управление техническими решениями
- **Принятие архитектурных решений** (Architecture Decision Records - ADR)
- **Оценка рисков** и компромиссов технических решений
- **Управление техническим долгом** и эволюцией системы
- **Обеспечение соответствия** стандартам и лучшим практикам
#### 3. Коммуникация и координация
- **Взаимодействие со стейкхолдерами** (бизнес, разработчики, DevOps, QA)
- **Презентация архитектурных решений** на разных уровнях
- **Техническое лидерство** и менторство команды
- **Координация между командами** разработки
### Отличия от других типов архитекторов
#### Solution Architect против Enterprise Architect
| Аспект | Solution Architect | Enterprise Architect |
|--------|-------------------|---------------------|
| **Фокус** | Конкретное решение/продукт | Вся организация |
| **Масштаб** | Один проект/система | Множество систем |
| **Временной горизонт** | 6-18 месяцев | 2-5 лет |
| **Детализация** | Высокая техническая детализация | Высокий уровень абстракции |
| **Взаимодействие** | Команды разработки | C-level, IT-директора |
**Пример различий:**
```java
// Solution Architect - детальное техническое решение
@RestController
@RequestMapping("/api/orders")
public class OrderController {
@Autowired
private OrderService orderService;
@Autowired
private PaymentService paymentService;
@PostMapping
public ResponseEntity<OrderResponse> createOrder(@RequestBody CreateOrderRequest request) {
// Детальная реализация бизнес-логики
Order order = orderService.createOrder(request);
PaymentResult payment = paymentService.processPayment(order);
if (payment.isSuccess()) {
return ResponseEntity.ok(OrderResponse.fromOrder(order));
} else {
throw new PaymentException("Payment failed");
}
}
}
// Enterprise Architect - высокоуровневая диаграмма
/*
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ E-commerce │ │ Payment │ │ Inventory │
│ Platform │◄──►│ Gateway │◄──►│ Management │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Customer │ │ Order │ │ Fulfillment │
│ Management │ │ Processing │ │ System │
└─────────────────┘ └─────────────────┘ └─────────────────┘
*/
```
#### Solution Architect против Technical Architect
| Аспект | Solution Architect | Technical Architect |
|--------|-------------------|-------------------|
| **Фокус** | Бизнес-решение | Техническая реализация |
| **Уровень** | Системный | Компонентный |
| **Взаимодействие** | Бизнес + Технические команды | Только технические команды |
| **Решения** | Архитектурные паттерны, технологии | Алгоритмы, структуры данных |
### Жизненный цикл проектирования архитектуры
#### 1. Анализ требований (Requirements Analysis)
**Входные данные:**
- Бизнес-требования (Business Requirements)
- Функциональные требования (Functional Requirements)
- Нефункциональные требования (Non-Functional Requirements)
- Ограничения (Constraints)
**Процесс анализа:**
```java
// Пример анализа требований
public class RequirementsAnalyzer {
public ArchitectureRequirements analyzeRequirements(BusinessRequirements businessReq) {
ArchitectureRequirements archReq = new ArchitectureRequirements();
// Анализ функциональных требований
analyzeFunctionalRequirements(businessReq, archReq);
// Анализ нефункциональных требований
analyzeNonFunctionalRequirements(businessReq, archReq);
// Анализ ограничений
analyzeConstraints(businessReq, archReq);
return archReq;
}
private void analyzeNonFunctionalRequirements(BusinessRequirements businessReq,
ArchitectureRequirements archReq) {
// Производительность
if (businessReq.getExpectedUsers() > 10000) {
archReq.addRequirement("HIGH_PERFORMANCE", "System must handle 10k+ concurrent users");
}
// Масштабируемость
if (businessReq.isGlobalDeployment()) {
archReq.addRequirement("SCALABILITY", "System must scale globally");
}
// Безопасность
if (businessReq.isFinancialData()) {
archReq.addRequirement("SECURITY", "Financial data must be encrypted");
}
}
}
```
#### 2. Проектирование архитектуры (Architecture Design)
**Этапы проектирования:**
1. **Высокоуровневое проектирование**
- Определение основных компонентов
- Выбор архитектурного стиля
- Определение интерфейсов
2. **Детальное проектирование**
- Проектирование компонентов
- Выбор технологий
- Определение паттернов взаимодействия
3. **Валидация архитектуры**
- Проверка соответствия требованиям
- Анализ рисков
- Прототипирование критических компонентов
#### 3. Документирование (Documentation)
**Типы архитектурной документации:**
```markdown
# Solution Architecture Document
## 1. Executive Summary
- Краткое описание решения
- Ключевые преимущества
- Основные риски
## 2. Business Context
- Бизнес-цели
- Пользователи системы
- Ключевые процессы
## 3. Architecture Overview
- Высокоуровневая диаграмма
- Основные компоненты
- Технологический стек
## 4. Detailed Design
- Детальные диаграммы компонентов
- API спецификации
- Схемы данных
## 5. Non-Functional Requirements
- Производительность
- Безопасность
- Масштабируемость
- Надежность
## 6. Implementation Plan
- Этапы реализации
- Ресурсы
- Временные рамки
## 7. Risks and Mitigation
- Технические риски
- Планы по их устранению
```
#### 4. Реализация и контроль (Implementation & Governance)
**Роль Solution Architect в реализации:**
- **Техническое лидерство** команды разработки
- **Контроль качества** архитектурных решений
- **Управление изменениями** в архитектуре
- **Решение технических проблем**
### Нефункциональные требования (NFR) в фокусе Solution Architect
#### 1. Масштабируемость (Scalability)
**Определение:** Способность системы обрабатывать увеличение нагрузки.
**Метрики:**
- Количество одновременных пользователей
- Объем обрабатываемых данных
- Количество транзакций в секунду
**Примеры требований:**
```yaml
scalability:
concurrent_users: 10000
data_volume: "1TB per day"
transactions_per_second: 1000
response_time: "< 200ms for 95% of requests"
availability: "99.9%"
```
#### 2. Надежность (Reliability)
**Определение:** Способность системы выполнять требуемые функции в заданных условиях.
**Аспекты:**
- Отказоустойчивость (Fault Tolerance)
- Восстановление после сбоев (Recovery)
- Предотвращение потери данных (Data Integrity)
#### 3. Безопасность (Security)
**Определение:** Защита системы от угроз и несанкционированного доступа.
**Компоненты:**
- Аутентификация и авторизация
- Шифрование данных
- Защита от атак
- Соответствие стандартам
#### 4. Производительность (Performance)
**Определение:** Скорость и эффективность выполнения операций.
**Метрики:**
- Время отклика (Response Time)
- Пропускная способность (Throughput)
- Использование ресурсов (Resource Utilization)
### Ключевые навыки Solution Architect
#### Технические навыки
1. **Архитектурные паттерны и стили**
- Микросервисы, SOA, Event-driven
- CQRS, Event Sourcing, Saga
- API Gateway, Service Mesh
2. **Облачные технологии**
- AWS, Azure, GCP
- Containerization (Docker, Kubernetes)
- Serverless архитектура
3. **Интеграционные технологии**
- REST, GraphQL, gRPC
- Message Queues (RabbitMQ, Kafka)
- API Management
#### Мягкие навыки
1. **Коммуникация**
- Презентация технических решений
- Взаимодействие с бизнесом
- Техническое письмо
2. **Лидерство**
- Техническое лидерство
- Менторство
- Управление конфликтами
3. **Аналитическое мышление**
- Анализ требований
- Принятие решений
- Управление рисками
### Карьерный путь Solution Architect
#### Уровни развития
1. **Junior Solution Architect** (2-3 года опыта)
- Помощь в проектировании решений
- Изучение архитектурных паттернов
- Работа под руководством Senior Architect
2. **Solution Architect** (3-7 лет опыта)
- Независимое проектирование решений
- Взаимодействие с бизнесом
- Техническое лидерство команды
3. **Senior Solution Architect** (7+ лет опыта)
- Сложные enterprise решения
- Архитектурная стратегия
- Менторство других архитекторов
4. **Principal/Lead Solution Architect** (10+ лет опыта)
- Архитектурная стратегия компании
- Управление архитектурной командой
- Влияние на технологическую стратегию
---
## Введение в архитектуру ПО
### Определение архитектуры ПО
**Архитектура программного обеспечения** — это структура системы, включающая:
- Компоненты системы и их взаимодействие
- Внешние интерфейсы
- Ограничения и требования
- Принципы проектирования
### Ключевые характеристики архитектуры
1. **Структурность** — архитектура определяет структуру системы
2. **Абстрактность** — скрывает детали реализации
3. **Системность** — рассматривает систему как целое
4. **Эволюционность** — может изменяться со временем
### Зачем нужна архитектура?
- **Управление сложностью** — разбиение сложной системы на понятные части
- **Коммуникация** — общий язык для обсуждения системы
- **Планирование** — основа для принятия технических решений
- **Переиспользование** — возможность использования компонентов в других проектах
### Архитектурные решения
**Архитектурное решение** — это выбор, который:
- Влияет на структуру системы
- Имеет долгосрочные последствия
- Трудно изменить в будущем
- Влияет на качественные атрибуты
### Примеры архитектурных решений:
#### 1. Выбор между монолитом и микросервисами
```java
// Монолитная архитектура
@SpringBootApplication
public class EcommerceApplication {
public static void main(String[] args) {
SpringApplication.run(EcommerceApplication.class, args);
}
}
// Все сервисы в одном приложении
@RestController
@RequestMapping("/api")
public class EcommerceController {
@Autowired
private UserService userService;
@Autowired
private OrderService orderService;
@Autowired
private PaymentService paymentService;
@PostMapping("/orders")
public ResponseEntity<Order> createOrder(@RequestBody CreateOrderRequest request) {
// Вся логика в одном месте
User user = userService.findById(request.getUserId());
Order order = orderService.createOrder(request);
PaymentResult payment = paymentService.processPayment(order);
return ResponseEntity.ok(order);
}
}
// Микросервисная архитектура
// User Service
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
return ResponseEntity.ok(userService.findById(id));
}
}
// Order Service
@RestController
@RequestMapping("/api/orders")
public class OrderController {
@Autowired
private OrderService orderService;
@Autowired
private UserServiceClient userServiceClient;
@PostMapping
public ResponseEntity<Order> createOrder(@RequestBody CreateOrderRequest request) {
// Вызов внешнего сервиса
User user = userServiceClient.getUser(request.getUserId());
Order order = orderService.createOrder(request);
return ResponseEntity.ok(order);
}
}
```
#### 2. Выбор базы данных (SQL vs NoSQL)
```java
// SQL подход (PostgreSQL)
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "name", nullable = false)
private String name;
@Column(name = "email", unique = true, nullable = false)
private String email;
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
private List<Order> orders = new ArrayList<>();
// Сложные запросы с JOIN
@Query("SELECT u FROM User u JOIN u.orders o WHERE o.status = :status")
List<User> findUsersWithOrdersByStatus(@Param("status") OrderStatus status);
}
// NoSQL подход (MongoDB)
@Document(collection = "users")
public class User {
@Id
private String id;
private String name;
private String email;
private List<Order> orders = new ArrayList<>();
// Гибкая схема данных
private Map<String, Object> additionalProperties = new HashMap<>();
}
// Пример использования
@Service
public class UserService {
// SQL: ACID транзакции
@Transactional
public User createUserWithOrder(CreateUserRequest request) {
User user = new User(request.getName(), request.getEmail());
user = userRepository.save(user);
Order order = new Order(user.getId(), request.getOrderItems());
order = orderRepository.save(order);
return user;
}
// NoSQL: Документная модель
public User createUserWithOrder(CreateUserRequest request) {
User user = new User();
user.setName(request.getName());
user.setEmail(request.getEmail());
// Встраивание заказа в документ пользователя
Order order = new Order(request.getOrderItems());
user.getOrders().add(order);
return userRepository.save(user);
}
}
```
#### 3. Выбор протокола коммуникации (REST vs GraphQL)
```java
// REST API
@RestController
@RequestMapping("/api/users")
public class UserRestController {
@GetMapping("/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
User user = userService.findById(id);
return ResponseEntity.ok(user);
}
@GetMapping("/{id}/orders")
public ResponseEntity<List<Order>> getUserOrders(@PathVariable Long id) {
List<Order> orders = orderService.findByUserId(id);
return ResponseEntity.ok(orders);
}
// Проблема: N+1 запросов
@GetMapping
public ResponseEntity<List<User>> getAllUsers() {
List<User> users = userService.findAll();
// Для каждого пользователя нужен отдельный запрос для заказов
users.forEach(user -> {
List<Order> orders = orderService.findByUserId(user.getId());
user.setOrders(orders);
});
return ResponseEntity.ok(users);
}
}
// GraphQL API
@Component
public class UserGraphQLResolver implements GraphQLQueryResolver {
public User getUser(Long id) {
return userService.findById(id);
}
public List<User> getAllUsers() {
return userService.findAll();
}
}
@Component
public class UserResolver implements GraphQLResolver<User> {
@Autowired
private OrderService orderService;
// Автоматическое разрешение заказов
public List<Order> orders(User user) {
return orderService.findByUserId(user.getId());
}
}
// GraphQL Schema
/*
type User {
id: ID!
name: String!
email: String!
orders: [Order!]!
}
type Query {
user(id: ID!): User
users: [User!]!
}
# Клиент может запросить только нужные поля
query {
users {
id
name
orders {
id
total
}
}
}
*/
```
#### 4. Выбор стратегии кэширования
```java
// Cache-Aside Pattern
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private RedisTemplate<String, User> redisTemplate;
private static final String CACHE_KEY_PREFIX = "user:";
private static final Duration CACHE_TTL = Duration.ofMinutes(30);
public User findById(Long id) {
String cacheKey = CACHE_KEY_PREFIX + id;
// 1. Проверяем кэш
User user = redisTemplate.opsForValue().get(cacheKey);
if (user != null) {
return user;
}
// 2. Загружаем из БД
user = userRepository.findById(id);
if (user != null) {
// 3. Сохраняем в кэш
redisTemplate.opsForValue().set(cacheKey, user, CACHE_TTL);
}
return user;
}
}
// Write-Through Pattern
@Service
public class UserService {
public User save(User user) {
// 1. Сохраняем в БД
user = userRepository.save(user);
// 2. Обновляем кэш
String cacheKey = CACHE_KEY_PREFIX + user.getId();
redisTemplate.opsForValue().set(cacheKey, user, CACHE_TTL);
return user;
}
}
// Write-Behind Pattern
@Service
public class UserService {
@Async
public CompletableFuture<User> saveAsync(User user) {
// 1. Обновляем кэш сразу
String cacheKey = CACHE_KEY_PREFIX + user.getId();
redisTemplate.opsForValue().set(cacheKey, user, CACHE_TTL);
// 2. Сохраняем в БД асинхронно
return CompletableFuture.supplyAsync(() -> {
return userRepository.save(user);
});
}
}
```
---
## Доменная модель и Domain-Driven Design
### Что такое домен?
**Домен (Domain)** — это предметная область, в которой работает приложение. Это сфера знаний, правил и процессов, которые определяют, как работает бизнес.
#### Ключевые характеристики домена:
1. **Бизнес-логика** — правила и процессы, специфичные для предметной области
2. **Терминология** — язык, используемый экспертами в данной области
3. **Ограничения** — правила и ограничения, которые должны соблюдаться
4. **Процессы** — последовательности действий, выполняемых в домене
### Domain-Driven Design (DDD)
**Domain-Driven Design** — подход к разработке программного обеспечения, который фокусируется на моделировании предметной области.
#### Основные принципы DDD:
1. **Фокус на домене** — архитектура отражает структуру домена
2. **Единый язык** — общий язык между разработчиками и экспертами
3. **Модель-код соответствие** — код отражает доменную модель
4. **Итеративное уточнение** — постоянное улучшение понимания домена
### Строительные блоки DDD
#### 1. Entity (Сущность)
**Entity** — объект, который имеет уникальную идентичность и жизненный цикл.
```java
// Пример Entity - Заказ
@Entity
public class Order {
@Id
private OrderId id;
private CustomerId customerId;
private OrderStatus status;
private Money totalAmount;
private List<OrderItem> items;
// Конструктор
public Order(OrderId id, CustomerId customerId) {
this.id = id;
this.customerId = customerId;
this.status = OrderStatus.DRAFT;
this.items = new ArrayList<>();
}
// Бизнес-методы
public void addItem(ProductId productId, Quantity quantity, Money unitPrice) {
if (status != OrderStatus.DRAFT) {
throw new IllegalStateException("Cannot modify confirmed order");
}
OrderItem item = new OrderItem(productId, quantity, unitPrice);
items.add(item);
recalculateTotal();
}
public void confirm() {
if (items.isEmpty()) {
throw new IllegalStateException("Cannot confirm empty order");
}
this.status = OrderStatus.CONFIRMED;
// Доменное событие
DomainEvents.raise(new OrderConfirmed(this.id));
}
private void recalculateTotal() {
this.totalAmount = items.stream()
.map(OrderItem::getSubtotal)
.reduce(Money.ZERO, Money::add);
}
// Геттеры
public OrderId getId() { return id; }
public CustomerId getCustomerId() { return customerId; }
public OrderStatus getStatus() { return status; }
public Money getTotalAmount() { return totalAmount; }
public List<OrderItem> getItems() { return Collections.unmodifiableList(items); }
}
```
#### 2. Value Object (Объект-значение)
**Value Object** — объект, который определяется только своими атрибутами, без идентичности.
```java
// Пример Value Object - Деньги
public class Money {
private final BigDecimal amount;
private final Currency currency;
public Money(BigDecimal amount, Currency currency) {
if (amount == null || amount.compareTo(BigDecimal.ZERO) < 0) {
throw new IllegalArgumentException("Amount must be non-negative");
}
if (currency == null) {
throw new IllegalArgumentException("Currency cannot be null");
}
this.amount = amount.setScale(2, RoundingMode.HALF_UP);
this.currency = currency;
}
public Money add(Money other) {
if (!this.currency.equals(other.currency)) {
throw new IllegalArgumentException("Cannot add different currencies");
}
return new Money(this.amount.add(other.amount), this.currency);
}
public Money multiply(BigDecimal factor) {
return new Money(this.amount.multiply(factor), this.currency);
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Money money = (Money) obj;
return Objects.equals(amount, money.amount) &&
Objects.equals(currency, money.currency);
}
@Override
public int hashCode() {
return Objects.hash(amount, currency);
}
// Геттеры
public BigDecimal getAmount() { return amount; }
public Currency getCurrency() { return currency; }
public static final Money ZERO = new Money(BigDecimal.ZERO, Currency.getInstance("USD"));
}
```
#### 3. Aggregate (Агрегат)
**Aggregate** — кластер связанных объектов, которые рассматриваются как единое целое.
```java
// Пример Aggregate - Заказ
public class Order {
private OrderId id;
private CustomerId customerId;
private OrderStatus status;
private Money totalAmount;
private List<OrderItem> items;
private List<DomainEvent> domainEvents;
// Конструктор и методы...
// Методы для работы с доменными событиями
public List<DomainEvent> getUncommittedEvents() {
return new ArrayList<>(domainEvents);
}
public void markEventsAsCommitted() {
domainEvents.clear();
}
private void raiseEvent(DomainEvent event) {
domainEvents.add(event);
}
}
// Корневой объект агрегата
public class OrderAggregate {
private Order order;
public OrderAggregate(OrderId id, CustomerId customerId) {
this.order = new Order(id, customerId);
}
public void addItem(ProductId productId, Quantity quantity, Money unitPrice) {
order.addItem(productId, quantity, unitPrice);
}
public void confirm() {
order.confirm();
}
public Order getOrder() {
return order;
}
}
```
#### 4. Domain Service (Доменный сервис)
**Domain Service** — сервис, который содержит бизнес-логику, не принадлежащую ни одной сущности.
```java
// Пример Domain Service - Расчет скидки
@Service
public class DiscountCalculationService {
public Money calculateDiscount(Order order, Customer customer) {
Money discount = Money.ZERO;
// Скидка за количество товаров
int totalItems = order.getItems().stream()
.mapToInt(item -> item.getQuantity().getValue())
.sum();
if (totalItems >= 10) {
discount = discount.add(order.getTotalAmount().multiply(new BigDecimal("0.1")));
}
// Скидка для VIP клиентов
if (customer.isVip()) {
discount = discount.add(order.getTotalAmount().multiply(new BigDecimal("0.05")));
}
// Скидка за праздники
if (isHolidayPeriod()) {
discount = discount.add(order.getTotalAmount().multiply(new BigDecimal("0.15")));
}
return discount;
}
private boolean isHolidayPeriod() {
LocalDate now = LocalDate.now();
return now.getMonth() == Month.DECEMBER && now.getDayOfMonth() >= 20;
}
}
```
#### 5. Repository (Репозиторий)
**Repository** — интерфейс для доступа к агрегатам.
```java
// Интерфейс репозитория
public interface OrderRepository {
void save(Order order);
Optional<Order> findById(OrderId id);
List<Order> findByCustomerId(CustomerId customerId);
List<Order> findByStatus(OrderStatus status);
void delete(OrderId id);
}
// Реализация репозитория
@Repository
public class JpaOrderRepository implements OrderRepository {
@PersistenceContext
private EntityManager entityManager;
@Override
public void save(Order order) {
entityManager.persist(order);
}
@Override
public Optional<Order> findById(OrderId id) {
Order order = entityManager.find(Order.class, id);
return Optional.ofNullable(order);
}
@Override
public List<Order> findByCustomerId(CustomerId customerId) {
return entityManager.createQuery(
"SELECT o FROM Order o WHERE o.customerId = :customerId", Order.class)
.setParameter("customerId", customerId)
.getResultList();
}
@Override
public List<Order> findByStatus(OrderStatus status) {
return entityManager.createQuery(
"SELECT o FROM Order o WHERE o.status = :status", Order.class)
.setParameter("status", status)
.getResultList();
}
@Override
public void delete(OrderId id) {
Order order = entityManager.find(Order.class, id);
if (order != null) {
entityManager.remove(order);
}
}
}
```
### Bounded Context (Ограниченный контекст)
**Bounded Context** — граница, в которой определенная доменная модель является валидной.
#### Примеры Bounded Context:
1. **Order Management** — управление заказами
2. **Customer Management** — управление клиентами
3. **Inventory Management** — управление складом
4. **Payment Processing** — обработка платежей
```java
// Bounded Context: Order Management
@BoundedContext("OrderManagement")
public class OrderContext {
// Агрегаты
public class Order { /* ... */ }
public class OrderItem { /* ... */ }
// Сервисы
public class OrderService { /* ... */ }
public class OrderValidationService { /* ... */ }
// Репозитории
public interface OrderRepository { /* ... */ }
}
// Bounded Context: Customer Management
@BoundedContext("CustomerManagement")
public class CustomerContext {
// Агрегаты
public class Customer { /* ... */ }
public class CustomerProfile { /* ... */ }
// Сервисы
public class CustomerService { /* ... */ }
public class CustomerRegistrationService { /* ... */ }
// Репозитории
public interface CustomerRepository { /* ... */ }
}
```
### Ubiquitous Language (Единый язык)
**Ubiquitous Language** — общий язык между разработчиками и экспертами предметной области.
#### Пример единого языка для e-commerce:
```java
// Термины домена
public enum OrderStatus {
DRAFT("Черновик"),
CONFIRMED("Подтвержден"),
SHIPPED("Отправлен"),
DELIVERED("Доставлен"),
CANCELLED("Отменен");
private final String description;
OrderStatus(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
}
// Доменные события
public class OrderConfirmed implements DomainEvent {
private final OrderId orderId;
private final LocalDateTime occurredOn;
public OrderConfirmed(OrderId orderId) {
this.orderId = orderId;
this.occurredOn = LocalDateTime.now();
}
public OrderId getOrderId() {
return orderId;
}
public LocalDateTime getOccurredOn() {
return occurredOn;
}
}
```
### Event Storming
**Event Storming** — методология для изучения домена через события.
#### Процесс Event Storming:
1. **Доменные события** — что происходит в системе
2. **Команды** — что вызывает события
3. **Агрегаты** — что обрабатывает команды
4. **Политики** — бизнес-правила
5. **Внешние системы** — интеграции
```markdown
# Event Storming для E-commerce
## Доменные события
- OrderCreated (Заказ создан)
- OrderConfirmed (Заказ подтвержден)
- PaymentProcessed (Платеж обработан)
- OrderShipped (Заказ отправлен)
- OrderDelivered (Заказ доставлен)
## Команды
- CreateOrder (Создать заказ)
- ConfirmOrder (Подтвердить заказ)
- ProcessPayment (Обработать платеж)
- ShipOrder (Отправить заказ)
## Агрегаты
- Order (Заказ)
- Customer (Клиент)
- Product (Товар)
- Payment (Платеж)
## Политики
- Скидка за количество товаров
- VIP статус клиента
- Праздничные скидки
```
### Антипаттерны DDD
#### 1. Anemic Domain Model (Анемичная доменная модель)
**Проблема:** Доменные объекты содержат только данные без поведения.
```java
// Плохо: Анемичная модель
public class Order {
private Long id;
private String customerId;
private String status;
private BigDecimal totalAmount;
// Только геттеры и сеттеры
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
// ...
}
// Хорошо: Богатая доменная модель
public class Order {
private OrderId id;
private CustomerId customerId;
private OrderStatus status;
private Money totalAmount;
// Бизнес-методы
public void addItem(ProductId productId, Quantity quantity, Money unitPrice) {
// Бизнес-логика
}
public void confirm() {
// Бизнес-логика
}
}
```
#### 2. God Object (Божественный объект)
**Проблема:** Один объект содержит слишком много ответственности.
```java
// Плохо: God Object
public class Order {
// Слишком много ответственности
public void addItem() { /* ... */ }
public void calculateTax() { /* ... */ }
public void sendEmail() { /* ... */ }
public void updateInventory() { /* ... */ }
public void processPayment() { /* ... */ }
}
// Хорошо: Разделение ответственности
public class Order {
public void addItem() { /* ... */ }
}
public class TaxCalculationService {
public Money calculateTax(Order order) { /* ... */ }
}
public class EmailService {
public void sendOrderConfirmation(Order order) { /* ... */ }
}
```
### Интеграция DDD с архитектурой
#### 1. Layered Architecture + DDD
```java
// Presentation Layer
@RestController
public class OrderController {
@Autowired
private OrderApplicationService orderService;
@PostMapping("/orders")
public ResponseEntity<OrderResponse> createOrder(@RequestBody CreateOrderRequest request) {
OrderId orderId = orderService.createOrder(request);
return ResponseEntity.ok(new OrderResponse(orderId));
}
}
// Application Layer
@Service
public class OrderApplicationService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private OrderDomainService orderDomainService;
public OrderId createOrder(CreateOrderRequest request) {
Order order = new Order(
new OrderId(UUID.randomUUID()),
new CustomerId(request.getCustomerId())
);
for (OrderItemRequest itemRequest : request.getItems()) {
order.addItem(
new ProductId(itemRequest.getProductId()),
new Quantity(itemRequest.getQuantity()),
new Money(itemRequest.getUnitPrice(), Currency.USD)
);
}
orderRepository.save(order);
return order.getId();
}
}
// Domain Layer
public class Order {
// Доменная логика
}
// Infrastructure Layer
@Repository
public class JpaOrderRepository implements OrderRepository {
// Реализация репозитория
}
```
#### 2. Hexagonal Architecture + DDD
```java
// Domain Layer (внутренний круг)
public class Order {
// Доменная логика
}
public interface OrderRepository {
void save(Order order);
Optional<Order> findById(OrderId id);
}
// Application Layer
@Service
public class OrderService {
private final OrderRepository orderRepository;
public OrderService(OrderRepository orderRepository) {
this.orderRepository = orderRepository;
}
public void createOrder(Order order) {
// Применение доменных правил
orderRepository.save(order);
}
}
// Infrastructure Layer (внешний круг)
@Repository
public class JpaOrderRepository implements OrderRepository {
// Реализация через JPA
}
// Adapters
@RestController
public class OrderController {
// REST API
}
@Component
public class OrderEventHandler {
// Обработка событий
}
```
### Практические рекомендации
#### 1. Начните с доменных событий
```java
// Сначала определите события
public class OrderCreated implements DomainEvent {
private final OrderId orderId;
private final CustomerId customerId;
private final LocalDateTime occurredOn;
// Конструктор, геттеры
}
// Затем создайте агрегаты
public class Order {
public void confirm() {
// Бизнес-логика
DomainEvents.raise(new OrderConfirmed(this.id));
}
}
```
#### 2. Используйте Value Objects для примитивов
```java
// Вместо String
public class CustomerId {
private final String value;
public CustomerId(String value) {
if (value == null || value.trim().isEmpty()) {
throw new IllegalArgumentException("Customer ID cannot be empty");
}
this.value = value;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
CustomerId that = (CustomerId) obj;
return Objects.equals(value, that.value);
}
@Override
public int hashCode() {
return Objects.hash(value);
}
}
```
#### 3. Следите за границами агрегатов
```java
// Правильно: Агрегат управляет только своими данными
public class Order {
private OrderId id;
private List<OrderItem> items;
public void addItem(ProductId productId, Quantity quantity, Money unitPrice) {
// Логика добавления товара в заказ
}
}
// Неправильно: Агрегат управляет чужими данными
public class Order {
public void updateProductInventory(ProductId productId, Quantity quantity) {
// Это не должно быть в Order агрегате
}
}
```
### Заключение
Domain-Driven Design помогает создавать архитектуру, которая:
1. **Отражает бизнес-логику** — код соответствует домену
2. **Улучшает коммуникацию** — единый язык между командами
3. **Повышает качество** — четкие границы и ответственности
4. **Упрощает тестирование** — изолированная доменная логика
5. **Обеспечивает эволюцию** — гибкая архитектура для изменений
DDD особенно эффективен для сложных бизнес-доменов, где важно точное понимание предметной области и создание архитектуры, которая может эволюционировать вместе с бизнесом.
---
## Принципы проектирования
### SOLID принципы
#### 1. Single Responsibility Principle (SRP)
**Принцип единственной ответственности**
> Класс должен иметь только одну причину для изменения
**Пример нарушения:**
```java
// Нарушение SRP - класс делает слишком много
class UserManager {
private UserRepository userRepository;
private EmailService emailService;
private PasswordEncoder passwordEncoder;
private AuditService auditService;
public User createUser(CreateUserRequest request) {
// 1. Валидация данных
if (request.getEmail() == null || !isValidEmail(request.getEmail())) {
throw new ValidationException("Invalid email");
}
// 2. Хеширование пароля
String hashedPassword = passwordEncoder.encode(request.getPassword());
// 3. Создание пользователя
User user = new User(request.getName(), request.getEmail(), hashedPassword);
user = userRepository.save(user);
// 4. Отправка приветственного email
emailService.sendWelcomeEmail(user.getEmail());
// 5. Аудит
auditService.logUserCreation(user.getId());
return user;
}
public void updateUser(Long userId, UpdateUserRequest request) {
// Аналогично много ответственностей...
}
public void deleteUser(Long userId) {
// Аналогично много ответственностей...
}
}
```
**Правильная реализация:**
```java
// Разделение ответственностей
class User {
private Long id;
private String name;
private String email;
private String passwordHash;
// Только данные пользователя и бизнес-логика
public void changePassword(String newPassword, PasswordEncoder encoder) {
this.passwordHash = encoder.encode(newPassword);
}
public boolean isEmailValid() {
return email != null && email.contains("@");
}
}
class UserValidator {
public void validateCreateRequest(CreateUserRequest request) {
if (request.getName() == null || request.getName().trim().isEmpty()) {
throw new ValidationException("Name is required");
}
if (request.getEmail() == null || !isValidEmail(request.getEmail())) {
throw new ValidationException("Invalid email");
}
if (request.getPassword() == null || request.getPassword().length() < 8) {
throw new ValidationException("Password must be at least 8 characters");
}
}
private boolean isValidEmail(String email) {
return email.matches("^[A-Za-z0-9+_.-]+@(.+)$");
}
}
class UserRepository {
public User save(User user) {
// Только операции с БД
return userRepository.save(user);
}
public Optional<User> findById(Long id) {
return userRepository.findById(id);
}
}
class UserNotificationService {
public void sendWelcomeEmail(String email) {
// Только отправка уведомлений
emailService.sendWelcomeEmail(email);
}
}
class UserAuditService {
public void logUserCreation(Long userId) {
// Только аудит
auditService.logUserCreation(userId);
}
}
// Координатор, который использует все сервисы
class UserService {
private UserValidator validator;
private UserRepository repository;
private UserNotificationService notificationService;
private UserAuditService auditService;
private PasswordEncoder passwordEncoder;
public User createUser(CreateUserRequest request) {
// 1. Валидация
validator.validateCreateRequest(request);
// 2. Создание пользователя
String hashedPassword = passwordEncoder.encode(request.getPassword());
User user = new User(request.getName(), request.getEmail(), hashedPassword);
// 3. Сохранение
user = repository.save(user);
// 4. Уведомления
notificationService.sendWelcomeEmail(user.getEmail());
// 5. Аудит
auditService.logUserCreation(user.getId());
return user;
}
}
```
#### 2. Open/Closed Principle (OCP)
**Принцип открытости/закрытости**
> Программные сущности должны быть открыты для расширения, но закрыты для модификации
**Пример нарушения:**
```java
// Нарушение OCP - нужно модифицировать класс для добавления новых типов
class OrderProcessor {
public void processOrder(Order order) {
switch (order.getPaymentType()) {
case CREDIT_CARD:
processCreditCardPayment(order);
break;
case PAYPAL:
processPayPalPayment(order);
break;
case BANK_TRANSFER:
processBankTransferPayment(order);
break;
// При добавлении нового типа платежа нужно модифицировать этот класс
}
}
private void processCreditCardPayment(Order order) {
// логика обработки кредитной карты
}
private void processPayPalPayment(Order order) {
// логика обработки PayPal
}
private void processBankTransferPayment(Order order) {
// логика обработки банковского перевода
}
}
```
**Правильная реализация:**
```java
// Соблюдение OCP - открыто для расширения, закрыто для модификации
interface PaymentProcessor {
boolean canProcess(PaymentType paymentType);
PaymentResult processPayment(PaymentRequest request);
}
class CreditCardProcessor implements PaymentProcessor {
@Override
public boolean canProcess(PaymentType paymentType) {
return paymentType == PaymentType.CREDIT_CARD;
}
@Override
public PaymentResult processPayment(PaymentRequest request) {
// логика обработки кредитной карты
return new PaymentResult(true, "Payment processed via credit card");
}
}
class PayPalProcessor implements PaymentProcessor {
@Override
public boolean canProcess(PaymentType paymentType) {
return paymentType == PaymentType.PAYPAL;
}
@Override
public PaymentResult processPayment(PaymentRequest request) {
// логика обработки PayPal
return new PaymentResult(true, "Payment processed via PayPal");
}
}
class CryptocurrencyProcessor implements PaymentProcessor {
@Override
public boolean canProcess(PaymentType paymentType) {
return paymentType == PaymentType.CRYPTOCURRENCY;
}
@Override
public PaymentResult processPayment(PaymentRequest request) {
// логика обработки криптовалюты
return new PaymentResult(true, "Payment processed via cryptocurrency");
}
}
// Основной класс не нужно модифицировать для добавления новых процессоров
class OrderProcessor {
private List<PaymentProcessor> processors;
public OrderProcessor(List<PaymentProcessor> processors) {
this.processors = processors;
}
public PaymentResult processOrder(Order order) {
PaymentProcessor processor = processors.stream()
.filter(p -> p.canProcess(order.getPaymentType()))
.findFirst()
.orElseThrow(() -> new UnsupportedPaymentTypeException(
"No processor found for payment type: " + order.getPaymentType()));
PaymentRequest request = new PaymentRequest(order.getAmount(), order.getPaymentDetails());
return processor.processPayment(request);
}
}
// Конфигурация процессоров
@Configuration
public class PaymentProcessorConfig {
@Bean
public List<PaymentProcessor> paymentProcessors() {
return Arrays.asList(
new CreditCardProcessor(),
new PayPalProcessor(),
new CryptocurrencyProcessor() // Новый процессор добавляется без модификации существующего кода
);
}
}
```
#### 3. Liskov Substitution Principle (LSP)
**Принцип подстановки Лисков**
> Объекты производного класса должны быть заменяемы объектами базового класса
**Пример нарушения:**
```java
// Нарушение LSP
class Rectangle {
protected int width;
protected int height;
public void setWidth(int width) {
this.width = width;
}
public void setHeight(int height) {
this.height = height;
}
public int getArea() {
return width * height;
}
}
class Square extends Rectangle {
@Override
public void setWidth(int width) {
this.width = width;
this.height = width; // Нарушение LSP - изменяет поведение
}
@Override
public void setHeight(int height) {
this.height = height;
this.width = height; // Нарушение LSP - изменяет поведение
}
}
// Проблема: код, работающий с Rectangle, может сломаться с Square
public void testRectangle(Rectangle rectangle) {
rectangle.setWidth(5);
rectangle.setHeight(4);
assert rectangle.getArea() == 20; // Для Square это будет 16!
}
```
**Правильная реализация:**
```java
// Соблюдение LSP
abstract class Shape {
public abstract int getArea();
}
class Rectangle extends Shape {
private int width;
private int height;
public Rectangle(int width, int height) {
this.width = width;
this.height = height;
}
public void setWidth(int width) {
this.width = width;
}
public void setHeight(int height) {
this.height = height;
}
@Override
public int getArea() {
return width * height;
}
}
class Square extends Shape {
private int side;
public Square(int side) {
this.side = side;
}
public void setSide(int side) {
this.side = side;
}
@Override
public int getArea() {
return side * side;
}
}
// Теперь любой Shape можно заменить другим Shape
public void testShape(Shape shape) {
int area = shape.getArea();
// Логика работает корректно для любого типа Shape
}
```
#### 4. Interface Segregation Principle (ISP)
**Принцип разделения интерфейсов**
> Клиенты не должны зависеть от интерфейсов, которые они не используют
**Пример нарушения:**
```java
// Нарушение ISP - слишком большой интерфейс
interface Worker {
void work();
void eat();
void sleep();
void code();
void design();
void test();
void deploy();
}
class Developer implements Worker {
@Override
public void work() { /* разработка */ }
@Override
public void eat() { /* еда */ }
@Override
public void sleep() { /* сон */ }
@Override
public void code() { /* кодирование */ }
@Override
public void design() { /* не используется */ }
@Override
public void test() { /* не используется */ }
@Override
public void deploy() { /* не используется */ }
}
class Designer implements Worker {
@Override
public void work() { /* работа */ }
@Override
public void eat() { /* еда */ }
@Override
public void sleep() { /* сон */ }
@Override
public void code() { /* не используется */ }
@Override
public void design() { /* дизайн */ }
@Override
public void test() { /* не используется */ }
@Override
public void deploy() { /* не используется */ }
}
```
**Правильная реализация:**
```java
// Соблюдение ISP - разделение интерфейсов
interface Human {
void eat();
void sleep();
}
interface Worker extends Human {
void work();
}
interface Developer extends Worker {
void code();
}
interface Designer extends Worker {
void design();
}
interface Tester extends Worker {
void test();
}
interface DevOps extends Worker {
void deploy();
}
// Теперь каждый класс реализует только нужные ему интерфейсы
class JavaDeveloper implements Developer {
@Override
public void eat() { /* еда */ }
@Override
public void sleep() { /* сон */ }
@Override
public void work() { /* работа */ }
@Override
public void code() { /* Java кодирование */ }
}
class UIDesigner implements Designer {
@Override
public void eat() { /* еда */ }
@Override
public void sleep() { /* сон */ }
@Override
public void work() { /* работа */ }
@Override
public void design() { /* UI дизайн */ }
}
// Можно комбинировать интерфейсы
class FullStackDeveloper implements Developer, Designer {
@Override
public void eat() { /* еда */ }
@Override
public void sleep() { /* сон */ }
@Override
public void work() { /* работа */ }
@Override
public void code() { /* кодирование */ }
@Override
public void design() { /* дизайн */ }
}
```
#### 5. Dependency Inversion Principle (DIP)
**Принцип инверсии зависимостей**
> Модули высокого уровня не должны зависеть от модулей низкого уровня
**Пример нарушения:**
```java
// Нарушение DIP - высокоуровневый модуль зависит от низкоуровневого
class EmailService {
public void sendEmail(String to, String subject, String body) {
// Прямая зависимость от конкретной реализации
SMTPClient smtpClient = new SMTPClient();
smtpClient.connect("smtp.gmail.com", 587);
smtpClient.send(to, subject, body);
smtpClient.disconnect();
}
}
class OrderService {
private EmailService emailService;
public OrderService() {
this.emailService = new EmailService(); // Жесткая зависимость
}
public void processOrder(Order order) {
// Обработка заказа
order.setStatus(OrderStatus.PROCESSED);
// Отправка уведомления
emailService.sendEmail(
order.getCustomerEmail(),
"Order Confirmation",
"Your order has been processed"
);
}
}
```
**Правильная реализация:**
```java
// Соблюдение DIP - зависимость от абстракций
interface NotificationService {
void sendNotification(String recipient, String subject, String message);
}
interface EmailProvider {
void sendEmail(String to, String subject, String body);
}
class SMTPEmailProvider implements EmailProvider {
@Override
public void sendEmail(String to, String subject, String body) {
SMTPClient smtpClient = new SMTPClient();
smtpClient.connect("smtp.gmail.com", 587);
smtpClient.send(to, subject, body);
smtpClient.disconnect();
}
}
class SendGridEmailProvider implements EmailProvider {
@Override
public void sendEmail(String to, String subject, String body) {
SendGridClient client = new SendGridClient();
client.send(to, subject, body);
}
}
class EmailNotificationService implements NotificationService {
private EmailProvider emailProvider;
public EmailNotificationService(EmailProvider emailProvider) {
this.emailProvider = emailProvider; // Инъекция зависимости
}
@Override
public void sendNotification(String recipient, String subject, String message) {
emailProvider.sendEmail(recipient, subject, message);
}
}
class SMSNotificationService implements NotificationService {
private SMSProvider smsProvider;
public SMSNotificationService(SMSProvider smsProvider) {
this.smsProvider = smsProvider;
}
@Override
public void sendNotification(String recipient, String subject, String message) {
smsProvider.sendSMS(recipient, message);
}
}
// Высокоуровневый модуль зависит от абстракции
class OrderService {
private NotificationService notificationService;
public OrderService(NotificationService notificationService) {
this.notificationService = notificationService; // Инъекция зависимости
}
public void processOrder(Order order) {
// Обработка заказа
order.setStatus(OrderStatus.PROCESSED);
// Отправка уведомления через абстракцию
notificationService.sendNotification(
order.getCustomerEmail(),
"Order Confirmation",
"Your order has been processed"
);
}
}
// Конфигурация зависимостей
@Configuration
public class AppConfig {
@Bean
public EmailProvider emailProvider() {
return new SMTPEmailProvider(); // Можно легко заменить на SendGridEmailProvider
}
@Bean
public NotificationService notificationService(EmailProvider emailProvider) {
return new EmailNotificationService(emailProvider);
}
@Bean
public OrderService orderService(NotificationService notificationService) {
return new OrderService(notificationService);
}
}
```
### Дополнительные принципы
#### DRY (Не повторяйся)
**Не повторяйся**
- Избегайте дублирования кода
- Выносите общую логику в отдельные функции/классы
- Используйте абстракции для переиспользования
**Пример нарушения:**
```java
// Нарушение DRY - дублирование кода
class UserService {
public void createUser(String email, String password) {
if (email == null || email.isEmpty()) {
throw new IllegalArgumentException("Email cannot be null or empty");
}
if (!email.contains("@")) {
throw new IllegalArgumentException("Invalid email format");
}
if (password == null || password.length() < 8) {
throw new IllegalArgumentException("Password must be at least 8 characters");
}
// создание пользователя
}
public void updateUser(String email, String password) {
if (email == null || email.isEmpty()) {
throw new IllegalArgumentException("Email cannot be null or empty");
}
if (!email.contains("@")) {
throw new IllegalArgumentException("Invalid email format");
}
if (password == null || password.length() < 8) {
throw new IllegalArgumentException("Password must be at least 8 characters");
}
// обновление пользователя
}
}
```
**Правильная реализация:**
```java
// Соблюдение DRY - выделение общей логики
class ValidationUtils {
public static void validateEmail(String email) {
if (email == null || email.isEmpty()) {
throw new IllegalArgumentException("Email cannot be null or empty");
}
if (!email.contains("@")) {
throw new IllegalArgumentException("Invalid email format");
}
}
public static void validatePassword(String password) {
if (password == null || password.length() < 8) {
throw new IllegalArgumentException("Password must be at least 8 characters");
}
}
}
class UserService {
public void createUser(String email, String password) {
ValidationUtils.validateEmail(email);
ValidationUtils.validatePassword(password);
// создание пользователя
}
public void updateUser(String email, String password) {
ValidationUtils.validateEmail(email);
ValidationUtils.validatePassword(password);
// обновление пользователя
}
}
```
#### KISS (Будь проще)
**Делай проще**
- Простые решения часто лучше сложных
- Избегайте преждевременной оптимизации
- Читаемость кода важнее "умности"
**Пример нарушения:**
```java
// Нарушение KISS - излишняя сложность
class ComplexCalculator {
public double calculate(String expression) {
if (expression == null || expression.trim().isEmpty()) {
throw new IllegalArgumentException("Expression cannot be null or empty");
}
String trimmedExpression = expression.trim();
if (!trimmedExpression.matches("^[0-9+\\-*/\\s()]+$")) {
throw new IllegalArgumentException("Invalid characters in expression");
}
// Сложная логика парсинга и вычисления...
return evaluateComplexExpression(trimmedExpression);
}
private double evaluateComplexExpression(String expr) {
// Очень сложная реализация
return 0.0;
}
}
```
**Правильная реализация:**
```java
// Соблюдение KISS - простое решение
class SimpleCalculator {
public double add(double a, double b) {
return a + b;
}
public double subtract(double a, double b) {
return a - b;
}
public double multiply(double a, double b) {
return a * b;
}
public double divide(double a, double b) {
if (b == 0) {
throw new IllegalArgumentException("Division by zero");
}
return a / b;
}
}
// Использование
SimpleCalculator calc = new SimpleCalculator();
double result = calc.add(calc.multiply(2, 3), calc.divide(10, 2)); // 2*3 + 10/2 = 11
```
#### YAGNI (Вам это не понадобится)
**Тебе это не понадобится**
- Не реализуйте функциональность "на будущее"
- Добавляйте функции только когда они действительно нужны
- Избегайте over-engineering
**Пример нарушения:**
```java
// Нарушение YAGNI - преждевременная оптимизация
class UserRepository {
private Map<String, User> cache = new ConcurrentHashMap<>();
private List<User> users = new ArrayList<>();
private UserIndexer indexer = new UserIndexer();
private UserValidator validator = new UserValidator();
private UserMetricsCollector metrics = new UserMetricsCollector();
public void saveUser(User user) {
// Валидация (может не понадобиться)
validator.validate(user);
// Индексация (может не понадобиться)
indexer.index(user);
// Кэширование (может не понадобиться)
cache.put(user.getId(), user);
// Сохранение
users.add(user);
// Метрики (может не понадобиться)
metrics.recordSave(user);
}
}
```
**Правильная реализация:**
```java
// Соблюдение YAGNI - только необходимая функциональность
class UserRepository {
private List<User> users = new ArrayList<>();
public void saveUser(User user) {
if (user == null) {
throw new IllegalArgumentException("User cannot be null");
}
users.add(user);
}
public User findById(String id) {
if (id == null) {
throw new IllegalArgumentException("ID cannot be null");
}
return users.stream()
.filter(u -> u.getId().equals(id))
.findFirst()
.orElse(null);
}
}
// Когда понадобится кэширование - добавим
// Когда понадобится валидация - добавим
// Когда понадобится индексация - добавим
```
### Принципы модульности
#### Слабая связанность (Loose Coupling)
- Компоненты минимально зависят друг от друга
- Изменения в одном компоненте не влияют на другие
- Легче тестировать и поддерживать
**Пример нарушения:**
```java
// Нарушение - тесная связанность
class OrderService {
private MySQLDatabase database;
private SMTPEmailService emailService;
private FileLogger logger;
public OrderService() {
this.database = new MySQLDatabase("localhost", "orders_db");
this.emailService = new SMTPEmailService("smtp.gmail.com", 587);
this.logger = new FileLogger("/var/log/orders.log");
}
public void processOrder(Order order) {
// Прямая зависимость от конкретных реализаций
database.save(order);
emailService.sendEmail(order.getCustomerEmail(), "Order processed");
logger.log("Order processed: " + order.getId());
}
}
```
**Правильная реализация:**
```java
// Соблюдение - слабая связанность
interface OrderRepository {
void save(Order order);
}
interface NotificationService {
void sendNotification(String recipient, String message);
}
interface LoggingService {
void log(String message);
}
class OrderService {
private OrderRepository repository;
private NotificationService notificationService;
private LoggingService loggingService;
public OrderService(OrderRepository repository,
NotificationService notificationService,
LoggingService loggingService) {
this.repository = repository;
this.notificationService = notificationService;
this.loggingService = loggingService;
}
public void processOrder(Order order) {
repository.save(order);
notificationService.sendNotification(
order.getCustomerEmail(),
"Order processed"
);
loggingService.log("Order processed: " + order.getId());
}
}
// Конкретные реализации
class MySQLOrderRepository implements OrderRepository {
@Override
public void save(Order order) {
// MySQL специфичная логика
}
}
class EmailNotificationService implements NotificationService {
@Override
public void sendNotification(String recipient, String message) {
// Email специфичная логика
}
}
class FileLoggingService implements LoggingService {
@Override
public void log(String message) {
// File logging специфичная логика
}
}
```
#### Высокая сплоченность (High Cohesion)
- Элементы внутри модуля тесно связаны
- Модуль имеет четко определенную ответственность
- Все функции модуля работают для достижения одной цели
**Пример нарушения:**
```java
// Нарушение - низкая сплоченность
class UtilityClass {
public void sendEmail(String to, String subject, String body) {
// Отправка email
}
public void calculateTax(double amount, double rate) {
// Расчет налога
}
public void validatePassword(String password) {
// Валидация пароля
}
public void formatDate(Date date, String format) {
// Форматирование даты
}
public void compressFile(String filePath) {
// Сжатие файла
}
}
```
**Правильная реализация:**
```java
// Соблюдение - высокая сплоченность
class EmailService {
public void sendEmail(String to, String subject, String body) {
validateEmail(to);
String formattedBody = formatEmailBody(body);
deliverEmail(to, subject, formattedBody);
}
private void validateEmail(String email) {
// Валидация email адреса
}
private String formatEmailBody(String body) {
// Форматирование тела письма
return body;
}
private void deliverEmail(String to, String subject, String body) {
// Доставка email
}
}
class TaxCalculator {
public double calculateTax(double amount, double rate) {
validateAmount(amount);
validateRate(rate);
return amount * rate;
}
private void validateAmount(double amount) {
if (amount < 0) {
throw new IllegalArgumentException("Amount cannot be negative");
}
}
private void validateRate(double rate) {
if (rate < 0 || rate > 1) {
throw new IllegalArgumentException("Rate must be between 0 and 1");
}
}
}
class PasswordValidator {
public boolean isValid(String password) {
return hasMinimumLength(password) &&
containsUppercase(password) &&
containsLowercase(password) &&
containsDigit(password);
}
private boolean hasMinimumLength(String password) {
return password.length() >= 8;
}
private boolean containsUppercase(String password) {
return password.chars().anyMatch(Character::isUpperCase);
}
private boolean containsLowercase(String password) {
return password.chars().anyMatch(Character::isLowerCase);
}
private boolean containsDigit(String password) {
return password.chars().anyMatch(Character::isDigit);
}
}
```
---
## Архитектурные стили и паттерны
### Архитектурные стили
#### 1. Монолитная архитектура
**Определение:** Вся система реализована как единое приложение
**Преимущества:**
- Простота разработки и развертывания
- Легкое тестирование
- Простая отладка
- Низкая сложность сети
**Недостатки:**
- Сложность масштабирования
- Технологическая привязка
- Сложность обновлений
- Риск каскадных отказов
**Когда использовать:**
- Небольшие команды
- Простые приложения
- Быстрое прототипирование
**Технический пример:**
```java
// Монолитное приложение - все в одном проекте
@SpringBootApplication
public class ECommerceApplication {
public static void main(String[] args) {
SpringApplication.run(ECommerceApplication.class, args);
}
}
// Все контроллеры в одном приложении
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserService userService;
@PostMapping
public ResponseEntity<User> createUser(@RequestBody User user) {
return ResponseEntity.ok(userService.createUser(user));
}
@GetMapping("/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
return ResponseEntity.ok(userService.getUser(id));
}
}
@RestController
@RequestMapping("/api/orders")
public class OrderController {
@Autowired
private OrderService orderService;
@PostMapping
public ResponseEntity<Order> createOrder(@RequestBody Order order) {
return ResponseEntity.ok(orderService.createOrder(order));
}
@GetMapping("/{id}")
public ResponseEntity<Order> getOrder(@PathVariable Long id) {
return ResponseEntity.ok(orderService.getOrder(id));
}
}
@RestController
@RequestMapping("/api/products")
public class ProductController {
@Autowired
private ProductService productService;
@PostMapping
public ResponseEntity<Product> createProduct(@RequestBody Product product) {
return ResponseEntity.ok(productService.createProduct(product));
}
@GetMapping("/{id}")
public ResponseEntity<Product> getProduct(@PathVariable Long id) {
return ResponseEntity.ok(productService.getProduct(id));
}
}
// Все сервисы в одном приложении
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public User createUser(User user) {
return userRepository.save(user);
}
public User getUser(Long id) {
return userRepository.findById(id).orElse(null);
}
}
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private UserService userService; // Прямая зависимость между сервисами
public Order createOrder(Order order) {
User user = userService.getUser(order.getUserId());
if (user == null) {
throw new IllegalArgumentException("User not found");
}
return orderRepository.save(order);
}
public Order getOrder(Long id) {
return orderRepository.findById(id).orElse(null);
}
}
// Единая база данных
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
// геттеры и сеттеры
}
@Entity
@Table(name = "orders")
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long userId;
private BigDecimal amount;
// геттеры и сеттеры
}
@Entity
@Table(name = "products")
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private BigDecimal price;
// геттеры и сеттеры
}
```
#### 2. Микросервисная архитектура
**Определение:** Система состоит из множества небольших независимых сервисов
**Преимущества:**
- Независимое развертывание
- Технологическое разнообразие
- Масштабируемость
- Отказоустойчивость
**Недостатки:**
- Сложность сети
- Проблемы с данными
- Сложность мониторинга
- Overhead коммуникации
**Когда использовать:**
- Большие команды
- Сложные домены
#### 3. Слоистая архитектура (Layered Architecture)
**Определение:** Система организована в горизонтальные слои, где каждый слой может использовать только слои ниже себя.
**Слои:**
- **Presentation Layer** — пользовательский интерфейс
- **Business Layer** — бизнес-логика
- **Data Access Layer** — работа с данными
- **Database Layer** — хранение данных
**Преимущества:**
- Четкое разделение ответственности
- Легкость тестирования
- Переиспользование компонентов
- Простота понимания
**Недостатки:**
- Производительность (много слоев)
- Сложность при изменении требований
- Потенциальные проблемы с производительностью
**Технические примеры:**
**Java (Spring Boot):**
```java
// Presentation Layer
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserService userService;
@PostMapping
public ResponseEntity<UserDto> createUser(@RequestBody CreateUserRequest request) {
UserDto user = userService.createUser(request);
return ResponseEntity.ok(user);
}
}
// Business Layer
@Service
@Transactional
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private EmailService emailService;
public UserDto createUser(CreateUserRequest request) {
// Бизнес-логика валидации
validateUserRequest(request);
// Создание пользователя
User user = new User();
user.setName(request.getName());
user.setEmail(request.getEmail());
user.setCreatedDate(LocalDateTime.now());
// Сохранение через Data Access Layer
User savedUser = userRepository.save(user);
// Бизнес-логика уведомления
emailService.sendWelcomeEmail(savedUser.getEmail());
return mapToDto(savedUser);
}
private void validateUserRequest(CreateUserRequest request) {
if (request.getName() == null || request.getName().trim().isEmpty()) {
throw new ValidationException("Name is required");
}
if (request.getEmail() == null || !isValidEmail(request.getEmail())) {
throw new ValidationException("Valid email is required");
}
}
}
// Data Access Layer
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByEmail(String email);
List<User> findByCreatedDateBetween(LocalDateTime start, LocalDateTime end);
}
// Database Layer (Entity)
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
@Column(unique = true, nullable = false)
private String email;
@CreationTimestamp
private LocalDateTime createdDate;
// геттеры и сеттеры
}
```
**Rust (Actix Web + Diesel):**
```rust
// Presentation Layer
use actix_web::{web, HttpResponse, Result};
use serde::{Deserialize, Serialize};
#[derive(Deserialize)]
pub struct CreateUserRequest {
pub name: String,
pub email: String,
}
#[derive(Serialize)]
pub struct UserDto {
pub id: i32,
pub name: String,
pub email: String,
pub created_date: chrono::DateTime<chrono::Utc>,
}
// Business Layer
pub struct UserService {
user_repository: Box<dyn UserRepository>,
email_service: Box<dyn EmailService>,
}
impl UserService {
pub fn new(user_repository: Box<dyn UserRepository>, email_service: Box<dyn EmailService>) -> Self {
Self {
user_repository,
email_service,
}
}
pub fn create_user(&self, request: CreateUserRequest) -> Result<UserDto, UserError> {
// Бизнес-логика валидации
self.validate_user_request(&request)?;
// Создание пользователя
let user = User {
id: 0, // Will be set by database
name: request.name,
email: request.email,
created_date: chrono::Utc::now(),
};
// Сохранение через Data Access Layer
let saved_user = self.user_repository.save(user)?;
// Бизнес-логика уведомления
self.email_service.send_welcome_email(&saved_user.email)?;
Ok(UserDto {
id: saved_user.id,
name: saved_user.name,
email: saved_user.email,
created_date: saved_user.created_date,
})
}
fn validate_user_request(&self, request: &CreateUserRequest) -> Result<(), UserError> {
if request.name.trim().is_empty() {
return Err(UserError::ValidationError("Name is required".to_string()));
}
if !self.is_valid_email(&request.email) {
return Err(UserError::ValidationError("Valid email is required".to_string()));
}
Ok(())
}
fn is_valid_email(&self, email: &str) -> bool {
email.contains('@') && email.contains('.')
}
}
// Data Access Layer
pub trait UserRepository {
fn save(&self, user: User) -> Result<User, UserError>;
fn find_by_email(&self, email: &str) -> Result<Option<User>, UserError>;
fn find_by_created_date_between(&self, start: chrono::DateTime<chrono::Utc>, end: chrono::DateTime<chrono::Utc>) -> Result<Vec<User>, UserError>;
}
pub struct DieselUserRepository {
connection: diesel::PgConnection,
}
impl UserRepository for DieselUserRepository {
fn save(&self, user: User) -> Result<User, UserError> {
use crate::schema::users::dsl::*;
use diesel::prelude::*;
diesel::insert_into(users)
.values(&user)
.get_result(&self.connection)
.map_err(|e| UserError::DatabaseError(e.to_string()))
}
fn find_by_email(&self, email: &str) -> Result<Option<User>, UserError> {
use crate::schema::users::dsl::*;
use diesel::prelude::*;
users
.filter(email.eq(email))
.first(&self.connection)
.optional()
.map_err(|e| UserError::DatabaseError(e.to_string()))
}
fn find_by_created_date_between(&self, start: chrono::DateTime<chrono::Utc>, end: chrono::DateTime<chrono::Utc>) -> Result<Vec<User>, UserError> {
use crate::schema::users::dsl::*;
use diesel::prelude::*;
users
.filter(created_date.between(start, end))
.load(&self.connection)
.map_err(|e| UserError::DatabaseError(e.to_string()))
}
}
// Database Layer (Model)
use diesel::prelude::*;
#[derive(Queryable, Insertable, Serialize, Deserialize)]
#[table_name = "users"]
pub struct User {
pub id: i32,
pub name: String,
pub email: String,
pub created_date: chrono::DateTime<chrono::Utc>,
}
// Error handling
#[derive(Debug)]
pub enum UserError {
ValidationError(String),
DatabaseError(String),
EmailError(String),
}
impl std::fmt::Display for UserError {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
UserError::ValidationError(msg) => write!(f, "Validation error: {}", msg),
UserError::DatabaseError(msg) => write!(f, "Database error: {}", msg),
UserError::EmailError(msg) => write!(f, "Email error: {}", msg),
}
}
}
impl std::error::Error for UserError {}
// Email Service trait
pub trait EmailService {
fn send_welcome_email(&self, email: &str) -> Result<(), UserError>;
}
```
**Python (FastAPI + SQLAlchemy):**
```python
# Presentation Layer
from fastapi import FastAPI, HTTPException, Depends
from pydantic import BaseModel, EmailStr
from typing import Optional
from datetime import datetime
app = FastAPI()
class CreateUserRequest(BaseModel):
name: str
email: EmailStr
class UserDto(BaseModel):
id: int
name: str
email: str
created_date: datetime
# Business Layer
class UserService:
def __init__(self, user_repository: 'UserRepository', email_service: 'EmailService'):
self.user_repository = user_repository
self.email_service = email_service
def create_user(self, request: CreateUserRequest) -> UserDto:
# Бизнес-логика валидации
self.validate_user_request(request)
# Создание пользователя
user = User(
name=request.name,
email=request.email,
created_date=datetime.utcnow()
)
# Сохранение через Data Access Layer
saved_user = self.user_repository.save(user)
# Бизнес-логика уведомления
self.email_service.send_welcome_email(saved_user.email)
return UserDto(
id=saved_user.id,
name=saved_user.name,
email=saved_user.email,
created_date=saved_user.created_date
)
def validate_user_request(self, request: CreateUserRequest) -> None:
if not request.name or not request.name.strip():
raise ValueError("Name is required")
if not self.is_valid_email(request.email):
raise ValueError("Valid email is required")
def is_valid_email(self, email: str) -> bool:
return '@' in email and '.' in email
# Data Access Layer
from abc import ABC, abstractmethod
from sqlalchemy import create_engine, Column, Integer, String, DateTime
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, Session
Base = declarative_base()
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, nullable=False)
email = Column(String, unique=True, nullable=False, index=True)
created_date = Column(DateTime, default=datetime.utcnow)
class UserRepository(ABC):
@abstractmethod
def save(self, user: User) -> User:
pass
@abstractmethod
def find_by_email(self, email: str) -> Optional[User]:
pass
@abstractmethod
def find_by_created_date_between(self, start: datetime, end: datetime) -> list[User]:
pass
class SQLAlchemyUserRepository(UserRepository):
def __init__(self, db: Session):
self.db = db
def save(self, user: User) -> User:
self.db.add(user)
self.db.commit()
self.db.refresh(user)
return user
def find_by_email(self, email: str) -> Optional[User]:
return self.db.query(User).filter(User.email == email).first()
def find_by_created_date_between(self, start: datetime, end: datetime) -> list[User]:
return self.db.query(User).filter(
User.created_date.between(start, end)
).all()
# Email Service
class EmailService(ABC):
@abstractmethod
def send_welcome_email(self, email: str) -> None:
pass
class SMTPEmailService(EmailService):
def __init__(self, smtp_server: str, smtp_port: int, username: str, password: str):
self.smtp_server = smtp_server
self.smtp_port = smtp_port
self.username = username
self.password = password
def send_welcome_email(self, email: str) -> None:
# Implementation for sending email
print(f"Sending welcome email to {email}")
# Dependency injection
def get_user_service(db: Session = Depends(get_db)) -> UserService:
user_repository = SQLAlchemyUserRepository(db)
email_service = SMTPEmailService("smtp.gmail.com", 587, "user", "password")
return UserService(user_repository, email_service)
# API endpoints
@app.post("/api/users", response_model=UserDto)
def create_user(request: CreateUserRequest, user_service: UserService = Depends(get_user_service)):
try:
return user_service.create_user(request)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
raise HTTPException(status_code=500, detail="Internal server error")
@app.get("/api/users/{user_id}", response_model=UserDto)
def get_user(user_id: int, user_service: UserService = Depends(get_user_service)):
user = user_service.get_user(user_id)
if not user:
raise HTTPException(status_code=404, detail="User not found")
return user
```
**Node.js (Express + TypeScript + Prisma):**
```typescript
import express, { Request, Response, NextFunction } from 'express';
import { PrismaClient } from '@prisma/client';
import { z } from 'zod';
import nodemailer from 'nodemailer';
const app = express();
const prisma = new PrismaClient();
// Presentation Layer
interface CreateUserRequest {
name: string;
email: string;
}
interface UserDto {
id: number;
name: string;
email: string;
createdDate: Date;
}
// Business Layer
class UserService {
constructor(
private userRepository: UserRepository,
private emailService: EmailService
) {}
async createUser(request: CreateUserRequest): Promise<UserDto> {
// Бизнес-логика валидации
this.validateUserRequest(request);
// Создание пользователя
const user = {
name: request.name,
email: request.email,
createdDate: new Date()
};
// Сохранение через Data Access Layer
const savedUser = await this.userRepository.save(user);
// Бизнес-логика уведомления
await this.emailService.sendWelcomeEmail(savedUser.email);
return {
id: savedUser.id,
name: savedUser.name,
email: savedUser.email,
createdDate: savedUser.createdDate
};
}
private validateUserRequest(request: CreateUserRequest): void {
if (!request.name || !request.name.trim()) {
throw new Error('Name is required');
}
if (!this.isValidEmail(request.email)) {
throw new Error('Valid email is required');
}
}
private isValidEmail(email: string): boolean {
return email.includes('@') && email.includes('.');
}
}
// Data Access Layer
interface UserRepository {
save(user: Omit<UserDto, 'id'>): Promise<UserDto>;
findByEmail(email: string): Promise<UserDto | null>;
findByCreatedDateBetween(start: Date, end: Date): Promise<UserDto[]>;
}
class PrismaUserRepository implements UserRepository {
async save(user: Omit<UserDto, 'id'>): Promise<UserDto> {
const savedUser = await prisma.user.create({
data: {
name: user.name,
email: user.email,
createdDate: user.createdDate
}
});
return {
id: savedUser.id,
name: savedUser.name,
email: savedUser.email,
createdDate: savedUser.createdDate
};
}
async findByEmail(email: string): Promise<UserDto | null> {
const user = await prisma.user.findUnique({
where: { email }
});
if (!user) return null;
return {
id: user.id,
name: user.name,
email: user.email,
createdDate: user.createdDate
};
}
async findByCreatedDateBetween(start: Date, end: Date): Promise<UserDto[]> {
const users = await prisma.user.findMany({
where: {
createdDate: {
gte: start,
lte: end
}
}
});
return users.map(user => ({
id: user.id,
name: user.name,
email: user.email,
createdDate: user.createdDate
}));
}
}
// Email Service
interface EmailService {
sendWelcomeEmail(email: string): Promise<void>;
}
class SMTPEmailService implements EmailService {
private transporter = nodemailer.createTransporter({
host: 'smtp.gmail.com',
port: 587,
secure: false,
auth: {
user: 'user@gmail.com',
pass: 'password'
}
});
async sendWelcomeEmail(email: string): Promise<void> {
const mailOptions = {
from: 'user@gmail.com',
to: email,
subject: 'Welcome!',
text: 'Welcome to our service!'
};
await this.transporter.sendMail(mailOptions);
}
}
// Dependency injection
function getUserService(): UserService {
const userRepository = new PrismaUserRepository();
const emailService = new SMTPEmailService();
return new UserService(userRepository, emailService);
}
// Validation schemas
const createUserSchema = z.object({
name: z.string().min(1, 'Name is required'),
email: z.string().email('Valid email is required')
});
// API endpoints
app.post('/api/users', async (req: Request, res: Response, next: NextFunction) => {
try {
const userService = getUserService();
const validatedData = createUserSchema.parse(req.body);
const user = await userService.createUser(validatedData);
res.json(user);
} catch (error) {
if (error instanceof z.ZodError) {
res.status(400).json({ error: error.errors });
} else {
next(error);
}
}
});
app.get('/api/users/:userId', async (req: Request, res: Response, next: NextFunction) => {
try {
const userService = getUserService();
const userId = parseInt(req.params.userId);
const user = await userService.getUser(userId);
if (!user) {
res.status(404).json({ error: 'User not found' });
return;
}
res.json(user);
} 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('Server running on port 3000');
});
```
#### 4. Архитектура на основе событий (Event-Driven Architecture)
**Определение:** Компоненты системы взаимодействуют через события, что обеспечивает слабую связанность.
**Компоненты:**
- **Event Producers** — генерируют события
- **Event Consumers** — обрабатывают события
- **Event Bus/Message Broker** — доставляет события
- **Event Store** — хранит события
**Преимущества:**
- Слабая связанность
- Масштабируемость
- Отказоустойчивость
- Асинхронная обработка
**Недостатки:**
- Сложность отладки
- Проблемы с порядком событий
- Сложность тестирования
- Потенциальная потеря событий
**Технические примеры:**
**Java (Spring Boot):**
```java
// Event Producer
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private ApplicationEventPublisher eventPublisher;
public Order createOrder(CreateOrderRequest request) {
Order order = new Order();
order.setUserId(request.getUserId());
order.setAmount(request.getAmount());
order.setStatus(OrderStatus.CREATED);
Order savedOrder = orderRepository.save(order);
// Публикация события
OrderCreatedEvent event = new OrderCreatedEvent(savedOrder.getId(),
savedOrder.getUserId(), savedOrder.getAmount());
eventPublisher.publishEvent(event);
return savedOrder;
}
}
// Event Consumer
@Component
public class OrderEventHandler {
@Autowired
private InventoryService inventoryService;
@Autowired
private NotificationService notificationService;
@EventListener
public void handleOrderCreated(OrderCreatedEvent event) {
// Резервирование товара
inventoryService.reserveItems(event.getOrderId());
// Уведомление пользователя
notificationService.sendOrderConfirmation(event.getUserId(), event.getOrderId());
}
}
// Event Class
public class OrderCreatedEvent {
private final Long orderId;
private final Long userId;
private final BigDecimal amount;
private final LocalDateTime timestamp;
public OrderCreatedEvent(Long orderId, Long userId, BigDecimal amount) {
this.orderId = orderId;
this.userId = userId;
this.amount = amount;
this.timestamp = LocalDateTime.now();
}
// геттеры
}
```
**Rust (Actix Web + Tokio):**
```rust
use serde::{Deserialize, Serialize};
use tokio::sync::mpsc;
use std::sync::Arc;
use tokio::sync::Mutex;
// Event types
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OrderCreatedEvent {
pub order_id: i32,
pub user_id: i32,
pub amount: f64,
pub timestamp: chrono::DateTime<chrono::Utc>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum DomainEvent {
OrderCreated(OrderCreatedEvent),
OrderPaid(OrderPaidEvent),
OrderCancelled(OrderCancelledEvent),
}
// Event Bus
pub struct EventBus {
subscribers: Arc<Mutex<Vec<mpsc::UnboundedSender<DomainEvent>>>>,
}
impl EventBus {
pub fn new() -> Self {
Self {
subscribers: Arc::new(Mutex::new(Vec::new())),
}
}
pub async fn subscribe(&self) -> mpsc::UnboundedReceiver<DomainEvent> {
let (tx, rx) = mpsc::unbounded_channel();
let mut subscribers = self.subscribers.lock().await;
subscribers.push(tx);
rx
}
pub async fn publish(&self, event: DomainEvent) {
let subscribers = self.subscribers.lock().await;
for subscriber in subscribers.iter() {
let _ = subscriber.send(event.clone());
}
}
}
// Event Producer
pub struct OrderService {
order_repository: Box<dyn OrderRepository>,
event_bus: Arc<EventBus>,
}
impl OrderService {
pub fn new(order_repository: Box<dyn OrderRepository>, event_bus: Arc<EventBus>) -> Self {
Self {
order_repository,
event_bus,
}
}
pub async fn create_order(&self, request: CreateOrderRequest) -> Result<Order, OrderError> {
let order = Order {
id: 0, // Will be set by database
user_id: request.user_id,
amount: request.amount,
status: OrderStatus::Created,
created_date: chrono::Utc::now(),
};
let saved_order = self.order_repository.save(order).await?;
// Публикация события
let event = DomainEvent::OrderCreated(OrderCreatedEvent {
order_id: saved_order.id,
user_id: saved_order.user_id,
amount: saved_order.amount,
timestamp: saved_order.created_date,
});
self.event_bus.publish(event).await;
Ok(saved_order)
}
}
// Event Consumer
pub struct OrderEventHandler {
inventory_service: Arc<InventoryService>,
notification_service: Arc<NotificationService>,
}
impl OrderEventHandler {
pub fn new(
inventory_service: Arc<InventoryService>,
notification_service: Arc<NotificationService>,
) -> Self {
Self {
inventory_service,
notification_service,
}
}
pub async fn start_listening(&self, mut event_receiver: mpsc::UnboundedReceiver<DomainEvent>) {
while let Some(event) = event_receiver.recv().await {
match event {
DomainEvent::OrderCreated(order_event) => {
self.handle_order_created(order_event).await;
}
DomainEvent::OrderPaid(payment_event) => {
self.handle_order_paid(payment_event).await;
}
DomainEvent::OrderCancelled(cancel_event) => {
self.handle_order_cancelled(cancel_event).await;
}
}
}
}
async fn handle_order_created(&self, event: OrderCreatedEvent) {
// Резервирование товара
if let Err(e) = self.inventory_service.reserve_items(event.order_id).await {
eprintln!("Failed to reserve items for order {}: {}", event.order_id, e);
}
// Уведомление пользователя
if let Err(e) = self.notification_service.send_order_confirmation(event.user_id, event.order_id).await {
eprintln!("Failed to send notification for order {}: {}", event.order_id, e);
}
}
async fn handle_order_paid(&self, event: OrderPaidEvent) {
// Логика обработки оплаты
println!("Order {} paid: {}", event.order_id, event.amount);
}
async fn handle_order_cancelled(&self, event: OrderCancelledEvent) {
// Логика обработки отмены
println!("Order {} cancelled: {}", event.order_id, event.reason);
}
}
// Event Store
pub struct EventStore {
events: Arc<Mutex<Vec<StoredEvent>>>,
}
#[derive(Debug, Clone)]
pub struct StoredEvent {
pub id: i32,
pub event_type: String,
pub event_data: String,
pub timestamp: chrono::DateTime<chrono::Utc>,
}
impl EventStore {
pub fn new() -> Self {
Self {
events: Arc::new(Mutex::new(Vec::new())),
}
}
pub async fn store_event(&self, event: DomainEvent) -> Result<(), EventStoreError> {
let stored_event = StoredEvent {
id: self.get_next_id().await,
event_type: self.get_event_type(&event),
event_data: serde_json::to_string(&event)
.map_err(|e| EventStoreError::SerializationError(e.to_string()))?,
timestamp: chrono::Utc::now(),
};
let mut events = self.events.lock().await;
events.push(stored_event);
Ok(())
}
async fn get_next_id(&self) -> i32 {
let events = self.events.lock().await;
events.len() as i32 + 1
}
fn get_event_type(&self, event: &DomainEvent) -> String {
match event {
DomainEvent::OrderCreated(_) => "OrderCreated".to_string(),
DomainEvent::OrderPaid(_) => "OrderPaid".to_string(),
DomainEvent::OrderCancelled(_) => "OrderCancelled".to_string(),
}
}
}
```
**Python (FastAPI + Celery):**
```python
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from datetime import datetime
from typing import Optional
import asyncio
from celery import Celery
from dataclasses import dataclass
import json
app = FastAPI()
# Celery configuration
celery_app = Celery('order_service', broker='redis://localhost:6379/0')
# Event types
@dataclass
class OrderCreatedEvent:
order_id: int
user_id: int
amount: float
timestamp: datetime
@dataclass
class OrderPaidEvent:
order_id: int
user_id: int
amount: float
payment_id: str
timestamp: datetime
@dataclass
class OrderCancelledEvent:
order_id: int
user_id: int
reason: str
timestamp: datetime
# Event Bus
class EventBus:
def __init__(self):
self.subscribers = {}
def subscribe(self, event_type: str, handler):
if event_type not in self.subscribers:
self.subscribers[event_type] = []
self.subscribers[event_type].append(handler)
async def publish(self, event):
event_type = type(event).__name__
if event_type in self.subscribers:
for handler in self.subscribers[event_type]:
try:
await handler(event)
except Exception as e:
print(f"Error handling event {event_type}: {e}")
# Global event bus
event_bus = EventBus()
# Event Producer
class OrderService:
def __init__(self, order_repository, event_bus):
self.order_repository = order_repository
self.event_bus = event_bus
async def create_order(self, request: CreateOrderRequest) -> Order:
order = Order(
user_id=request.user_id,
amount=request.amount,
status=OrderStatus.CREATED,
created_date=datetime.utcnow()
)
saved_order = await self.order_repository.save(order)
# Публикация события
event = OrderCreatedEvent(
order_id=saved_order.id,
user_id=saved_order.user_id,
amount=saved_order.amount,
timestamp=saved_order.created_date
)
await self.event_bus.publish(event)
return saved_order
# Event Consumers
class OrderEventHandler:
def __init__(self, inventory_service, notification_service):
self.inventory_service = inventory_service
self.notification_service = notification_service
async def handle_order_created(self, event: OrderCreatedEvent):
# Резервирование товара
try:
await self.inventory_service.reserve_items(event.order_id)
except Exception as e:
print(f"Failed to reserve items for order {event.order_id}: {e}")
# Уведомление пользователя
try:
await self.notification_service.send_order_confirmation(event.user_id, event.order_id)
except Exception as e:
print(f"Failed to send notification for order {event.order_id}: {e}")
async def handle_order_paid(self, event: OrderPaidEvent):
print(f"Order {event.order_id} paid: {event.amount}")
async def handle_order_cancelled(self, event: OrderCancelledEvent):
print(f"Order {event.order_id} cancelled: {event.reason}")
# Celery tasks for async processing
@celery_app.task
def process_order_created_async(event_data: dict):
"""Process order created event asynchronously"""
event = OrderCreatedEvent(**event_data)
# Heavy processing that doesn't need to be synchronous
print(f"Processing order {event.order_id} asynchronously")
# Send to external systems
# Update analytics
# Generate reports
pass
@celery_app.task
def send_email_notification_async(user_id: int, order_id: int):
"""Send email notification asynchronously"""
print(f"Sending email notification to user {user_id} for order {order_id}")
# Actual email sending logic here
pass
# Event Store
class EventStore:
def __init__(self):
self.events = []
async def store_event(self, event):
stored_event = {
'id': len(self.events) + 1,
'event_type': type(event).__name__,
'event_data': event.__dict__,
'timestamp': datetime.utcnow()
}
self.events.append(stored_event)
return stored_event
async def get_events_by_type(self, event_type: str):
return [event for event in self.events if event['event_type'] == event_type]
async def get_events_by_order_id(self, order_id: int):
return [event for event in self.events
if event['event_data'].get('order_id') == order_id]
# Event Replay functionality
class EventReplayer:
def __init__(self, event_store, event_bus):
self.event_store = event_store
self.event_bus = event_bus
async def replay_events_for_order(self, order_id: int):
"""Replay all events for a specific order"""
events = await self.event_store.get_events_by_order_id(order_id)
for event_data in events:
event_type = event_data['event_type']
event_class = globals()[event_type]
event = event_class(**event_data['event_data'])
await self.event_bus.publish(event)
async def replay_events_by_type(self, event_type: str):
"""Replay all events of a specific type"""
events = await self.event_store.get_events_by_type(event_type)
for event_data in events:
event_class = globals()[event_type]
event = event_class(**event_data['event_data'])
await self.event_bus.publish(event)
# Setup event handlers
async def setup_event_handlers():
inventory_service = InventoryService()
notification_service = NotificationService()
event_handler = OrderEventHandler(inventory_service, notification_service)
# Subscribe to events
event_bus.subscribe('OrderCreatedEvent', event_handler.handle_order_created)
event_bus.subscribe('OrderPaidEvent', event_handler.handle_order_paid)
event_bus.subscribe('OrderCancelledEvent', event_handler.handle_order_cancelled)
# API endpoints
@app.post("/api/orders")
async def create_order(request: CreateOrderRequest):
order_repository = OrderRepository()
order_service = OrderService(order_repository, event_bus)
try:
order = await order_service.create_order(request)
return order
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.post("/api/events/replay/{order_id}")
async def replay_order_events(order_id: int):
event_store = EventStore()
replayer = EventReplayer(event_store, event_bus)
try:
await replayer.replay_events_for_order(order_id)
return {"message": f"Events replayed for order {order_id}"}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
# Startup event
@app.on_event("startup")
async def startup_event():
await setup_event_handlers()
```
**Node.js (Express + TypeScript + EventEmitter):**
```typescript
import express, { Request, Response, NextFunction } from 'express';
import { EventEmitter } from 'events';
import { PrismaClient } from '@prisma/client';
import { z } from 'zod';
import Redis from 'ioredis';
const app = express();
const prisma = new PrismaClient();
const redis = new Redis();
// Event types
interface OrderCreatedEvent {
orderId: number;
userId: number;
amount: number;
timestamp: Date;
}
interface OrderPaidEvent {
orderId: number;
userId: number;
amount: number;
paymentId: string;
timestamp: Date;
}
interface OrderCancelledEvent {
orderId: number;
userId: number;
reason: string;
timestamp: Date;
}
// Event Bus
class EventBus extends EventEmitter {
private static instance: EventBus;
private constructor() {
super();
this.setMaxListeners(100);
}
static getInstance(): EventBus {
if (!EventBus.instance) {
EventBus.instance = new EventBus();
}
return EventBus.instance;
}
async publish(event: string, data: any): Promise<void> {
// Store event in Redis for persistence
await redis.lpush('events', JSON.stringify({
type: event,
data,
timestamp: new Date()
}));
// Emit event to local listeners
this.emit(event, data);
}
subscribe(event: string, handler: (data: any) => Promise<void>): void {
this.on(event, async (data) => {
try {
await handler(data);
} catch (error) {
console.error(`Error handling event ${event}:`, error);
}
});
}
}
// Event Producer
class OrderService {
constructor(
private orderRepository: OrderRepository,
private eventBus: EventBus
) {}
async createOrder(request: CreateOrderRequest): Promise<Order> {
const order = {
userId: request.userId,
amount: request.amount,
status: 'CREATED',
createdDate: new Date()
};
const savedOrder = await this.orderRepository.save(order);
// Публикация события
const event: OrderCreatedEvent = {
orderId: savedOrder.id,
userId: savedOrder.userId,
amount: savedOrder.amount,
timestamp: savedOrder.createdDate
};
await this.eventBus.publish('order.created', event);
return savedOrder;
}
async payOrder(orderId: number, paymentId: string): Promise<void> {
const order = await this.orderRepository.findById(orderId);
if (!order) {
throw new Error('Order not found');
}
await this.orderRepository.updateStatus(orderId, 'PAID');
const event: OrderPaidEvent = {
orderId: order.id,
userId: order.userId,
amount: order.amount,
paymentId,
timestamp: new Date()
};
await this.eventBus.publish('order.paid', event);
}
async cancelOrder(orderId: number, reason: string): Promise<void> {
const order = await this.orderRepository.findById(orderId);
if (!order) {
throw new Error('Order not found');
}
await this.orderRepository.updateStatus(orderId, 'CANCELLED');
const event: OrderCancelledEvent = {
orderId: order.id,
userId: order.userId,
reason,
timestamp: new Date()
};
await this.eventBus.publish('order.cancelled', event);
}
}
// Event Consumers
class OrderEventHandler {
constructor(
private inventoryService: InventoryService,
private notificationService: NotificationService,
private analyticsService: AnalyticsService
) {}
async handleOrderCreated(event: OrderCreatedEvent): Promise<void> {
console.log(`Processing order created: ${event.orderId}`);
// Резервирование товара
try {
await this.inventoryService.reserveItems(event.orderId);
} catch (error) {
console.error(`Failed to reserve items for order ${event.orderId}:`, error);
}
// Уведомление пользователя
try {
await this.notificationService.sendOrderConfirmation(event.userId, event.orderId);
} catch (error) {
console.error(`Failed to send notification for order ${event.orderId}:`, error);
}
// Аналитика
try {
await this.analyticsService.trackOrderCreated(event);
} catch (error) {
console.error(`Failed to track order ${event.orderId}:`, error);
}
}
async handleOrderPaid(event: OrderPaidEvent): Promise<void> {
console.log(`Order ${event.orderId} paid: ${event.amount}`);
// Обновление инвентаря
await this.inventoryService.confirmReservation(event.orderId);
// Отправка чека
await this.notificationService.sendReceipt(event.userId, event.orderId);
// Аналитика
await this.analyticsService.trackOrderPaid(event);
}
async handleOrderCancelled(event: OrderCancelledEvent): Promise<void> {
console.log(`Order ${event.orderId} cancelled: ${event.reason}`);
// Освобождение резерва
await this.inventoryService.releaseReservation(event.orderId);
// Уведомление об отмене
await this.notificationService.sendCancellationNotice(event.userId, event.orderId);
// Аналитика
await this.analyticsService.trackOrderCancelled(event);
}
}
// Event Store
class EventStore {
async storeEvent(event: any): Promise<void> {
await redis.lpush('event_store', JSON.stringify({
...event,
storedAt: new Date()
}));
}
async getEventsByOrderId(orderId: number): Promise<any[]> {
const events = await redis.lrange('event_store', 0, -1);
return events
.map(event => JSON.parse(event))
.filter(event => event.data.orderId === orderId);
}
async getEventsByType(eventType: string): Promise<any[]> {
const events = await redis.lrange('event_store', 0, -1);
return events
.map(event => JSON.parse(event))
.filter(event => event.type === eventType);
}
}
// Event Replay functionality
class EventReplayer {
constructor(
private eventStore: EventStore,
private eventBus: EventBus
) {}
async replayEventsForOrder(orderId: number): Promise<void> {
const events = await this.eventStore.getEventsByOrderId(orderId);
for (const event of events) {
await this.eventBus.publish(event.type, event.data);
}
}
async replayEventsByType(eventType: string): Promise<void> {
const events = await this.eventStore.getEventsByType(eventType);
for (const event of events) {
await this.eventBus.publish(event.type, event.data);
}
}
}
// Services implementations
class InventoryService {
async reserveItems(orderId: number): Promise<void> {
console.log(`Reserving items for order ${orderId}`);
// Implementation for inventory reservation
}
async confirmReservation(orderId: number): Promise<void> {
console.log(`Confirming reservation for order ${orderId}`);
// Implementation for reservation confirmation
}
async releaseReservation(orderId: number): Promise<void> {
console.log(`Releasing reservation for order ${orderId}`);
// Implementation for reservation release
}
}
class NotificationService {
async sendOrderConfirmation(userId: number, orderId: number): Promise<void> {
console.log(`Sending order confirmation to user ${userId} for order ${orderId}`);
// Implementation for sending confirmation email/SMS
}
async sendReceipt(userId: number, orderId: number): Promise<void> {
console.log(`Sending receipt to user ${userId} for order ${orderId}`);
// Implementation for sending receipt
}
async sendCancellationNotice(userId: number, orderId: number): Promise<void> {
console.log(`Sending cancellation notice to user ${userId} for order ${orderId}`);
// Implementation for sending cancellation notice
}
}
class AnalyticsService {
async trackOrderCreated(event: OrderCreatedEvent): Promise<void> {
console.log(`Tracking order created: ${event.orderId}`);
// Implementation for analytics tracking
}
async trackOrderPaid(event: OrderPaidEvent): Promise<void> {
console.log(`Tracking order paid: ${event.orderId}`);
// Implementation for analytics tracking
}
async trackOrderCancelled(event: OrderCancelledEvent): Promise<void> {
console.log(`Tracking order cancelled: ${event.orderId}`);
// Implementation for analytics tracking
}
}
// Repository
interface OrderRepository {
save(order: Omit<Order, 'id'>): Promise<Order>;
findById(id: number): Promise<Order | null>;
updateStatus(id: number, status: string): Promise<void>;
}
class PrismaOrderRepository implements OrderRepository {
async save(order: Omit<Order, 'id'>): Promise<Order> {
return await prisma.order.create({ data: order });
}
async findById(id: number): Promise<Order | null> {
return await prisma.order.findUnique({ where: { id } });
}
async updateStatus(id: number, status: string): Promise<void> {
await prisma.order.update({
where: { id },
data: { status }
});
}
}
// Setup event handlers
function setupEventHandlers(): void {
const eventBus = EventBus.getInstance();
const inventoryService = new InventoryService();
const notificationService = new NotificationService();
const analyticsService = new AnalyticsService();
const eventHandler = new OrderEventHandler(
inventoryService,
notificationService,
analyticsService
);
// Subscribe to events
eventBus.subscribe('order.created', eventHandler.handleOrderCreated.bind(eventHandler));
eventBus.subscribe('order.paid', eventHandler.handleOrderPaid.bind(eventHandler));
eventBus.subscribe('order.cancelled', eventHandler.handleOrderCancelled.bind(eventHandler));
}
// Validation schemas
const createOrderSchema = z.object({
userId: z.number().positive(),
amount: z.number().positive()
});
// API endpoints
app.post('/api/orders', async (req: Request, res: Response, next: NextFunction) => {
try {
const eventBus = EventBus.getInstance();
const orderRepository = new PrismaOrderRepository();
const orderService = new OrderService(orderRepository, eventBus);
const validatedData = createOrderSchema.parse(req.body);
const order = await orderService.createOrder(validatedData);
res.json(order);
} catch (error) {
if (error instanceof z.ZodError) {
res.status(400).json({ error: error.errors });
} else {
next(error);
}
}
});
app.post('/api/orders/:orderId/pay', async (req: Request, res: Response, next: NextFunction) => {
try {
const eventBus = EventBus.getInstance();
const orderRepository = new PrismaOrderRepository();
const orderService = new OrderService(orderRepository, eventBus);
const orderId = parseInt(req.params.orderId);
const paymentId = req.body.paymentId;
await orderService.payOrder(orderId, paymentId);
res.json({ message: 'Order paid successfully' });
} catch (error) {
next(error);
}
});
app.post('/api/orders/:orderId/cancel', async (req: Request, res: Response, next: NextFunction) => {
try {
const eventBus = EventBus.getInstance();
const orderRepository = new PrismaOrderRepository();
const orderService = new OrderService(orderRepository, eventBus);
const orderId = parseInt(req.params.orderId);
const reason = req.body.reason;
await orderService.cancelOrder(orderId, reason);
res.json({ message: 'Order cancelled successfully' });
} catch (error) {
next(error);
}
});
app.post('/api/events/replay/:orderId', async (req: Request, res: Response, next: NextFunction) => {
try {
const eventStore = new EventStore();
const eventBus = EventBus.getInstance();
const replayer = new EventReplayer(eventStore, eventBus);
const orderId = parseInt(req.params.orderId);
await replayer.replayEventsForOrder(orderId);
res.json({ message: `Events replayed for order ${orderId}` });
} 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' });
});
// Initialize event handlers
setupEventHandlers();
app.listen(3000, () => {
console.log('Server running on port 3000');
});
```
#### 5. Архитектура на основе микросервисов с CQRS (Command Query Responsibility Segregation)
**Определение:** Разделение операций чтения и записи на отдельные модели.
**Компоненты:**
- **Command Side** — обработка команд (запись)
- **Query Side** — обработка запросов (чтение)
- **Event Store** — хранилище событий
- **Projection** — представления для чтения
**Преимущества:**
- Оптимизация для чтения и записи
- Масштабируемость
- Независимость моделей
- Аудит изменений
**Недостатки:**
- Сложность реализации
- Потенциальная несогласованность
- Overhead синхронизации
- Сложность отладки
**Технические примеры:**
**Java (Spring Boot):**
```java
// Command Side
@Service
public class OrderCommandService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private EventStore eventStore;
public void createOrder(CreateOrderCommand command) {
// Валидация команды
validateCommand(command);
// Создание агрегата
OrderAggregate order = new OrderAggregate();
order.createOrder(command.getUserId(), command.getItems());
// Сохранение событий
eventStore.saveEvents(order.getUncommittedEvents());
// Публикация событий
publishEvents(order.getUncommittedEvents());
}
}
// Query Side
@Service
public class OrderQueryService {
@Autowired
private OrderReadRepository orderReadRepository;
public OrderView getOrder(Long orderId) {
return orderReadRepository.findById(orderId);
}
public List<OrderView> getUserOrders(Long userId) {
return orderReadRepository.findByUserId(userId);
}
}
// Read Model
@Entity
@Table(name = "order_views")
public class OrderView {
@Id
private Long orderId;
private Long userId;
private String status;
private BigDecimal totalAmount;
private LocalDateTime createdDate;
// геттеры и сеттеры
}
// Event Handler для синхронизации
@Component
public class OrderProjectionHandler {
@Autowired
private OrderReadRepository orderReadRepository;
@EventHandler
public void handle(OrderCreatedEvent event) {
OrderView view = new OrderView();
view.setOrderId(event.getOrderId());
view.setUserId(event.getUserId());
view.setStatus("CREATED");
view.setTotalAmount(event.getAmount());
view.setCreatedDate(event.getTimestamp());
orderReadRepository.save(view);
}
}
```
**Rust (Actix Web + Diesel):**
```rust
use serde::{Deserialize, Serialize};
use diesel::prelude::*;
use std::sync::Arc;
use tokio::sync::Mutex;
// Command Side
#[derive(Debug, Deserialize)]
pub struct CreateOrderCommand {
pub user_id: i32,
pub items: Vec<OrderItemCommand>,
}
#[derive(Debug, Deserialize)]
pub struct OrderItemCommand {
pub product_id: i32,
pub quantity: i32,
pub price: f64,
}
pub struct OrderCommandService {
event_store: Arc<EventStore>,
command_repository: Arc<dyn CommandRepository>,
}
impl OrderCommandService {
pub fn new(event_store: Arc<EventStore>, command_repository: Arc<dyn CommandRepository>) -> Self {
Self {
event_store,
command_repository,
}
}
pub async fn create_order(&self, command: CreateOrderCommand) -> Result<(), OrderError> {
// Валидация команды
self.validate_command(&command)?;
// Создание агрегата
let mut order_aggregate = OrderAggregate::new();
order_aggregate.create_order(command.user_id, command.items);
// Сохранение событий
let events = order_aggregate.get_uncommitted_events();
self.event_store.save_events(&events).await?;
// Публикация событий
self.publish_events(&events).await?;
Ok(())
}
fn validate_command(&self, command: &CreateOrderCommand) -> Result<(), OrderError> {
if command.user_id <= 0 {
return Err(OrderError::ValidationError("Invalid user ID".to_string()));
}
if command.items.is_empty() {
return Err(OrderError::ValidationError("Order must have items".to_string()));
}
Ok(())
}
async fn publish_events(&self, events: &[DomainEvent]) -> Result<(), OrderError> {
for event in events {
self.event_store.publish_event(event).await?;
}
Ok(())
}
}
// Query Side
pub struct OrderQueryService {
read_repository: Arc<dyn ReadRepository>,
}
impl OrderQueryService {
pub fn new(read_repository: Arc<dyn ReadRepository>) -> Self {
Self { read_repository }
}
pub async fn get_order(&self, order_id: i32) -> Result<Option<OrderView>, OrderError> {
self.read_repository.find_by_id(order_id).await
}
pub async fn get_user_orders(&self, user_id: i32) -> Result<Vec<OrderView>, OrderError> {
self.read_repository.find_by_user_id(user_id).await
}
pub async fn get_orders_by_status(&self, status: OrderStatus) -> Result<Vec<OrderView>, OrderError> {
self.read_repository.find_by_status(status).await
}
}
// Read Model
#[derive(Debug, Clone, Serialize, Deserialize, Queryable)]
pub struct OrderView {
pub order_id: i32,
pub user_id: i32,
pub status: String,
pub total_amount: f64,
pub created_date: chrono::DateTime<chrono::Utc>,
pub updated_date: chrono::DateTime<chrono::Utc>,
}
// Read Repository
#[async_trait::async_trait]
pub trait ReadRepository: Send + Sync {
async fn find_by_id(&self, order_id: i32) -> Result<Option<OrderView>, OrderError>;
async fn find_by_user_id(&self, user_id: i32) -> Result<Vec<OrderView>, OrderError>;
async fn find_by_status(&self, status: OrderStatus) -> Result<Vec<OrderView>, OrderError>;
async fn save(&self, view: OrderView) -> Result<OrderView, OrderError>;
async fn update(&self, view: OrderView) -> Result<OrderView, OrderError>;
}
pub struct DieselReadRepository {
connection: Arc<Mutex<PgConnection>>,
}
#[async_trait::async_trait]
impl ReadRepository for DieselReadRepository {
async fn find_by_id(&self, order_id: i32) -> Result<Option<OrderView>, OrderError> {
use crate::schema::order_views::dsl::*;
let connection = self.connection.lock().await;
order_views
.filter(order_id.eq(order_id))
.first(&*connection)
.optional()
.map_err(|e| OrderError::DatabaseError(e.to_string()))
}
async fn find_by_user_id(&self, user_id: i32) -> Result<Vec<OrderView>, OrderError> {
use crate::schema::order_views::dsl::*;
let connection = self.connection.lock().await;
order_views
.filter(user_id.eq(user_id))
.load(&*connection)
.map_err(|e| OrderError::DatabaseError(e.to_string()))
}
async fn find_by_status(&self, status: OrderStatus) -> Result<Vec<OrderView>, OrderError> {
use crate::schema::order_views::dsl::*;
let connection = self.connection.lock().await;
order_views
.filter(status.eq(status.to_string()))
.load(&*connection)
.map_err(|e| OrderError::DatabaseError(e.to_string()))
}
async fn save(&self, view: OrderView) -> Result<OrderView, OrderError> {
use crate::schema::order_views::dsl::*;
let connection = self.connection.lock().await;
diesel::insert_into(order_views)
.values(&view)
.get_result(&*connection)
.map_err(|e| OrderError::DatabaseError(e.to_string()))
}
async fn update(&self, view: OrderView) -> Result<OrderView, OrderError> {
use crate::schema::order_views::dsl::*;
let connection = self.connection.lock().await;
diesel::update(order_views.filter(order_id.eq(view.order_id)))
.set(&view)
.get_result(&*connection)
.map_err(|e| OrderError::DatabaseError(e.to_string()))
}
}
// Event Handler для синхронизации
pub struct OrderProjectionHandler {
read_repository: Arc<dyn ReadRepository>,
}
impl OrderProjectionHandler {
pub fn new(read_repository: Arc<dyn ReadRepository>) -> Self {
Self { read_repository }
}
pub async fn handle_order_created(&self, event: OrderCreatedEvent) -> Result<(), OrderError> {
let view = OrderView {
order_id: event.order_id,
user_id: event.user_id,
status: "CREATED".to_string(),
total_amount: event.amount,
created_date: event.timestamp,
updated_date: chrono::Utc::now(),
};
self.read_repository.save(view).await?;
Ok(())
}
pub async fn handle_order_paid(&self, event: OrderPaidEvent) -> Result<(), OrderError> {
if let Some(mut view) = self.read_repository.find_by_id(event.order_id).await? {
view.status = "PAID".to_string();
view.updated_date = chrono::Utc::now();
self.read_repository.update(view).await?;
}
Ok(())
}
pub async fn handle_order_cancelled(&self, event: OrderCancelledEvent) -> Result<(), OrderError> {
if let Some(mut view) = self.read_repository.find_by_id(event.order_id).await? {
view.status = "CANCELLED".to_string();
view.updated_date = chrono::Utc::now();
self.read_repository.update(view).await?;
}
Ok(())
}
}
// Event Store
pub struct EventStore {
events: Arc<Mutex<Vec<StoredEvent>>>,
}
impl EventStore {
pub fn new() -> Self {
Self {
events: Arc::new(Mutex::new(Vec::new())),
}
}
pub async fn save_events(&self, events: &[DomainEvent]) -> Result<(), OrderError> {
let mut stored_events = self.events.lock().await;
for event in events {
let stored_event = StoredEvent {
id: stored_events.len() as i32 + 1,
event_type: self.get_event_type(event),
event_data: serde_json::to_string(event)
.map_err(|e| OrderError::SerializationError(e.to_string()))?,
timestamp: chrono::Utc::now(),
};
stored_events.push(stored_event);
}
Ok(())
}
pub async fn publish_event(&self, event: &DomainEvent) -> Result<(), OrderError> {
// Здесь можно добавить логику публикации в message broker
println!("Publishing event: {:?}", event);
Ok(())
}
fn get_event_type(&self, event: &DomainEvent) -> String {
match event {
DomainEvent::OrderCreated(_) => "OrderCreated".to_string(),
DomainEvent::OrderPaid(_) => "OrderPaid".to_string(),
DomainEvent::OrderCancelled(_) => "OrderCancelled".to_string(),
}
}
}
```
**Python (FastAPI + SQLAlchemy):**
```python
from fastapi import FastAPI, HTTPException, Depends
from pydantic import BaseModel
from datetime import datetime
from typing import List, Optional
from abc import ABC, abstractmethod
from sqlalchemy import create_engine, Column, Integer, String, Float, DateTime
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, Session
import asyncio
app = FastAPI()
Base = declarative_base()
# Command Side
class CreateOrderCommand(BaseModel):
user_id: int
items: List['OrderItemCommand']
class OrderItemCommand(BaseModel):
product_id: int
quantity: int
price: float
class OrderCommandService:
def __init__(self, event_store: 'EventStore', command_repository: 'CommandRepository'):
self.event_store = event_store
self.command_repository = command_repository
async def create_order(self, command: CreateOrderCommand) -> None:
# Валидация команды
self.validate_command(command)
# Создание агрегата
order_aggregate = OrderAggregate()
order_aggregate.create_order(command.user_id, command.items)
# Сохранение событий
events = order_aggregate.get_uncommitted_events()
await self.event_store.save_events(events)
# Публикация событий
await self.publish_events(events)
def validate_command(self, command: CreateOrderCommand) -> None:
if command.user_id <= 0:
raise ValueError("Invalid user ID")
if not command.items:
raise ValueError("Order must have items")
for item in command.items:
if item.quantity <= 0:
raise ValueError("Item quantity must be positive")
if item.price <= 0:
raise ValueError("Item price must be positive")
async def publish_events(self, events: List['DomainEvent']) -> None:
for event in events:
await self.event_store.publish_event(event)
# Query Side
class OrderQueryService:
def __init__(self, read_repository: 'ReadRepository'):
self.read_repository = read_repository
async def get_order(self, order_id: int) -> Optional['OrderView']:
return await self.read_repository.find_by_id(order_id)
async def get_user_orders(self, user_id: int) -> List['OrderView']:
return await self.read_repository.find_by_user_id(user_id)
async def get_orders_by_status(self, status: str) -> List['OrderView']:
return await self.read_repository.find_by_status(status)
# Read Model
class OrderView(Base):
__tablename__ = "order_views"
order_id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, nullable=False, index=True)
status = Column(String, nullable=False, index=True)
total_amount = Column(Float, nullable=False)
created_date = Column(DateTime, nullable=False)
updated_date = Column(DateTime, nullable=False, default=datetime.utcnow)
# Read Repository
class ReadRepository(ABC):
@abstractmethod
async def find_by_id(self, order_id: int) -> Optional[OrderView]:
pass
@abstractmethod
async def find_by_user_id(self, user_id: int) -> List[OrderView]:
pass
@abstractmethod
async def find_by_status(self, status: str) -> List[OrderView]:
pass
@abstractmethod
async def save(self, view: OrderView) -> OrderView:
pass
@abstractmethod
async def update(self, view: OrderView) -> OrderView:
pass
class SQLAlchemyReadRepository(ReadRepository):
def __init__(self, db: Session):
self.db = db
async def find_by_id(self, order_id: int) -> Optional[OrderView]:
return self.db.query(OrderView).filter(OrderView.order_id == order_id).first()
async def find_by_user_id(self, user_id: int) -> List[OrderView]:
return self.db.query(OrderView).filter(OrderView.user_id == user_id).all()
async def find_by_status(self, status: str) -> List[OrderView]:
return self.db.query(OrderView).filter(OrderView.status == status).all()
async def save(self, view: OrderView) -> OrderView:
self.db.add(view)
self.db.commit()
self.db.refresh(view)
return view
async def update(self, view: OrderView) -> OrderView:
self.db.commit()
self.db.refresh(view)
return view
# Event Handler для синхронизации
class OrderProjectionHandler:
def __init__(self, read_repository: ReadRepository):
self.read_repository = read_repository
async def handle_order_created(self, event: 'OrderCreatedEvent') -> None:
view = OrderView(
order_id=event.order_id,
user_id=event.user_id,
status="CREATED",
total_amount=event.amount,
created_date=event.timestamp,
updated_date=datetime.utcnow()
)
await self.read_repository.save(view)
async def handle_order_paid(self, event: 'OrderPaidEvent') -> None:
view = await self.read_repository.find_by_id(event.order_id)
if view:
view.status = "PAID"
view.updated_date = datetime.utcnow()
await self.read_repository.update(view)
async def handle_order_cancelled(self, event: 'OrderCancelledEvent') -> None:
view = await self.read_repository.find_by_id(event.order_id)
if view:
view.status = "CANCELLED"
view.updated_date = datetime.utcnow()
await self.read_repository.update(view)
# Event Store
class EventStore:
def __init__(self):
self.events = []
async def save_events(self, events: List['DomainEvent']) -> None:
for event in events:
stored_event = {
'id': len(self.events) + 1,
'event_type': type(event).__name__,
'event_data': event.__dict__,
'timestamp': datetime.utcnow()
}
self.events.append(stored_event)
async def publish_event(self, event: 'DomainEvent') -> None:
# Здесь можно добавить логику публикации в message broker
print(f"Publishing event: {event}")
async def get_events_by_order_id(self, order_id: int) -> List[dict]:
return [event for event in self.events
if event['event_data'].get('order_id') == order_id]
# Domain Events
@dataclass
class OrderCreatedEvent:
order_id: int
user_id: int
amount: float
timestamp: datetime
@dataclass
class OrderPaidEvent:
order_id: int
user_id: int
amount: float
payment_id: str
timestamp: datetime
@dataclass
class OrderCancelledEvent:
order_id: int
user_id: int
reason: str
timestamp: datetime
# Order Aggregate
class OrderAggregate:
def __init__(self):
self.uncommitted_events = []
def create_order(self, user_id: int, items: List[OrderItemCommand]) -> None:
total_amount = sum(item.price * item.quantity for item in items)
event = OrderCreatedEvent(
order_id=0, # Will be set by database
user_id=user_id,
amount=total_amount,
timestamp=datetime.utcnow()
)
self.uncommitted_events.append(event)
def get_uncommitted_events(self) -> List['DomainEvent']:
return self.uncommitted_events.copy()
def mark_events_as_committed(self) -> None:
self.uncommitted_events.clear()
# API endpoints
@app.post("/api/orders")
async def create_order(command: CreateOrderCommand):
event_store = EventStore()
command_repository = CommandRepository() # Implementation needed
command_service = OrderCommandService(event_store, command_repository)
try:
await command_service.create_order(command)
return {"message": "Order created successfully"}
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
raise HTTPException(status_code=500, detail="Internal server error")
@app.get("/api/orders/{order_id}")
async def get_order(order_id: int):
read_repository = SQLAlchemyReadRepository(get_db())
query_service = OrderQueryService(read_repository)
order = await query_service.get_order(order_id)
if not order:
raise HTTPException(status_code=404, detail="Order not found")
return order
@app.get("/api/users/{user_id}/orders")
async def get_user_orders(user_id: int):
read_repository = SQLAlchemyReadRepository(get_db())
query_service = OrderQueryService(read_repository)
orders = await query_service.get_user_orders(user_id)
return orders
@app.get("/api/orders/status/{status}")
async def get_orders_by_status(status: str):
read_repository = SQLAlchemyReadRepository(get_db())
query_service = OrderQueryService(read_repository)
orders = await query_service.get_orders_by_status(status)
return orders
```
**Node.js (Express + TypeScript + Prisma):**
```typescript
import express, { Request, Response, NextFunction } from 'express';
import { PrismaClient } from '@prisma/client';
import { z } from 'zod';
import { EventEmitter } from 'events';
const app = express();
const prisma = new PrismaClient();
// Command Side
interface CreateOrderCommand {
userId: number;
items: OrderItemCommand[];
}
interface OrderItemCommand {
productId: number;
quantity: number;
price: number;
}
class OrderCommandService {
constructor(
private eventStore: EventStore,
private commandRepository: CommandRepository
) {}
async createOrder(command: CreateOrderCommand): Promise<void> {
// Валидация команды
this.validateCommand(command);
// Создание агрегата
const orderAggregate = new OrderAggregate();
orderAggregate.createOrder(command.userId, command.items);
// Сохранение событий
const events = orderAggregate.getUncommittedEvents();
await this.eventStore.saveEvents(events);
// Публикация событий
await this.publishEvents(events);
}
private validateCommand(command: CreateOrderCommand): void {
if (command.userId <= 0) {
throw new Error('Invalid user ID');
}
if (!command.items || command.items.length === 0) {
throw new Error('Order must have items');
}
for (const item of command.items) {
if (item.quantity <= 0) {
throw new Error('Item quantity must be positive');
}
if (item.price <= 0) {
throw new Error('Item price must be positive');
}
}
}
private async publishEvents(events: DomainEvent[]): Promise<void> {
for (const event of events) {
await this.eventStore.publishEvent(event);
}
}
}
// Query Side
class OrderQueryService {
constructor(private readRepository: ReadRepository) {}
async getOrder(orderId: number): Promise<OrderView | null> {
return await this.readRepository.findById(orderId);
}
async getUserOrders(userId: number): Promise<OrderView[]> {
return await this.readRepository.findByUserId(userId);
}
async getOrdersByStatus(status: string): Promise<OrderView[]> {
return await this.readRepository.findByStatus(status);
}
}
// Read Model
interface OrderView {
orderId: number;
userId: number;
status: string;
totalAmount: number;
createdDate: Date;
updatedDate: Date;
}
// Read Repository
interface ReadRepository {
findById(orderId: number): Promise<OrderView | null>;
findByUserId(userId: number): Promise<OrderView[]>;
findByStatus(status: string): Promise<OrderView[]>;
save(view: OrderView): Promise<OrderView>;
update(view: OrderView): Promise<OrderView>;
}
class PrismaReadRepository implements ReadRepository {
async findById(orderId: number): Promise<OrderView | null> {
const order = await prisma.orderView.findUnique({
where: { orderId }
});
if (!order) return null;
return {
orderId: order.orderId,
userId: order.userId,
status: order.status,
totalAmount: order.totalAmount,
createdDate: order.createdDate,
updatedDate: order.updatedDate
};
}
async findByUserId(userId: number): Promise<OrderView[]> {
const orders = await prisma.orderView.findMany({
where: { userId }
});
return orders.map(order => ({
orderId: order.orderId,
userId: order.userId,
status: order.status,
totalAmount: order.totalAmount,
createdDate: order.createdDate,
updatedDate: order.updatedDate
}));
}
async findByStatus(status: string): Promise<OrderView[]> {
const orders = await prisma.orderView.findMany({
where: { status }
});
return orders.map(order => ({
orderId: order.orderId,
userId: order.userId,
status: order.status,
totalAmount: order.totalAmount,
createdDate: order.createdDate,
updatedDate: order.updatedDate
}));
}
async save(view: OrderView): Promise<OrderView> {
const savedOrder = await prisma.orderView.create({
data: {
orderId: view.orderId,
userId: view.userId,
status: view.status,
totalAmount: view.totalAmount,
createdDate: view.createdDate,
updatedDate: view.updatedDate
}
});
return {
orderId: savedOrder.orderId,
userId: savedOrder.userId,
status: savedOrder.status,
totalAmount: savedOrder.totalAmount,
createdDate: savedOrder.createdDate,
updatedDate: savedOrder.updatedDate
};
}
async update(view: OrderView): Promise<OrderView> {
const updatedOrder = await prisma.orderView.update({
where: { orderId: view.orderId },
data: {
status: view.status,
totalAmount: view.totalAmount,
updatedDate: view.updatedDate
}
});
return {
orderId: updatedOrder.orderId,
userId: updatedOrder.userId,
status: updatedOrder.status,
totalAmount: updatedOrder.totalAmount,
createdDate: updatedOrder.createdDate,
updatedDate: updatedOrder.updatedDate
};
}
}
// Event Handler для синхронизации
class OrderProjectionHandler {
constructor(private readRepository: ReadRepository) {}
async handleOrderCreated(event: OrderCreatedEvent): Promise<void> {
const view: OrderView = {
orderId: event.orderId,
userId: event.userId,
status: 'CREATED',
totalAmount: event.amount,
createdDate: event.timestamp,
updatedDate: new Date()
};
await this.readRepository.save(view);
}
async handleOrderPaid(event: OrderPaidEvent): Promise<void> {
const existingView = await this.readRepository.findById(event.orderId);
if (existingView) {
existingView.status = 'PAID';
existingView.updatedDate = new Date();
await this.readRepository.update(existingView);
}
}
async handleOrderCancelled(event: OrderCancelledEvent): Promise<void> {
const existingView = await this.readRepository.findById(event.orderId);
if (existingView) {
existingView.status = 'CANCELLED';
existingView.updatedDate = new Date();
await this.readRepository.update(existingView);
}
}
}
// Event Store
class EventStore {
private events: any[] = [];
async saveEvents(events: DomainEvent[]): Promise<void> {
for (const event of events) {
const storedEvent = {
id: this.events.length + 1,
eventType: this.getEventType(event),
eventData: event,
timestamp: new Date()
};
this.events.push(storedEvent);
}
}
async publishEvent(event: DomainEvent): Promise<void> {
console.log(`Publishing event: ${JSON.stringify(event)}`);
// Здесь можно добавить логику публикации в message broker
}
async getEventsByOrderId(orderId: number): Promise<any[]> {
return this.events.filter(event =>
event.eventData.orderId === orderId
);
}
private getEventType(event: DomainEvent): string {
if ('orderId' in event && 'userId' in event && 'amount' in event) {
return 'OrderCreated';
}
if ('orderId' in event && 'paymentId' in event) {
return 'OrderPaid';
}
if ('orderId' in event && 'reason' in event) {
return 'OrderCancelled';
}
return 'Unknown';
}
}
// Domain Events
interface OrderCreatedEvent {
orderId: number;
userId: number;
amount: number;
timestamp: Date;
}
interface OrderPaidEvent {
orderId: number;
userId: number;
amount: number;
paymentId: string;
timestamp: Date;
}
interface OrderCancelledEvent {
orderId: number;
userId: number;
reason: string;
timestamp: Date;
}
type DomainEvent = OrderCreatedEvent | OrderPaidEvent | OrderCancelledEvent;
// Order Aggregate
class OrderAggregate {
private uncommittedEvents: DomainEvent[] = [];
createOrder(userId: number, items: OrderItemCommand[]): void {
const totalAmount = items.reduce((sum, item) => sum + (item.price * item.quantity), 0);
const event: OrderCreatedEvent = {
orderId: 0, // Will be set by database
userId,
amount: totalAmount,
timestamp: new Date()
};
this.uncommittedEvents.push(event);
}
getUncommittedEvents(): DomainEvent[] {
return [...this.uncommittedEvents];
}
markEventsAsCommitted(): void {
this.uncommittedEvents = [];
}
}
// Command Repository
interface CommandRepository {
save(command: any): Promise<any>;
}
class PrismaCommandRepository implements CommandRepository {
async save(command: any): Promise<any> {
// Implementation for saving commands
return command;
}
}
// Validation schemas
const createOrderSchema = z.object({
userId: z.number().positive(),
items: z.array(z.object({
productId: z.number().positive(),
quantity: z.number().positive(),
price: z.number().positive()
}))
});
// API endpoints
app.post('/api/orders', async (req: Request, res: Response, next: NextFunction) => {
try {
const eventStore = new EventStore();
const commandRepository = new PrismaCommandRepository();
const commandService = new OrderCommandService(eventStore, commandRepository);
const validatedData = createOrderSchema.parse(req.body);
await commandService.createOrder(validatedData);
res.json({ message: 'Order created successfully' });
} catch (error) {
if (error instanceof z.ZodError) {
res.status(400).json({ error: error.errors });
} else {
next(error);
}
}
});
app.get('/api/orders/:orderId', async (req: Request, res: Response, next: NextFunction) => {
try {
const readRepository = new PrismaReadRepository();
const queryService = new OrderQueryService(readRepository);
const orderId = parseInt(req.params.orderId);
const order = await queryService.getOrder(orderId);
if (!order) {
res.status(404).json({ error: 'Order not found' });
return;
}
res.json(order);
} catch (error) {
next(error);
}
});
app.get('/api/users/:userId/orders', async (req: Request, res: Response, next: NextFunction) => {
try {
const readRepository = new PrismaReadRepository();
const queryService = new OrderQueryService(readRepository);
const userId = parseInt(req.params.userId);
const orders = await queryService.getUserOrders(userId);
res.json(orders);
} catch (error) {
next(error);
}
});
app.get('/api/orders/status/:status', async (req: Request, res: Response, next: NextFunction) => {
try {
const readRepository = new PrismaReadRepository();
const queryService = new OrderQueryService(readRepository);
const status = req.params.status;
const orders = await queryService.getOrdersByStatus(status);
res.json(orders);
} 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('Server running on port 3000');
});
```
#### 6. Гексагональная архитектура (Hexagonal Architecture)
**Определение:** Архитектура, где бизнес-логика изолирована от внешних зависимостей через порты и адаптеры.
**Компоненты:**
- **Core (Domain)** — бизнес-логика
- **Ports** — интерфейсы для взаимодействия
- **Adapters** — реализации портов
- **Infrastructure** — внешние системы
**Преимущества:**
- Независимость от фреймворков
- Легкость тестирования
- Гибкость в выборе технологий
- Четкое разделение ответственности
**Недостатки:**
- Дополнительная сложность
- Потенциальный over-engineering
- Кривая обучения
- Больше кода
**Технический пример:**
```java
// Domain Core
public class Order {
private Long id;
private Long userId;
private List<OrderItem> items;
private OrderStatus status;
public void addItem(Product product, int quantity) {
if (status != OrderStatus.DRAFT) {
throw new IllegalStateException("Cannot add items to non-draft order");
}
OrderItem item = new OrderItem(product, quantity);
items.add(item);
}
public void confirm() {
if (items.isEmpty()) {
throw new IllegalStateException("Cannot confirm empty order");
}
this.status = OrderStatus.CONFIRMED;
}
}
// Ports (Interfaces)
public interface OrderRepository {
Order save(Order order);
Optional<Order> findById(Long id);
List<Order> findByUserId(Long userId);
}
public interface PaymentService {
PaymentResult processPayment(PaymentRequest request);
}
public interface NotificationService {
void sendOrderConfirmation(Long userId, Long orderId);
}
// Application Service (Use Cases)
@Service
public class OrderApplicationService {
private final OrderRepository orderRepository;
private final PaymentService paymentService;
private final NotificationService notificationService;
public OrderApplicationService(OrderRepository orderRepository,
PaymentService paymentService,
NotificationService notificationService) {
this.orderRepository = orderRepository;
this.paymentService = paymentService;
this.notificationService = notificationService;
}
public Order createOrder(CreateOrderRequest request) {
Order order = new Order();
order.setUserId(request.getUserId());
for (OrderItemRequest itemRequest : request.getItems()) {
order.addItem(itemRequest.getProduct(), itemRequest.getQuantity());
}
order.confirm();
return orderRepository.save(order);
}
public void processPayment(Long orderId, PaymentRequest paymentRequest) {
Order order = orderRepository.findById(orderId)
.orElseThrow(() -> new OrderNotFoundException("Order not found"));
PaymentResult result = paymentService.processPayment(paymentRequest);
if (result.isSuccess()) {
order.markAsPaid();
orderRepository.save(order);
notificationService.sendOrderConfirmation(order.getUserId(), orderId);
}
}
}
// Adapters (Infrastructure)
@Repository
public class JpaOrderRepository implements OrderRepository {
@Autowired
private OrderJpaRepository jpaRepository;
@Override
public Order save(Order order) {
OrderEntity entity = mapToEntity(order);
OrderEntity saved = jpaRepository.save(entity);
return mapToDomain(saved);
}
@Override
public Optional<Order> findById(Long id) {
return jpaRepository.findById(id)
.map(this::mapToDomain);
}
}
@Component
public class StripePaymentService implements PaymentService {
@Autowired
private StripeClient stripeClient;
@Override
public PaymentResult processPayment(PaymentRequest request) {
try {
Charge charge = stripeClient.charges().create(
ChargeCreateParams.builder()
.setAmount(request.getAmount().multiply(BigDecimal.valueOf(100)).longValue())
.setCurrency("usd")
.setSource(request.getToken())
.build()
);
return PaymentResult.success(charge.getId());
} catch (StripeException e) {
return PaymentResult.failure(e.getMessage());
}
}
}
@Component
public class EmailNotificationService implements NotificationService {
@Autowired
private EmailService emailService;
@Override
public void sendOrderConfirmation(Long userId, Long orderId) {
String email = getUserEmail(userId);
String subject = "Order Confirmation";
String body = "Your order #" + orderId + " has been confirmed.";
emailService.sendEmail(email, subject, body);
}
}
```
#### 7. Архитектура на основе доменов (Domain-Driven Design)
**Определение:** Архитектура, ориентированная на бизнес-домен и его модели.
**Компоненты:**
- **Domain Model** — бизнес-модели
- **Aggregates** — консистентные группы объектов
- **Value Objects** — неизменяемые объекты
- **Domain Services** — бизнес-логика
- **Repositories** — доступ к данным
- **Bounded Contexts** — границы доменов
**Преимущества:**
- Фокус на бизнес-логике
- Четкие границы доменов
- Переиспользование моделей
- Легкость тестирования
**Недостатки:**
- Сложность для простых систем
- Кривая обучения
- Потенциальный over-engineering
- Сложность интеграции
**Технический пример:**
```java
// Domain Model
public class Order {
private OrderId id;
private CustomerId customerId;
private List<OrderItem> items;
private OrderStatus status;
private Money totalAmount;
public static Order create(CustomerId customerId) {
Order order = new Order();
order.id = OrderId.generate();
order.customerId = customerId;
order.status = OrderStatus.DRAFT;
order.items = new ArrayList<>();
order.totalAmount = Money.zero();
return order;
}
public void addItem(Product product, Quantity quantity) {
if (status != OrderStatus.DRAFT) {
throw new OrderDomainException("Cannot add items to non-draft order");
}
OrderItem item = OrderItem.create(product, quantity);
items.add(item);
recalculateTotal();
}
public void confirm() {
if (items.isEmpty()) {
throw new OrderDomainException("Cannot confirm empty order");
}
if (totalAmount.isZero()) {
throw new OrderDomainException("Cannot confirm order with zero amount");
}
this.status = OrderStatus.CONFIRMED;
// Domain Event
DomainEvents.raise(new OrderConfirmedEvent(this.id, this.customerId));
}
private void recalculateTotal() {
this.totalAmount = items.stream()
.map(OrderItem::getSubtotal)
.reduce(Money.zero(), Money::add);
}
}
// Value Objects
public class Money {
private final BigDecimal amount;
private final Currency currency;
public Money(BigDecimal amount, Currency currency) {
if (amount.compareTo(BigDecimal.ZERO) < 0) {
throw new IllegalArgumentException("Amount cannot be negative");
}
this.amount = amount;
this.currency = currency;
}
public Money add(Money other) {
if (!this.currency.equals(other.currency)) {
throw new IllegalArgumentException("Cannot add different currencies");
}
return new Money(this.amount.add(other.amount), this.currency);
}
public boolean isZero() {
return amount.compareTo(BigDecimal.ZERO) == 0;
}
}
public class OrderId {
private final String value;
private OrderId(String value) {
this.value = value;
}
public static OrderId generate() {
return new OrderId(UUID.randomUUID().toString());
}
public static OrderId of(String value) {
return new OrderId(value);
}
}
// Aggregate Root
@Entity
@Table(name = "orders")
public class OrderAggregate {
@Id
private String id;
@Embedded
private CustomerId customerId;
@Enumerated(EnumType.STRING)
private OrderStatus status;
@Embedded
private Money totalAmount;
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<OrderItemEntity> items;
@Transient
private List<DomainEvent> uncommittedEvents = new ArrayList<>();
// Domain methods
public void addItem(Product product, Quantity quantity) {
OrderItem item = OrderItem.create(product, quantity);
this.items.add(new OrderItemEntity(item));
recalculateTotal();
}
public void confirm() {
if (items.isEmpty()) {
throw new OrderDomainException("Cannot confirm empty order");
}
this.status = OrderStatus.CONFIRMED;
// Raise domain event
uncommittedEvents.add(new OrderConfirmedEvent(
OrderId.of(this.id),
this.customerId
));
}
public List<DomainEvent> getUncommittedEvents() {
return uncommittedEvents;
}
public void markEventsAsCommitted() {
uncommittedEvents.clear();
}
}
// Domain Service
@Service
public class OrderDomainService {
public boolean canAddItem(Order order, Product product) {
// Бизнес-правило: нельзя добавлять товары из разных категорий
if (order.hasItems()) {
String existingCategory = order.getFirstItemCategory();
return product.getCategory().equals(existingCategory);
}
return true;
}
public Money calculateDiscount(Order order, Customer customer) {
// Бизнес-правило: скидка для VIP клиентов
if (customer.isVip()) {
return order.getTotalAmount().multiply(0.1); // 10% скидка
}
return Money.zero();
}
}
// Repository Interface
public interface OrderRepository {
void save(OrderAggregate order);
Optional<OrderAggregate> findById(OrderId orderId);
List<OrderAggregate> findByCustomerId(CustomerId customerId);
}
// Application Service
@Service
@Transactional
public class OrderApplicationService {
private final OrderRepository orderRepository;
private final OrderDomainService orderDomainService;
private final EventPublisher eventPublisher;
public void addItemToOrder(OrderId orderId, ProductId productId, Quantity quantity) {
OrderAggregate order = orderRepository.findById(orderId)
.orElseThrow(() -> new OrderNotFoundException("Order not found"));
Product product = productService.getProduct(productId);
if (!orderDomainService.canAddItem(order, product)) {
throw new OrderDomainException("Cannot add item from different category");
}
order.addItem(product, quantity);
orderRepository.save(order);
// Publish domain events
order.getUncommittedEvents().forEach(eventPublisher::publish);
order.markEventsAsCommitted();
}
}
```
#### 8. Архитектура на основе компонентов (Component-Based Architecture)
**Определение:** Система строится из переиспользуемых компонентов с четко определенными интерфейсами.
**Компоненты:**
- **Components** — переиспользуемые модули
- **Interfaces** — контракты взаимодействия
- **Connectors** — механизмы связи
- **Configuration** — настройка компонентов
**Преимущества:**
- Переиспользование кода
- Модульность
- Легкость тестирования
- Независимая разработка
**Недостатки:**
- Сложность проектирования интерфейсов
- Потенциальные проблемы с производительностью
- Сложность управления зависимостями
- Риск over-engineering
**Технический пример:**
```java
// Component Interface
public interface PaymentComponent {
PaymentResult processPayment(PaymentRequest request);
RefundResult processRefund(RefundRequest request);
PaymentStatus getPaymentStatus(String paymentId);
}
// Component Implementation
@Component("stripePaymentComponent")
public class StripePaymentComponent implements PaymentComponent {
@Autowired
private StripeClient stripeClient;
@Override
public PaymentResult processPayment(PaymentRequest request) {
try {
Charge charge = stripeClient.charges().create(
ChargeCreateParams.builder()
.setAmount(request.getAmount().multiply(BigDecimal.valueOf(100)).longValue())
.setCurrency("usd")
.setSource(request.getToken())
.build()
);
return PaymentResult.success(charge.getId());
} catch (StripeException e) {
return PaymentResult.failure(e.getMessage());
}
}
@Override
public RefundResult processRefund(RefundRequest request) {
try {
Refund refund = stripeClient.refunds().create(
RefundCreateParams.builder()
.setCharge(request.getChargeId())
.setAmount(request.getAmount().multiply(BigDecimal.valueOf(100)).longValue())
.build()
);
return RefundResult.success(refund.getId());
} catch (StripeException e) {
return RefundResult.failure(e.getMessage());
}
}
@Override
public PaymentStatus getPaymentStatus(String paymentId) {
try {
Charge charge = stripeClient.charges().retrieve(paymentId);
return PaymentStatus.valueOf(charge.getStatus().toUpperCase());
} catch (StripeException e) {
return PaymentStatus.UNKNOWN;
}
}
}
// Component Registry
@Service
public class ComponentRegistry {
private final Map<String, PaymentComponent> paymentComponents = new HashMap<>();
@PostConstruct
public void initializeComponents() {
// Регистрация компонентов
paymentComponents.put("stripe", stripePaymentComponent);
paymentComponents.put("paypal", paypalPaymentComponent);
paymentComponents.put("square", squarePaymentComponent);
}
public PaymentComponent getPaymentComponent(String provider) {
return paymentComponents.get(provider);
}
}
// Component Manager
@Service
public class PaymentComponentManager {
@Autowired
private ComponentRegistry componentRegistry;
public PaymentResult processPayment(String provider, PaymentRequest request) {
PaymentComponent component = componentRegistry.getPaymentComponent(provider);
if (component == null) {
throw new UnsupportedPaymentProviderException("Provider not supported: " + provider);
}
return component.processPayment(request);
}
}
```
### Сравнение архитектурных стилей
| Стиль | Сложность | Производительность | Масштабируемость | Тестируемость | Гибкость |
|-------|------------|-------------------|------------------|---------------|----------|
| Монолитная | Низкая | Высокая | Низкая | Высокая | Низкая |
| Микросервисы | Высокая | Средняя | Высокая | Средняя | Высокая |
| Слоистая | Средняя | Средняя | Средняя | Высокая | Средняя |
| Event-Driven | Высокая | Средняя | Высокая | Низкая | Высокая |
| CQRS | Очень высокая | Высокая | Высокая | Средняя | Высокая |
| Гексагональная | Высокая | Средняя | Средняя | Высокая | Высокая |
| DDD | Высокая | Средняя | Средняя | Высокая | Высокая |
| Component-Based | Средняя | Средняя | Средняя | Высокая | Высокая |
### Выбор архитектурного стиля
**Факторы для выбора:**
1. **Размер команды** — большие команды лучше подходят для микросервисов
2. **Сложность домена** — сложные домены требуют DDD или гексагональной архитектуры
3. **Требования к производительности** — критичная производительность может потребовать монолита
4. **Необходимость масштабирования** — микросервисы лучше для горизонтального масштабирования
5. **Технологическое разнообразие** — микросервисы позволяют использовать разные технологии
6. **Время разработки** — монолит быстрее в разработке для простых систем
7. **Сложность тестирования** — слоистая архитектура проще для тестирования
8. **Необходимость аудита** — CQRS лучше для аудита изменений
- Высокие требования к масштабируемости
**Технический пример:**
**User Service (отдельное приложение):**
```java
// User Service - отдельный микросервис
@SpringBootApplication
public class UserServiceApplication {
public static void main(String[] args) {
SpringApplication.run(UserServiceApplication.class, args);
}
}
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserService userService;
@PostMapping
public ResponseEntity<User> createUser(@RequestBody User user) {
return ResponseEntity.ok(userService.createUser(user));
}
@GetMapping("/{id}")
public ResponseEntity<User> getUser(@PathVariable Long id) {
return ResponseEntity.ok(userService.getUser(id));
}
}
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public User createUser(User user) {
return userRepository.save(user);
}
public User getUser(Long id) {
return userRepository.findById(id).orElse(null);
}
}
// Собственная база данных для User Service
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
// геттеры и сеттеры
}
```
**Order Service (отдельное приложение):**
```java
// Order Service - отдельный микросервис
@SpringBootApplication
public class OrderServiceApplication {
public static void main(String[] args) {
SpringApplication.run(OrderServiceApplication.class, args);
}
}
@RestController
@RequestMapping("/api/orders")
public class OrderController {
@Autowired
private OrderService orderService;
@PostMapping
public ResponseEntity<Order> createOrder(@RequestBody Order order) {
return ResponseEntity.ok(orderService.createOrder(order));
}
@GetMapping("/{id}")
public ResponseEntity<Order> getOrder(@PathVariable Long id) {
return ResponseEntity.ok(orderService.getOrder(id));
}
}
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private UserServiceClient userServiceClient; // HTTP клиент для User Service
public Order createOrder(Order order) {
// Проверяем пользователя через HTTP вызов
User user = userServiceClient.getUser(order.getUserId());
if (user == null) {
throw new IllegalArgumentException("User not found");
}
return orderRepository.save(order);
}
public Order getOrder(Long id) {
return orderRepository.findById(id).orElse(null);
}
}
// HTTP клиент для взаимодействия с User Service
@Component
public class UserServiceClient {
@Autowired
private RestTemplate restTemplate;
private static final String USER_SERVICE_URL = "http://user-service:8080";
public User getUser(Long userId) {
try {
ResponseEntity<User> response = restTemplate.getForEntity(
USER_SERVICE_URL + "/api/users/" + userId,
User.class
);
return response.getBody();
} catch (Exception e) {
// Обработка ошибок сети
return null;
}
}
}
// Собственная база данных для Order Service
@Entity
@Table(name = "orders")
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long userId; // Ссылка на пользователя в другом сервисе
private BigDecimal amount;
// геттеры и сеттеры
}
```
**API Gateway (для маршрутизации):**
```java
// API Gateway - точка входа для всех микросервисов
@SpringBootApplication
@EnableZuulProxy
public class ApiGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayApplication.class, args);
}
}
// Конфигурация маршрутов
@Configuration
public class GatewayConfig {
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("user-service", r -> r.path("/api/users/**")
.uri("http://user-service:8080"))
.route("order-service", r -> r.path("/api/orders/**")
.uri("http://order-service:8080"))
.route("product-service", r -> r.path("/api/products/**")
.uri("http://product-service:8080"))
.build();
}
}
```
**Docker Compose для развертывания:**
```yaml
version: '3.8'
services:
user-service:
build: ./user-service
ports:
- "8081:8080"
environment:
- SPRING_DATASOURCE_URL=jdbc:postgresql://user-db:5432/userdb
depends_on:
- user-db
order-service:
build: ./order-service
ports:
- "8082:8080"
environment:
- SPRING_DATASOURCE_URL=jdbc:postgresql://order-db:5432/orderdb
- USER_SERVICE_URL=http://user-service:8080
depends_on:
- order-db
product-service:
build: ./product-service
ports:
- "8083:8080"
environment:
- SPRING_DATASOURCE_URL=jdbc:postgresql://product-db:5432/productdb
depends_on:
- product-db
api-gateway:
build: ./api-gateway
ports:
- "8080:8080"
depends_on:
- user-service
- order-service
- product-service
user-db:
image: postgres:13
environment:
- POSTGRES_DB=userdb
- POSTGRES_USER=user
- POSTGRES_PASSWORD=password
order-db:
image: postgres:13
environment:
- POSTGRES_DB=orderdb
- POSTGRES_USER=order
- POSTGRES_PASSWORD=password
product-db:
image: postgres:13
environment:
- POSTGRES_DB=productdb
- POSTGRES_USER=product
- POSTGRES_PASSWORD=password
```
#### 3. Service-Oriented Architecture (SOA)
**Определение:** Архитектура, основанная на сервисах
**Характеристики:**
- Сервисы как основные компоненты
- Стандартизированные интерфейсы
- Переиспользование сервисов
- Слабая связанность
#### 4. Event-Driven Architecture
**Определение:** Архитектура, основанная на событиях
**Компоненты:**
- Event Producers (производители событий)
- Event Consumers (потребители событий)
- Event Bus (шина событий)
**Преимущества:**
- Асинхронная обработка
- Слабая связанность
- Масштабируемость
- Отказоустойчивость
### Архитектурные паттерны
#### 1. Layered Architecture (Слоистая архитектура)
```
┌─────────────────┐
│ Presentation │ ← UI Layer
├─────────────────┤
│ Business │ ← Business Logic
├─────────────────┤
│ Persistence │ ← Data Access
└─────────────────┘
```
**Принципы:**
- Каждый слой зависит только от нижележащего
- Запрет на обход слоев
- Четкое разделение ответственности
#### 2. Hexagonal Architecture (Порты и адаптеры)
```
┌─────────────┐
│ Adapters │ ← Внешние интерфейсы
└─────────────┘
│
┌─────────────┐
│ Ports │ ← Интерфейсы
└─────────────┘
│
┌─────────────┐
│ Core │ ← Бизнес-логика
└─────────────┘
```
**Преимущества:**
- Независимость от внешних систем
- Легкое тестирование
- Гибкость в выборе технологий
#### 3. CQRS (Command Query Responsibility Segregation)
**Разделение команд и запросов**
```
Команды → Модель записи → Хранилище событий
Запросы → Модель чтения → База данных чтения
```
**Когда использовать:**
- Разные требования к чтению и записи
- Сложные запросы
- Высокая нагрузка на чтение
#### 4. Event Sourcing
**Хранение событий вместо состояния**
```
События → Хранилище событий → Проекции → Модели чтения
```
**Преимущества:**
- Полная история изменений
- Возможность воспроизведения состояния
- Аудит и отладка
---
## Качественные атрибуты
### Определение
**Качественные атрибуты** — это свойства системы, которые определяют её качество с точки зрения пользователей и стейкхолдеров.
### Классификация качественных атрибутов
#### 1. Функциональные требования
- **Что** система должна делать
- Описывают функциональность
- Примеры: аутентификация, обработка платежей
#### 2. Нефункциональные требования (NFR)
- **Как** система должна работать
- Описывают качество системы
- Влияют на архитектурные решения
### Основные качественные атрибуты
#### Производительность (Performance)
**Определение:** Скорость и эффективность выполнения операций
**Метрики:**
- Время отклика (Response Time)
- Пропускная способность (Throughput)
- Использование ресурсов (Resource Utilization)
**Архитектурные тактики:**
- Кэширование
- Пулы соединений
- Асинхронная обработка
- Оптимизация запросов
#### Масштабируемость (Scalability)
**Определение:** Способность системы обрабатывать увеличение нагрузки
**Типы масштабирования:**
- **Горизонтальное** (Scale Out) — добавление серверов
- **Вертикальное** (Scale Up) — увеличение мощности сервера
**Паттерны:**
- Load Balancing
- Database Sharding
- Microservices
- CDN
#### Надежность (Reliability)
**Определение:** Способность системы работать без сбоев
**Метрики:**
- MTBF (Mean Time Between Failures)
- MTTR (Mean Time To Repair)
- Availability (Доступность)
**Тактики:**
- Redundancy (Избыточность)
- Fault Tolerance (Отказоустойчивость)
- Health Checks
- Circuit Breaker
#### Безопасность (Security)
**Определение:** Защита системы от угроз
**Аспекты:**
- Аутентификация
- Авторизация
- Шифрование
- Аудит
**Принципы:**
- Defense in Depth
- Least Privilege
- Security by Design
#### Удобство использования (Usability)
**Определение:** Простота использования системы
**Факторы:**
- Интуитивность интерфейса
- Производительность пользователя
- Обучаемость
- Доступность
#### Совместимость (Compatibility)
**Определение:** Способность работать с другими системами
**Типы:**
- Обратная совместимость
- Форвардная совместимость
- Межплатформенная совместимость
#### Переносимость (Portability)
**Определение:** Легкость переноса системы на другие платформы
**Факторы:**
- Независимость от ОС
- Стандартизированные интерфейсы
- Абстракция от аппаратуры
#### Тестируемость (Testability)
**Определение:** Легкость тестирования системы
**Принципы:**
- Dependency Injection
- Mocking
- Unit Testing
- Integration Testing
#### Поддерживаемость (Maintainability)
**Определение:** Легкость внесения изменений
**Аспекты:**
- Читаемость кода
- Документирование
- Модульность
- Рефакторинг
### Trade-offs между атрибутами
Часто улучшение одного атрибута ухудшает другой:
- **Производительность vs Безопасность** — шифрование замедляет работу
- **Масштабируемость vs Простота** — распределенные системы сложнее
- **Надежность vs Производительность** — репликация добавляет overhead
- **Безопасность vs Удобство** — сложные пароли неудобны
### Архитектурные тактики
**Тактика** — это архитектурное решение для достижения качественного атрибута.
#### Тактики для производительности:
1. **Управление ресурсами**
- Увеличение ресурсов
- Уменьшение накладных расходов
- Управление ресурсами
2. **Управление запросами**
- Приоритизация запросов
- Уменьшение времени обработки
- Управление событиями
#### Тактики для надежности:
1. **Предотвращение ошибок**
- Удаление точек отказа
- Транзакции
- Валидация
2. **Обнаружение ошибок**
- Ping/Echo
- Heartbeat
- Exception handling
3. **Восстановление после ошибок**
- Voting
- Active redundancy
- Passive redundancy
---
## Роль архитектора в команде
### Определение роли архитектора
**Архитектор ПО** — это специалист, который:
- Проектирует структуру системы
- Принимает ключевые технические решения
- Обеспечивает соответствие архитектуры требованиям
- Руководит техническим развитием проекта
### Обязанности архитектора
#### 1. Проектирование архитектуры
- Анализ требований
- Выбор архитектурных решений
- Проектирование компонентов
- Определение интерфейсов
#### 2. Техническое лидерство
- Наставничество разработчиков
- Code review
- Техническое планирование
- Управление техническим долгом
#### 3. Коммуникация
- Взаимодействие с бизнесом
- Объяснение технических решений
- Презентация архитектуры
- Документирование решений
#### 4. Контроль качества
- Архитектурные ревью
- Соответствие стандартам
- Мониторинг метрик
- Управление рисками
### Навыки архитектора
#### Технические навыки
- Глубокое понимание технологий
- Знание архитектурных паттернов
- Опыт работы с различными платформами
- Понимание качественных атрибутов
#### Мягкие навыки
- Лидерские качества
- Коммуникативные навыки
- Аналитическое мышление
- Принятие решений
#### Бизнес-навыки
- Понимание бизнес-процессов
- Анализ требований
- Управление проектами
- Работа со стейкхолдерами
### Типы архитекторов
#### 1. Solution Architect
- Проектирует решения для конкретных бизнес-задач
- Работает с бизнес-требованиями
- Фокусируется на интеграции систем
#### 2. Enterprise Architect
- Проектирует архитектуру на уровне предприятия
- Стратегическое планирование
- Управление технологическим портфелем
#### 3. Technical Architect
- Фокусируется на технических аспектах
- Выбор технологий
- Проектирование компонентов
#### 4. Cloud Architect
- Специализируется на облачных решениях
- Проектирование cloud-native приложений
- Оптимизация облачных ресурсов
### Закон Конвея
> "Организации проектируют системы, которые копируют структуру коммуникации в организации"
**Следствия:**
- Структура команды влияет на архитектуру
- Необходимо учитывать организационную структуру
- Команды должны быть организованы по доменам
**Примеры:**
- Команда по фронтенду → отдельный фронтенд-сервис
- Команда по платежам → отдельный платежный сервис
- Команда по аналитике → отдельный аналитический сервис
### Архитектурные ревью
**Цель:** Оценка соответствия архитектуры требованиям
**Типы ревью:**
1. **Архитектурный анализ** — оценка текущей архитектуры
2. **Архитектурная оценка** — оценка предлагаемых изменений
3. **Архитектурный аудит** — проверка соответствия стандартам
**Процесс ревью:**
1. Подготовка материалов
2. Презентация архитектуры
3. Обсуждение и вопросы
4. Выработка рекомендаций
5. Документирование результатов
### Управление техническим долгом
**Технический долг** — это компромиссы в качестве кода, которые делаются для ускорения разработки.
**Типы технического долга:**
- Устаревшие технологии
- Неоптимальная архитектура
- Отсутствие тестов
- Плохая документация
**Стратегии управления:**
- Регулярный рефакторинг
- Планирование технических спринтов
- Мониторинг метрик качества
- Обучение команды
---
## Практические задания
### Задание 1: Анализ архитектуры
Проанализируйте архитектуру известной системы (например, Twitter, Netflix, Uber) и определите:
1. Архитектурный стиль
2. Основные компоненты
3. Качественные атрибуты
4. Архитектурные решения
### Задание 2: Применение SOLID принципов
Возьмите пример кода с нарушениями SOLID принципов и рефакторите его согласно принципам.
### Задание 3: Trade-off анализ
Рассмотрите конкретный случай, где нужно выбрать между двумя качественными атрибутами, и обоснуйте выбор.
---
## Дополнительные ресурсы
### Книги
- "Software Architecture in Practice" by Len Bass, Paul Clements, Rick Kazman
- "Clean Architecture" by Robert C. Martin
- "Building Microservices" by Sam Newman
- "Domain-Driven Design" by Eric Evans
### Стандарты
- IEEE 1471-2000: Recommended Practice for Architectural Description
- ISO/IEC 42010: Systems and software engineering — Architecture description
### Онлайн ресурсы
- [Software Architecture Guide](https://martinfowler.com/architecture/)
- [Architecture Decision Records](https://adr.github.io/)
- [C4 Model](https://c4model.com/)
---
## Контрольные вопросы
1. Что такое архитектура ПО и зачем она нужна?
2. Объясните принципы SOLID с примерами.
3. Какие архитектурные стили вы знаете? В чем их различия?
4. Что такое качественные атрибуты? Приведите примеры.
5. Какова роль архитектора в команде?
6. Что такое Закон Конвея и как он влияет на архитектуру?
7. Как управлять техническим долгом?
8. Какие trade-offs существуют между качественными атрибутами?