Валидируем концепции DDD с помощью jMolecules

от автора

В этой статье мы повторим основные концепции предметно-ориентированного проектирования (Domain-Driven Design, DDD) и покажем, как с помощью jMolecules можно выразить технические аспекты в виде метаданных.

Мы изучим, какие преимущества дает такой подход, а также обсудим интеграцию jMolecules с популярными библиотеками и фреймворками из экосистемы Java и Spring.

Наконец, мы посмотрим на интеграцию с ArchUnit и узнаем, как использовать его для проверки, что структура исходников соответствует принципам DDD.

Цель jMolecules

jMolecules — это библиотека, которая позволяет явно выражать архитектурные концепции, улучшая читаемость кода. В статье авторов содержится подробное объяснение целей проекта и основных функций.

Если кратко, jMolecules позволяет избежать лишних зависимостей в коде бизнес-логики и выразить технические концепции через аннотации и интерфейсы.

В зависимости от подхода и архитектуры, которую мы выбираем, мы можем импортировать соответствующий модуль JMolecules для выражения технических концепций, специфичных для этой архитектуры. Например, вот некоторые поддерживаемые стили архитектуры и связанные с ними аннотации, которые они предоставляют:

  • DDD — вы можете отмечать аннотациями @Entity, @ValueObject, @Repository@AggregateRoot

  • CQRS — аннотации @Command@CommandHandler, @QueryModel

  • Архитектурные слои — аннотации @DomainLayer@ApplicationLayer, @InfrastructureLayer

Кроме того, эти метаданные могут затем использоваться инструментами и плагинами для таких задач, как генерация кода, генерация документации или обеспечение корректности структуры. Несмотря на то, что проект находится на ранней стадии, он поддерживает интеграции с различными библиотеками.

Например, мы можем подключить интеграции Jackson, ByteBuddy и JPA и транслировать аннотации jMolecules в их аналоги для Spring.

jMolecules и DDD

В этой статье мы сосредоточимся на модуле DDD и используем его для создания доменной модели приложения для ведения блога. Во-первых, давайте добавим зависимости jmolecules-starter-ddd и jmolecules-starter-test в pom.xml:

<dependency>     <groupId>org.jmolecules.integrations</groupId>     <artifactId>jmolecules-starter-ddd</artifactId>     <version>0.21.0</version> </dependency> <dependency>     <groupId>org.jmolecules.integrations</groupId>     <artifactId>jmolecules-starter-test</artifactId>     <version>0.21.0</version>     <scope>test</scope> </dependency>

В приведенных ниже примерах кода мы заметим сходство между аннотациями jMolecules и других фреймворков. Это происходит потому, что такие фреймворки, как Spring Boot или JPA, также следуют принципам DDD. Давайте кратко рассмотрим некоторые ключевые концепции DDD и связанные с ними аннотации.

Value Objects

Value object — это неизменяемый объект предметной области, который инкапсулирует атрибуты и логику, не имея собственной идентичности. Такие объекты определяются исключительно своими атрибутами.

В контексте приложения для ведения блога slug статьи неизменен и может справиться с собственной валидацией при создании. Это делает его идеальным кандидатом для @ValueObject:

@ValueObject class Slug {     private final String value;      public Slug(String value) {         Assert.isTrue(value != null, "Article's slug cannot be null!"); Assert.isTrue(value.length() >= 5, "Article's slug should be at least 5 characters long!"); this.value = value;     }      // getters }

Record в java по своей природе неизменны, что делает их отличным выбором для реализации объектов-значений. Давайте используем record для создания еще одного объекта-значения для представления имени пользователя:

@ValueObject record Username(String value) {     public Username {         Assert.isTrue(value != null && !value.isBlank(), "Username value cannot be null or blank.");     } }

Сущности

Сущности отличаются от value objects тем, что они обладают идентичностью и изменяемым состоянием. Они представляют концепции предметной области, которые требуют отдельной идентификации и могут быть изменены с течением времени при сохранении своей идентичности в разных состояниях.

Например, мы можем представить себе комментарий в качестве сущности: каждый комментарий имеет уникальный идентификатор, автора, текст сообщения и дату/время. Кроме того, сущность может инкапсулировать логику, необходимую для редактирования сообщения комментария:

@Entity class Comment {     @Identity     private final String id;     private final Username author;     private String message;     private Instant lastModified;      // constructor, getters      public void edit(String editedMessage) {         this.message = editedMessage;         this.lastModified = Instant.now();     } }

Агрегаты

В DDD агрегаты представляют собой группы связанных объектов, которые рассматриваются как единый блок при изменении и имеют выделенный объект, называемый корень агрегата. Корень агрегата инкапсулирует логику, которая гарантирует, что изменения всех связанных объектов происходят в рамках одной транзакции.

Например, в нашей модели сущность Статья (Article) будет корнем агрегата. Статья может быть идентифицирована с использованием уникального slug и будет отвечать за управление её содержанием, лайками и комментариями:

@AggregateRoot class Article {     @Identity     private final Slug slug;     private final Username author;     private String title;     private String content;     private Status status;     private List<Comment> comments;     private List<Username> likedBy;        // constructor, getters      void comment(Username user, String message) {         comments.add(new Comment(user, message));     }      void publish() {         if (status == Status.DRAFT || status == Status.HIDDEN) {             // ...other logic             status = Status.PUBLISHED;         }         throw new IllegalStateException("we cannot publish an article with status=" + status);     }      void hide() { /* ... */ }      void archive() { /* ... */ }      void like(Username user) { /* ... */ }      void dislike(Username user) { /* ... */ } }

Как мы видим, сущность Article является корнем агрегата, который включает в себя сущности комментариев и некоторые value objects. Агрегаты не могут непосредственно ссылаться на сущности из других агрегатов. Таким образом, мы можем взаимодействовать с сущностью комментарий только через корень (статью), а не напрямую из других агрегатов или сущностей.

Кроме того, агрегаты должны ссылаться на другие агрегаты только через их идентификаторы. Например, статья ссылается на другой агрегат — автора. Это происходит через объект Username, который является естественным ключом сущности автор.

Репозитории

Репозитории — это абстракции, которые предоставляют методы доступа, хранения и получения корней агрегатов. Снаружи они выглядят как простые коллекции агрегатов.

Поскольку мы определили статью как корень агрегата, мы можем создать класс Articles и аннотировать его @Repository. Этот класс будет инкапсулировать взаимодействие со слоем базы данных и обеспечивать интерфейс для доступа к данным:

@Repository class Articles {     Slug save(Article draft) {         // save to DB     }      Optional<Article> find(Slug slug) {         // query DB     }      List<Article> filterByStatus(Status status) {         // query DB     }      void remove(Slug article) {         // update DB and mark article as removed     } }

Соблюдение принципов DDD

Использование аннотаций jmolecules позволяет определить архитектурные концепции в нашем коде в виде метаданных. Как обсуждалось ранее, это позволяет нам интегрироваться с другими библиотеками для генерации кода и документации. В данный момент сосредоточимся на обеспечении соблюдения принципов DDD с использованием Arch-unit и jmolecules-archunit:

<dependency>     <groupId>com.tngtech.archunit</groupId>     <artifactId>archunit</artifactId>     <version>1.3.0</version>     <scope>test</scope> </dependency> <dependency>     <groupId>org.jmolecules</groupId>     <artifactId>jmolecules-archunit</artifactId>     <version>1.0.0</version>     <scope>test</scope> </dependency>

Давайте создадим новый корень агрегата и намеренно нарушим некоторые правила DDD. Например, мы можем создать класс автора без идентификатора, который ссылается на статью непосредственно (в виде прямой ссылки на сущность) вместо использования slug. Кроме того, у нас может быть value object для представления электронной почты, который включает в себя ссылку на сущность автора, что также нарушало бы принципы DDD:

@AggregateRoot public class Author { // <-- entities and aggregate roots should have an identifier     private Article latestArticle; // <-- aggregates should not directly reference other aggregates      @ValueObject     record Email(       String address,       Author author // <-- value objects should not reference entities     ) {     }       // constructor, getter, setter }

Теперь давайте напишем простой тест на archunit, чтобы провалидировать структуру исходников. Основные правила DDD уже определены через JMoleculesDddRules. Так что нам просто нужно указать пакеты, которые мы хотим проверить в этом тесте:

@AnalyzeClasses(packages = "com.baeldung.dddjmolecules") class JMoleculesDddUnitTest {     @ArchTest     void whenCheckingAllClasses_thenCodeFollowsAllDddPrinciples(JavaClasses classes) {         JMoleculesDddRules.all().check(classes);     } }

Если мы попытаемся запустить тест, то увидим следующие нарушения:

Author.java: Invalid aggregate root reference! Use identifier reference or Association instead!  Author.java: Author needs identity declaration on either field or method!  Author.java: Value object or identifier must not refer to identifiables!

Давайте исправим ошибки и убедимся, что наш код проходит проверки:

@AggregateRoot public class Author {     @Identity     private Username username;     private Email email;     private Slug latestArticle;      @ValueObject     record Email(String address) {     }      // constructor, getters, setters }

Заключение

В этой статье мы обсудили разделение технических аспектов и бизнес-логики, а также преимущества явного объявления этих технических концепций. Мы выяснили, что jMolecules помогает достичь такого разделения и обеспечивает соблюдение лучших архитектурных практик в зависимости от выбранного архитектурного стиля.

Кроме того, мы вновь повторили ключевые концепции DDD и использовали агрегаты, сущности, объекты-значения и репозитории для построения доменной модели сайта для блогов. Понимание этих концепций помогло нам создать предметную область, а интеграция jMolecules с ArchUnit позволила убедиться в соблюдении лучших практик DDD.


ссылка на оригинал статьи https://habr.com/ru/articles/909206/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *