Работая над уменьшением связанности и размышляя о SOA пришел к идее построения типов путем композиции интерфейсов.
В классическом 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-схема автоматически пополнится нужными таблицами, колонками и другими объектами.
- Идея еще не реализована =). Но для того, кто захочет реализовать этот подход, это будет плюсом – он первым воплотит её в жизнь.