Работая над уменьшением связанности и размышляя о SOA пришел к идее построения типов путем композиции интерфейсов.
В классическом DDD нужно выделить домен – совокупность объектов и их связей. Но когда я применял этот принцип в жизни, встретился с двумя трудностями:
Выход один – объединить сервис и его сущности в один модуль. Будут это две сборки ( домен и сервис) или одна единая сборка – не важно. Цель – подключить всю функциональность модуля просто добавив reference к сборке, в которой выполняется композиция приложения (AppCore, например).
Сделав это, нужно как-то оставить возможность коммуникации между доменами разных модулей. Вполне вероятно, в нашем примере нужно будет реализовать свойство Car.Owner типа User, а потом House.Owner, CableTv.User и т.д. Добавить reference на Membership – не вариант, это уже противоречит главной идее – не допускать связанности модулей.
Выделить все такие зависимости в сборку, но уже не с целыми сущностями, а с интерфейсами-пустышками. В данном примере это будет пустой интерфейс IUser. Потом, при инициализации каждого модуля (в его Bootstrapper-е) расширить этот интерфейс тем, что указан внутри модуля (ICarOwner), в котором можно указать какие-то свойства, которые требуются для User в рамках этого модуля.
Таким образом каждый модуль дополняет интерфейс IUser своими полями. Потом нужно реализовать фабрику, которая на основе перечня интерфейсов будет создавать единый тип User, который будет закэширован в AppCore.
+ Каждый модуль знает только только о тех полях, которые ему нужны
+ За исключением базовых интерфейсов модули будут абсолютно независимы.
+ При добавлении нового модуля необходимые для персистенции свойства добавятся автоматически. Т.е. при добавлении модуля Sql-схема автоматически пополнится нужными таблицами, колонками и другими объектами.
— Идея еще не реализована =). Но для того, кто захочет реализовать этот подход, это будет плюсом – он первым воплотит её в жизнь.
Цель – сделать функциональность Plug&Play.
Нужны Orders – добавил reference на OrdersLib, запустил, всё!
БД автоматически пополняется новыми таблицами, колонками, связями, которые требуются для работы OrdersLib. У Пользователя автоматически появится User.Orders и т.д.
Никаких работ по адаптации кода, только расширение там, где необходимо.
Как разобраться из чего же состоит объект Пользователь? Есть два варианта:
Первый путь сохранит строгую типизацию, второй – сделает разработку полностью динамичной.
В классическом DDD нужно выделить домен – совокупность объектов и их связей. Но когда я применял этот принцип в жизни, встретился с двумя трудностями:
- Если есть большой домен и кучка сервисов вокруг него, то становится сложным управление доступом к членам класса. Выглядит это так: есть объект User со свойством CreatedAt, которое я хочу разрешить редактировать только сервису MembershipService. Чтож, пишем InternalsVisibleTo с указанием MembershipService. Дальше нам нужно сделать объект, например Car, у которого есть свойство PassedDistance, которое я хочу открыть только для CarService. Опять повторяем манипуляции с InternalsVisibleTo, но тут появляется проблема: теперь Membersip может изменять километраж автомобиля, а CarService – дату регистрации пользователя.
- DDD всё еще не дает возможности строить приложение «по кирпичикам» – просто подключая нужные модули. Говорят, что в Ruby можно, поэтому хочется такой же легкости в .Net =). А не получается всё по той же причине – домен выделен в отдельную сборку, и, подключая сервис, приходится вручную тянуть из домена все зависимые сущности, перебирая их свойства, т.к. большинство из них в новом проекте не понадобятся. Т.е. проблему связанности сервисов DDD решает, а вот связанность домена всё еще не решена.
Выход один – объединить сервис и его сущности в один модуль. Будут это две сборки ( домен и сервис) или одна единая сборка – не важно. Цель – подключить всю функциональность модуля просто добавив reference к сборке, в которой выполняется композиция приложения (AppCore, например).
Сделав это, нужно как-то оставить возможность коммуникации между доменами разных модулей. Вполне вероятно, в нашем примере нужно будет реализовать свойство Car.Owner типа User, а потом House.Owner, CableTv.User и т.д. Добавить reference на Membership – не вариант, это уже противоречит главной идее – не допускать связанности модулей.
Идеи о решении
Выделить все такие зависимости в сборку, но уже не с целыми сущностями, а с интерфейсами-пустышками. В данном примере это будет пустой интерфейс IUser. Потом, при инициализации каждого модуля (в его Bootstrapper-е) расширить этот интерфейс тем, что указан внутри модуля (ICarOwner), в котором можно указать какие-то свойства, которые требуются для User в рамках этого модуля.
Таким образом каждый модуль дополняет интерфейс IUser своими полями. Потом нужно реализовать фабрику, которая на основе перечня интерфейсов будет создавать единый тип User, который будет закэширован в AppCore.
Плюсы и минусы:
+ Каждый модуль знает только только о тех полях, которые ему нужны
+ За исключением базовых интерфейсов модули будут абсолютно независимы.
+ При добавлении нового модуля необходимые для персистенции свойства добавятся автоматически. Т.е. при добавлении модуля Sql-схема автоматически пополнится нужными таблицами, колонками и другими объектами.
— Идея еще не реализована =). Но для того, кто захочет реализовать этот подход, это будет плюсом – он первым воплотит её в жизнь.
Цель – сделать функциональность Plug&Play.
Нужны Orders – добавил reference на OrdersLib, запустил, всё!
БД автоматически пополняется новыми таблицами, колонками, связями, которые требуются для работы OrdersLib. У Пользователя автоматически появится User.Orders и т.д.
Никаких работ по адаптации кода, только расширение там, где необходимо.
Как разобраться из чего же состоит объект Пользователь? Есть два варианта:
- кодогенерация общего интерфейса, что даже упростит создание фабрики
- разработкой в среде разрабатываемого приложения. Intellisense будет считывать не байт-код, а реальный инстанс динамичного объекта.
Первый путь сохранит строгую типизацию, второй – сделает разработку полностью динамичной.