Где я должен поставить уникальную проверку в DDD?

Я работаю над своим первым проектом DDD, и я думаю, что понимаю основные роли объектов, объектов доступа к данным и их отношений. У меня есть базовая реализация проверки, которая хранит каждое правило проверки с его ассоциированным объектом. Это отлично подходит для правил, которые применяются только к текущему объекту, но разваливается, когда нужны другие данные. Например, если у меня есть ограничение на то, что имя пользователя должно быть уникальным, я бы хотел, чтобы вызов IsValid() возвращал false, когда есть существующий пользователь с текущим именем.

Однако я не нахожу никакого чистого способа сохранить это правило проверки для самой сущности. Я хотел бы иметь функцию IsNameUnique для сущности, но большинство решений для этого потребовали бы, чтобы я вводил объект доступа к пользовательским данным. Должна ли эта логика находиться во внешней службе? Если да, то как я все еще сохраняю логику с самой сущностью? Или это то, что должно быть вне пользовательского объекта?

Спасибо!

Ответ 1

Мне нравится ответ Сэмюэля, но для простоты я рекомендовал бы реализовать Specification. Вы создаете спецификацию, которая возвращает логическое значение, чтобы увидеть, соответствует ли объект определенным критериям. Внесите IUserRepository в спецификацию, проверьте, существует ли пользователь с этим именем и возвращает логический результат.

public interface ISpecification<T>
{
  bool IsSatisfiedBy(TEntity entity);
}

public class UniqueUsernameSpecification : ISpecification<User>
{
  private readonly IUserRepository _userRepository;

  public UniqueUsernameSpecification(IUserRepository userRepository)
  {
    _userRepository = userRepository;
  }

  public bool IsSatisfiedBy(User user)
  {
    User foundUser = _userRepository.FindUserByUsername(user.Username);
    return foundUser == null;
  }
}

//App code    
User newUser;

// ... registration stuff...

var userRepository = new UserRepository();
var uniqueUserSpec = new UniqueUsernameSpecification(userRepository);
if (uniqueUserSpec.IsSatisfiedBy(newUser))
{
  // proceed
}

Ответ 2

В DDD существует концепция, называемая aggregate. Он в основном отвечает за согласованность в приложении.

IMHO, в данном случае конкретно, я полагаю, что CustomerRepository будет внутри чего-то вроде "агрегата клиента", являющегося классом Customer, совокупным корнем.

Корень будет отвечать за выполнение всего этого, и никто другой не сможет получить доступ к параметрам CustomerRepository. И есть некоторые возможности:

  • КлиентRepository может генерировать исключение, если имя не уникально (и Клиент будет улавливать и возвращать ошибку или что-то в этом роде)
  • У CustomerRepository может быть IsValidUserName(), которое Клиент вызывал, прежде чем делать что-либо еще.
  • Любой другой вариант, который вы можете придумать

Ответ 3

Я собираюсь сказать, что это просто выходит за рамки DDD.

Что вы имеете с DDD - это совокупность событий, которые создают полезную модель. Однако отношения данных между такими моделями необязательно возможны.

Какую модель согласованности вы используете?

Если вы можете совершать несколько событий в транзакции ACID, вы можете гарантировать, что изменения в группе агрегатов происходят атомарно.

Если вы используете конечную модель согласованности, вы, возможно, не сможете фактически подтвердить эти вещи до конца. И когда вы это сделаете, вам, возможно, придется компенсировать вещи, которые предположительно произошли, но больше не действительны.

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

Если нет, то вы просто проецируете свою модель на базу данных, которая поддерживает ограничение уникальности. Если этот провал не сработает, вы должны вернуться к своему агрегату и как-то пометить его как недействительный. Все это время нужно учитывать неудачу. Здесь можно использовать распределенный долговременный процесс, такой как шаблон саги, но также требует, чтобы вы думали о дополнительных вещах.

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