Сущности зависят от абстракций репозиториев

Как заставить объекты лениво загружать свои отношения?

Например: модели постов и комментариев, где пост может иметь 0 или более комментариев. Как заставить метод getComments() объекта Post лениво загружать свои комментарии?

Моя первая мысль — внедрить CommentRepository в мою сущность Post, чем это плохо? Поскольку сущности и репозитории являются частью майского домена, почему они не могут иметь двустороннее знание друг о друге?

Спасибо

ОБНОВЛЕНИЕ
Я знаю, что существует множество отличных стандартных ORM, которые выполняют отложенную загрузку для основных языков, но я не хочу полагаться на его магию. Я ищу решение, не зависящее от ORM/DBAL, чтобы убедиться в низкой связанности приложения.


person Leo Cavalcante    schedule 01.09.2014    source источник
comment
Какое решение вы используете для уровня доступа к данным. Что такое реализация ваших репозиториев?   -  person Ilya Palkin    schedule 02.09.2014


Ответы (3)


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

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

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

Но в вашем случае я бы все же попытался не откладывать загрузку.

person Eben Roux    schedule 02.09.2014
comment
Соглашаться; кроме того, Post не должен содержать ссылки на объекты Comment, только идентификаторы. См., например, ответ с наибольшим количеством голосов в stackoverflow .com/questions/4919687/ - person Alexander Langer; 02.09.2014
comment
Так что мне не следует использовать метод getComments() в моем объекте Post? - person Leo Cavalcante; 03.09.2014
comment
Ну, насколько я могу судить, у меня не было бы метода getComments() на Post. Если он у вас есть, лучше используйте двойную отправку, чтобы не внедрять репозиторий: Post.getComments(ICommentRepository). Вопрос, который вы должны задать, заключается в том, зачем вам нужны комментарии к Post. Если только для запросов, то может быть достаточно отдельной реализации ICommentQuery, которая отправляет обратно самую базовую структуру. Хотя, если у вас есть репозиторий, это подразумевает сущность. Если Comment не является сущностью, принадлежащей другому объекту, то все комментарии в любом случае должны быть загружены вместе с агрегатом. - person Eben Roux; 03.09.2014

Рассмотрите возможность использования прокси, который является подклассом Post, переопределяет метод getComments(). Вставьте прокси-сервер с CommentRepository и получите к нему доступ в переопределенном методе getComment().

Вот как это обычно делает ORM. Он поддерживает классы вашего домена в чистоте, поскольку только прокси-сервер зависит от механизма доступа к данным.

person David Osborne    schedule 02.09.2014
comment
И в чем будет разница между прокси и сущностями? Для каждой сущности у меня будет прокси, так зачем поддерживать оба? - person Leo Cavalcante; 03.09.2014
comment
Потому что вы можете поменять местами прокси и его технологию доступа к данным, не затрагивая сущность. ИМХО, хорошо спроектированная модель объекта / предметной области должна быть независимой от этих вопросов. Кроме того, вы можете изменить стратегию, которую прокси используют для отложенной загрузки, и, по сути, подключить наиболее подходящий плагин. Опять же, все, не затрагивая основную модель. Соответствует SRP и OCP. Аккуратный. - person David Osborne; 03.09.2014

Во-первых, вы должны отделить понятие предметной области от деталей реализации. Шаблон Agreagate — это то, как организовать ваш домен, а отложенная загрузка — это деталь реализации.

Кроме того, я не согласен с @Eben Roux по поводу несоответствия решений. Ленивая загрузка, на мой взгляд, ничему не противоречит. Я выражаю почему.

Ленивая загрузка себя

Чтобы понять, как можно реализовать ленивую загрузку, вы можете обратиться к шаблону PoEAAA Мартина Фаулера 'Lazy loading'. Для меня шаблон прокси - лучшее решение. Кроме того, важно, что большинство современных ORM поддерживает ленивую загрузку, НО для модели данных (а не для модели предметной области).

Рекомендуется разделить модель данных и модель предметной области и использовать репозитории, чтобы скрыть это преобразование:

Разделенные модели предметной области и данных

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

Вопрос в том, как подгружать некоторые ассоциации не при создании доменного объекта, а при его жизни. Вы можете использовать Repoisotry внутри объекта, и я не вижу в этом ничего плохого. Это будет выглядеть так:

public class Post {

    private ICommentsRepository _commentsRepository;

    private IList<Comments> _comments;

    //necessary to perform lazy loading (repository always wroks with ids)
    private IList<int> _commentIds;

    //realize lazy loading
    ...
}

есть проблемы:

  1. Ваша модель теперь становится непонятной. Он содержит «техническую» информацию, такую ​​как _commentIds.
  2. Как только вы хотите определить ICommentsRepository, вы заявляете, что Comment является совокупным корнем. Если мы введем паттерн агрегации в модель предметной области, репозитории должны создаваться только для корней агрегации. Таким образом, это означает, что Comment и Post являются разными совокупными корнями. И возможно, что это не то, что вы хотите.

Есть лучшее решение:

public interface ICommentList {
...
}

public class CommentList : ICommentList {
...
}

public class CommentListProxy : ICommentList {

   private CommentList _realCommentList;

   private IList<int> _commentIds;

   //realize lazy loading here using ORMs capabilities! 
   //don't use repository here!

}

public class Post {

   private ICommentList _commentList;

   ...
}

Почтовый репозиторий инициирует поле _commentList с прокси-объектом. Также необходимо сказать:

  1. CommentListProxy относится к уровню модели данных, а не к модели предметной области. Он использует возможности ORM для реализации ленивой загрузки.
  2. и поэтому не использует репозитории, поэтому вы можете рассматривать CommentList как часть совокупности Post.

Единственный возможный недостаток этого подхода заключается в неявных запросах к базе данных при работе с объектами предметной области. Это должно быть понятно пользователям класса Post.

Умные ORM

Наконец, есть виды ORM, которые позволяют вам использовать одну и ту же модель как для домена, так и для данных. Он реализует ленивую загрузку для модели предметной области так же, как и для модели данных. Взгляните на DataObjects.Net. Для некоторых случаев это хорошее решение.

person Valentin P.    schedule 02.09.2014
comment
Почему мне нужно иметь commentIds? Метод getComments() для объекта Post просто делегирует вызов введенному CommentRepository, я не понимаю, почему мне нужно хранить идентификаторы связанных комментариев... - person Leo Cavalcante; 03.09.2014
comment
Хорошо, вы рассказываете об ORM-независимом решении, так какие параметры вы хотите передать в репозиторий, чтобы получить комментарии? Если вы храните экземпляр данных в своем экземпляре домена — это плохая практика. Кроме того, я только что объяснил вам, почему создание репозитория для неагрегированного корневого объекта (я считаю комментарий) — это плохо: как только вы хотите определить ICommentsRepository, вы заявляете, что комментарий является совокупным корнем. Если мы введем паттерн агрегации в модель предметной области, репозитории должны создаваться только для корней агрегации. Таким образом, это означает, что комментарий и сообщение являются разными совокупными корнями... - person Valentin P.; 03.09.2014
comment
Я думаю, что второе решение (с CommentListProxy) является лучшим. Если вы хотите, вы можете хранить экземпляр данных (!) Post в CommentListProxy вместо идентификаторов комментариев. Это возможно, потому что CommentListProxy относится к слою данных (!), а не к домену. Таким образом, он может использовать возможности ORM для ленивой загрузки всех комментариев. - person Valentin P.; 03.09.2014