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

Агрегати в 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);
    }
}

Коментарі

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

Шпаргалка по базових командах PostgreSQL

1. Підключення до PostgreSQL через командний рядок: psql -h <host> -p <port> -U <username> -d <database> 2. Підключення до бази без параметрів (якщо користувач і база мають однакове ім’я): psql 3. Показати список усіх баз даних: \l 4. Підключитися до іншої бази даних: \c <database_name> 5. Показати список таблиць у поточній базі: \dt 6. Показати всі об'єкти (таблиці, індекси, секвенції): \d 7. Показати таблиці з усіх схем: \dt *.* 8. Переглянути структуру конкретної таблиці: \d <table_name> 9. Виконати SQL-запит (приклад): SELECT * FROM users; 10. Вийти з psql: \q 11. Створити нову базу даних: CREATE DATABASE mydb; 12. Створити нову таблицю: CREATE TABLE users ( id SERIAL PRIMARY KEY, name TEXT NOT NULL, email TEXT UNIQUE ); 13. Додати новий запис: INSERT INTO users (name, email) VALUES ('Іван', 'ivan@example.com'); 14. Оновити дані в таблиці: UPDATE users SET name = 'Петро' WH...

Встановлення Visual Studio Code на Ubuntu

Visual Studio Code (VS Code) — це потужний, легкий та кросплатформний редактор коду, який став стандартом для розробників завдяки універсальності та швидкості роботи. Він підтримує Windows, Linux і macOS, запускається швидше за повноцінні IDE, але водночас може перетворюватися на повнофункціональне середовище розробки завдяки тисячам розширень. VS Code має продуману архітектуру, інтуїтивний інтерфейс, вбудований термінал та інтелектуальну систему автодоповнення на базі Language Server Protocol, що забезпечує високоякісну підтримку синтаксису для багатьох мов програмування. Серед ключових можливостей — розумне автодоповнення коду (IntelliSense), навігація між класами, символами та файлами, рефакторинг, форматування коду, інтеграція з Git, підтримка віддаленої розробки через SSH або контейнерні середовища (Dev Containers). Завдяки відлагоджувачу (debugger), який налаштовується під будь-яку мову, розробник може запускати код у контрольованому середовищі, ставити брейкпоїнти, переглядат...

Дамп бази даних Postgresql локально

Що потрібно встановити локально (Ubuntu) Встанови клієнтські утиліти PostgreSQL: pg_dump , psql , pg_restore . sudo apt update sudo apt install postgresql-client Кроки міграції Ідея: не зберігати дамп на серверах. Зробити дамп з віддаленого сервера на свій локальний ПК, потім передати його на новий сервер і там відновити. 1) Створення дампу з віддаленого сервера локально pg_dump -h OLD_SERVER_IP -U OLD_USER -Fc -d OLD_DB -f dump_file.dump Пояснення ключів: -h (хост), -U (користувач), -Fc (custom format), -d (БД), -f (вихідний файл локально). 1.1) Варіант з розбиттям на частини (якщо треба) pg_dump -h OLD_SERVER_IP -U OLD_USER -Fc -d OLD_DB | split -b 10G - dump_file.dump.part_ Це створить локально файли типу dump_file.dump.part_aa , dump_file.dump.part_ab тощо. 2) Передача дампу на новий сервер Один файл: scp dump_file.dump NEW_USER@NEW_SERVER_IP:/tmp/ Або частинами: scp dump_file.dump.part_* NEW_USER@NEW_SERVER_IP:/tmp/ 3) Збирання файл...

Керуючі конструкції в Elixir

Elixir пропонує різноманітні керуючі конструкції для управління потоком виконання програми. На відміну від імперативних мов, всі конструкції в Elixir повертають значення, що робить код більш виразним. if та else Конструкція if перевіряє умову і виконує код відповідно до результату: # Базовий синтаксис if true do "Це буде виконано" else "Це не буде виконано" end # Однорядковий варіант if connected?, do: "Підключено", else: "Відключено" # Присвоєння результату message = if age >= 18 do "Ви повнолітній" else "Ви неповнолітній" end # Без else (повертає nil) if user_logged_in? do show_dashboard() end Примітка: В Elixir тільки false і nil вважаються хибними. Всі інші значення (включаючи 0, "", []) вважаються істинними. unless Конструкція unless — це протилежність if, виконується коли умова хибна: # ...

Angular CLI

CLI (command-line interface) – інтерфейс командного рядка. Перед початком роботи має бути встановлений Node.js Встановлення: npm install -g @angular/cli Отримання допомоги: ng help Буде приблизно такий результат: add Adds support for an external library to your project. analytics Configures the gathering of Angular CLI usage metrics. See https://angular.io/cli/usage-analytics-gathering. build (b) Compiles an Angular app into an output directory named dist/ at the given output path. Must be executed from within a workspace directory. deploy Invokes the deploy builder for a specified project or for the default project in the workspace. config Retrieves or sets Angular configuration values in the angular.json file for the workspace. doc (d) Opens the official Angular documentation (angular.io) in a browser, and searches for a given keyword. e2e (e) Builds and serves an Angular app, then runs end-to-end tests. extract-i18n (i18n-extract, xi18n) Extracts i18n mes...