Перейти до основного вмісту

Агрегати в DDD

Domain-Driven Design (DDD, предметно-орієнтоване проєктування) — це підхід до розробки програмного забезпечення, який зосереджується на моделюванні бізнес-логіки на основі реального домену (предметної області). Його запропонував Ерік Еванс у своїй книзі "Domain-Driven Design: Tackling Complexity in the Heart of Software".

Основні принципи DDD

  1. Фокус на домені – головна увага приділяється предметній області, а не технічним деталям.
  2. Єдина мова (Ubiquitous Language) – розробники, бізнес-аналітики та інші учасники проєкту використовують спільну термінологію, щоб уникнути непорозумінь.
  3. Бізнес-логіка відокремлена від технічної реалізації – код моделюється так, щоб він чітко відображав реальний бізнес-процес.

Основні концепції DDD

  • Entity (Сутність) – об’єкт з унікальним ідентифікатором, що зберігається в системі (наприклад, Користувач, Замовлення).
  • Value Object (Об’єкт-значення) – об’єкт, який не має унікального ідентифікатора та є незмінним (наприклад, Адреса або Гроші).
  • Aggregate (Агрегат) – група сутностей та об'єктів-значень, що складають логічне ціле з єдиною точкою доступу (кореневою сутністю).
  • Repository (Репозиторій) – клас, що відповідає за отримання та збереження агрегатів у базі даних.
  • Service (Сервіс домену) – містить бізнес-логіку, яка не може бути розміщена всередині окремої сутності або агрегату.
  • Factory (Фабрика) – використовується для створення складних об'єктів, щоб уникнути складної логіки створення в самому коді бізнес-логіки.
  • Domain Event (Доменна подія) – повідомлення про те, що в домені сталася певна важлива подія.

Агрегати в DDD

Основні характеристики агрегату
  1. Кореневий об'єкт (Aggregate Root) – головна сутність, через яку взаємодіють із агрегатом.
  2. Інкапсуляція – агрегат приховує внутрішню логіку та правила інваріантів.
  3. Консистентність – операції над агрегатом гарантують узгоджений стан його складових.
  4. Обмеження доступу – зовнішній код може працювати лише з коренем агрегату, а не з його підлеглими об'єктами напряму.
Приклад реалізації в Java (Spring Boot, JPA)

Припустимо, у нас є система замовлень. Логічно об'єднати такі об'єкти в один агрегат:

  • Order (Замовлення) – кореневий об'єкт
  • OrderItem (Позиція замовлення) – частина агрегату
  • Payment (Платіж) – теж може бути частиною

Всі ці об'єкти разом формують єдину транзакційну одиницю.

Створюємо кореневий об'єкт Order


import jakarta.persistence.*;
import java.util.ArrayList;
import java.util.List;

@Entity
@Table(name = "orders")
public class Order {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String status;

    @OneToMany(cascade = CascadeType.ALL, mappedBy = "order", orphanRemoval = true)
    private List<OrderItem> items = new ArrayList<>();

    protected Order() {}

    public Order(String status) {
        this.status = status;
    }

    public void addItem(String productName, int quantity, double price) {
        items.add(new OrderItem(this, productName, quantity, price));
    }

    public void removeItem(OrderItem item) {
        items.remove(item);
    }

    public void completeOrder() {
        if (items.isEmpty()) {
            throw new IllegalStateException("Замовлення не може бути завершене без товарів");
        }
        this.status = "COMPLETED";
    }

    // Гетери та сетери
}

Створюємо підлеглий об'єкт OrderItem


import jakarta.persistence.*;

@Entity
@Table(name = "order_items")
public class OrderItem {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String productName;
    private int quantity;
    private double price;

    @ManyToOne
    @JoinColumn(name = "order_id", nullable = false)
    private Order order;

    protected OrderItem() {}

    public OrderItem(Order order, String productName, int quantity, double price) {
        this.order = order;
        this.productName = productName;
        this.quantity = quantity;
        this.price = price;
    }

    // Гетери та сетери
}

Репозиторій для збереження агрегату:


import org.springframework.data.jpa.repository.JpaRepository;

public interface OrderRepository extends JpaRepository<Order, Long> {
}

Сервіс для роботи з агрегатом


import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class OrderService {

    private final OrderRepository orderRepository;

    public OrderService(OrderRepository orderRepository) {
        this.orderRepository = orderRepository;
    }

    @Transactional
    public void createOrderWithItems() {
        Order order = new Order("NEW");
        order.addItem("Ноутбук", 1, 1500.00);
        order.addItem("Мишка", 1, 50.00);

        orderRepository.save(order);
    }

    @Transactional
    public void completeOrder(Long orderId) {
        Order order = orderRepository.findById(orderId)
                .orElseThrow(() -> new RuntimeException("Order not found"));

        order.completeOrder();
        orderRepository.save(order);
    }
}

Коментарі

Популярні публікації

Колекції в Elixir

Списки Що таке список у Elixir У Elixir список — це впорядкована колекція елементів, реалізована як однозв’язний список . Це означає, що кожен елемент (вузол) зберігає посилання на наступний, але не на попередній. Така структура дозволяє швидко додавати елементи на початок списку, але повільно доступати до довільного елементу. У Java подібну структуру представляє LinkedList — частина Java Collections Framework. Вона реалізована як двозв’язний список, що забезпечує зручне додавання/видалення елементів з початку або кінця списку. Створення списків У Elixir список створюється за допомогою квадратних дужок: list = [1, 2, 3, 4] Додавання елементів У Elixir новий елемент можна додати тільки на початок списку за допомогою оператора | : # Elixir list = [2, 3, 4] new_list = [1 | list] # [1, 2, 3, 4] Доступ до елементів У Elixir немає прямого доступу до елементів за індексом, але це можна зробити через Enum.at : Enum.at([10, 20, 30], 1) # 20 Ітерація по списку У Elixir ...

Інструменти для роботи з Node.js

Що таке npm? npm (Node Package Manager) — це офіційний пакетний менеджер для Node.js . Він дозволяє: Встановлювати сторонні бібліотеки та фреймворки Керувати залежностями проєкту Запускати скрипти (команди) через package.json Приклад ініціалізації проєкту з npm npm init -y Файл package.json (скорочений приклад) { "name": "my-project", "version": "1.0.0", "scripts": { "start": "ts-node src/index.ts", "build": "tsc" }, "dependencies": { "express": "^4.18.0" }, "devDependencies": { "typescript": "^5.0.0", "ts-node": "^10.0.0" } } Що таке tsconfig.json? tsconfig.json — це файл конфігурації для компілятора TypeScript, який визначає, як слід компілювати код. Приклад файлу tsconfig.json { "compilerOptions": { "target": "ES2020...

Атоми в мові програмування Elixir

Атоми в Elixir Атоми є фундаментальною концепцією в Elixir , що відіграє ключову роль у створенні надійних та масштабованих систем. В Elixir це специфічний тип даних, який є константою , незмінною , ідентифікованою за своїм ім'ям . Отже, атом в Elixir — це іменована константа, що представляє себе. Уявіть, що ви даєте унікальне ім'я певній речі, і це ім'я завжди посилається саме на цю річ, і ніколи на щось інше. Наприклад, атом :ok завжди буде означати саме успішне завершення операції, а не якесь інше значення. Технічно, атоми є похідними від чисел . Кожен унікальний атом зберігається у таблиці атомів, і йому присвоюється унікальний цілочисельний ідентифікатор. Це робить їх надзвичайно ефективними для порівняння: замість порівняння рядків (що є повільною операцією), Elixir порівнює цілочисельні ідентифікатори. Переваги та особливості використання атомів Переваги атомів: Ефективність. Завдяки своєму числовому представленню, порівняння атомів є дуже швидким. Це осо...

Встановлення PostgreSQL на Ubuntu-сервер

Встановлення Оновлюємо пакети та встановлюємо PostgreSQL: sudo apt update sudo apt install -y postgresql postgresql-contrib Перевіряємо статус сервісу: sudo systemctl status postgresql Якщо PostgreSQL не запущений, запустимо його: sudo systemctl start postgresql sudo systemctl enable postgresql Налаштування безпеки Зміна пароля: sudo -u postgres psql У консолі PostgreSQL: ALTER USER postgres PASSWORD 'міцний_пароль'; \q \q - вихід з консолі. Список основних команд для роботи з PostgreSQL можна переглянути за посиланням. За замовчуванням PostgreSQL слухає localhost (127.0.0.1). Щоб дозволити доступ із зовнішніх машин, редагуємо конфігурацію: sudo nano /etc/postgresql/17/main/postgresql.conf (замість 17 вкажи версію PostgreSQL, яку встановлено) Шукаємо рядок: #listen_addresses = 'localhost' та замінюємо на listen_addresses = '*' Зберігаємо (Ctrl + X, Y, Enter). Тепер редагуємо pg_hba.conf: sudo nano /etc/postgresql/17/main/pg_hba.conf...

Основи Node.js

Що таке Node.js? Node.js — це середовище виконання JavaScript поза браузером, побудоване на рушії Google V8 . Воно дозволяє запускати JavaScript на сервері, створюючи серверні застосунки з високою продуктивністю. Основні характеристики: Однопотокова модель з неблокуючим I/O Асинхронне виконання за допомогою event loop Висока продуктивність у роботі з мережевими запитами npm — найбільший реєстр пакетів Що таке event loop? Event loop — це механізм в Node.js, який дозволяє неблокуючим асинхронним операціям виконуватись у середовищі з єдиним потоком. Він постійно перевіряє наявність подій у черзі та викликає відповідні колбеки. Як працює однопоточність у Node.js? Node.js використовує один потік (main thread) для обробки JavaScript-коду. Операції, які займають час (мережеві запити, читання з файлової системи), делегуються до системних API або thread pool, і після завершення результат повертається у основний потік через event loop. Приклад: асинхронна о...