В последнее время можно услышать много аббревиатур, которые оканчиваются на 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! применительно к вебу).
Начнем, как и полагается, с предметной области:
Сущность «Пост»:
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, можно сделать смелое заявление, что ООП используется довольно редко.
Вот сам проект.