Pull to refresh

Comments 56

Думаю, немного (псевдо)кода не помешало бы в качестве демонстрации, а то я что-то и в русских предложениях, описывающих, что от чего должно наследоваться и что что реализовывать, стал терять смысл.
Базовая библиотека
ServicesBase
— IUser { name }

Библиотека Membersip
— IUserProfile: IUser { Email, Icq }

Библиотека OrdersLib
— IOrdersCreator: IUser { CreatedOrders }

Библиотека AppCore референсит Membersip и OrdersLib, кодогенерацией создается на основе IUserProfile и IOrdersCreator, единый объект User, который уже используется в GUI или отдается этим библиотекам соответственно в виде IUserProfile, IOrdersCreator и т.д.
Теперь понял.

То есть в классе User сложной логики быть уже не может (или она должна быть как-то написана в модулях в виде понятном для кодогенератора)? Только то, что может кодогенератор нарубить?
Да, либо получим анемичную модель, которую можно расширять через Extension methods, либо специальными атрибутами для кодогенератора указать класс с методами.
UFO just landed and posted this here
Я бы не был столь категоричным, зачастую действительно в объекте модели не нужно никакой бизнес-логики, кроме геттеров/сеттеров. Например, вот хоть убей, но не могу представить какая логика может быть у объекта, представляющего сообщение в чате… Никогда после создания в контроллере или другом объекте не изменяется, хранится в репозитории, выводится представлением. Типичный объект-значение.
Immutable != anemic. То есть, одно дело — то, что данные объекта не меняются, другое — то, что в нем содержится/не содержится логики взаимодействия с другими объектами Domain Model'и.
Я понимаю, что не равно, но вероятность случайного совпдаения выше :)
Ха, крутая фраза, я запомню :)
Верно, если в модели и менять нечего, но она, скорее всего и будет immutable. Это, впрочем, не отменяет моего тезиса.
Согласен с предыдущим ответом. Всему свое применение. Тем более что тот же DDD описывает по карйней мере два анемичных паттерна ValueObject и DomainEvent (тот что фаулеровский).

На самом то деле во многих случаях, анемичтонсть это громадный плюс, это может быть и сокорость выполнения, и сокрость разработки. А могут и не быть. Короч надо не слепо применять паттерны, а выбирать паттерн под задачу.
Ох, чертовски верно в плане концепции, хотя на практике мы отделяем Domain Entities и ViewModels.
Не-не-не, конешно же вью модели отдельно. У нас они вообще в другом проекте и вообще на другой ОРМ ;).

Я как раз имел ввиду что в домене иногда нужны анемичные сущности. Мы например регистрируем накладные. В них нема никакой логики. Ими извне упровляют сущности магазна. Это просто данные. Но в тоже время они идентифицируемые, поэтому их нельзя отнести к ValueObject. В теории можно конешно к DomainEvent.
А, понял, ну разумеется, бывают и ValueObject, но я больше про работу именно с сущностями (Entity). С VO как-то вообще проблем нет (причем как с самими конкретными VO, так и с отдельными LINQ-проекциями domain-сущностей).
Еще как может. Вся логика взаимодействия с другими объектами — именно в классе User. Вся внешняя логика по работе с объектом (достать из базы, например) — в сервисах. Анемичную модель делать не стоит, ни в «строгом» DDD, ни в каком-то еще.
Как сложная логика работы со свойством Email может оказаться в классе User, если на момент когда вы пишите код этого класса, он даже не знает, что у него это свойство будет?
А как, по-вашему, логика работы с Email может там оказаться? Email, как по мне, аспект (часть AOP), а не часть модели домена. «Взаимодействие с другими объектами» подразумевает взаимодействие с другими объектами Domain Model, а не с инфраструктурными сервисами.
Я не имел в виду работу с сервисом электронной почты, а только с полем, как со строковыми данными. Банальный валидатор по регэкспу откуда возьмётся? В библиотеке Membership будет описан?
VolCh, ну вы же понимаете, что вы поднимаете очередную серьезную тему «валидации модели»? :))

Валидация может быть двух типов — принадлежать к домену, как неотложная догма, или же содержаться во ViewModel'и, как часть конкретного приложения. При желании можем обсудить отдельные сценарии.
Что ж у меня такие примеры сегодня неудачные :)
Все удачно, вы такой же любитель копать до деталей, как и я (и это положительное качество, я считаю :)
«Нужны Orders – добавил reference на OrdersLib, запустил, всё!»
Ксли задача именно в этом — нужно отдельно и очень тщательно работать по этому направлению по конкретному domain. Универсального решения на все случаи жизни, скорее всего, добиться не удастся.
В интерпретируемых языках с динамической типизацией практически получается.
Ну, тут неважен тип языка — я имел в виду буквально — «все случаи жизни». У кого-то Orders — это одно, а у кого-то — принципиально другое.
У одного свои неймспейсы, у другого свои. пересечения не получится :)
Черт, аргумент железобетонный как Deus Ex Machina :) Да, наверное, зависит от конкретной реализации.
Сразу по теме:

«DDD всё еще не дает возможности строить приложение «по кирпичикам» – просто подключая нужные модули.»
Попробуйте сразу отделить процесс Discovery (мы так его называем — поиск модулей), и DDD. DDD ни при каких условиях не должен знать, как происходит композиция вашего приложения (это просто тупо не его задача).

Если вы хотите сделать «все правильно», вам не нужно смешивать понятия, а нужно срочно заинтересоваться, что такое AOP (Aspect Oriented Programming). В частности — понять, что приложение _вообще_ не должно знать о такой вещи, как Membership — нельзя включать сам Membership в логику приложения, ибо оно в момент станет немасштабируемым. «Круто» на сегодня — это использовать claims-based authorization — подумайте над этим. Нет пользователей, нет ролей, нет прав — есть claims.
И да, запомните — InternalVisibleTo — это, по большому счету, хак. Он не решает проблем архитектуры, что вы, впрочем, и сами знаете. Нужно строить по другому. Ваша архитектура должна быть целостной без такой вещи, как явное определение области видимости, как InternalVisibleTo.
Оно и понятно.
Я упомянул его т.к. у нас кроме наследования и разделения проекта на библиотеки нет других средств для контроля областей видимости. Я хочу более гибкий инструмент для решения таких задач.
И последний совет — ради бога, не пытайтесь следовать Strict DDD, на практике встречается очень много сценариев, когда хочется «перейти за черту». Например, мы пришли к тому, что репозитории — это круто, но на практике в сотню раз удобнее позволять создавать запросы _вне_ репозитория, используя LINQ, паттерны QueryObject и Specification, чем постоянно бороться с неповоротливостью true-DDD-репозиториев.
UFO just landed and posted this here
Я, наверное, был слишком категоричен :) Я имел в виду скорее не неповоротливость DDD-Repository, а наоборот, офигенную гибкость LINQ-репозиториев, которые мы используем, с которой мы чувствуем себя абсолютно комфортно.

Дело в том, что при появлении дополнительной логики, мы зачастую вынуждены дописывать метод FindBySomethingElse к репозиторию, тем самым нарушая OCP (Open/Closed Principle). С нашими LINQ-репозиториями, мы просто создаем новый IQueryExpression для нового кусочка логики по выборке.

Это вообще большой (и холиварный) вопрос — выставлять ли IQueryable-интерфейс (речь про .NET) за пределы репозитория, позволяя тем самым работать с репозиториями куда большим количеством способов, «чем можно даже протестировать», и однозначного ответа у нас нет. Могу сказать лишь то, что это удобно, но это не укладывается на сто процентов в концепции DDD (Repository Encapsulation).

Сами же задачи не имеют значения — мы используем LINQ-репозитории для всего и вся.
А почему вы считаете что не укладывается в концепцию DDD? ИМХО все укладывается.
Ну просто потому что «classic-DDD» по Эрику Эвансу подразумевает, что мы должны работать с репозиторием, как с обычной «in-memory» коллекцией объектов, и не можем повлиять на то, что будет происходить внутри репозитория. А IQueryable — использует отложенное исполнение (deferred/lazy evaluation) и то, что мы можем повлиять на это «снаружи», добавив новых методов и тем самым изменив конечное выражение (Expression Tree) нарушает инкапсуляцию репозитория.
IQueryable фактически такой же интерфейс как ICollection. Вполне себе инкапслуирующий вид «in-memory» коллекциею объектов. Просто более современный. Если отложенность полностью закрыта интефейсом и для клиентского кода не выносит ничего инфраструктурного, то по идее все ок. Я бы на вашем месте не расстраивался ;). Просто у Ерика Еванса была тока Джава ;). На самом то деле, есть паттерн спецификация, который вполне себе применяют в «classic-DDD». Фактически он выполняет те же фугеции что и IQueryable тока средствами возможными в Джаве. По ходу в Джаве добавивилсь лямбды, так что скокро и в Джаве станут применять более современные средства ;).
Мы кроме Спецификации применяем еще и паттерн QueryObject, в том числе чтобы инкапсулировать специфичную для конкретной технологии (например, NHibernate) логики запросов.

IQueryable, все-таки, специфичен, как минимум тем, что позволяет «взять больше, чем надо» :) То есть, он выполняется отнюдь не ДО того, как вы начнете его использовать, и вы сможете изменить изначальный Expresion Tree.
Угу, мы старамся придерживаться правила ничего не запрещать, а просто не делать ничего плохого ;). Тоесть теоретически при помощи IQueryable можно навредить. Но мы же не враги сами себе ;).
Ох как я с этим согласен! Были варианты — отказаться от LINQ и быть уверенными, что «new devs will not fuck everything up», или использовать LINQ и все остальные возможности, но контролировать «дисциплину». Пришел ко второму, поскольку В РАЗЫ проще писать LINQ-запросы, исходя напярмую из use-case'ов.

P.S. Однако, в больших командах это может быть проблемой.
True-DDD-репозитории это так сказать емуляция коллекций. У нас эти репозитарии имплементят IQueriable поэтому с квериньем проблем нема.

Обьясните поподробней что вы имели ввиду под «создавать запросы _вне_ репозитория».
Имеется в виду то, что classic-репозиторий имеет возможность обратиться к нему за данными, скажем, через UserRepository.FindById(5) или FindByName(«Vasilio»), а LINQ-репозиторий позволяет сделать UserRepository.Query(u => u.Name == «Vasilio» || u.Age > 13434), чего явно нельзя было «предугадать» изначально. То есть, конкретный запрос создан уже независимо от репозитория (другими словами — репозиторий «не знает» особенностей запроса).
Та то просто уровень абстракции разный. В любом случае я вас уже понял ;).
Именно! Вопрос только в том, где эти абстракции находятся, и где их позволить. Только об этом и речь. Ибо в случае с IQueryable никто не мешает, используя паттерн MVC, «добавить» к IQueryable пару дополнительных инструкций. И в то время как на сервер БД будет отправляться «абсолютно правильный SQL», в архитектуре самого приложения будет злостное нарушение полномочий. В этом смысле, наши LINQ-репозитории требуют некоторой… дисциплины, что ли :)
А я так и непонял зачем базовый IUser, почему не может быть отдельных сущностей UserProfile и OrdersCreator.

Просто для этого в DDD есть правило границ контекста (context boundaries), оно какраз и предназначенно для того чтобы решать подобные проблемы. Фактически физическая сущность в разных контекстах может быть совершенно разным представлением. Это нормально. И чесно сказать необходимости их обьеденять я не вижу.
Plus one. User и Customer (aka OrdersCreator) — объекты разных Domain'ов и даже если это физически один и тот же человек, должны рассматриваться с разных точек зрения (так как контексты разные).
Черт, посмотрел комментарии к посту — они в основном мои. Да, архитектура — это мое больное место :)
>Т.е. при добавлении модуля Sql-схема автоматически пополнится нужными таблицами, колонками и другими объектами.

Атас ваще.

А в чем «Атас ваще»?
Генераторы схем БД на основе модели существуют не первый год.
Nhibernate, DataObjects.Net, XPO, OpenAccess, EF CodeFirst — все генерируют схему. Первые 4 генерят даже для нескольких СУБД. Рекомендую попробовать как-нибудь.

В Ruby при подключении gems, также импортируется схема.

Атас в том, что такие простые идеи воспринимаются как «управлятор вселенной». То, что .Net-чики считают чем-то невероятным, в Ruby отлично работает.

Вместо того, чтобы заниматься дополнением ранее выполненной работы, мы каждый раз заново переписываем то, что уже делалось не раз, потому что где-то немного изменились требования.

Сейчас я думаю .Net-чики излишне консервативны, и то что сегодня кажется фантастикой будет такой же обыденностью, как когда-то Linq казался чем-то невероятным.
ваша модель не будет работать в случае, если бд наполнена — в этом случае вам все равно придется лезть ручками в БД и что-то вычищать.

а еще вы не допускаете, что существующие модули могут друг другу противоречить.

на все ваши стремления рассказать про «невероятное» можно ответить просто — займитесь делом.
ваша модель не будет работать в случае, если бд наполнена — в этом случае вам все равно придется лезть ручками в БД и что-то вычищать.

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

а еще вы не допускаете, что существующие модули могут друг другу противоречить.
И это тоже препятствие которое никак не разрешить?!

на все ваши стремления рассказать про «невероятное» можно ответить просто — займитесь делом.
Спасибо за конструктивное предложение.
>> а еще вы не допускаете, что существующие модули могут друг другу противоречить.
И это тоже препятствие которое никак не разрешить?!


DDD делает все чтобы упростить логику. Для этого отсеивается все инфраструктурное. Вы же фактически предлагаете в домене разруливать ситуации когда Userу нужен емайл и он без него жить не может. А какойнибуть OrderCreator вполне себе может жить без него. Вы реально зачем то усложняете, и кроме того что так есть в раби других плюсов я не вижу. В том же раби (АктивРекорд) вы сможете валидатор повесить на поле. Но чтобы сделать валидатор в контексте уже приходится морочится.
У вас это работает? Мы для себя не смогли использовать генерацию схемы. 90% случаев для уже существующих систем требуют миграций. 80% требуют модификации данных.
Да просто там БД наверное, 10 таблиц, 50Мб данных, вот и всё
Работает, когда когда генератор умеет делать мапинги, указанные вручную.
Т.е. да, нужно допиливать то, что генерируется по умолчанию.
>Цель — сделать функциональность Plug&Play
Имхо, тут цель — сделать управлятор вселенной.
Sign up to leave a comment.

Articles