← Назад к вопросам

Какие знаешь сущности в Domain слое Clean Architecture?

2.0 Middle🔥 111 комментариев
#Архитектура и паттерны

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI5 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Сущности (Entities) в Domain-слое Clean Architecture

В Domain-слое (слое бизнес-логики) Clean Architecture ключевой абстракцией являются сущности (Entities). Это ядро приложения, которое полностью независимо от фреймворков, UI, баз данных и внешних сервисов. Сущности инкапсулируют наиболее общие и высокоуровневые бизнес-правила.

Характеристики сущностей

  1. Бизнес-логика: Содержат критически важные для предметной области данные и методы, которые их обрабатывают. Это «сердце» приложения.
  2. Независимость: Не имеют зависимостей от других слоёв (Data, Presentation). Они не знают, как данные сохраняются или отображаются.
  3. Уникальность идентичности: Отличаются не своими атрибутами, а уникальным идентификатором (ID). Даже если все поля двух объектов совпадают, но ID разный — это разные сущности.
  4. Инварианты: Обеспечивают целостность своих данных через соблюдение инвариантов (неизменных условий) предметной области.

Типы сущностей в Domain-слое

Обычно в Domain-слое выделяют несколько типов объектов, но все они вращаются вокруг центральной концепции Entity.

  • Entity (Классическая сущность): Объект с уникальной идентичностью и жизненным циклом.

    // Kotlin пример
    data class Product(
        val id: ProductId, // Value Object для типа ID
        val name: String,
        val price: Money,   // Value Object
        val stockQuantity: Int
    ) {
        // Бизнес-логика внутри сущности
        fun reduceStock(quantity: Int) {
            require(quantity > 0) { "Quantity must be positive" }
            require(stockQuantity >= quantity) { "Insufficient stock" }
            stockQuantity -= quantity
        }
    
        fun isAvailable(): Boolean = stockQuantity > 0
    }
    
  • Value Object (Объект-значение): Не имеет идентификатора. Определяется исключительно своими атрибутами. Неизменяем (immutable). Используется для описания свойств.

    // Kotlin пример
    data class Money(
        val amount: BigDecimal,
        val currency: Currency
    ) {
        operator fun plus(other: Money): Money {
            require(currency == other.currency) { "Currency mismatch" }
            return Money(amount + other.amount, currency)
        }
    }
    
    data class Address(
        val street: String,
        val city: String,
        val postalCode: String
    ) // Два адреса с одинаковыми полями считаются одинаковыми.
    
  • Aggregate Root (Агрегат): Особый тип сущности, который инкапсулирует одну или несколько связанных сущностей и Value Objects в единую границу транзакционной согласованности. Внешние классы могут ссылаться только на корень агрегата.

    // Kotlin пример
    class Order(
        val id: OrderId,
        private val items: MutableList<OrderItem> = mutableListOf(),
        var status: OrderStatus = OrderStatus.CREATED
    ) : AggregateRoot() { // AggregateRoot - маркерный класс/интерфейс
    
        // Доступ к внутренней коллекции только через методы агрегата
        fun addItem(productId: ProductId, price: Money, quantity: Int) {
            require(status == OrderStatus.CREATED) { "Cannot modify completed order" }
            items.add(OrderItem(productId, price, quantity))
            // Может генерировать Domain Event
            registerEvent(OrderItemAddedEvent(this.id, productId))
        }
    
        fun totalAmount(): Money {
            return items.map { it.price * it.quantity }.reduce { acc, money -> acc + money }
        }
    }
    
    // Внутренняя сущность, принадлежащая агрегату Order
    data class OrderItem(
        val productId: ProductId,
        val price: Money,
        val quantity: Int
    )
    
  • Domain Service (Сервис предметной области): Содержит бизнес-логику, которая не естественным образом принадлежит какой-либо одной сущности или Value Object. Часто координирует работу нескольких сущностей.

    // Kotlin пример
    interface PaymentService { // Domain Service как интерфейс
        fun processPayment(order: Order, paymentDetails: PaymentDetails): PaymentResult
    }
    
    class OrderProcessingService(
        private val paymentService: PaymentService,
        private val inventoryService: InventoryService
    ) {
        fun completeOrder(order: Order, paymentDetails: PaymentDetails) {
            // Координация нескольких правил и внешних сервисов (определённых в Domain)
            val result = paymentService.processPayment(order, paymentDetails)
            if (result.isSuccess) {
                order.items.forEach { item ->
                    inventoryService.reserve(item.productId, item.quantity)
                }
                order.status = OrderStatus.PAID
            }
        }
    }
    
  • Domain Event (Событие предметной области): Объект, который представляет факт, произошедший в доменной области. Используется для ослабления связей между агрегатами.

    // Kotlin пример
    sealed interface DomainEvent {
        val aggregateId: String
        val occurredOn: Instant
    }
    
    data class OrderCompletedEvent(
        override val aggregateId: String,
        val totalAmount: Money,
        override val occurredOn: Instant = Instant.now()
    ) : DomainEvent
    
  • Repository Interface (Интерфейс репозитория): Определяется в Domain-слое, а реализуется в Data-слое. Позволяет Domain-слою декларативно работать с хранением данных, не зная деталей реализации.

    // Kotlin пример. Интерфейс живет в Domain!
    interface ProductRepository {
        suspend fun findById(id: ProductId): Product?
        suspend fun save(product: Product)
        suspend fun findAllAvailable(): List<Product>
    }
    

Принципы проектирования

  • Анемичная модель — антипаттерн: Сущности не должны быть просто наборами полей с геттерами/сеттерами (анемичная модель). Они должны содержать поведение (методы), соответствующее бизнес-правилам.
  • Соответствие языку предметной области (Ubiquitous Language): Имена классов, методов и полей должны максимально точно соответствовать терминам, используемым экспертами в данной предметной области.
  • Инкапсуляция: Состояние сущности должно изменяться только через её публичные методы, которые гарантируют соблюдение всех инвариантов.

Итог: Domain-слой в Clean Architecture — это не просто набор POJO-классов. Это богатая модель, построенная вокруг сущностей (Entities), Value Objects и агрегатов (Aggregates), которые вместе с Domain Services, Domain Events и интерфейсами репозиториев полностью и точно описывают бизнес-логику приложения, обеспечивая её максимальную независимость и тестируемость.

Какие знаешь сущности в Domain слое Clean Architecture? | PrepBro