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
This commit is contained in:
commit
15e744da49
107 files changed
+61762
No files matched your search
@@ -0,0 +1,683 @@
|
||||
# Доменная модель и Domain-Driven Design
|
||||
|
||||
## Навигация
|
||||
- [🏠 Главная страница курса](../../../README.md)
|
||||
- [📚 Модуль 1](../README.md)
|
||||
|
||||
---
|
||||
|
||||
## Что такое домен?
|
||||
|
||||
**Домен (Domain)** — это предметная область, в которой работает приложение. Это сфера знаний, правил и процессов, которые определяют, как работает бизнес.
|
||||
|
||||
### Ключевые характеристики домена:
|
||||
|
||||
1. **Бизнес-логика** — правила и процессы, специфичные для предметной области
|
||||
2. **Терминология** — язык, используемый экспертами в данной области
|
||||
3. **Ограничения** — правила и ограничения, которые должны соблюдаться
|
||||
4. **Процессы** — последовательности действий, выполняемых в домене
|
||||
|
||||
## Domain-Driven Design (DDD)
|
||||
|
||||
**Domain-Driven Design** — подход к разработке программного обеспечения, который фокусируется на моделировании предметной области.
|
||||
|
||||
### Основные принципы DDD:
|
||||
|
||||
1. **Фокус на домене** — архитектура отражает структуру домена
|
||||
2. **Единый язык** — общий язык между разработчиками и экспертами
|
||||
3. **Модель-код соответствие** — код отражает доменную модель
|
||||
4. **Итеративное уточнение** — постоянное улучшение понимания домена
|
||||
|
||||
## Строительные блоки DDD
|
||||
|
||||
### 1. Entity (Сущность)
|
||||
|
||||
**Entity** — объект, который имеет уникальную идентичность и жизненный цикл.
|
||||
|
||||
```java
|
||||
// Пример Entity - Заказ
|
||||
@Entity
|
||||
public class Order {
|
||||
@Id
|
||||
private OrderId id;
|
||||
private CustomerId customerId;
|
||||
private OrderStatus status;
|
||||
private Money totalAmount;
|
||||
private List<OrderItem> items;
|
||||
|
||||
// Конструктор
|
||||
public Order(OrderId id, CustomerId customerId) {
|
||||
this.id = id;
|
||||
this.customerId = customerId;
|
||||
this.status = OrderStatus.DRAFT;
|
||||
this.items = new ArrayList<>();
|
||||
}
|
||||
|
||||
// Бизнес-методы
|
||||
public void addItem(ProductId productId, Quantity quantity, Money unitPrice) {
|
||||
if (status != OrderStatus.DRAFT) {
|
||||
throw new IllegalStateException("Cannot modify confirmed order");
|
||||
}
|
||||
|
||||
OrderItem item = new OrderItem(productId, quantity, unitPrice);
|
||||
items.add(item);
|
||||
recalculateTotal();
|
||||
}
|
||||
|
||||
public void confirm() {
|
||||
if (items.isEmpty()) {
|
||||
throw new IllegalStateException("Cannot confirm empty order");
|
||||
}
|
||||
|
||||
this.status = OrderStatus.CONFIRMED;
|
||||
// Доменное событие
|
||||
DomainEvents.raise(new OrderConfirmed(this.id));
|
||||
}
|
||||
|
||||
private void recalculateTotal() {
|
||||
this.totalAmount = items.stream()
|
||||
.map(OrderItem::getSubtotal)
|
||||
.reduce(Money.ZERO, Money::add);
|
||||
}
|
||||
|
||||
// Геттеры
|
||||
public OrderId getId() { return id; }
|
||||
public CustomerId getCustomerId() { return customerId; }
|
||||
public OrderStatus getStatus() { return status; }
|
||||
public Money getTotalAmount() { return totalAmount; }
|
||||
public List<OrderItem> getItems() { return Collections.unmodifiableList(items); }
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Value Object (Объект-значение)
|
||||
|
||||
**Value Object** — объект, который определяется только своими атрибутами, без идентичности.
|
||||
|
||||
```java
|
||||
// Пример Value Object - Деньги
|
||||
public class Money {
|
||||
private final BigDecimal amount;
|
||||
private final Currency currency;
|
||||
|
||||
public Money(BigDecimal amount, Currency currency) {
|
||||
if (amount == null || amount.compareTo(BigDecimal.ZERO) < 0) {
|
||||
throw new IllegalArgumentException("Amount must be non-negative");
|
||||
}
|
||||
if (currency == null) {
|
||||
throw new IllegalArgumentException("Currency cannot be null");
|
||||
}
|
||||
|
||||
this.amount = amount.setScale(2, RoundingMode.HALF_UP);
|
||||
this.currency = currency;
|
||||
}
|
||||
|
||||
public Money add(Money other) {
|
||||
if (!this.currency.equals(other.currency)) {
|
||||
throw new IllegalArgumentException("Cannot add different currencies");
|
||||
}
|
||||
return new Money(this.amount.add(other.amount), this.currency);
|
||||
}
|
||||
|
||||
public Money multiply(BigDecimal factor) {
|
||||
return new Money(this.amount.multiply(factor), this.currency);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (obj == null || getClass() != obj.getClass()) return false;
|
||||
Money money = (Money) obj;
|
||||
return Objects.equals(amount, money.amount) &&
|
||||
Objects.equals(currency, money.currency);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(amount, currency);
|
||||
}
|
||||
|
||||
// Геттеры
|
||||
public BigDecimal getAmount() { return amount; }
|
||||
public Currency getCurrency() { return currency; }
|
||||
|
||||
public static final Money ZERO = new Money(BigDecimal.ZERO, Currency.getInstance("USD"));
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Aggregate (Агрегат)
|
||||
|
||||
**Aggregate** — кластер связанных объектов, которые рассматриваются как единое целое.
|
||||
|
||||
```java
|
||||
// Пример Aggregate - Заказ
|
||||
public class Order {
|
||||
private OrderId id;
|
||||
private CustomerId customerId;
|
||||
private OrderStatus status;
|
||||
private Money totalAmount;
|
||||
private List<OrderItem> items;
|
||||
private List<DomainEvent> domainEvents;
|
||||
|
||||
// Конструктор и методы...
|
||||
|
||||
// Методы для работы с доменными событиями
|
||||
public List<DomainEvent> getUncommittedEvents() {
|
||||
return new ArrayList<>(domainEvents);
|
||||
}
|
||||
|
||||
public void markEventsAsCommitted() {
|
||||
domainEvents.clear();
|
||||
}
|
||||
|
||||
private void raiseEvent(DomainEvent event) {
|
||||
domainEvents.add(event);
|
||||
}
|
||||
}
|
||||
|
||||
// Корневой объект агрегата
|
||||
public class OrderAggregate {
|
||||
private Order order;
|
||||
|
||||
public OrderAggregate(OrderId id, CustomerId customerId) {
|
||||
this.order = new Order(id, customerId);
|
||||
}
|
||||
|
||||
public void addItem(ProductId productId, Quantity quantity, Money unitPrice) {
|
||||
order.addItem(productId, quantity, unitPrice);
|
||||
}
|
||||
|
||||
public void confirm() {
|
||||
order.confirm();
|
||||
}
|
||||
|
||||
public Order getOrder() {
|
||||
return order;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Domain Service (Доменный сервис)
|
||||
|
||||
**Domain Service** — сервис, который содержит бизнес-логику, не принадлежащую ни одной сущности.
|
||||
|
||||
```java
|
||||
// Пример Domain Service - Расчет скидки
|
||||
@Service
|
||||
public class DiscountCalculationService {
|
||||
|
||||
public Money calculateDiscount(Order order, Customer customer) {
|
||||
Money discount = Money.ZERO;
|
||||
|
||||
// Скидка за количество товаров
|
||||
int totalItems = order.getItems().stream()
|
||||
.mapToInt(item -> item.getQuantity().getValue())
|
||||
.sum();
|
||||
|
||||
if (totalItems >= 10) {
|
||||
discount = discount.add(order.getTotalAmount().multiply(new BigDecimal("0.1")));
|
||||
}
|
||||
|
||||
// Скидка для VIP клиентов
|
||||
if (customer.isVip()) {
|
||||
discount = discount.add(order.getTotalAmount().multiply(new BigDecimal("0.05")));
|
||||
}
|
||||
|
||||
// Скидка за праздники
|
||||
if (isHolidayPeriod()) {
|
||||
discount = discount.add(order.getTotalAmount().multiply(new BigDecimal("0.15")));
|
||||
}
|
||||
|
||||
return discount;
|
||||
}
|
||||
|
||||
private boolean isHolidayPeriod() {
|
||||
LocalDate now = LocalDate.now();
|
||||
return now.getMonth() == Month.DECEMBER && now.getDayOfMonth() >= 20;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Repository (Репозиторий)
|
||||
|
||||
**Repository** — интерфейс для доступа к агрегатам.
|
||||
|
||||
```java
|
||||
// Интерфейс репозитория
|
||||
public interface OrderRepository {
|
||||
void save(Order order);
|
||||
Optional<Order> findById(OrderId id);
|
||||
List<Order> findByCustomerId(CustomerId customerId);
|
||||
List<Order> findByStatus(OrderStatus status);
|
||||
void delete(OrderId id);
|
||||
}
|
||||
|
||||
// Реализация репозитория
|
||||
@Repository
|
||||
public class JpaOrderRepository implements OrderRepository {
|
||||
|
||||
@PersistenceContext
|
||||
private EntityManager entityManager;
|
||||
|
||||
@Override
|
||||
public void save(Order order) {
|
||||
entityManager.persist(order);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Order> findById(OrderId id) {
|
||||
Order order = entityManager.find(Order.class, id);
|
||||
return Optional.ofNullable(order);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Order> findByCustomerId(CustomerId customerId) {
|
||||
return entityManager.createQuery(
|
||||
"SELECT o FROM Order o WHERE o.customerId = :customerId", Order.class)
|
||||
.setParameter("customerId", customerId)
|
||||
.getResultList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Order> findByStatus(OrderStatus status) {
|
||||
return entityManager.createQuery(
|
||||
"SELECT o FROM Order o WHERE o.status = :status", Order.class)
|
||||
.setParameter("status", status)
|
||||
.getResultList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(OrderId id) {
|
||||
Order order = entityManager.find(Order.class, id);
|
||||
if (order != null) {
|
||||
entityManager.remove(order);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Bounded Context (Ограниченный контекст)
|
||||
|
||||
**Bounded Context** — граница, в которой определенная доменная модель является валидной.
|
||||
|
||||
### Примеры Bounded Context:
|
||||
|
||||
1. **Order Management** — управление заказами
|
||||
2. **Customer Management** — управление клиентами
|
||||
3. **Inventory Management** — управление складом
|
||||
4. **Payment Processing** — обработка платежей
|
||||
|
||||
```java
|
||||
// Bounded Context: Order Management
|
||||
@BoundedContext("OrderManagement")
|
||||
public class OrderContext {
|
||||
|
||||
// Агрегаты
|
||||
public class Order { /* ... */ }
|
||||
public class OrderItem { /* ... */ }
|
||||
|
||||
// Сервисы
|
||||
public class OrderService { /* ... */ }
|
||||
public class OrderValidationService { /* ... */ }
|
||||
|
||||
// Репозитории
|
||||
public interface OrderRepository { /* ... */ }
|
||||
}
|
||||
|
||||
// Bounded Context: Customer Management
|
||||
@BoundedContext("CustomerManagement")
|
||||
public class CustomerContext {
|
||||
|
||||
// Агрегаты
|
||||
public class Customer { /* ... */ }
|
||||
public class CustomerProfile { /* ... */ }
|
||||
|
||||
// Сервисы
|
||||
public class CustomerService { /* ... */ }
|
||||
public class CustomerRegistrationService { /* ... */ }
|
||||
|
||||
// Репозитории
|
||||
public interface CustomerRepository { /* ... */ }
|
||||
}
|
||||
```
|
||||
|
||||
## Ubiquitous Language (Единый язык)
|
||||
|
||||
**Ubiquitous Language** — общий язык между разработчиками и экспертами предметной области.
|
||||
|
||||
### Пример единого языка для e-commerce:
|
||||
|
||||
```java
|
||||
// Термины домена
|
||||
public enum OrderStatus {
|
||||
DRAFT("Черновик"),
|
||||
CONFIRMED("Подтвержден"),
|
||||
SHIPPED("Отправлен"),
|
||||
DELIVERED("Доставлен"),
|
||||
CANCELLED("Отменен");
|
||||
|
||||
private final String description;
|
||||
|
||||
OrderStatus(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
}
|
||||
|
||||
// Доменные события
|
||||
public class OrderConfirmed implements DomainEvent {
|
||||
private final OrderId orderId;
|
||||
private final LocalDateTime occurredOn;
|
||||
|
||||
public OrderConfirmed(OrderId orderId) {
|
||||
this.orderId = orderId;
|
||||
this.occurredOn = LocalDateTime.now();
|
||||
}
|
||||
|
||||
public OrderId getOrderId() {
|
||||
return orderId;
|
||||
}
|
||||
|
||||
public LocalDateTime getOccurredOn() {
|
||||
return occurredOn;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Event Storming
|
||||
|
||||
**Event Storming** — методология для изучения домена через события.
|
||||
|
||||
### Процесс Event Storming:
|
||||
|
||||
1. **Доменные события** — что происходит в системе
|
||||
2. **Команды** — что вызывает события
|
||||
3. **Агрегаты** — что обрабатывает команды
|
||||
4. **Политики** — бизнес-правила
|
||||
5. **Внешние системы** — интеграции
|
||||
|
||||
```markdown
|
||||
# Event Storming для E-commerce
|
||||
|
||||
## Доменные события
|
||||
- OrderCreated (Заказ создан)
|
||||
- OrderConfirmed (Заказ подтвержден)
|
||||
- PaymentProcessed (Платеж обработан)
|
||||
- OrderShipped (Заказ отправлен)
|
||||
- OrderDelivered (Заказ доставлен)
|
||||
|
||||
## Команды
|
||||
- CreateOrder (Создать заказ)
|
||||
- ConfirmOrder (Подтвердить заказ)
|
||||
- ProcessPayment (Обработать платеж)
|
||||
- ShipOrder (Отправить заказ)
|
||||
|
||||
## Агрегаты
|
||||
- Order (Заказ)
|
||||
- Customer (Клиент)
|
||||
- Product (Товар)
|
||||
- Payment (Платеж)
|
||||
|
||||
## Политики
|
||||
- Скидка за количество товаров
|
||||
- VIP статус клиента
|
||||
- Праздничные скидки
|
||||
```
|
||||
|
||||
## Антипаттерны DDD
|
||||
|
||||
### 1. Anemic Domain Model (Анемичная доменная модель)
|
||||
|
||||
**Проблема:** Доменные объекты содержат только данные без поведения.
|
||||
|
||||
```java
|
||||
// Плохо: Анемичная модель
|
||||
public class Order {
|
||||
private Long id;
|
||||
private String customerId;
|
||||
private String status;
|
||||
private BigDecimal totalAmount;
|
||||
|
||||
// Только геттеры и сеттеры
|
||||
public Long getId() { return id; }
|
||||
public void setId(Long id) { this.id = id; }
|
||||
// ...
|
||||
}
|
||||
|
||||
// Хорошо: Богатая доменная модель
|
||||
public class Order {
|
||||
private OrderId id;
|
||||
private CustomerId customerId;
|
||||
private OrderStatus status;
|
||||
private Money totalAmount;
|
||||
|
||||
// Бизнес-методы
|
||||
public void addItem(ProductId productId, Quantity quantity, Money unitPrice) {
|
||||
// Бизнес-логика
|
||||
}
|
||||
|
||||
public void confirm() {
|
||||
// Бизнес-логика
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. God Object (Божественный объект)
|
||||
|
||||
**Проблема:** Один объект содержит слишком много ответственности.
|
||||
|
||||
```java
|
||||
// Плохо: God Object
|
||||
public class Order {
|
||||
// Слишком много ответственности
|
||||
public void addItem() { /* ... */ }
|
||||
public void calculateTax() { /* ... */ }
|
||||
public void sendEmail() { /* ... */ }
|
||||
public void updateInventory() { /* ... */ }
|
||||
public void processPayment() { /* ... */ }
|
||||
}
|
||||
|
||||
// Хорошо: Разделение ответственности
|
||||
public class Order {
|
||||
public void addItem() { /* ... */ }
|
||||
}
|
||||
|
||||
public class TaxCalculationService {
|
||||
public Money calculateTax(Order order) { /* ... */ }
|
||||
}
|
||||
|
||||
public class EmailService {
|
||||
public void sendOrderConfirmation(Order order) { /* ... */ }
|
||||
}
|
||||
```
|
||||
|
||||
## Интеграция DDD с архитектурой
|
||||
|
||||
### 1. Layered Architecture + DDD
|
||||
|
||||
```java
|
||||
// Presentation Layer
|
||||
@RestController
|
||||
public class OrderController {
|
||||
@Autowired
|
||||
private OrderApplicationService orderService;
|
||||
|
||||
@PostMapping("/orders")
|
||||
public ResponseEntity<OrderResponse> createOrder(@RequestBody CreateOrderRequest request) {
|
||||
OrderId orderId = orderService.createOrder(request);
|
||||
return ResponseEntity.ok(new OrderResponse(orderId));
|
||||
}
|
||||
}
|
||||
|
||||
// Application Layer
|
||||
@Service
|
||||
public class OrderApplicationService {
|
||||
@Autowired
|
||||
private OrderRepository orderRepository;
|
||||
|
||||
@Autowired
|
||||
private OrderDomainService orderDomainService;
|
||||
|
||||
public OrderId createOrder(CreateOrderRequest request) {
|
||||
Order order = new Order(
|
||||
new OrderId(UUID.randomUUID()),
|
||||
new CustomerId(request.getCustomerId())
|
||||
);
|
||||
|
||||
for (OrderItemRequest itemRequest : request.getItems()) {
|
||||
order.addItem(
|
||||
new ProductId(itemRequest.getProductId()),
|
||||
new Quantity(itemRequest.getQuantity()),
|
||||
new Money(itemRequest.getUnitPrice(), Currency.USD)
|
||||
);
|
||||
}
|
||||
|
||||
orderRepository.save(order);
|
||||
return order.getId();
|
||||
}
|
||||
}
|
||||
|
||||
// Domain Layer
|
||||
public class Order {
|
||||
// Доменная логика
|
||||
}
|
||||
|
||||
// Infrastructure Layer
|
||||
@Repository
|
||||
public class JpaOrderRepository implements OrderRepository {
|
||||
// Реализация репозитория
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Hexagonal Architecture + DDD
|
||||
|
||||
```java
|
||||
// Domain Layer (внутренний круг)
|
||||
public class Order {
|
||||
// Доменная логика
|
||||
}
|
||||
|
||||
public interface OrderRepository {
|
||||
void save(Order order);
|
||||
Optional<Order> findById(OrderId id);
|
||||
}
|
||||
|
||||
// Application Layer
|
||||
@Service
|
||||
public class OrderService {
|
||||
private final OrderRepository orderRepository;
|
||||
|
||||
public OrderService(OrderRepository orderRepository) {
|
||||
this.orderRepository = orderRepository;
|
||||
}
|
||||
|
||||
public void createOrder(Order order) {
|
||||
// Применение доменных правил
|
||||
orderRepository.save(order);
|
||||
}
|
||||
}
|
||||
|
||||
// Infrastructure Layer (внешний круг)
|
||||
@Repository
|
||||
public class JpaOrderRepository implements OrderRepository {
|
||||
// Реализация через JPA
|
||||
}
|
||||
|
||||
// Adapters
|
||||
@RestController
|
||||
public class OrderController {
|
||||
// REST API
|
||||
}
|
||||
|
||||
@Component
|
||||
public class OrderEventHandler {
|
||||
// Обработка событий
|
||||
}
|
||||
```
|
||||
|
||||
## Практические рекомендации
|
||||
|
||||
### 1. Начните с доменных событий
|
||||
|
||||
```java
|
||||
// Сначала определите события
|
||||
public class OrderCreated implements DomainEvent {
|
||||
private final OrderId orderId;
|
||||
private final CustomerId customerId;
|
||||
private final LocalDateTime occurredOn;
|
||||
|
||||
// Конструктор, геттеры
|
||||
}
|
||||
|
||||
// Затем создайте агрегаты
|
||||
public class Order {
|
||||
public void confirm() {
|
||||
// Бизнес-логика
|
||||
DomainEvents.raise(new OrderConfirmed(this.id));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Используйте Value Objects для примитивов
|
||||
|
||||
```java
|
||||
// Вместо String
|
||||
public class CustomerId {
|
||||
private final String value;
|
||||
|
||||
public CustomerId(String value) {
|
||||
if (value == null || value.trim().isEmpty()) {
|
||||
throw new IllegalArgumentException("Customer ID cannot be empty");
|
||||
}
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (obj == null || getClass() != obj.getClass()) return false;
|
||||
CustomerId that = (CustomerId) obj;
|
||||
return Objects.equals(value, that.value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(value);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Следите за границами агрегатов
|
||||
|
||||
```java
|
||||
// Правильно: Агрегат управляет только своими данными
|
||||
public class Order {
|
||||
private OrderId id;
|
||||
private List<OrderItem> items;
|
||||
|
||||
public void addItem(ProductId productId, Quantity quantity, Money unitPrice) {
|
||||
// Логика добавления товара в заказ
|
||||
}
|
||||
}
|
||||
|
||||
// Неправильно: Агрегат управляет чужими данными
|
||||
public class Order {
|
||||
public void updateProductInventory(ProductId productId, Quantity quantity) {
|
||||
// Это не должно быть в Order агрегате
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Заключение
|
||||
|
||||
Domain-Driven Design помогает создавать архитектуру, которая:
|
||||
|
||||
1. **Отражает бизнес-логику** — код соответствует домену
|
||||
2. **Улучшает коммуникацию** — единый язык между командами
|
||||
3. **Повышает качество** — четкие границы и ответственности
|
||||
4. **Упрощает тестирование** — изолированная доменная логика
|
||||
5. **Обеспечивает эволюцию** — гибкая архитектура для изменений
|
||||
|
||||
DDD особенно эффективен для сложных бизнес-доменов, где важно точное понимание предметной области и создание архитектуры, которая может эволюционировать вместе с бизнесом.
|
||||
Reference in new issue
Block a user