Pull to refresh

Введение в Rich Domain Model

Reading time5 min
Views25K
В последнее время можно услышать много аббревиатур, которые оканчиваются на DD: TDD, BDD, FDD, etc. Меня заинтересовал один из представителей «DD-семейства» — DDD, Domain Driven Development. Я не стану описывать здесь все тонкости этой методологии, ведь всю необходимую информацию можно легко найти в сети. Моя цель — рассказать о наиболее важной концепции DDD, о Rich Domain Model и на небольшом примере показать основные нюансы реализации.

Rich Domain Model противопоставляют Anemic Domain Model. «Толстая» модель характеризуется состоянием и поведение, в отличии от «худой», где есть только состояние. По теме могу порекомендовать презентацию. В конечном итоге для себя решил так: ничего плохого в anemic нету, но это процедурный подход со всеми вытекающими последствиями. Более глубоко вдаваться в дебри я пока не хочу, просто примем, что Rich Domain Model — это модель с состоянием и поведением (бизнес-логикой).
При более близком рассмотрении я столкнулся со сложностями понимания паттерна Repository и его отличием от DAO, но об этом позже.
Азы использования Rich Domain Model рассмотрим на примере мини-сервис блога (мне кажется блог последнее время претендует на роль Hello, World! применительно к вебу).

Начнем, как и полагается, с предметной области:

Сущность «Пост»:
public class Post {
  private String text;
  private Date date;
  private List<Comment> comments;
  
  public Post(String text) {
    this.text = text;
    this.date = new Date();
    this.comments = new ArrayList<Comment>();    
  }
  
  // getters and setters...
}

* This source code was highlighted with Source Code Highlighter.



Сущность «Комментарий»:
public class Comment {
  private String text;
  private Date date;
  
  public Comment(String text) {
    this.text = text;
    this.date = new Date();
  }

  // getters and setters...
}


* This source code was highlighted with Source Code Highlighter.



Пользователи могут оставлять комментарии к постам, поэтому добавим метод comment к модели поста. На этом методе ограничимся всей бизнес-логикой нашего прототипа:
public Comment comment(String text) {
  Comment comment = new Comment(text);
  this.comments.add(comment);
  return comment;
}

* This source code was highlighted with Source Code Highlighter.



Наше приложение должно обеспечить сохранение объектов, так что очередь за Repository. Поведение Repository похоже на поведение обычной java-коллекции. Беглый взгляд на этот паттерн создает впечатление, что никакого отличия от DAO нету, но это не так. Рассмотрим пример сеанса работы с DAO:
Item item = new Item();
item.set...
itemDao.insert(item);
...
Item item = itemDao.select(itemId);
item.set..
itemDao.update(item);


* This source code was highlighted with Source Code Highlighter.


А вот пример работы с Repository:
Item = new Item();
item.set...
itemRepository.add(item);
...
Item item = itemRepository.get(itemId);
item.set...

* This source code was highlighted with Source Code Highlighter.


И все! Никакого update нету. Как видите, работа с repository подобна работе с коллекцией объектов, в то время как работая с DAO мы все время должны вручную фиксировать состояние объекта в хранилище.

Вот собственно интерфейс:

public interface PostRepository {
  void add(Post post);
  void remove(PostId postId);
  Post get(PostId postId);
  List<Post> getAll();
}

* This source code was highlighted with Source Code Highlighter.



Для доступа к элементом нужен идентификатор, поэтому я создал вспомогательный класс PostId:
public class PostId {}

public class Post {
  private PostId postId;
  private String text;
  private Date date;
  private List<Comment> comments;
  ...
}


* This source code was highlighted with Source Code Highlighter.



Я нарочно не использую численные идентификаторы, которые обычно используют в качестве первичных ключей в БД. Мы идем от домена и о БД пока ничего знать не должны. В последствии может быть реализован любой repository и не обязательно на основе БД, где к примеру может не быть числовых идентификаторов.

А вот простая реализация PostRepository:

public class InMemoryPostRepository implements PostRepository {
 private Map<PostId, Post> identityMap = new HashMap<PostId, Post>();

 @Override
 public Post get(PostId postId) {
  return this.identityMap.get(postId);
 }

 @Override
 public List<Post> getAll() {
  return new ArrayList<Post>(this.identityMap.values());
 }

 @Override
 public void add(Post post) {
  PostId postId = new PostId();
  post.setPostId(postId);
  this.identityMap.put(postId, post);
 }

 @Override
 public void remove(PostId postId) {
  this.identityMap.remove(postId);
 }
}


* This source code was highlighted with Source Code Highlighter.



Теперь создадим сервис-слой. Хочу обратить внимание, что в сервис-слое не должно быть бизнес-логики, он нужен лишь что бы обозначить границы приложения, делегируя пользовательские вызовы объектам домена, которые извлекаются из repository:

public class BlogService {
  private PostRepository posts;
  public void setPostRepository(PostRepository posts) {
    this.posts = posts;
  }
  
  public Post post(String text) {
    Post post = new Post(text);
    this.posts.add(post);
    return post;
  }
  
  public Comment comment(PostId postId, String text) {
    Post post = this.posts.get(postId);
    return post.comment(text);    
  }
  
  public Post get(PostId postId) {
    return this.posts.get(postId);
  }
  
  public List<Post> getAll() {
    return this.posts.getAll();
  }
  
  public void delete(PostId postId) {
    this.posts.remove(postId);
  }
}

* This source code was highlighted with Source Code Highlighter.



Вот собственно и все. Конечно на этом примере сложно увидеть все преимущества Rich Domain Model, потому как пример слишком уж тривиальный. Но я надеюсь, что он поможет кому-нибудь в практической реализации. Когда я пытался разобраться самостоятельно, то запрос в гугле «rich domain model example» не давал ничего вразумительного. Но теперь, когда в голове уже есть более или менее целостная картина, решил поделиться своими выводами с сообществом. Если статья понравится, то я могу написать продолжение, в котором будет уже более реальная реализация на основе Hibernate и постараюсь показать на практике такое важное свойство, как Persistence Ignorance.

Отдельно хотелось бы сказать о 2-х открытиях, которые я сделал для себя ища материалы по теме в сети:
1) Если используется Hibernate или любой другой ORM, то использование DAO неуместно.
2) Учитывая большую популярность Anemic Domain Model, можно сделать смелое заявление, что ООП используется довольно редко.

Вот сам проект.
Tags:
Hubs:
+5
Comments12

Articles

Change theme settings