Files
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

198 KiB
Raw Permalink Blame History

Модуль 1: Основы архитектуры программного обеспечения

Содержание модуля

  1. Роль и обязанности Solution Architect
  2. Введение в архитектуру ПО
  3. Доменная модель и 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-директора

Пример различий:

// 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)

Процесс анализа:

// Пример анализа требований
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)

Типы архитектурной документации:

# 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)

Определение: Способность системы обрабатывать увеличение нагрузки.

Метрики:

  • Количество одновременных пользователей
  • Объем обрабатываемых данных
  • Количество транзакций в секунду

Примеры требований:

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. Выбор между монолитом и микросервисами

// Монолитная архитектура
@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)

// 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)

// 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. Выбор стратегии кэширования

// 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 — объект, который имеет уникальную идентичность и жизненный цикл.

// Пример 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 — объект, который определяется только своими атрибутами, без идентичности.

// Пример 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 — кластер связанных объектов, которые рассматриваются как единое целое.

// Пример 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 — сервис, который содержит бизнес-логику, не принадлежащую ни одной сущности.

// Пример 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 — интерфейс для доступа к агрегатам.

// Интерфейс репозитория
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 — обработка платежей
// 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:

// Термины домена
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. Внешние системы — интеграции
# Event Storming для E-commerce

## Доменные события
- OrderCreated (Заказ создан)
- OrderConfirmed (Заказ подтвержден)
- PaymentProcessed (Платеж обработан)
- OrderShipped (Заказ отправлен)
- OrderDelivered (Заказ доставлен)

## Команды
- CreateOrder (Создать заказ)
- ConfirmOrder (Подтвердить заказ)
- ProcessPayment (Обработать платеж)
- ShipOrder (Отправить заказ)

## Агрегаты
- Order (Заказ)
- Customer (Клиент)
- Product (Товар)
- Payment (Платеж)

## Политики
- Скидка за количество товаров
- VIP статус клиента
- Праздничные скидки

Антипаттерны DDD

1. Anemic Domain Model (Анемичная доменная модель)

Проблема: Доменные объекты содержат только данные без поведения.

// Плохо: Анемичная модель
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 (Божественный объект)

Проблема: Один объект содержит слишком много ответственности.

// Плохо: 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

// 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

// 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. Начните с доменных событий

// Сначала определите события
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 для примитивов

// Вместо 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. Следите за границами агрегатов

// Правильно: Агрегат управляет только своими данными
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)

Принцип единственной ответственности

Класс должен иметь только одну причину для изменения

Пример нарушения:

// Нарушение 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) {
        // Аналогично много ответственностей...
    }
}

Правильная реализация:

// Разделение ответственностей
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)

Принцип открытости/закрытости

Программные сущности должны быть открыты для расширения, но закрыты для модификации

Пример нарушения:

// Нарушение 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) {
        // логика обработки банковского перевода
    }
}

Правильная реализация:

// Соблюдение 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)

Принцип подстановки Лисков

Объекты производного класса должны быть заменяемы объектами базового класса

Пример нарушения:

// Нарушение 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!
}

Правильная реализация:

// Соблюдение 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)

Принцип разделения интерфейсов

Клиенты не должны зависеть от интерфейсов, которые они не используют

Пример нарушения:

// Нарушение 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() { /* не используется */ }
}

Правильная реализация:

// Соблюдение 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)

Принцип инверсии зависимостей

Модули высокого уровня не должны зависеть от модулей низкого уровня

Пример нарушения:

// Нарушение 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"
        );
    }
}

Правильная реализация:

// Соблюдение 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 (Не повторяйся)

Не повторяйся

  • Избегайте дублирования кода
  • Выносите общую логику в отдельные функции/классы
  • Используйте абстракции для переиспользования

Пример нарушения:

// Нарушение 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");
        }
        // обновление пользователя
    }
}

Правильная реализация:

// Соблюдение 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 (Будь проще)

Делай проще

  • Простые решения часто лучше сложных
  • Избегайте преждевременной оптимизации
  • Читаемость кода важнее "умности"

Пример нарушения:

// Нарушение 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;
    }
}

Правильная реализация:

// Соблюдение 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

Пример нарушения:

// Нарушение 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);
    }
}

Правильная реализация:

// Соблюдение 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)

  • Компоненты минимально зависят друг от друга
  • Изменения в одном компоненте не влияют на другие
  • Легче тестировать и поддерживать

Пример нарушения:

// Нарушение - тесная связанность
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());
    }
}

Правильная реализация:

// Соблюдение - слабая связанность
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)

  • Элементы внутри модуля тесно связаны
  • Модуль имеет четко определенную ответственность
  • Все функции модуля работают для достижения одной цели

Пример нарушения:

// Нарушение - низкая сплоченность
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) {
        // Сжатие файла
    }
}

Правильная реализация:

// Соблюдение - высокая сплоченность
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. Монолитная архитектура

Определение: Вся система реализована как единое приложение

Преимущества:

  • Простота разработки и развертывания
  • Легкое тестирование
  • Простая отладка
  • Низкая сложность сети

Недостатки:

  • Сложность масштабирования
  • Технологическая привязка
  • Сложность обновлений
  • Риск каскадных отказов

Когда использовать:

  • Небольшие команды
  • Простые приложения
  • Быстрое прототипирование

Технический пример:

// Монолитное приложение - все в одном проекте
@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):

// 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):

// 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):

# 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):

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):

// 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):

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):

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):

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):

// 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):

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):

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):

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
  • Кривая обучения
  • Больше кода

Технический пример:

// 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
  • Сложность интеграции

Технический пример:

// 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

Технический пример:

// 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 (отдельное приложение):

// 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 (отдельное приложение):

// 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 (для маршрутизации):

// 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 для развертывания:

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

Онлайн ресурсы


Контрольные вопросы

  1. Что такое архитектура ПО и зачем она нужна?
  2. Объясните принципы SOLID с примерами.
  3. Какие архитектурные стили вы знаете? В чем их различия?
  4. Что такое качественные атрибуты? Приведите примеры.
  5. Какова роль архитектора в команде?
  6. Что такое Закон Конвея и как он влияет на архитектуру?
  7. Как управлять техническим долгом?
  8. Какие trade-offs существуют между качественными атрибутами?