Диаграммы классов я не рисую, занятие в большинстве случаев бесполезное. Слишком трудоёмко их поддерживать в актуальном состоянии, а с применением DDD код сам по себе является прекрасной документацией. Обычно хватает use case diagram, activity diagram и/или упомянутой ER-модели. В общем тут по ситуации.
Но несомненно, всегда лучше начать с того, что бы немного порисовать, как я и сказал, к данному моменту вы «изучили предметную область, сформировали единый язык, выделили ограниченные контексты и определились с требованиями», написали проектную документацию, в соответствии с требованиями и принятым регламентом в вашей компании, почистили зубы и возможно много чего ещё сделали. Здесь речь о проектировании класса, это я делаю без схем.
Ну, и как я уже писал выше:
О диаграммах, схемах и документировании в книге Эванса так же есть отличная глава.
Рекомендую, там подробнее.
Проектирование — ERM/UML/IDEF/ARIS/итд.
Я к «и т.д.» отношу ещё и объектно-ориентированное проектирование, предметно-ориентированное проектирование, а SQL/ORM/%language_name% — это детали, ООП и DDD от них принципиально не зависят, хотя и приходится считаться с техническими ограничениями одно из которых в статье разобрано.
Так вот разработка предварительных проектных решений вместе с разработкой документации на информационную систему относится к стадии реализации проекта в соответствии с ГОСТом (если не ошибаюсь 34.601-90, могу не точно помнить) и относятся к этапу технического и рабочего проектирования. Это если уж совсем в формализм.
Для регистрации Клиента требуется одна валидация, для создания Заказа требуется другая валидация
Это два разных бизнес-процесса связанных с одной сущностью.
Вот вы же сами цитируете:
сущность может быть либо валидна либо нет, если под валидностью подразумевать непротиворечивость её состояния с точки зрения бизнес-требований
Есть конкретное бизнес-требование "клиент может быть зарегистрирован без менеджера". Не важно, что я могу его сохранить в БД и вообще что храню в БД, в бизнесе нет никаких «сохранений» и «баз данных». Есть «регистрация клиента» — конкретное оговоренное документированное требование, которое позволяет регистрировать клиентов без менеджера.
Есть другое бизнес-требование "для оформления Заказа у Клиента должен быть Менеджер", вот там вы и будете проверять наличие менеджера и обрабатывать эту ситуацию так как оговорено в соответствии с данным бизнес-требованием.
Билдер не знает для каких целей, его это не касается. Он знает как построить и знает, что если у него нет цемента, то он не сможет построить кирпичную кладку. А как будет использоваться постройка — дело заказчика, строителю без разницы. Всё это уже похоже на софистику, просто оставим.
А если таких требований десятки, будет десяток конструкторов?
Разумеется, если есть требование, то оно должно быть выражено в коде, в этом и смысл. Правда десятки бизнес-правил создания сущности мне представить сложно. Я поделился конкретным опытом и вполне возможна ситуация где он окажется неэффективным или вовсе неприемлемым.
Ну, вот я тут вижу три размытых бизнес-требования. И при чем здесь конструкторы мне не понятно. Вы собираетесь в конструкторе осуществлять «оформления Заказа» и «отгрузки товара»?
1. «сохранить клиента можно без этих сведений»
Значит Client::register(...): Client; будет без менеджера и адреса или они будут необязательными.
2. «для оформления Заказа этому клиенту необходимо наличие у него как Адреса, так и ОтветственногоМенеджера»
3. «для отгрузки товара по Заказу достаточно знать только Адрес Клиента»
А вот эти два бизнес-правила к регистрации клиента (а значит и к конструкторам) уже не имеют никакого отношения.
Очевидно наличие необходимых для создания объекта параметров. А какого поведения вы ожидаете, например, от QueryBuilder'а, если не укажите таблицу? Он может вам построить кривой SQL без указания FROM и ваш скрипт будет падать при попытке его выполнить, либо в момент построения билдер проверит наличие обязательных параметров и выбросит исключение. Третьего я здесь не вижу.
Представим сущность Клиент с тем же набором свойств, что предлагаете вы. Вы всем пользователям отказываете в создании Клиента без указания GeneralManager. Через n времени пользователь обращается к вам с просьбой дать возможность создания Клиента без указания GeneralManager с целью регистрации всех Клиентов, но запретить оформлять на этого Клиента заказы, при отсутствии у него GeneralManager. Как вы будете решать эту задачу?
В соответствии с DDD, бизнес-правила явным образом выражены в коде, есть явное бизнес-требование: «клиент не может быть без менеджера, при регистрации за ним обязательно закрепляется менеджер». Изменение бизнес требований требует внесения изменений в код.
А вот дальше мы затрагиваем тоже очень интересный вопрос, который я хотел осветить в статье, но посчитал уже излишним, возможно напрасно.
Именованные конструкторы. Более правильным решением будет не использовать дефолтный конструктор совсем, а пользоваться для этого именованными конструкторами, которые так же будут явным образом отображать единый язык и предметную область.
Client::register(...): Client;
Когда появляется новое бизнес-правило, которое требует регистрацию клиента без менеджера вы вводите ещё один именованный конструктор, который явно выражает это требование.
// имя должно характеризовать бизнес требование и соответствовать единому языку
Client::registerWithoutClient(...): Client;
Состояние сущности может быть не валидно для одной цели, и валидно для другой. Тут мы упираемся в «Контекстуальную валидацию».
Это не верно, сущность может быть либо валидна либо нет, если под валидностью подразумевать непротиворечивость её состояния с точки зрения бизнес-требований. А какие инварианты для неё доступны — это инкапсулировано в саму сущность, отсутствие сеттеров гарантирует, что эти инварианты нарушены не будут.
Если это одна сущность, то её контексты зашиты у ней внутри. В рамках же разных ограниченных контекстов проектируются разные объекты. Один объект, являющийся в данном контексте сущностью, в другом даже может быть представлен как объект-значение. В общем это тема отдельная и достаточно масштабная, подробнее у Эванса, Вернона и Фаулера.
А что мешает программисту создать клиента с невалидным состоянием не вызвав пару методов Строителя
Во-первых, ваша реализация строителя может (и должна) выбросить RuntimeException или скорее LogicException
Во-вторых, если в Строителе вы забыли проверить, то ваш скрипт упадет с TypeError или ArgumentCountError.
В общем это уже ошибка разработчика и должна быть разрешена разработчиком. Строитель просто дает возможность создавать сложный объект более гибко и прозрачно.
Может проще не использовать генерацию PK на стороне СУБД, а воспользоваться генераторами UUID?
Это прекрасный вариант и когда это возможно я так делаю, мне нравится UUID, так же я запрашивал идентификаторы у последовательностей Postges, но речь о конкретном случае. Структура БД уже существует и изменить её невозможно, я нашел вот такое решение и оно неплохо вписалось.
А если появится возможность перейти на MySQL или использовать UUID, то без проблем я только заменю реализации Строителей.
UML-проектирование — это о другом. Вероятно не совсем удачно подобрано название статьи. Тут речь о проектировании классов представляющих в коде сущности предметной-области, собственно акцент на создание объектов, а поведение и отношения остаются за рамками.
Изучение предметной области, формирование единого языка, выделение ограниченных контекстов, документирование, проектирование, описание и формализация в виде диаграмм или каким-то ещё способом отношений и бизнес-процессов и ещё много чего, всё это невозможно объять одной статьей, даже одной книгой если только в общем виде. О диаграммах, схемах и документировании в книге Эванса так же есть отличная глава. Как я указал вначале, всё это выходит за рамки данной статьи. Тут я просто поделился решением пары конкретных проблем, с которыми сам столкнулся в своём небольшом опыте.
США до сих пор на метрическую систему перейти не может, а вы одним махом весь мир хотите на UTC перевести… Для этого понадобится международное согласование, массовая пропаганда и, вероятно, очень много ресурсов при сомнительном уровне пользы.
Но несомненно, всегда лучше начать с того, что бы немного порисовать, как я и сказал, к данному моменту вы «изучили предметную область, сформировали единый язык, выделили ограниченные контексты и определились с требованиями», написали проектную документацию, в соответствии с требованиями и принятым регламентом в вашей компании, почистили зубы и возможно много чего ещё сделали. Здесь речь о проектировании класса, это я делаю без схем.
Ну, и как я уже писал выше: Рекомендую, там подробнее.
Я к «и т.д.» отношу ещё и объектно-ориентированное проектирование, предметно-ориентированное проектирование, а SQL/ORM/%language_name% — это детали, ООП и DDD от них принципиально не зависят, хотя и приходится считаться с техническими ограничениями одно из которых в статье разобрано.
Так вот разработка предварительных проектных решений вместе с разработкой документации на информационную систему относится к стадии реализации проекта в соответствии с ГОСТом (если не ошибаюсь 34.601-90, могу не точно помнить) и относятся к этапу технического и рабочего проектирования. Это если уж совсем в формализм.
http://gorodinski.com/blog/2012/05/19/validation-in-domain-driven-design-ddd/
http://verraes.net/2015/02/form-command-model-validation/
Как проверять, соответствует ли клиент бизнес-требованию «оформить заказ можно только для Клиента у которого есть Менеджер» я показал выше:
Создание инстансов и тема статьи тут вообще не при чем.
Это вы уже нафантазировали.
В соответствии с моим решением оно будет выглядеть:
Только это всё уже несколько за рамками статьи.
Это два разных бизнес-процесса связанных с одной сущностью.
Вот вы же сами цитируете:
Есть конкретное бизнес-требование "клиент может быть зарегистрирован без менеджера". Не важно, что я могу его сохранить в БД и вообще что храню в БД, в бизнесе нет никаких «сохранений» и «баз данных». Есть «регистрация клиента» — конкретное оговоренное документированное требование, которое позволяет регистрировать клиентов без менеджера.
Есть другое бизнес-требование "для оформления Заказа у Клиента должен быть Менеджер", вот там вы и будете проверять наличие менеджера и обрабатывать эту ситуацию так как оговорено в соответствии с данным бизнес-требованием.
Разумеется, если есть требование, то оно должно быть выражено в коде, в этом и смысл. Правда десятки бизнес-правил создания сущности мне представить сложно. Я поделился конкретным опытом и вполне возможна ситуация где он окажется неэффективным или вовсе неприемлемым.
Ну, вот я тут вижу три размытых бизнес-требования. И при чем здесь конструкторы мне не понятно. Вы собираетесь в конструкторе осуществлять «оформления Заказа» и «отгрузки товара»?
1. «сохранить клиента можно без этих сведений»
Значит Client::register(...): Client; будет без менеджера и адреса или они будут необязательными.
2. «для оформления Заказа этому клиенту необходимо наличие у него как Адреса, так и ОтветственногоМенеджера»
3. «для отгрузки товара по Заказу достаточно знать только Адрес Клиента»
А вот эти два бизнес-правила к регистрации клиента (а значит и к конструкторам) уже не имеют никакого отношения.
Очевидно наличие необходимых для создания объекта параметров. А какого поведения вы ожидаете, например, от QueryBuilder'а, если не укажите таблицу? Он может вам построить кривой SQL без указания FROM и ваш скрипт будет падать при попытке его выполнить, либо в момент построения билдер проверит наличие обязательных параметров и выбросит исключение. Третьего я здесь не вижу.
В соответствии с DDD, бизнес-правила явным образом выражены в коде, есть явное бизнес-требование: «клиент не может быть без менеджера, при регистрации за ним обязательно закрепляется менеджер». Изменение бизнес требований требует внесения изменений в код.
А вот дальше мы затрагиваем тоже очень интересный вопрос, который я хотел осветить в статье, но посчитал уже излишним, возможно напрасно.
Именованные конструкторы. Более правильным решением будет не использовать дефолтный конструктор совсем, а пользоваться для этого именованными конструкторами, которые так же будут явным образом отображать единый язык и предметную область.
Когда появляется новое бизнес-правило, которое требует регистрацию клиента без менеджера вы вводите ещё один именованный конструктор, который явно выражает это требование.
Это не верно, сущность может быть либо валидна либо нет, если под валидностью подразумевать непротиворечивость её состояния с точки зрения бизнес-требований. А какие инварианты для неё доступны — это инкапсулировано в саму сущность, отсутствие сеттеров гарантирует, что эти инварианты нарушены не будут.
Если это одна сущность, то её контексты зашиты у ней внутри. В рамках же разных ограниченных контекстов проектируются разные объекты. Один объект, являющийся в данном контексте сущностью, в другом даже может быть представлен как объект-значение. В общем это тема отдельная и достаточно масштабная, подробнее у Эванса, Вернона и Фаулера.
Во-первых, ваша реализация строителя может (и должна) выбросить RuntimeException или скорее LogicException
Во-вторых, если в Строителе вы забыли проверить, то ваш скрипт упадет с TypeError или ArgumentCountError.
В общем это уже ошибка разработчика и должна быть разрешена разработчиком. Строитель просто дает возможность создавать сложный объект более гибко и прозрачно.
Это прекрасный вариант и когда это возможно я так делаю, мне нравится UUID, так же я запрашивал идентификаторы у последовательностей Postges, но речь о конкретном случае. Структура БД уже существует и изменить её невозможно, я нашел вот такое решение и оно неплохо вписалось.
А если появится возможность перейти на MySQL или использовать UUID, то без проблем я только заменю реализации Строителей.
Изучение предметной области, формирование единого языка, выделение ограниченных контекстов, документирование, проектирование, описание и формализация в виде диаграмм или каким-то ещё способом отношений и бизнес-процессов и ещё много чего, всё это невозможно объять одной статьей, даже одной книгой если только в общем виде. О диаграммах, схемах и документировании в книге Эванса так же есть отличная глава. Как я указал вначале, всё это выходит за рамки данной статьи. Тут я просто поделился решением пары конкретных проблем, с которыми сам столкнулся в своём небольшом опыте.
С годовщиной!