2012-05-30 4 views
3

Это вопрос о дизайне модели домена.Как разделить репозиторий и сущности

Скажем, для дизайна домена с участием пользователей и групп, мы имеем следующие интерфейсы для реализации:

interface IUser 
{ 
    string Name{get;} 
    DateTime DOB {get;} 
} 

interface IGroup 
{ 
    string Name {get;} 
    bool IsUserInGroup(IUser user); // #1 
    void IncludeUser(IUser user); // #2 
    void ExcludeUser(IUser user); // #3 
} 

interface IUserRepository 
{ 
    IUser Create(string name); 
    IUser GetByName(string name); 
    void Remove(IUser user); 
    void Save(IUser user); 
} 

interface IGroupRepository 
{ 
    IGroup Create(string name); 
    IGroup GetByName(string name); 
    void Remove(IGroup group); 
    void Save(IGroup group); 
} 

хитрый бит реализовать # 1 # 2 и # 3, сохраняя классы сущностей (User , Group), отделяемых от классов репозитория (UserRepository, GroupRepository.)

Еще одна важная особенность заключается в том, что большинство систем RMDB не реализуют отношения «многие ко многим», и на практике всегда существует отдельная таблица (например, UserGroupAssociation), чтобы иметь записи, каждый ассоциирует пользователя и группу с помощью внешних ключей. Я хотел бы скрыть эту деталь реализации от интерфейсов домена и выставить эквивалентную логику через члены # 1 # 2 и # 3.

Эффект вызова # 2 и # 3 не должны сохраняться, пока объект группы в вопросе не было сохранено (т.е. передается методу() объекта хранилища Сохранить)

Как вы обычно это делаете?

ответ

1

Я этого не делаю. Мои объекты Repository тесно связаны с корнем агрегата, к которому они относятся, и (как отчасти) я не беспокоюсь о создании интерфейсов для объектов моей модели домена, если не нахожу, что у меня есть все основания для этого - do у вас есть особая причина для этого?

Я не сталкивался ни с какими Repository примерами, которые не используют тип реализации сущности в классе репозитория (например, this one) и не могут думать о каких-либо реальных преимуществах использования интерфейса. Интерфейсы зарабатывают на инфраструктурные компоненты (например, Repository), упрощая издевательство над целыми слоями системы при тестировании, вы не получаете такого же преимущества, используя интерфейсы для объектов домена.

И, возможно, на самом деле ответить на этот вопрос ... не

Я никогда имеют доступа к объекту Домен Repository - объект домена в конце концов должен представлять что-то в области в реальной жизни, и Репозитории компоненты инфраструктуры, которых нет в реальной жизни, так почему объект домена знает об одном?

Для конкретного примера добавления User к Group, я хотел бы использовать Service Layer класс, и сделать это:

public class UserService 
{ 
    private readonly IGroupRepository _groupRepository; 
    private readonly IUserRepository _userRepository; 

    public UserService(
     IGroupRepository groupRepository, 
     IUserRepository userRepository) 
    { 
     this._groupRepository = groupRepository; 
     this._userRepository = userRepository; 
    } 

    public void IncludeUserInGroup(string groupName, string userName) 
    { 
     var group = this._groupRepository.FindByName(groupName); 
     var user = this._userRepository.FindByName(userName); 

     group.IncludeUser(user); 

     this._userRepository.SaveChanges(); 
    } 
} 

public class User 
{ 
    public void AddToGroup(Group group) 
    { 
     this.Groups.Add(group); 
    } 

    public void RemoveFromGroup(Group group) 
    { 
     this.Groups.Remove(group); 
    } 
} 

Некоторые пункты отметить:

  1. Чтобы избежать ленивым - загрузка большого количества User с при добавлении User в Group Я переместил методы администрирования Group на User - depend о том, какое поведение вы действительно имеете для Group, вы можете даже подумать о том, чтобы превратить его в перечисление, а не в класс.Имейте в виду, что если вы используете Entity Framework POCO T4 шаблонов с FixupCollection с, это будет по-прежнему ленивы нагрузки все User s в Group, но вы можете обойти, что так или иначе :)

  2. Класс Service Layer будет реализовывать метод Create(), который у вас есть в ваших репозиториях. Репозитории будут иметь метод Add, методы Find и метод SaveChanges(). Add добавит объект, созданный уровнем сервиса, в контекст объекта.

  3. Все классы Repository будут настроены на использование одного и того же базового контекста объекта с запросом, поэтому не имеет значения, какой из них вы вызываете SaveChanges().

  4. SaveChanges() вызвал бы все изменения, которые произошли с объектами во время этого запроса должны быть сохранены, например, User, имеющими новый Group «S добавлены к их Groups коллекции.

Наконец, еще один хороший метод для расцепления сущностей из Хранилища и других компонентов инфраструктуры является Domain Events.

+0

Использование интерфейса - представить более высокий уровень понятия, чем просто ведро свойств для чтения/записи. Например, вместо 'class User {public byte [] PasswordHash {get; set;}}' не было бы лучше, если бы это был интерфейс IUser {bool VerifyPassword (строковый пароль);} '? – Ben

+0

В этом примере вы не могли бы просто сделать свойство 'PasswordHash' частным (или защищенным, если вы ограничены ORM)? 'User' будет по-прежнему иметь реализацию' VerifyPassword() ', поэтому он не просто будет ведром свойств. –

+0

Для простых методов это совершенно верно. Но для других, таких как IncludeUser() и ExcludeUser() (пример № 2 и № 3), реализации необходимо получить доступ к объекту репозитория и необязательно его собственный объект репозитория. В случае IncludeUser() классу группы необходимо будет поговорить с GroupRepository, UserGroupAssociationRepository. Однако, сущность, чтобы знать репозиторий, однако, очень нежелательна связь. – Ben

Смежные вопросы