Как стать автором
Обновить

Комментарии 66

Ни разу ни в одном .NET проекте не видел его применение. Мне кажется, что он может быть применен только для небольших проектов. Иначе — паттерны Repository, Unit Of Work, Specification и всё остальное из DDD.
Использую AR Для доступа к БД. Есть 2 реализации — поверх EF и поверх ADO.NET для разных требований по производительности.
А разве Linq2Sql и EF сами по себе не являются реализациями паттерна AR?
По сути да, но закрываются чем-то на подобии UserPrincipal из System.DirectoryServices.AccountManagement. DbContext выступает аналогом PrincipalContext в данном случае.
Cкорее — Unit of work.
UoF это часть. Он может существовать как в рамках AR подхода, так и Repository.
Существовать — да. Но из коробки он — Unit Of Work.
И АР одновременно=) Они не иcключают друг друга, а дополняют.
Нет, не являются. В AR каждый объект сам отвечает за взаимодействие с БД. L2S и EF — это DataMapper (в L2S попроще, в EF — посложнее).
Сколько .NET проектов Вы видели изнутри?
Работаю 5ый год, так что с десяток-два крупных наберется.
Вы работали с АД программно?
Может хоть объяснить минус за простой вопрос? Или кого-то так сильно коробит AR? В рамках работы с определенными областями AR более приспособлен чем Repository и UnitOfWork.
Много макарон, зато теперь у нас есть многопоточность и отчет о здоровье нашего справочника, в качестве бонуса от архитектуры.

Рекомендую использовать Lazy<Dictionary<string, Country>> для нормальной, человеческой поддержки многопоточности. Оно как раз для этого и предназначено.

И ещё, я что-то не совсем понял, от кого вы хотите ещё больше спрятать переменную _all и зачем.
Это интересная мысль. Спасибо.

_all Должна использоваться ровно в одной функции.
Очень нежелательно видеть ее в списке полей класса и тем более использовать где-бы то ни было.
Но ведь это библиотечный класс. Тем кто его будет использовать это поле видно не будет, а тот кто его разрабатывает, по идее должен понимать, что это и для чего и не использовать неправильным способом. Кстати, если заюзать Lazy<>, то и эта проблема решится сама собой:

private static readonly Lazy<Dictionary<string, Country>> _all = new Lazy<Dictionary<string, Country>>(() => {... инициализация ...});
public static IDictionary<string, Country> All { get { return _all.Value; } }
Попробовал разобраться и применить на практике: в конструкторе делегат можно указывать только со статическими параметрами.

Очень узкие рамки для применения — мне такое не подходит. :(
Вообще-то к Lazy можно подключить свою фабрику, но это уже выходит за рамки одной строки. Кроме того, какими, кроме статических, параметрами вы хотите инициализировать статическое поле?
Список позиций для документа, например.

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

Своя фабрика — это уже похоже на workaround. Я такого стараюсь избегать во Имя Чистого Кода.
В 4.0 есть класс Lazy. А для младших весрий стоит его один раз написать.
А зачем это все? Что мешает использовать любой существующий ORM?
В доменной логике очень удобно иногда использовать. Да и не ко всем хранилищам есть ORM. Допустим у вас дерево папок. содержащих разные Xml файлы, которые должны обрабатываться в соответствие с определенной логикой.
Тот же Linq2Xml вполне себе АР.
В доменной логике очень удобно иногда использовать.

В доменной логике удобно использовать доменные объекты.

Допустим у вас дерево папок. содержащих разные Xml файлы, которые должны обрабатываться в соответствие с определенной логикой.

Repository?

Тот же Linq2Xml вполне себе АР.

Linq2Xml не AR, потому что у объектов нет доменных свойств.
Repository для работы с деревьями? Уже пройденный этап. Добавьте сюда разные механизмы нэйминга и политики и весь репозиторий разваливается. Точнее код становится громоздким и хрупким.

Вообще-то для AR не обязательно иметь доменные свойства в явном виде.
Repository для работы с деревьями? Уже пройденный этап. Добавьте сюда разные механизмы нэйминга и политики и весь репозиторий разваливается. Точнее код становится громоздким и хрупким.

Тогда DataMapper.

Вообще-то для AR не обязательно иметь доменные свойства в явном виде.

Вообще-то, обязательно.
Рекурсивный поиск в LDAP. Репозиторий там весьма неудобен и дата маппером не отделаться. Плюс тогда у вас распухнет объект отвечающий за маппинг, что опять же будет нехорошо.

То есть если объект содержит только Save, Update, Delete то он уже не АР? =)

Фаулер хорош, но надо и свою голову на плечах иметь.
Рекурсивный поиск в LDAP. Репозиторий там весьма неудобен и дата маппером не отделаться.

А какое отношение вид поиска имеет к провайдеру? Вид поиска инкапсулируется в Query object, и пофиг.

Плюс тогда у вас распухнет объект отвечающий за маппинг, что опять же будет нехорошо.

Зато объект, отвечающий за бизнес, будет отвечать только за бизнес. SoC в чистом виде.

То есть если объект содержит только Save, Update, Delete то он уже не АР?

Нет, конечно. AR — это паттерн представления доменных объектов, если у вас в объекте нет домена, то он и не AR.
A Query то куда полезет? Сколько разных классов вам необходимо для реализации простейшей функциональности типа UserPrincipal.ChangePassword(..)?

При том что поле password — private, a аксессор только read?

АР это не паттерн представления доменных объектов. При использовании этого паттерна хранение логики доступа к данным находится в объекте сущности.

A Query то куда полезет? Сколько разных классов вам необходимо для реализации простейшей функциональности типа UserPrincipal.ChangePassword(..)?

UserPrincipal.ChangePassword — это не рекурсивный поиск в LDAP, не надо подменять одну задачу другой.

АР это не паттерн представления доменных объектов.

Вам, несомненно, виднее, чем человеку, который этот паттерн ввел.

При использовании этого паттерна хранение логики доступа к данным находится в объекте сущности.

Да, это входит в его определение. Но это не делает его не-доменным.
1. Вы инкапсулировали логику поиска в объект. Почему тогда не вынесли в репозиторий?
Чем вас не устраивает наличие логики сохранения в самом объекте? Допустим что в зависимости от параметров объект может выбирать разные стратегии сохранения состояния. Как раз работа с LDAP хранилищами очень удобна при использовании AR, а вот Repository(+DataMapper) весьма неудобны и сильно усложняют логику. Несмотря на рекламу Reposiotiry, MS активно использует AR в нэймспэйсе DirectoryServices.

2. Паттерн не библия, а информация к размышлению. Вы ведь вкурсе, что MVC может быть реализован с отступлениями? Или то, что кроме MVVM есть еще и MVVMP например?
3. Что вы подразумеваете под доменом. Давайте придем к общей алгебре в этом вопросе.

Вы инкапсулировали логику поиска в объект. Почему тогда не вынесли в репозиторий?

Вы что-то путаете. Я не инкапсулировал логику поиска в объект.

Чем вас не устраивает наличие логики сохранения в самом объекте?

Тем, что нарушает SoC. Я крупный сторонник подхода persistence ignorance, он радикально упрощает жизнь.

Несмотря на рекламу Reposiotiry, MS активно использует AR в нэймспэйсе DirectoryServices.

И что?

Паттерн не библия, а информация к размышлению

Главное — помнить, что есть граничные условия, после невыполнения которых паттерн распадается (или переходит в другой).

Что вы подразумеваете под доменом

«Logic that's a real point of the system».
1. А зачем вам объект типа Query?
2. А если сам объект будет сохранять себя используя репозиторий?=)
Я тоже сторонник SoC, но противник анемичных моделей.
3. Наверное. то что ServiceLocator и IoC не исключают друг друга, а способны дополнять.
4. Согласен.
5. То есть конфигурация системы — это не доменная логика?
А зачем вам объект типа Query?

Чтобы инкапсулировать поисковый запрос.

А если сам объект будет сохранять себя используя репозиторий?

А зачем?

Я тоже сторонник SoC, но противник анемичных моделей.

Ну так вносите туда логику… доменную, а не сервисную.

ServiceLocator и IoC не исключают друг друга, а способны дополнять.

Это здесь каким боком? Ну и да, утверждение неверно; повторюсь: SL — это один из способов реализации IoC.

То есть конфигурация системы — это не доменная логика?

В общем случае — нет.
1. И что вы инкапсулируете? Логику? Данные?
2. В коде удобно делать
user.ChangePassword(byte[] oldPassword, byte[] newPassword) вместо UserRepository.ChangePassword(user,byte[] oldPassword, byte[] newPassword).
При том что поле password — private.
3. Отвечу Фаулером на Фаулера=) Inversion of Control Containers and the Dependency Injection pattern

4. То есть если использовать AR для хранения конфигурации, он перестает быть AR? =)
И что вы инкапсулируете? Логику? Данные?

Данные, конечно. Поисковый запрос — это данные.

В коде удобно делать

Ну и делайте. Что мешает-то?

Отвечу Фаулером на Фаулера=) Inversion of Control Containers and the Dependency Injection pattern

И что? Там не написано ничего, что противоречило бы тому, что я сказал.

То есть если использовать AR для хранения конфигурации, он перестает быть AR? =)

Нет, у вас конфигурация становится доменом в рамках той задачи, которую вы решаете. Очередной уровень абстракции.
А сам алгоритм обхода дерева где?=)

Вот последнюю фразу я и хотел у вас услышать=))
А сам алгоритм обхода дерева где?

В репозитории.
А если он отличается в зависимости от внутреннего состояния объекта? =)
Скорее всего, там же. Но не видя задачи целиком я не могу ответить на ваш вопрос.
Моя предметная область в основном связана у управлением пользовательскими данными и доступом. В эти места редко кто заглядывает =( Но набор задач весьма интересен и специфичен с точки проектирования объектно-ориентированного решения.
В эти места редко кто заглядывает

Я в своей жизни написал как минимум одну полную подсистему управления доступом.

Но набор задач весьма интересен и специфичен с точки проектирования объектно-ориентированного решения.

Вопрос в том, надо ли его так решать.
Поэтому и разрабатываю по несколько подходов, сравниваю импакт и возможности к расширению. Контролем доступа все не ограничивается. Самое интересное — механизмы хранения атрибутов идентификаторов. Что-то типа абстрактного LDAP хранилища, только деревья можно хранить в разных бэкэндах одновременно.
Почему именно byte[], а не string?
Если вы переживаете за тестирование, то например в OpenLDAP в конце цепочки находятся так называемые бэкенды. Переключая бэкенды через ServiceLocator или IoC и тд. возможно воткнуть NullBackend И погонять тесты.
Переключая бэкенды через ServiceLocator или IoC

Не может быть «или», Service Locator — частный случай IoC. И, учитывая, что Service Locator считается антипаттерном, мы как раз и передаем привет тестируемости.

(и да, сделать в типовом AR dependency injection очень сложно)
Посмотрите ТФС рефлектором ;-)
ServiceLocator актуален при разработке фреймворка. Напимер Asp.Net MVC.

А мы не говорим про типовые. Типовые решения применимы в достаточно узком кругу задач. В той предметной области, которой увлекаюсь, AR гораздо предпочтительнее чем Repository.

Здесь есть еще момент. Я не зря упомянул смену пароля. Если у объекта Пользователь выставить пароль наружу, то существует вероятность изменения этого поля в обход определенных правил.
Наш разговор уже свелся к обсуждению типов доменных моделей=) Ваше решение хорошо подойдет для анемичной доменной модели, которая, кстати, то же является антипаттерном Anemic Domain Model by Martin Fowler ;-)
ServiceLocator актуален при разработке фреймворка. Напимер Asp.Net MVC.

Из-за чего в этом самом MVC куча проблем при тестировании (хотя и меньше, чем вообще без IoC, и на том спасибо). И именно поэтому там максимальное количество мест сделано так, чтобы можно было заменить SL на DI.

Типовые решения применимы в достаточно узком кругу задач.

Как раз наоборот, типовые решения (паттерны) делаются так, чтобы быть применимыми в максимально широком кругу задач. И у каждого паттерна есть не только слабые, но и сильные стороны.

Здесь есть еще момент. Я не зря упомянул смену пароля. Если у объекта Пользователь выставить пароль наружу, то существует вероятность изменения этого поля в обход определенных правил.

А зачем это делать?

Ваше решение хорошо подойдет для анемичной доменной модели, которая, кстати, то же является антипаттерном

Какое «мое решение»?
1. DI подключается через локатор… =)
2. Если у вас часто возникают типовые задачи, то вам повезло. Мне так не везет.
3. Что именно?
4. Решение с Repository(+DataMapper)
Что именно?

Выставлять пароль наружу.

Решение с Repository(+DataMapper)

Расскажите это Фаулеру, который считает, что DataMapper как раз нужен для сложных доменных моделей.

Ну и да, вы забываете тот факт, что противопоставление AR/DM ортогонально противопоставлению полной/анемичной доменной модели.
1. Как вы будете организовывать сохранение пользователя с измененным паролем, через репозиторий?
2. Сложный доменный объект != объект со сложной логикой, которую маппинг заменить не в состоянии. Ну без велосипедов.
Как вы будете организовывать сохранение пользователя с измененным паролем, через репозиторий?

Если мне нужно, чтобы оно снаружи выглядело как изменение свойства, то через вызов соответствующего метода внутри репозитория. Но вообще такие вещи делаются через доменную операцию (которая уже заведена, например, на сервисный слой).

Сложный доменный объект != объект со сложной логикой, которую маппинг заменить не в состоянии.

Маппинг вообще не призван заменять доменную логику.
А если у вас на этапе компиляции не известны свойства объекта и политики работы с ними?=)
То это очередная новая задача, не имеющая отношения к обсуждению.

И да, для этого класса задач есть отдельные способы их решения, но к паттернам обращения к данным они отношения не имеют.
Поиск данных это один из способов обращения, не так ли? Допустим объект может найти всех своих потомков и сказать кто его родитель.
Поиск данных — это одно. А перемещение по графу связанных объектов — другое.

(и да, перемещение по графу — одна из наименее тривиально реализуемых в DM операций)
Этот вопрос мне задают каждый раз, когда я показываю свой код.
Видимо что-то со мною не так. =)

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

Проще сделать все при помощи палки и веревки под себя.
Вместо использования кучи модных библиотек и шаблонов.
Стоимость разработки и поддержки вас не волнует?

Ну и да, когда я слышу про «требования к производительности», мне сразу хочется спросить — а где результаты реального тестирования для вашей задачи? И почему вы думаете, что ваше решение будет заведомо быстрее уже существующих?
1) мой опыт говорит, что они существенно ниже, чем при использовании модных технологий. В первую очередь за счет дешевизны модификаций и уменьшения обвязки.
2) 1k online при 20 связанных сервисах на каждого пользователя.
3) такое мнения у меня сложилось из разговора с коллегой, который использовал hibernate.
мой опыт говорит, что они существенно ниже, чем при использовании модных технологий

А вы не используйте «модные», вы используйте работающие.

1k online при 20 связанных сервисах на каждого пользователя.

И что? Это не результаты тестирования.

такое мнения у меня сложилось из разговора с коллегой, который использовал hibernate.

Ну то есть вы не пробовали, но уже знаете, что вам не подойдет. Круто.
Кстати, а как вы тестируете (собираетесь тестировать) код, зависящий от вашего Country?
Ничего умного рассказать не смогу.

Смотрю подробные логи с ошибками и исправляю.

Строгая типизация и ограничение прав — помогает мне видеть многие ошибки на уровне компиляции.
Понятно. То есть о такой вещи как «автоматизированное тестирование» вы вообще не думали?

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

… а вот был бы у вас репозиторий — не было бы у вас этой проблемы.

dictionaryRepository.Get<Country>("Russia");
dictionaryRepository.Get<Currency>("USD");


Ну и так далее.
я бы этот паттерн причислил к антипаттернам.
сколько с ним работал, всегда Active Record вырастал в нечто, с чем очень не удобно работать.
логику и данные лучше хранить раздельно.
Все зависит от задачи. В одном проекте могут быть использованы и ORM и чистый ADO.NET, так же как и AR и Repository. Отделение данных от логики не всегда правильно и может в перспективе быть опасным в определенном классе систем. Мы с товарищем lair выше обсудили этот вопрос. Исходить надо от задачи и от механизмов использования. Серебряных пуль не бывает =)
Отделение данных от логики противоречит ООП, если что.

Разделять надо доменную логику и код работы с хранилищем.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории