Проверка без ServiceLocator - PullRequest
0 голосов
/ 11 мая 2010

Я снова и снова возвращаюсь к нему, думая о наилучшем способе проверки объектов POCO, которым требуется доступ к некоторому контексту (например, ISession in NH, IRepository).

Единственный вариант, который я все еще вижу, это использовать Service Locator , поэтому моя проверка будет выглядеть так:

public User : ICanValidate {
    public User() {} // We need this constructor (so no context known)

    public virtual string Username { get; set; }

    public IEnumerable<ValidationError> Validate() {
        if (ServiceLocator.GetService<IUserRepository>().FindUserByUsername(Username) != null)
            yield return new ValidationError("Username", "User already exists.")
    }
}

Я уже использую Инверсия управления и внедрение зависимостей и действительно не люблю ServiceLocator из-за ряда фактов:

  • Сложнее поддерживать неявные зависимости.
  • Сложнее проверить код.
  • Потенциальные проблемы с многопоточностью.
  • Явная зависимость только от ServiceLocator.
  • Код становится все труднее понять.
  • Необходимо зарегистрировать интерфейсы ServiceLocator во время тестирования.

Но с другой стороны, с простыми объектами POCO я не вижу другого способа выполнить валидацию, как описано выше, без ServiceLocator и только с использованием IoC / DI.

В настоящее время я выполняю такую ​​проверку в сервисном слое . Таким образом, всякий раз, когда субъект пытается изменить имя пользователя (может быть, конечно, что-то другое), служба выполняет эту проверку. Очевидным недостатком является то, что каждая служба, использующая пользователя, должна выполнять эту проверку (даже если это один вызов).

Таким образом, вопрос будет: есть ли способ использовать DI / IoC для ситуации, описанной выше ?

Спасибо
Дмитрий.

Ответы [ 2 ]

1 голос
/ 11 мая 2010

Просто чтобы добавить, что говорит Аарона. Существует большая проблема с этим дизайном, так как валидация модели предметной области должна проверять только атрибуты, присущие модели, а не в контексте большей системы. Некоторыми примерами таких внутренних свойств могут быть требования к длине имени пользователя, допустимые символы, указание имени и фамилии и т. Д.

Проверка, которую вы выполняете, является общесистемной проверкой и относится к службе / хранилищу. Вот как будет выглядеть эта система, если она разработана с использованием доменного управления:

public class User : ICanValidate {
    public User() {} 

    public virtual string Username { get; set; }

    public IEnumerable<ValidationError> Validate() {
        if (!string.IsNullOrEmpty(this.UserName))
          yield return new ValidationError("Username must not be empty");
    }
}

public class UserRepository : IUserRepository {
}

public static class UserService { 
  readonly IUserRepository Repository;

  static UserService() {
    this.Repository = ServiceLocator.GetService<IUserRepository>();
  }

  public static IEnumerable<ValidationError> Validate(User user) {
      if (Repository.FindUserByUsername(user.Username) != null)
          yield return new ValidationError("Username", "User already exists.")
  }
}
1 голос
/ 11 мая 2010

Репозитории обычно находятся на более высоком уровне абстракции, чем доменные объекты, которые они выбирают / хранят. Если вы обнаружите, что ваши доменные объекты зависят от репозиториев, это свидетельствует о проблемах проектирования в восходящем направлении.

То, что у вас есть, это круговая зависимость. IUserRepository зависит от User, а User зависит от IUserRepository. Это технически работает, в том смысле, что оно будет компилироваться, если оба объекта находятся в одной сборке, но это приведет к неприятностям в качестве общего проекта. Могут быть всевозможные объекты, которые хотят иметь дело с User, но ничего не знают о IUserRepository, откуда он взялся.

Я предлагаю вам не делать это «валидационным» свойством User. Проверка должна выполняться самим хранилищем, или, что еще лучше, просто заставьте хранилище вызвать исключение, если имя пользователя уже существует, когда оно пытается сохранить.

Есть вторичная причина для этого предложения. Эта причина - параллелизм. Даже если вы подтвердите имя пользователя и обнаружите, что оно не уже существует, это может оказаться неверным через 1 секунду, когда вы попытаетесь сохранить этого пользователя. Поэтому вам нужно обработать исключительный случай (попытался вставить имя пользователя, которое уже существует) в любом случае . Учитывая это, вы также можете отложить это до последнего возможного момента, поскольку у вас нет возможности заранее дать гарантии.

Объекты домена должны иметь никаких зависимостей; если они самоутверждаются, то проверка должна зависеть только от фактического объекта, который проверяется, а не от других данных в базе данных. Ограничение на дублирование имени пользователя на самом деле является ограничением данных, а не ограничением домена.

Резюме: Переместите эту конкретную проверку за пределы User класса. Это не принадлежит там; вот почему вы используете именно этот анти-шаблон.

...