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

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

Поздравляю, теперь вы тоже знаете, что все абстракции текут.
То из за чего я НЕНАВИЖУ программирование
Да ладно, это же intrinsic challenge. Если он вам не нравится, вы как-то не в той профессии.
Да. Я давно склоняюсь к тому, что думать — очень вредно для здоровья.
Писал когда-то статью по спецификациям тут на хабре. У меня там IsSatisfiedBy имел возращающее значение
Expression<Func<T, bool>>
а не
Predicate<T>
или
Func<T, bool>
что давало возможность репозиторию использовать его в фильтрах без маппингов и загрузки всей таблицы в память.
PS: не течёт. немножко подтекает ;-)
Я на нее ссылаюсь в своем описание, именно ваша работа с подвигла написать этот пост. Expression<Func<T, bool>> это конечно круто, но когда речь идет об объектах предметной области (так же они являются Aggregation root) вы попробуйте написать адекватный Expression. Объект хранилища может не обладать свойствами объекта предметной области как решить эту проблему?
Вы не нашли решения проблемы? Или может быть видели где-нибудь пример реализации ISpecification в терминах доменной модели(не объектов EF)?
Подтекает она пока не копаешь :) А как копнешь — так водопад.
Data Access Layer в .net уже года три как полностью закрывается EF. Больше ничего писать не нужно. Более того, любой дополнительный код скорее вреден.
Каждый для себя сам решает — нужна ли ему абстракция повверх технологии хранения данных или нет.
Тем не менее пользы от абстракции в большинстве случаев не будет, а в некоторых случаях будет вред.
«в большинстве случаев» — слишком абстрактно (кол-во однокоренных слов к «абстракция» зашкаливает в этом треде: Р). Лично участвовал в проекте, в котором EF меняли на nHibernate и благодаря абстракции это прошло безболезненно.
А зачем меняли?

Вообще EF имеет в разы больше возможностей по генерации запросов, значит вы его банально не использовали, поэтому и прошло безболезненно. А если не использовали эти возможности, то ваше приложение работало (и сейчас работает) гораздо медленнее, чем могло бы.

Короче вы подтвердили, что абстракция несла вред. И вам очень повезло, что хоть какая-то польза от нее была.
На тот момент nHibernate на голову превосходил по мощности EF (4ой версии), хотя есть мнение что и сейчас. Вот статейка, в которой человек утверждает, что nHibernate всё еще лучше и много удобнее ложиться на DDD: enterprisecraftsmanship.com/2014/11/29/entity-framework-6-7-vs-nhibernate-4-ddd-perspective
PS: минусую не я :-)
Linq в NH сильно отстает даже от EF4, а без Linq все остальные возможности — ненужный фетиш. Что по поводу статьи, то там автор пытается решить проблемы, которые сам же создал. Например я не вижу смысла даже загружать из базы Order, чтобы удалить OrderLine. Более того, если у тебя есть id OrderLine, то можно даже OrderLine не загружать, чтобы его удалить.
Я работаю над проектом в котором пользователь сам выбирает где хранить свои данные. И конфигурация DAL происходит при инициализации модуля. Без абстракции это не возможно.

а в некоторых случаях будет вред
от всего может быть вред, если пренебрегать правилами использования. ( касается не только IT сферы, а всего медицины, техники… )

Я полностью согласен с Nagg
EF без всяких абстракций поддерживает работу с различными движками. Зачем вам абстракции?
Абсолютно верно, так вот именно для этого и нужна. Вот есть контекст данных например MyDbEntities (название так себе)
и теперь везде мы прописываем, что то типа этого
using ( var context = new MyDbEntities() )
и ясно что проект использует не в одном месте доступ к хранилищу.
и что, вот пользователь установил софт, при установке указал какой тип хранения его устраивает.
абстракция? кому нужна абстракция? мы же используем EF который .
без всяких абстракций поддерживает работу с различными движками.

нам в коде всего лишь нужно прописать
If (mode == someOne)
    using ( var context = new MyDbEntitiesOne() )
    {...}
else
   using ( var context = new MyDbEntitiesTwo() )
    {...}

а чего, нормальный функционал, за то работает.
я уж не говорю что запросы так же придётся для каждого контекста делать отдельно, т.к. с точки зрения типов даже если полностью совпадает набор полей и методов MyDbEntitiesOne не является MyDbEntitiesTwo
Вы, похоже, не знаете о чем пишите. Для поддержки разных баз в EF не нужны разные контексты. Все правится в конфиге или, при желании, парой строк при инициализации приложения.
Да. Написал ерунду. msdn.microsoft.com/en-us/library/vstudio/ee789835(v=vs.100).aspx
но тот факт, что не для всего есть провайдер (хотя конечно его можно написать при особом желании) (кстати эта особенность возможно только благодаря абстракции), И то что объект домена может сильно отличаться от объекта хранилища, а так же с абстракцией куда удобнее работать и еще много всяких плюсов при работе с Repository и UoW, толкают на реализацию своего DAL в приложении.
так же с абстракцией куда удобнее работать

А конкретные примеры можете показать? Только надо понимать, что EF — это тоже абстракция, так что вам придется показывать, чем одна абстракция лучше другой.
Есть система сообщений, каждое сообщение подтверждается подписью (ЭЦП), причем подписей может быть несколько, подпись характеризуется содержимым, признаком корректности и сертификатом. Сама по себе сущность подписи бесполезна, она агрегируется в сообщении и отдельно ее запрашивать у хранилища с точки зрения предметной области бессмысленно (а значит хорошо бы заблокировать эту возможность) — для подписей не делаем Repository (вообще Repository принято делать только для корней агрегации, в данном случае для сообщения). Удалить подпись у созданного сообщения запрещено ( сообщение создается и подписывается и назад уже дороги нет, так же как бы если подпись была поставлена ручкой ), поэтому во вне предоставляется интерфейс сообщения, который предоставляет не изменяемое перечисление подписей и метод по добавлению подписи. Сертификат подписи храниться в виде отпечатка ( строка ), но при работе всегда приходиться работать с сертификатом а не с отпечатком ( злобная криптография ), поэтому пусть подпись сразу возвращается с сертификатом, а не с отпечатком ( задача маппера ).
Я описал чем Объекты предметной области могут отличаться от объектов хранилища, а так же отсеиваются заведомо бесполезные операции, такие как запрос подписей. Если надо могу привести еще что ни будь.
Сама по себе сущность подписи бесполезна, она агрегируется в сообщении и отдельно ее запрашивать у хранилища с точки зрения предметной области бессмысленно (а значит хорошо бы заблокировать эту возможность)


Страшное заблуждение. Логика должна быть обратная. Насколько полезна сущность сообщения без подписей, если есть операции с сообщениями без подписей, то подписи и сообщения — разные сущности, а если нет, то одна.

Даже в таком описании я не вижу какую проблему вы пытаетесь решить. Будет у вас в BL две функции — «подписать сообщение» и «получить readonly список immutable-объектов подписей». Внутри этих функций вы будете вызывать EF и мапить результаты на бизнес-сущности. Зачем тут Repository, UoW и еще какие-то абстракции — ума не приложу.
Страшное заблуждение. Логика должна быть обратная. Насколько полезна сущность сообщения без подписей, если есть операции с сообщениями без подписей, то подписи и сообщения — разные сущности, а если нет, то одна.
Сообщение может существовать без подписи ( не подписанное ), а подпись без сообщения? Существование подписи без сообщения это плохо. Исходя из операций которые можно совершать над подписью -> проверить ее подлинность, без содержимого исходного сообщения это не возможно. Сообщение агрегирует в себе подпись и ни как иначе. Что касается
Repository и UoW я о них не писал
Я описал чем Объекты предметной области могут отличаться от объектов хранилища, а так же отсеиваются заведомо бесполезные операции

Существование подписи без сообщения это плохо

Что плохого?

Исходя из операций которые можно совершать над подписью -> проверить ее подлинность, без содержимого исходного сообщения это не возможно.

У тебя будет функция, которой нужны два параметра — сообщение и подпись. Это вовсе не значит, что тебе надо будет загружать все подписи для каждого сообщения.

Сообщение агрегирует в себе подпись и ни как иначе.

Это заблуждение. Если два объекта используются вместе, то это не означает что они должны всегда загружаться вместе.
Ну и что вы из этого не можете удобно сделать в EF?
Каким образом на EF ( да же с CodeFirst ) я могу организовать описанную абстракцию сообщения и запретить получать Отдельно подписи?
Выставить на дб-контексте метод получения сообщений, не выставлять метод получения подписей. На полученном дб-контексте сделать extract interface (что все равно надо делать).
Согласен, но как и где тогда создавать экземпляр этого контекста?
И вот получена абстракция…
В Composition Root.
Отлично. Теперь наш DC наверное и возвращать будет коллекции доменных объектов ( хорошо бы интерфейсов )? Что бы мы не могли допустим снести подписи?
Нет, зачем? Это не задача DAL.

(заметим, если мне очень нужно добавить такое ограничение — я могу его сделать на этом уровне)
Смотря что назвать DAL ( здесь рассматривается доступ к хранилищу по средствам репозитория ). Тогда к чему отнести Маппер?
Понимаете, вы зачем-то считаете, что доступ к хранилищу делается через репозиторий. Но репозиторий — это доменный паттерн, а не паттерн доступа к данным.
Mediates between the domain and data mapping layers using a collection-like interface for accessing domain objects.
Так в чем я не прав, это слой между предметкой и хранилищем ( либо как говориться у Фоулера слоем отображения данных ). Так в чем я не прав то?
EF — это data-mapping layer, он же DAL (data abstraction layer).
Ну так это все равно Storage level и его нужно отделить от Domain layer и лучше всего для этого подходит реализация Repository
А вот это все уже не очевидно. И необходимость доменного слоя вообще, и необходимость его жесткого разделения с DAL, и то, что для этого лучше всего подходит репозиторий — все это далеко не однозначные решения, и они сильно зависят от того, какое у вас приложение.

В ощутимом количестве случаев уровень абстракции, предоставляемый DAL, достаточен сам по себе.
Согласен на все сто.
ждый для себя сам решает — нужна ли ему абстракция повверх технологии хранения данных или нет.
. Вообще любое приложение может быть написано без какого либо проектирования и оно будет работать, до тех пор пока не изменяться требования к системе или не придётся менять технологии, а уже от этого любой более менее рыночный продукт не то что не застрахован, а наоборот этот этап его настигнет, и дальнейшее существование будет завесить от того на сколько он адаптируемый под новые задачи. Разделение слоев и выделение доменного слоя это очень мощная и гибкая система, хотя и очень при очень затратная по ресурсам.
Просто программируя на .net всегда надо помнить, что одна (на самом деле — не меньше двух) абстракция над хранилищем у вас уже есть, и как следствие — не плодить лишних.
В чем мощность и гибкость по сравнению с другими?
Гибкость — в нашей системе используется 2 различных хранилища (это боевое) (при установке пользователь сам выбирает как ему удобно их хранить) и 1 хранилище для демонстрации ( быстрое, использует оперативку ), без хорошей прослойки отделяющей предметную область от хранилища это будет сделать туго.
Мощь в том что при работе с объектами предметной области клиента репозитория вообще не интересует ( он этого даже не знает) где, как и в каком виде хранятся данные, он просто формирует запрос в терминах предметной области.

Но это стоит дорого, но оно того стоит.
Подключение к разным хранилищам — задача ORM. EF прекрасно справляется с этим. Запрос в терминах предметной области — неясно что ты имеешь ввиду, Linq может к любым таблицам\полям сделать запрос и вытянуть любую проекцию. Если попытаться ограничить использование linq, то все начинает тормозить.
Справедливости ради, если демонстрационное хранилище вообще не в БД (а, например, прямо в памяти), то тут EF уже не поможет.
EF7 поддерживает inmemory изкаропки. Для EF6 есть effort.codeplex.com

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

Вообще не понял, но каждую абстракцию кто то должен реализовывать и ух как здорово если ее кто то нормально реализовал за тебя.
Вот именно поэтому чем их меньше, тем, на самом деле, лучше.
Ну у всего есть предел, если их вообще не будет, то хорошего тоже маловато.
Вы не можете написать код ни на одном современном языке программирования, не используя абстракций, так что не беспокойтесь, «вообще не будет» с вами не случится.
Я имею ввиду что объекты предметной области могут сильно отличаться от того какими объектами они хранятся. Пример на ходу в бд одна таблица с кучей полей, в домене эта свалка представлена 2 объектами по средствам агрегации.
TPH, что ли?
Аббревиатура не понятная, Яндех выдал tons per hour, но это вряд ли по теме.
да это можно, но много других вариантов когда эти сущности отличаются
Да понятно, что их много, только каждый раз надо задуматься: а точно ли вам нужны такие потери на преобразовании? А не сменить ли вам хранилище на более подходящее?
Я считаю что предметная область не должна зависеть от области хранения. Яблоко будет оставаться яблоком и не важно где оно будет лежать холодильник, стол, сумка.
Вот я и пишу: не сменить ли вам хранилище на более подходящее. Хранилище, не предметную область.

(но вообще ваш подход хорош только в областях, где домен очень сложный, а требования к производительности, напротив, невысоки)
При нашем подходе замена хранилища не составит труда, но его менять не зачем. Домен действительно не простой, и у нас большой минус — отсутствие документации, поэтому нам важны чистые описания объектов предметной области (интерфейсы). Оптимизация это проблема, с которой приходиться бороться. Но я сторонник «Код лучшая документация».
При нашем подходе замена хранилища не составит труда, но его менять не зачем.

Видимо, вас не смущает постоянная необходимость преобразований домен-хранилище и обратно, и соответствующая этому потеря производительности. Ну ок, это ваше личное дело.
EF так же является обёрткой, которая так же замедляет выполнение. .NET использует интерпретатор для исполнения программ, что так же замедляет, причем сильно. Вообще если нужна максимальная скорость, то скорее всего нужно работать на прямую с хранилищем (например с sql) и уже «чистые» данные мапить в домен ( тут хорошо подходит Repository ). При таком подходе со слоями будет меньше всего проблем, без потери производительности. Но есть 1 ОГРОМНЫЙ -минус — это надо делать самому.
.NET использует интерпретатор для исполнения программ, что так же замедляет, причем сильно.

Определите понятие «сильно».

Но есть 1 ОГРОМНЫЙ -минус — это надо делать самому.

А ваш репозиторий с преобразованием доменных запросов в специфичные для хранилища и обратно — не надо?
Интерпретируемые языки программирования отличаются тем, что исходники не преобразовываются в машинный код для непосредственного выполнения на ЦПУ, а исполняются с помощью специальной программы-интерпретатора.
Как промежуточный вариант, многие языки (Java, C#, Python) транслируются в машинно-независимый байт-код,
который все еще не может быть исполнен напрямую на ЦПУ, и чтобы его исполнить все еще необходим интерпретатор.

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

взял от сюда habrahabr.ru/company/spbau/blog/250841
вопрос на сколько? однозначно сказать не могу, но факт.
А ваш репозиторий с преобразованием доменных запросов в специфичные для хранилища и обратно — не надо?
Конечно надо, но это не самое страшное, больше всего времени затрачивает маппинг.
Во-первых, начнем с того, что в C# — не интепретируемый язык. А автор вашего поста не слышал про JIT-компилятор.

Во-вторых, я же не зря спросил — насколько медленнее. В наше время не важен абсолютный факт, важны относительные значения. Если вы теряете на неэффективном SQL-запросе десятки и сотни миллисекунд, десяток потерянных на управляемом коде наносекунд вы не заметите.

Ну и в-третьих, вы правы, больше всего времени уходит на маппинг… и вообще на поддержание полудесятка введенных абстрактных слоев, практический смысл которых ускользает.
Занятно, я тоже заблуждался habrahabr.ru/post/107585

что касательно
и вообще на поддержание полудесятка введенных абстрактных слоев, практический смысл которых ускользает.
есть несколько вопросов, на которые отвечают эти абстракции
1. Что делать если нужно поменять слой данных, а нужного провайдера нет или он тугой ( вряд ли можно утверждать, что смена провайдера окажется без болезнена )
2. Каким образом предоставлять запросы для получения данных? (на прямую через linq?)
3. Как сохранять сущности? (процесс сохранения мб сложнее чем просто добавить объект в контекст)

Repository отвечает на все эти вопросы.
Нет, repository не отвечает на эти вопросы, он только прячет их от конечного пользователя. Например, как же именно репозиторий отвечает на вопрос «Каким образом предоставлять запросы для получения данных?»
Мы используем QueryObject, запрос сформированный в предметной области. Результат который вернется на совести Repository.
Замечательно, фактически, вы написали свой собственный язык запросов. А где вы взяли на него спецификацию? Иными словами, кто именно ответил на вопросы
  • какова должна быть структура Query Object?
  • как представлять произвольный фильтр в Query Object?
  • как ссылаться на свойства объектов в Query Object?
  • как отображать пейджинг и сортировку в Query Object?
какова должна быть структура Query Object?

зависит только от предметной области
как представлять произвольный фильтр в Query Object?

произвольный фильтр это зло, мы должны ограничить пользователя, тк сразу появяться дубли запросов по коду, почему это плохо, надеюсь писать не придется.
как ссылаться на свойства объектов в Query Object?

расшифруйте, я не понял
как отображать пейджинг и сортировку в Query Object?

так же как и суть запроса
зависит только от предметной области

Значит, репозиторий не отвечает на этот вопрос, он просто сдвигает его в предметную область. QED.

произвольный фильтр это зло

Расскажите это пользователю (настоящему реальному пользователю вашей программы), который хочет выбирать данные по заказчикам, отфильтрованные по произвольному критерию поверх 18 признаков.

расшифруйте, я не понял

Query Object подразумевает конструкции вида "возраст сотрудника больше 15". Как именно репозиторий отвечает на вопрос «как сослаться на свойство „возраст“ класса „сотрудник“»?
Значит, репозиторий не отвечает на этот вопрос, он просто сдвигает его в предметную область. QED.
Передав запрос в терминах предметной области в репозиторий, который итерпритирует его и возвращает то, что требуется в запросе.
Расскажите это пользователю (настоящему реальному пользователю вашей программы), который хочет выбирать данные по заказчикам, отфильтрованные по произвольному критерию поверх 18 признаков.
а почему все эти 18 критериев не описать в объекте запроса?
«как сослаться на свойство „возраст“ класса „сотрудник“»?
конкретный репозиторий возвращает определенный тип объектов, запросы для получения этих объектов нужно формировать запросы, по которым реп разберет что от него хотят. Проблемы в упор не вижу.
Передав запрос в терминах предметной области в репозиторий, который итерпритирует его и возвращает то, что требуется в запросе.

Вы только что описали работу IQueryable.

а почему все эти 18 критериев не описать в объекте запроса?

Количество кода себе представляете?

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

Да я понимаю, что вы ее не видите.

Проблема состоит в том, что вы утверждаете, что репозиторий дает ответ на некоторые вопросы — но стоит нам ткнуть в конкретный вопрос, как выясняется, что сам репозиторий-то на это никакого ответа не дает, а просто смещает эти вопросы в другую область (в данном конкретном случае — в паттерн Query Object, который не является неотъемлимой частью Repository и прекрасно реализуется на других уровнях).
Вы только что описали работу IQueryable.
нет, я описал ее идею.
Количество кода себе представляете?

не на много больше чем все эти критерии засунуть в LINQ
который не является неотъемлемой частью Repository и прекрасно реализуется на других уровнях).
как сказать.
Mediates between the domain and data mapping layers using a collection-like interface for accessing domain objects.
действительно про запросы ни где не сказано, но использовать чистый Repository, как и любую абстракцию сложно, одно ясно что запрос должен идти в терминах предметной области и передаваться репозиторию, для этого можно использовать QueryObject или другой подход, но то что реп должен умет все это переводить в запросы хранилища и именно он должен это делать.
не на много больше чем все эти критерии засунуть в LINQ

Сравним на конкретных примерах?

как сказать.

А как ни говори. Query Object можно реализовать на уровне Data Mapper (и Фаулер об этом пишет, и в EF это сделано).

Давайте еще раз повторим: как именно репозиторий отвечает на вопрос «Каким образом предоставлять запросы для получения данных»?
На маппинг уходит 0,3 микросекунды на объект. Посчитайте сколько надо тянуть объектов в секунду, чтобы затраты на мапинг стали заметны.

Медленный мапинг уже превратился в городскую легенду и не имеет отношения к реальности. Компиляция запроса гораздо медленнее.
Я под маппингом понимаю процесс написания оного. Выставление соответствий «такое-то поле доменного объекта соответствует такому-то полю объекта модели хранилища».
не верно. хранилище может только частично совпадать с объектом домена.
Что неверного-то? Я где-то сказал, что маппинг этим ограничивается? Или вы считаете, что маппинг не должен содержать описанной мной информации?
А зачем тогда мапинг писать? Почему нельзя хранилище подогнать под нужные модели или алгоритмы поменять, чтобы они работали с объектами хранилища?
Потому что InWake считает, что это неправильно, модель должна соответствовать бизнесу, а хранилище — чему-нибудь.

Я-то как раз считаю, что лишнего слоя абстракции (в виде storage entity — domain entity) лучше избегать до тех пор, пока он не станет окончательно неизбежен.
я никогда не говорил и не считаю
модель должна соответствовать бизнесу, а хранилище — чему-нибудь.
я говорю что они могут сильно отличаться
А чем вызвано это отличие? Почему вы, действительно, не можете, как пишет gandjustas, подогнать одно под другое (обычно хранилище под домен), и тем самым радикально уменьшить свои проблемы?
мы же это с вами обсуждали, про сообщения и подписи. И лично у меня проблем с этим нет.
Вы же пост написали как раз о проблемах, и утверждаете, что проблем нет? В вашем примере вы придумали сами себе ограничения, которые негативно сказываются на быстродействии, хотя никаких причин для этого не было и продолжаете утверждать, что проблем нет?
я пишу о проблемах Спецификаций… Да действительно можно максисмально приблизить предметную область к хранилищу, но это и дает много возможности сломать систему, провести не правильные операции, которые с точки зрения хранилища будут верны, но с точки зрения предметной области нет. Я защищаю себя и тем более пользователей своего кода от заранее не правильных вариантов использования, или это плохо?
Пользователи работают с UI или сервисами, соответственно между хранилищем и пользователем находится как минимум два слоя, в которых вы вполне можете проверить всю корректность операций и отсечь некорректные. Зачем еще один лишний слой в виде репозитария поверх EF — категорически неясно.

Если вы пользователями называете других программистов, то у вас два варианта:
1) Сделать набор сервисов (как для внешних, только без передачи по сети), которые выставляют функции и отдают\получают данные (не объекты).
2) Научить их работать с моделью, зашив проверку инвариантов при сохранении (DbContext.SaveChanges).

Все остальные варианты нежизнеспособны.

именно пользователи это программисты
1) Сделать набор сервисов (как для внешних, только без передачи по сети), которые выставляют функции и отдают\получают данные (не объекты).

не понятно что значит данные ( не объекты ), но разве репозиторий этого не делает? он возвращает объекты предметной модели (области, слоя, как не назови)
2) Научить их работать с моделью, зашив проверку инвариантов при сохранении (DbContext.SaveChanges).
а зачем дожидаться этого самого сохранения, ведь инварианты можно проверить и раньше CodeContracts research.microsoft.com/en-us/projects/contracts проверка инвариантов это уровень домена, но как правило это забывают и не используют, тк «Да этж сколько писать? в два раза больше кода.....».
а зачем дожидаться этого самого сохранения, ведь инварианты можно проверить и раньше CodeContracts

Во-первых, не все инварианты легко выражаются в Code Contracts. Во-вторых, вы проверку инвариантов будете делать на каждой операции, модифицирующей состояние объекта?

Проверка на сохранении гарантирует, что невалидный объект не попадет в хранилище, это весьма сильная гарантия. Параноики еще и в самом хранилище делают проверки.
Во-первых, не все инварианты легко выражаются в Code Contracts.
согласен
Во-вторых, вы проверку инвариантов будете делать на каждой операции, модифицирующей состояние объекта?
по хорошему бы да, но это затратно ( хотя если просто проверять поля или свойства с простыми проверками, то это быстро )
Проверка на сохранении гарантирует, что невалидный объект не попадет в хранилище, это весьма сильная гарантия.
согласен, но как правило невалидный объект попадает в хранилище тк невалидным является объект предметной области и можно проверять его, хотя тут большой вопрос искажения объекта может быть во время маппинга, но тогда проверка объекта предметки не поможет, я наверное параноик нужно проверять и предметную область и то что может попасть в хранилище, сразу будет видно откуда у проблемы растут ноги.
как правило невалидный объект попадает в хранилище тк невалидным является объект предметной области и можно проверять его

Это до тех пор, пока у вас только один код пишет в хранилище.
Объясните.
Во-первых, программист может миновать ваш репозиторий (вместе с его проверками), и записать напрямую через DAL. Во-вторых, программист может миновать DAL и записать через нижний уровень (тот же ado.net). Ну и в-третьих, программист вообще может работать с БД напрямую из другой программы.
А ведь он может! да он все это может. Именно поэтому и вводят проверку архитектуры и слоев Добавление пользовательской проверки архитектуры в схемы слоев а вот если
программист вообще может работать с БД напрямую из другой программы
тут уже ни какие проверки не помогут, только
Параноики еще и в самом хранилище делают проверки.
ну то есть паранойя оправдана?
Никто не может за вас ответить, насколько оправдан расход ресурсов (как разработческих, так и вычислительных) на решение некоей потенциальной проблемы.
вот тут в точку, каждый должен выбрать сам какой уровень абстракции выбрать, какой уровень проверок и защиты… а самое главное когд остановиться.
ну то есть паранойя оправдана?


Нет. Проверки надо делать не в хранилище, а перед выполнением операций. По сути в модели данных не должно быть инвариантов, а должны быть предусловия перед операциями. Но тогда и приложение надо проектировать как набор операций, а не как последовательное изменение объектов (данных) с сохранением состояния.
У вас банально может работать несколько экземпляров приложения, каждый из которых обращается к одним и тем же данным.
блокировки на уровне кода
На уровне какого кода? У вас два экземпляра кода работают на разных компьютерах.
На разных машинах, но на одной СУБД, бд, хранилище ( не важно ). Есть одна замечательная вещь, без которой многопользовательская работа ( корректная ) не возможна блокировки. Реализовав бизнес транзакции, а с ними если нужно блокировки, которые позволят предотвратить одновременное внесение изменений, Предоставлять одновременное редактирование одних и тех же данных — сумасшествие.
Есть одна замечательная вещь, без которой многопользовательская работа ( корректная ) не возможна блокировки.

Эээ, вы это серьезно? Вы понимаете, что на большой нагрузке (большом количестве одновременно работающих пользователей) блокировки — это смерть системы?

Предоставлять одновременное редактирование одних и тех же данных — сумасшествие.

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

по хорошему бы да, но это затратно


А расскажите пожалуйста как вы будете реализовывать такой инвариант — сумма всех заказов в ожидании не должна превышать текущего счета клиента?

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

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

а зачем дожидаться этого самого сохранения, ведь инварианты можно проверить и раньше CodeContracts
Вы не знаете о чем говорите. Когда у вас данные вводятся пользователям, то проверить стаически почти невозможно. Более того, как проверить инвариант вроде «у менеджера должно быть не более 10 подчиненных». Сразу учитывайте, что пользователи будут конкурентно добавять и удалять связанные элементы, а ваш код будет работать более чем на одном компьютере.
Какой смысл ограничивать выборки — я не понимаю.
простой, по проекту в 25 местах делаются общие выборки по каким либо критериям, настал момент и уже те же самые запросы должны осуществляться с еще парой параметров. и что? лезть по всему проекту искать где и кто делает такие выборки? Запросы должны быт четко сформулированы в терминах предметной области
А какая разница в чем сформулированы запросы, если все равно надо искать и править? Кто мешает вместо написания одних и тех же запросов вынести их в отдельные функции?
ни кто не мешает, но так же ни кто не мешает их не использовать и легко идти в обход системы.
А какая разница в чем сформулированы запросы, если все равно надо искать и править?
Вот у нас есть User вот у нас запрос в терминах предметной области query.AvaibleUsers, отлично avaible определяется заблокирован пользователь или нет, через какое то время вводиться еще одно свойство Baned, и теперь доступность определяется не заблокирован и не забанен, обратите внимание что запрос как был так и остался, измениось только его исполнение.
А что мешает сделать Extension Metod для фильтра available users? Вы же в статье мой кусок кода скопировали с подобным методом.

Linq как раз очень помогает в решении таких задач.
Ничего не мешает, так же как и не использовать его
Вы не знаете о чем говорите. Когда у вас данные вводятся пользователям, то проверить статически почти невозможно.
вариантов может быть много, согласен что не все они могут сразу быть проверены, но те которые могут, лучше всего проверять не отходя далеко, в момент подтверждения действий пользователя
Лучше, чем что? Каких действий? У вас каждое действие — набор изменений свойств объектов, в какой момент проверять? А если нужные для проверки объекты просто не загружены? А если возникает конкурентный доступ с нескольких серверов к одним и тем же данным?
А если возникает конкурентный доступ с нескольких серверов к одним и тем же данным?
а вот тут забавная ситуация, сидел пользователь пол часа правил, пытается сохранить, опаньки кто то до него внес изменения и сохранил, и мы рушим его работу, эх
Да совершенно необязательно ждать. Представьте систему продажи билетов театр. 100500 точек по москве, которые продают билеты. Надо гарантированно не продать два билета на одно место. Естественно серверная часть в нескольких экземплярах для отказоустойчивости. Как вы сделаете инвариант, что нельзя продать два билета на одно место?
действительно классный пример, я бы ввел блокировку, признак того что билет на данное место продается, но еще не подтвержден, тогда и не получиться проблем. Кстати не сложно.
Несложно?

(а) когда вы будете ее ставить?
(б) когда вы будете ее снимать?
ставить — когда начнем редактировать данные о билете.
снимать — когда подтверждаем изменения, на тот случай если кто то уснет при редактировании timeout по истечению которого блокировка считается не действительной.
О каком редактировании вы говорите, если речь идет о продаже билета (т.е. создании нового объекта)?
Таблица блокировок, туда помещается записи о местах которые покупают на данный момент.
В какой момент в нее добавляется запись и в какой — удаляется? Чем гарантируется то, что в таблице не появится две записи на одно и то же место?
ответ описал ниже
«на уровне бд делаем уникальность по полю»? А вы знаете, что в результате lock escalation у вас может возникнуть ситуация, когда у вас будет не больше одной записи в эту таблицу в один момент времени?

И вы так и не ответили, в какой момент добавляются и удаляются записи.
приходит клиент, выбирает место, кассир блокирует место, поле оплаты подтверждается продажа, билет продан блокировка снята.
lock escalation
а где я про это писал?
приходит клиент, выбирает место, кассир блокирует место, поле оплаты подтверждается продажа, билет продан блокировка снята.

А что делать, если после того, как клиент выбрал место (и оно заблокировано), клиент отвалился?

а где я про это писал?

Вы — нигде. Но это никак не отменяет существования этой проблемы.
А что делать, если после того, как клиент выбрал место (и оно заблокировано), клиент отвалился?
удалит запись о блокировке из таблицы.
Но это никак не отменяет существования этой проблемы.
почитайте внимательно, то о чем я пишу, такой проблемы не будет.
удалит запись о блокировке из таблицы.

Кто удалит? В какой момент?

Вот давайте на примере. Хорошо известный сайт «киноход» (или «рамблер.касса», имя им легион). Алгоритм везде один и тот же:
  1. Выбрали сеанс
  2. Увидели список свободных мест
  3. Выбрали нужные места
  4. Нажали «купить»
  5. Нажали «оплатить»
  6. Оплатили
  7. Получили билеты в почту/на смс


В какой момент добавляются записи в вашу «таблицу блокировок»? А в какой — удаляются?

почитайте внимательно, то о чем я пишу, такой проблемы не будет.

Почему не будет?
4.Нажали «купить»
блокировка
6.Оплатили
сохранили, сняли блокировку
Почему не будет?
в таблице блокировок уникальность записи по составному ключу. lock escalation там не нужен, возможно вы не правильно поняли, таблица блокировок это НАШЕ творение.
Нажали «купить»
блокировка

Прекрасно. А если к этому моменту другой пользователь нажал «купить» с теми же местами?

Оплатили

сохранили, сняли блокировку

Еще лучше.

(а) что делать, если пользователь не оплатил?
(б) как в дальнейшем не продать билеты на эти места?

в таблице блокировок уникальность записи по составному ключу. lock escalation там не нужен, возможно вы не правильно поняли, таблица блокировок это НАШЕ творение.

Это обычная таблица в SQL, или что-то другое? Если да, то как, по-вашему, БД обеспечивает уникальность записей в индексе? Если нет, то как та система, которая «что-то другое», это делает?
Прекрасно. А если к этому моменту другой пользователь нажал «купить» с теми же местами?
одновременно внести записи в таблицу с одинаковыми ключами не возможно.
(а) что делать, если пользователь не оплатил?
я ж писал, дата (таймаут) по которой блокировка считается не действительной
(б) как в дальнейшем не продать билеты на эти места?
при сохранении отмечаем что билет продан.
Это обычная таблица в SQL, или что-то другое? Если да, то как, по-вашему, БД обеспечивает уникальность записей в индексе?
да, ALTER TABLE [dbo].[Table] ADD CONSTRAINT [DF_Table_number] DEFAULT ((0)) FOR [number] уникальность, правда это не ключ, просто вариантов несколько, как унифицировать запись
одновременно внести записи в таблицу с одинаковыми ключами не возможно.

И что произойдет с точки зрения пользователя?

я ж писал, дата (таймаут) по которой блокировка считается не действительной

И кто/как эту дату обрабатывает?

при сохранении отмечаем что билет продан.

Отмечаем где?

да, ALTER TABLE [dbo].[Table] ADD CONSTRAINT [DF_Table_number] DEFAULT ((0)) FOR [number] уникальность, правда это не ключ, просто вариантов несколько, как унифицировать запись

Вообще-то, это не уникальность, а дефолтное значение. Но даже если вы используете UNIQUE CONSTRAINT, то мой вопрос остается актуальным: как, по-вашему, БД гарантирует уникальность? (кстати, в случае с констрейнтом будет хуже, чем в случае с индексом)
И что произойдет с точки зрения пользователя?
инфа о том что билет продан
И кто/как эту дату обрабатывает?
1. проверка есть ли блокировка
2. если нет, то ставиться блокировка и устанавливается тайм
3. если есть, то проверяется таймаут, если он просрочен, то сносим блокировку, переход к 2
Отмечаем где?
хороший вопрос, ну мы же где то должны сохранить, что такие то билеты проданы.
Вообще-то, это не уникальность, а дефолтное значение.
да, не оттуда копипаст, промазал
как, по-вашему, БД гарантирует уникальность?
видимо блокирует таблицу на время записи.
инфа о том что билет продан

Ну то есть пользователь выбрал билет, нажал кнопку, а ему и говорят: билет продан? Прекрасно, в чем тогда вообще смысл этого упражнения с «блокировками»?

1. проверка есть ли блокировка
2. если нет, то ставиться блокировка и устанавливается тайм
3. если есть, то проверяется таймаут, если он просрочен, то сносим блокировку, переход к 2

Я так понимаю, что это происходит при нажатии кнопки «купить»? Прекрасно! Теперь складываем это вместе с «мы же где то должны сохранить, что такие то билеты проданы» и пытаемся ответить на вопрос «как же показать пользователю карту незанятых мест?»

видимо блокирует таблицу на время записи.

Бинго. Это худший из возможных вариантов эскалации блокировок, который, собственно, и приведет к описанному мной поведению: ровно одна запись в таблицу в один момент времени. Правда, печально?
Ну то есть пользователь выбрал билет, нажал кнопку, а ему и говорят: билет продан? Прекрасно, в чем тогда вообще смысл этого упражнения с «блокировками»?
в том что он это узнает сразу, а не после того как он ввел все данные и подтвердил
«как же показать пользователю карту незанятых мест?»
свободные места — не проданные билеты + на которых нет действительных блокировок
Правда, печально?
да я то же понял что вы описали именно эту проблему, но смотря в глаза реальности скорость продажи билетов не космическая.
в том что он это узнает сразу, а не после того как он ввел все данные и подтвердил

Так какое же «сразу», когда он уже и сеанс выбрал, и места (а выбор мест на компанию в 9 человек — штука непростая)?

Но я вам могу еще более занятный сценарий рассказать:

  1. Пользователь А выбирает места, нажимает «Купить», проводит оплату
  2. Оплата проходит через его банк, но не доходит до сайта продажи билетов
  3. Места пользователя А сбрасываются по таймауту
  4. Пользователь Б выбирает места, нажимает «Купить», проводит оплату
  5. Оплата пользователя Б успешно доходит, места помечаются как выкупленные
  6. Оплата пользователя А приходит из банка


Ваши действия?

свободные места — не проданные билеты + на которых нет действительных блокировок

То есть проход по двум таблицам (который, заметим, их блокирует на время прохода).

да я то же понял что вы описали именно эту проблему

Тогда зачем вы утверждаете, что ее не будет?

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

Это пока вы не продаете билеты на единственное выступление широко известного исполнителя.

А главное, что подходы-то одни и те же везде, что вы будете делать, когда вам нужна скорость выше?
Правильнее было бы сказать «акторы». erlang, akka, akka.net, orleans и так далее.
Можно ли их рассматривать как часть domain model?
Это бессмысленный вопрос, приблизительно на уровне «можно ли рассматривать классы как часть domain model».
отлично, нужно с этим всем познакомится поближе
А как вы будете делать блокировку, чтобы два клиента не могли одновременно блокировку навесить?
Каждый билет имеет уникальный номер ( признак ), на уровне бд делаем уникальность по полю, и в таблицу блокировок одни и те же билеты (места) не попадут ( хотя это можно расценивать как паранойю
Параноики еще и в самом хранилище делают проверки.
)
Во, у нас уже часть логики уехала в базу. Оказывается важные проверки и не получится сделать в объектной модели. Может стоит вообще пересмотреть как логика приложения строится?
та, что касается паралельной работы, я думаю все таки идеальный вариант — это симбиоз клиента и бд.
То есть все эти рассуждения про domain model и прочую муть актуальны только когда нету конкурентного доступа к данным и объем данных небольшой?
нет, но ограничивать доступ между клиентами лучше на сервере чем на клиенте — проще
Сам себе противоречишь. Если на сервере проще, то зачем делать в коде приложения? Более того, не все можно сделать в коде как мы выяснили.
Эм нет, все же я пришел к выводу, что лучше использовать сервис (например wcf) и работать с ним а не с бд, решает все проблемы
Один и строго один экземпляр?
да
Прощай, масштабируемость (как в терминах нагрузки, так и в терминах отказоусточивости). Это вас устраивает, я так понимаю?
для продажи билетов — в полне
Ну во-первых, это само по себе достаточно забавно, и если бы разработчики вас слушали, продавцы билетов потеряли бы немалые деньги. Но не важно.

Важнее узнать — а что же делать, когда нужны и параллельная работа, и масштабируемость? (а это, на самом деле, значимая часть line-of-business applications)
а что же делать, когда нужны и параллельная работа, и масштабируемость?
да, не все так радужно, а что бы предложили вы?
Агенты без хранения состояния, как можно меньшая область необходимой консистентности, оптимистичные сценарии обработки конкуренции, готовность к откату.
Это не решает пробелму. Вполне может быть, что будет два инстанса сервиса (а в реальности — будет обязательно, для отказоустойчивости). Да еще логику все равно придется реализовывать на стороне сервиса, иначе нерадивый клиент (агент) продаст по 10 билетов на место и скроется с деньгами.

Так что единственное решение проблемы — когда часть логики уезжает в базу. Получается все рассуждения про domain model ничего не стоят для любого нетривального случая.
Получается все рассуждения про domain model ничего не стоят
сервис будет входить в DM
Вполне может быть, что будет два инстанса сервиса (а в реальности — будет обязательно, для отказоустойчивости).
если на 1 машине, то mutex, а вот если на нескольких, то в сеж бд
Без разницы куда будет ходить сервис. Все равно синхронизацию только на уровне базы можно будет обеспечить и, внезапно, никаких следов о ней в БД не останется. Максимум что получится — ловить эксепшены на SaveChanges.

Получается что domain model не может выразить самую важную часть логики. При этом если делать запросы, то выразить логику вполне. Их этого вопрос, не сильно ли много внимания domain model и попыткам обложить её кучей паттернов?

ЗЫ. Сервис конечно же на нескольких машинах будет.
Их этого вопрос, не сильно ли много внимания domain model и попыткам обложить её кучей паттернов?
видимо
Максимум что получится — ловить эксепшены на SaveChanges.
тут видимо то же не получится lair выше описывает проблему, бд нас не спасает
все таки решить поставленную задачу можно только с помощью реализации не тривиальных бизнес — процессов.
Вообще-то, бизнес-процесс там сугубо тривиальный (как и сама задача).
Агенты без хранения состояния, как можно меньшая область необходимой консистентности, оптимистичные сценарии обработки конкуренции, готовность к откату.
это тривиально?
Это не бизнес-процесс. Это детали реализации.
готовность к откату.
а разве это не учитывается процессом?
В том смысле, в котором это писал я, это всего лишь учитывает тот факт, что в бизнес-процессе такой откат уже есть (возврат билетов, отмена сеанса, и так далее), соответственно, мы должны учесть это в реализации.

(это вообще очень полезное замечание, на которое я недавно наткнулся в какой-то статье: проектируя системы с требованиями согласованности, посмотрите сначала на то, как были устроены человеческие взаимодействия до появления компьютеров — скорее всего, там уже есть решение)
все таки решить поставленную задачу можно только с помощью не тривиальной реализации бизнес — процессов.
наверное так будет лучше.
Да нет там ничего «нетривиального», как вы не поймете. Это просто подход, к которому вы не привыкли, и который ориентирован на другой спектр задач, нежели вы привыкли.
От привычек не легко избавиться, так же как перейти с ооп на фп. Нужно думаьб по другому, в других терминах, а это уже не легко.
Ваши привычки не имеют никакого отношения к тривиальности задачи или реализации.
согласен, но для разных людей одни и те же задачи имеею разный уровень сложности.
Я все-таки склонен считать, что уровень сложности задачи — это уровень сложности задачи, а то, насколько конкретному человеку/команде легко или сложно ее выполнить — это несколько другое.
абсолютно верно, но оценка сложности задач, как правило выводиться из личных знаний, опыта и еще кучи параметров.
Для всех популярных реляционных движков есть провайдеры для EF. Правда где-то платные, но цена не превышает $200, что стоит как 3-4 дня работы программиста. А еще есть linq2db, где автор самостоятельно поддерживает коннекторы ко всем популярным базам и обеспечивает паритет по фичам. Но в linq2db вообще нет UoW, все делается запросами.

кстати эта особенность возможно только благодаря абстракции
Эта абстракция уже есть в ORM, вам не нужно свою городить поверх.

И то что объект домена может сильно отличаться от объекта хранилища, а так же с абстракцией куда удобнее работать и еще много всяких плюсов при работе с Repository и UoW, толкают на реализацию своего DAL в приложении.
Мне кажется вы сами себе выдумываете проблему и пытаетесь её решать. Я вот не увидел в этом посте или комментах удобства и «плюсов» от Repository и UoW. Также не очень понятно в чем проблема, что «объект домена» отличается от объекта хранилища. Домен — операции, хранилище — данные, они должны отличаться.

Если же вы в домене еще и данные имеете, которые потом сохраняются как-то, то это проблема, созданная вами, ORM не должен её решать.
Эта абстракция уже есть в ORM, вам не нужно свою городить поверх
нужно написать ее конкретную реализацию
Я вот не увидел в этом посте или комментах удобства и «плюсов» от Repository и UoW.
так ведь пост изначально был про Specification.
Про выдуманную проблему я описал выше
22 июня 2015 в 15:40
Есть система сообщений, каждое сообщение подтверждается подписью (ЭЦП), ...
нужно написать ее конкретную реализацию

Не нужно, реализации тоже есть.

так ведь пост изначально был про Specification.

Это не ответ на вопрос в чем плюсы от repository и uow.
.
Не нужно, реализации тоже есть
думаю что не для всего ( но для самого распространённого )
Это не ответ на вопрос в чем плюсы от repository и uow.
так и есть, но для раскрытия данной темы нужно писать отдельный пост ( обещаю написать раз такое дело ).
К сожалению не всегда можно описать сущность хранилища что бы она отражала суть предметной области. Так же EF как и любая современная ORM реализует UoW только частично.
А не надо это делать на уровне хранилища данных. Суть предметной области должна отображаться на уровне домена.
Я об этом и пишу
К сожалению не всегда можно описать сущность хранилища что бы она отражала суть предметной области.

в ответ на
Data Access Layer в .net уже года три как полностью закрывается EF. Больше ничего писать не нужно. Более того, любой дополнительный код скорее вреден.
Так какая вам разница, какое хранилище, если все равно сущности в хранилище и в домене будут различаться?
Эм… так я про то и говорю. Что это разные понятия и что для их связи нужен слой который знает про домен и про хранилище ( Repository ).Но некоторые считают ( и это их право )
Data Access Layer в .net уже года три как полностью закрывается EF. Больше ничего писать не нужно.
Ну, я тоже так считаю (за исключением специальных случаев). Домен — отдельно, DAL — прекрасно пишется на EF, трансляция между ними — на чем угодно.
Суть предметной области не в данных, а в операциях. У Липперта есть замечательный цикл статей на эту тему ericlippert.com/2015/04/27/wizards-and-warriors-part-one (и еще 4 части, причем последняя часть ключевая).

Основная идея, что надо отделить состояние от операций. Операции — это и есть предметная область, которой надо уделять много внимания. А состояние — вопрос удобства программиста и не более того. Так вот EF прекрасно работает с состоянием системы, хранимом в БД.

UoW кстати не сильно нужен для управления состоянием. Так как массовые операции делаются с помощью DML-запросов, а для единичных изменений вполне хватает того, что предлагает EF.
В теории вам не нужны дополнительные сущности для хранение — вы можете использовать доменные + EF mapping (ведь EF'у не обязательно нужны всякие атрибуты и т.п., но есть небольшие ограничения над сущностями (типа обязательного paramterless конструктора) что опять же чуть-чуть подтекающая абстракция ;-)
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Изменить настройки темы

Истории