Здравствуйте.
Суть такова: допустим пишется проект в стиле DDD. Есть класс MyObject, с помощью ОРМ он отображается на таблицу, и у этого класса есть, скажем, какое-то вычисляемое свойство, которое обновляется при вызове метода.
А этот метод таков, что он обращается к БД.
И вот объекты класса MyObject обрабатываются обычно по нескольку десятков штук.
Вот мы получили из базы список этих объектов, и в цикле вызываем этот метод, чтобы он установил это свойство. Итого, получается множество запросов к базе.
Можно это оптимизировать и внешним кодом извлечь целую табличку значений и из нее уже обновить значения свойств у всего списка объектов.
Вопрос — как совместить хорошую производительность и хороший дизайн. С точки зрения дизайна, мне кажется, что данный класс должен иметь у себя такой метод, там ему самое место. Но с другой стороны, если каждый экземпляр полезет в базу, будут тормоза. Как быть? Есть какой-то типовой прием?
Здравствуйте, IT, Вы писали:
IT>Здравствуйте, dmitry_npi, Вы писали:
_>>С точки зрения дизайна, мне кажется, что данный класс должен иметь у себя такой метод, там ему самое место.
IT>Это как минимум нарушение SRP.
а почему? Ведь загрузка происходит неявно, через ОРМ. Вполне себе бизнес-логика объекта, который ничего и не знает про хранилище. Но особо спорить не буду, пусть так. Тогда куда поместить такой код? Или за это должны отвечать т.н. доменные сервисы?
Здравствуйте, dmitry_npi, Вы писали:
_>Вопрос — как совместить хорошую производительность и хороший дизайн. С точки зрения дизайна, мне кажется, что данный класс должен иметь у себя такой метод, там ему самое место. Но с другой стороны, если каждый экземпляр полезет в базу, будут тормоза. Как быть? Есть какой-то типовой прием?
Самый стандартный приём когда нужно отвязать доменную модель от инфраструктуры это связка из Unit Of Work и Detached Interface. Detached Interface заставляет интерфейс UnitOfWork объявляеть в доменной модели, а реализацию в сервисном слое, таким образом исключается циклическая зависимость между пакетами, и инициатором обращения к инфраструктуре как и положено является сервисный слой, так как реализация UnitOfWork помещается в него. В метод сущности для которого возможна понадобиться дополнительная информация передаешь инстанс UnitOfWork и если сущности что-то понадобиться она запрашивает это у UnitOfWork но ей не обязательно ничего знать об оптимизации, так и о том что она не единственная кто поработает с этим экземпляром UnitOfWork.
Здравствуйте, vermouth, Вы писали:
V>Здравствуйте, dmitry_npi, Вы писали:
_>>Вопрос — как совместить хорошую производительность и хороший дизайн. С точки зрения дизайна, мне кажется, что данный класс должен иметь у себя такой метод, там ему самое место. Но с другой стороны, если каждый экземпляр полезет в базу, будут тормоза. Как быть? Есть какой-то типовой прием?
V>Самый стандартный приём когда нужно отвязать доменную модель от инфраструктуры это связка из Unit Of Work и Detached Interface. Detached Interface заставляет интерфейс UnitOfWork объявляеть в доменной модели, а реализацию в сервисном слое, таким образом исключается циклическая зависимость между пакетами, и инициатором обращения к инфраструктуре как и положено является сервисный слой, так как реализация UnitOfWork помещается в него. В метод сущности для которого возможна понадобиться дополнительная информация передаешь инстанс UnitOfWork и если сущности что-то понадобиться она запрашивает это у UnitOfWork но ей не обязательно ничего знать об оптимизации, так и о том что она не единственная кто поработает с этим экземпляром UnitOfWork.
А как это все избавит от множественных обращений?
Например у тебя БЛ находится в методе класса сущности. Логика дергает базу. Естественно логика в классе сущности не знает что есть еще сущности, которые обрабатываются аналогично.
Правильно решение — выполнить один апдейт, который запишет нужные данные в БД. Это вроде как только bltoolkit умеет делать.
Для других ORM — можно получить список обрабатываемых сущностей, пробежаться по ним, сформировать запрос и вытащить данные, нужные для вычислений.
Путь с использованием доменной модели — каждый раз бегать в базу. Даже если сделать кеш, то не факт что он поможет, а заранее тянуть в память все значения, которые могут понадобиться при вычислении — все равно что сразу вытянуть всю базу из памяти и работать с ней. Только в таком случае нарываемся на множество проблем с конкурентным доступом. Такой подход работает для маленьких и простых задач.
Здравствуйте, gandjustas, Вы писали:
G>А как это все избавит от множественных обращений? G>Например у тебя БЛ находится в методе класса сущности. Логика дергает базу. Естественно логика в классе сущности не знает что есть еще сущности, которые обрабатываются аналогично.
А почему вы считаете что не избавит, я например в упор не вижу почему паттерн специально созданный для batch операций здесь не сработает? Сущность преподаватель запрашивает UnitOfWork типа дай мне моих задолженников и передаёт как параметр свой ID, но поскольку UnitOfWork знает что он создан сразу для группы преподователей то подгрузит двоишников всех кучей, и например покладёт в карту Map<Long, List<Student>>, но для каждого отдельно взятого преподавателя будет казаться что UnitOfWork работает только для него любимого, таким образом и Rich Domain Model не париться об оптимизации и оптимизация имеется и реализованна там где и положенно ей быть в уровне сервисов.
Здравствуйте, vermouth, Вы писали:
V>А почему вы считаете что не избавит, я например в упор не вижу почему паттерн специально созданный для batch операций здесь не сработает? Сущность преподаватель запрашивает UnitOfWork типа дай мне моих задолженников и передаёт как параметр свой ID, но поскольку UnitOfWork знает что он создан сразу для группы преподователей то подгрузит двоишников всех кучей, и например покладёт в карту Map<Long, List<Student>>, но для каждого отдельно взятого преподавателя будет казаться что UnitOfWork работает только для него любимого, таким образом и Rich Domain Model не париться об оптимизации и оптимизация имеется и реализованна там где и положенно ей быть в уровне сервисов.
В том и дело что всех двоешников грузить не надо. Надо только загрузить связанных с выбранными преподавателями. Это может быть очень малая часть всех учеников.
То что ты предлагаешь — тянуть все из базы, а потом работать с этим в памяти. Причем оставаясь в рамках rich domain model эффективное решение придумать не получится. Вот я и говорю что работать будет, но в очень простых случаях, и будет это далеко не оптимизация.
Re[5]: ORM и пакетные операции
От:
Аноним
Дата:
06.02.12 19:50
Оценка:
Здравствуйте, gandjustas, Вы писали:
V>>А почему вы считаете что не избавит, я например в упор не вижу почему паттерн специально созданный для batch операций здесь не сработает? Сущность преподаватель запрашивает UnitOfWork типа дай мне моих задолженников и передаёт как параметр свой ID, но поскольку UnitOfWork знает что он создан сразу для группы преподователей то подгрузит двоишников всех кучей, и например покладёт в карту Map<Long, List<Student>>, но для каждого отдельно взятого преподавателя будет казаться что UnitOfWork работает только для него любимого, таким образом и Rich Domain Model не париться об оптимизации и оптимизация имеется и реализованна там где и положенно ей быть в уровне сервисов.
G>В том и дело что всех двоешников грузить не надо. Надо только загрузить связанных с выбранными преподавателями. Это может быть очень малая часть всех учеников.
Где я предлагал грузить всех, зачем приписывать то чего никто не говорил или Вы наверно всех кто RDD использует тупыми считаете? Что при подходе ниже помешает загрузить лишь нужных?
void someServiceMethod(List<Lecture> lectureList) {
UnitOfWork work = new UnitOfWork(lectureList); // всё работа знает для кого она создана и будет вести себя оптимально
// и нет абсолютно никакой разницы RDD будет являться ее клиентом или слой сервисов.for (Lecture lecture : lectureList) {
lecture.doSomething(work);
}
}
G>То что ты предлагаешь — тянуть все из базы, а потом работать с этим в памяти.
У Вас есть другое решение? Будет у вас логика в сервисах или в модели, чтобы обработать данные их придется вытащить, кроме простых случаев когда можно обойтись одним update.
G> Причем оставаясь в рамках rich domain model эффективное решение придумать не получится.
См. пример выше.
Здравствуйте, Аноним, Вы писали:
А>Здравствуйте, gandjustas, Вы писали:
V>>>А почему вы считаете что не избавит, я например в упор не вижу почему паттерн специально созданный для batch операций здесь не сработает? Сущность преподаватель запрашивает UnitOfWork типа дай мне моих задолженников и передаёт как параметр свой ID, но поскольку UnitOfWork знает что он создан сразу для группы преподователей то подгрузит двоишников всех кучей, и например покладёт в карту Map<Long, List<Student>>, но для каждого отдельно взятого преподавателя будет казаться что UnitOfWork работает только для него любимого, таким образом и Rich Domain Model не париться об оптимизации и оптимизация имеется и реализованна там где и положенно ей быть в уровне сервисов.
G>>В том и дело что всех двоешников грузить не надо. Надо только загрузить связанных с выбранными преподавателями. Это может быть очень малая часть всех учеников. А>Где я предлагал грузить всех, зачем приписывать то чего никто не говорил или Вы наверно всех кто RDD использует тупыми считаете? Что при подходе ниже помешает загрузить лишь нужных?
А как узнать всех нужных? Каждый объект знает какие нужны ему, чтобы собрать эти знания в один предикат надо вынести логику из объекта.
А>
А> void someServiceMethod(List<Lecture> lectureList) {
А> UnitOfWork work = new UnitOfWork(lectureList); // всё работа знает для кого она создана и будет вести себя оптимально
А> // и нет абсолютно никакой разницы RDD будет являться ее клиентом или слой сервисов.
А> for (Lecture lecture : lectureList) {
А> lecture.doSomething(work);
А> }
А> }
А>
Тут один тип сущностей. А нужно как минимум два для демонстрации.
G>>То что ты предлагаешь — тянуть все из базы, а потом работать с этим в памяти. А>У Вас есть другое решение? Будет у вас логика в сервисах или в модели, чтобы обработать данные их придется вытащить, кроме простых случаев когда можно обойтись одним update.
В сервисе можно сформировать один запрос на основе списка сущностей, который вытянет все данные. Куда в DDD помещать код, обрабатывающий список?
G>> Причем оставаясь в рамках rich domain model эффективное решение придумать не получится. А>См. пример выше.
Он ни о чем.
Здравствуйте, gandjustas, Вы писали:
G>>>В том и дело что всех двоешников грузить не надо. Надо только загрузить связанных с выбранными преподавателями. Это может быть очень малая часть всех учеников. А>>Где я предлагал грузить всех, зачем приписывать то чего никто не говорил или Вы наверно всех кто RDD использует тупыми считаете? Что при подходе ниже помешает загрузить лишь нужных?
G>А как узнать всех нужных? Каждый объект знает какие нужны ему, чтобы собрать эти знания в один предикат надо вынести логику из объекта.
Собственно вот так:
UnitOfWork work = new UnitOfWork(lectureList);
Допустим всего у нас в базе 200 преподавателей, в текущей ситуации допустим у нас в обрабатываемом списке их 10. Решение загрузить задолжников для всех 10 за один раз одним запросом, когда первый из преподавателей попросит конкретно своих мне кажеться настолько очевидным, что его код приводить не нужно, так как ежу понятно что UnitOfWork в конструктуре уже получил список преподавателей с которым будет вестись работа и ему не нужно ни грузить данные для всех 200 ни грузить данные 10 раз по разу для каждого преподавателя.
Здравствуйте, vermouth, Вы писали:
V>Здравствуйте, gandjustas, Вы писали:
G>>>>В том и дело что всех двоешников грузить не надо. Надо только загрузить связанных с выбранными преподавателями. Это может быть очень малая часть всех учеников. А>>>Где я предлагал грузить всех, зачем приписывать то чего никто не говорил или Вы наверно всех кто RDD использует тупыми считаете? Что при подходе ниже помешает загрузить лишь нужных?
G>>А как узнать всех нужных? Каждый объект знает какие нужны ему, чтобы собрать эти знания в один предикат надо вынести логику из объекта. V>Собственно вот так: V>
V>UnitOfWork work = new UnitOfWork(lectureList);
V>
V>Допустим всего у нас в базе 200 преподавателей, в текущей ситуации допустим у нас в обрабатываемом списке их 10. Решение загрузить задолжников для всех 10 за один раз одним запросом, когда первый из преподавателей попросит конкретно своих мне кажеться настолько очевидным, что его код приводить не нужно, так как ежу понятно что UnitOfWork в конструктуре уже получил список преподавателей с которым будет вестись работа и ему не нужно ни грузить данные для всех 200 ни грузить данные 10 раз по разу для каждого преподавателя.
То есть ты предлагаешь всю загрузку делать внутри UoW? То есть для каждого сценария свой UoW? А потом еще надо контролировать что в каждый сценарий нужный UoW подсунули?
А если предикат формируется динамически в процессе работы БЛ, как эти знания передать в UoW?
Кстати получается еще круче, чем в просто rich. логика размазана аж по трем классам: UoW отвечает за загрузку нужных данных, BL в методах сущностей за обработку данных, контроллер за то чтобы правильно совместить UoW и BL. Причем при изменении одного компонента надо менять два других. Получается низкий cohesion, очень хрупкая архитектура.
Здравствуйте, gandjustas, Вы писали: G>Кстати получается еще круче, чем в просто rich. логика размазана аж по трем классам: UoW отвечает за загрузку нужных данных, BL в методах сущностей за обработку данных, контроллер за то чтобы правильно совместить UoW и BL. Причем при изменении одного компонента надо менять два других. Получается низкий cohesion, очень хрупкая архитектура.
Не нравиться UoW можете просто сделать классы энтити абстрактными, добавив в них абстрактные методы запроса информации которая понадобиться, а может и не понадобиться по ходу выполнения бизнес операции, а наследников реализовать в слое сервисов. Кстате холивар изначально пошел не потому руслу, так как сложно представить себе когда такое возможно, чтобы операция над списком сущностей выполнялась вне контекста какой нибудь другой более сильной сущности, например: список накладных проводиться в контексте склада, список авансовых отчетов в контексте кассы, и с большой вероятностью у топикстартера в предметной области есть сущность находящаяся на уровне выше, которая могла бы взять на себя оптимизацию выполнения операции над списком подчиненных сущностей.