LazyLoad нельзя использовать, чтобы избежать этого?
LazyLoad приводит к ошибкам доступа в случае если к коллекции идет обращение когда сессия уже закрыта. Получить на клиенте количество ордеров таким образом не получится
Как правило, Value Object-ы (так же как и Entities) не используются для передачи данных между приложениями. В целом, в простых случаях (как например в вашем) Value Object-ы действительно можно использовать как DTO, но опять же — это до тех пор, пока в них не скопилось достаточно логики.
Хорошей практикой считается изолировать домен приложения. На практике это означает, что доменные классы (Entities and Value Objects) не должны знать о том, как они сериализуются. В более-менее сложных случаях этого трудно достичь если сериализовывать их напрямую.
Мнение, которое вы высказали, и послужило причиной написания этой статьи.
Value Object — иммутабельные объекты
Это верно.
Они как бы строительные кирпичики для Entity
Это тоже верно.
Никакой логики в них быть не должно
Вот это уже неверно. Value Object-ы вполне себе могут содержать логику. Более того, Эванс и ко рекомендуют помещать доменную логику именно в Value Object-ы там где это возможно, т.к. работать с ними проще из-за их неизменяемости. Хороший пример Value Object-а — DateTime в .NET. Неизменямый тип данных с большим количеством логики внутри.
POCO (POJO, PODS) — это принцип организации данных
POCO — это в первую очередь принцип, который говорит о том, что при моделировании предметной области следует использовать настолько простые классы, насколько возможно. Это понятие никак не связано с DTO (кроме того, что DTO тоже не следует наследовать от тяжеловесных компонент).
Еще хотел добавить. Пожалуй главный плюс подхода с Maybe для меня в том, что он улучшает читаемость кода благодаря тому, что мы разделяем нулевые и ненулевые типы.
Не совсем так. Проверка на нал действительно идет в ран-тайме, но мы получаем ошибку компилятора в случае если используем нулевую ссылку там где подразумевалась ненулевая.
Code annotations от решарпера мне не нравятся тем, что
1) Это всего лишь warning
2) Используется opt-in схема. Т.е. все типы по умолчанию нулевые. По-хорошему нам нужно обратное поведение — сделать все типы по умолчанию ненулевыми и затем opt-out в случае если какой-то из них нулевой.
Но вообще Code annotations и Code Contracts — тоже вполне себе хорошая альтернатива
Запретить не получится. Но я бы поспорил с посылкой этого утверждения. Программисты-коллеги — друзья, а не враги, цель нового класса EmailStrong — не запретить им что-то делать, а подсказать, направить на правильный путь. Ну и плюс общение между коллегами должны быть intensive, чтобы все были в курсе нововведений.
А что мешает использовать методы со старыми сигнатурами?
Ну мы же хотим чтобы кастомерам можно было присваивать только имейлы с новыми инвариантами? Если так, то можно на уровне компилятора обозначить это изменение.
… и теперь сломался ранее работавший маппинг.
Маппинг куда? Если речь про ORM, то они умеют памить на protected setter-ы. В EF это посложнее сделать, в NH — попроще.
особенно учитывая, что где-то присваивать можно (мало ли, мы данные из системы в систему гоним). Опять-таки, в систему типов этот запрет присвоения уже не обернешь, снова боль.
Обернуть можно. Где-то метод принимает старый Email, а где-то — только новый EmailStrong, который является наследником старого Email.
Еще раз посмотрел на код. Да, вы правы.
Старался сделать примеры как можно проще, не хотел накручивать какую-то логику поверх обычного изменения имейла и нэйма.
1) По поводу ручного биндинга — вы правы, здесь теряется часть встроенного функционала, который есть в ASP.NET. Это вопрос взвешивания «за» и «против». Для меня плюсы более выразительной доменной модели перевешивают минусы необходимости писать подобный код вручную. Для более простых проектов вполне можно отказаться от этого подхода и делать по старинке.
Но при этом для других свойств (скажем, City и Country) такой проверки нет, и теперь программисту нужно помнить про два разных механизма. Не ужас, но неудобно.
Если у City и Country есть какие-то более-менее сложные инварианты, то их тоже стоит обернуть в классы-обертки.
2) По поводу невалидных данных в БД — отличный point. Есть два распространенных подхода к проблеме. Первый — вместе с ужесточением инвариантов писать скрипты для миграции данных в БД, чтобы они соответствовали новым инвариантам. Второй — создавать отдельный класс, к примеру EmailStrong для хранения имейлов, инварианты в которых были ужесточены и не давать присваивать кастомерам объекты старого Email, только нового. Со временем, когда БД придет в соответствие с новыми требованиями, старый Email удаляется, новый EmailStrong переименовывется в Email. Второй вариант сложнее, я как правило пользуюсь первым.
Отдельные методы-сеттеры сделаны для большей выразительности. Всю ту же логику можно написать и в сеттере свойства, безусловно, разницы в коде при этом не будет.
С ридонли полями примерно так же как и с private set пропертями в плане читаемости. Т.е. для того чтобы понять является ли класс изменяемым нам все равно нужно смотреть внутрь этого класса, по сигнатуре это непонятно. Тут бы помогло ключевое слово immutable.
>P.S. Почему статья не оформлена как перевод?
Я думаю свои статьи не очень правильно оформлять переводом, хотя тут не уверен насчет правил хабра.
Да, под предусловиями я имею ввиду именно предусловия контрактов. Я выделяю предусловия (и оставляюю за скобками инварианты и постусловия), т.к. предусловия обычно путают с валидацией приходящих извне данных, с постусловиями и инвариантами в этом плане проще.
LazyLoad приводит к ошибкам доступа в случае если к коллекции идет обращение когда сессия уже закрыта. Получить на клиенте количество ордеров таким образом не получится
Хорошей практикой считается изолировать домен приложения. На практике это означает, что доменные классы (Entities and Value Objects) не должны знать о том, как они сериализуются. В более-менее сложных случаях этого трудно достичь если сериализовывать их напрямую.
Это верно.
Это тоже верно.
Вот это уже неверно. Value Object-ы вполне себе могут содержать логику. Более того, Эванс и ко рекомендуют помещать доменную логику именно в Value Object-ы там где это возможно, т.к. работать с ними проще из-за их неизменяемости. Хороший пример Value Object-а — DateTime в .NET. Неизменямый тип данных с большим количеством логики внутри.
POCO — это в первую очередь принцип, который говорит о том, что при моделировании предметной области следует использовать настолько простые классы, насколько возможно. Это понятие никак не связано с DTO (кроме того, что DTO тоже не следует наследовать от тяжеловесных компонент).
Что-то не вижу как сказанное вами противоречит тому, что написано в статье
Проверяются все входящие и выходящие значения. Т.е. в вашем примере NRE будет тут:
var customer = CustomerService.GetCustomer(); // будет NRE
Console.WriteLine(customer.Name);
Code annotations от решарпера мне не нравятся тем, что
1) Это всего лишь warning
2) Используется opt-in схема. Т.е. все типы по умолчанию нулевые. По-хорошему нам нужно обратное поведение — сделать все типы по умолчанию ненулевыми и затем opt-out в случае если какой-то из них нулевой.
Но вообще Code annotations и Code Contracts — тоже вполне себе хорошая альтернатива
Ну мы же хотим чтобы кастомерам можно было присваивать только имейлы с новыми инвариантами? Если так, то можно на уровне компилятора обозначить это изменение.
Маппинг куда? Если речь про ORM, то они умеют памить на protected setter-ы. В EF это посложнее сделать, в NH — попроще.
Здесь как раз пригодится сделать небольшие методы-сеттеры для обозначения новых инвариантов класса.
Обернуть можно. Где-то метод принимает старый Email, а где-то — только новый EmailStrong, который является наследником старого Email.
Старался сделать примеры как можно проще, не хотел накручивать какую-то логику поверх обычного изменения имейла и нэйма.
1) По поводу ручного биндинга — вы правы, здесь теряется часть встроенного функционала, который есть в ASP.NET. Это вопрос взвешивания «за» и «против». Для меня плюсы более выразительной доменной модели перевешивают минусы необходимости писать подобный код вручную. Для более простых проектов вполне можно отказаться от этого подхода и делать по старинке.
Если у City и Country есть какие-то более-менее сложные инварианты, то их тоже стоит обернуть в классы-обертки.
2) По поводу невалидных данных в БД — отличный point. Есть два распространенных подхода к проблеме. Первый — вместе с ужесточением инвариантов писать скрипты для миграции данных в БД, чтобы они соответствовали новым инвариантам. Второй — создавать отдельный класс, к примеру EmailStrong для хранения имейлов, инварианты в которых были ужесточены и не давать присваивать кастомерам объекты старого Email, только нового. Со временем, когда БД придет в соответствие с новыми требованиями, старый Email удаляется, новый EmailStrong переименовывется в Email. Второй вариант сложнее, я как правило пользуюсь первым.
>P.S. Почему статья не оформлена как перевод?
Я думаю свои статьи не очень правильно оформлять переводом, хотя тут не уверен насчет правил хабра.
Конкретно эту книгу Мейера я не читал, но вообще первоисточник моих знаних о контрактах именно Мейер, конкретно эта его книга: www.amazon.com/Object-Oriented-Software-Construction-CD-ROM-Edition/dp/0136291554