Лучший подход к генерации идентификаторов и хэшированию паролей с использованием модели DDD для пользовательского домена

Я новичок в DDD и сейчас занимаюсь регистрацией пользователей.

В основном поток:

  1. Контроллер получает запрос
  2. Сопоставляет полученные данные от клиента с моделью пользовательского домена.
  3. Сопоставляет модель пользовательского домена с моделью сущности EF-core.
  4. Добавляет пользователя в базу данных с помощью EF-Core.

У меня есть некоторые сомнения относительно того, где разместить класс IdGeneratorService и класс BCryptService. В настоящее время оба они имеют интерфейс на уровне домена: src/Domain/Model/Services. Их реализация находится внутри моего уровня инфраструктуры: src/Infrastructure/Services. Оба они также вызываются внутри модели домена пользователя:

public class User
{
    private User(
        long id, 
        string name, 
        string lastName, 
        string contactPhone, 
        string contactEmail, 
        string userName, 
        string password)
    {
        Id = id;
        Name = name;
        LastName = lastName;
        ContactPhone = contactPhone;
        ContactEmail = contactEmail;
        UserName = userName;
        Password = password;
    }

    public IEnumerable<Role> Type { get; set; }

    public static async Task<User> ConstructAsync(
        string name, string lastName, 
        string contactPhone, string contactEmail, 
        string userName, string password, 
        IIdGeneratorService<long> idGeneratorService, IBCryptService bCryptService)
    {
        var id = await idGeneratorService.GenerateAsync();

        password = bCryptService.HashPassword(password);

        return new User(id, name, lastName, contactPhone, contactEmail, userName, password); 
    } 

Это вызывается моим контроллером:

[HttpPost("[Action]")]
public async Task<IActionResult> Create([FromBody] UserModel userModel)
{
    var user = 
        await DomainUser.ConstructAsync(
            userModel.Name, userModel.LastName,
            userModel.ContactPhone, userModel.ContactEmail,
            userModel.UserName, userModel.Password,
            _idGeneratorService, _bCryptService);

    await _userRepository.AddAsync(user);

    return sampleResponse;
}

Я чувствую, что вызов IdGenerator и BCrypt внутри модели домена - плохая практика, потому что, насколько я понимаю, уровень домена не может знать об уровне инфраструктуры, поэтому я не совсем уверен, как мне это сделать. Любая другая помощь/предложения относительно других реализаций, основанных на том, что вы здесь поняли, приветствуется.


person G. Oliveira    schedule 15.08.2019    source источник


Ответы (1)


Рекомендуется вводить промежуточный уровень "Служба приложений" между контроллером и уровнем домена.

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

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

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

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

person Subhash    schedule 15.08.2019
comment
Спасибо за ответ, очень легко понять. У меня вопрос: вы сказали, что слою домена не нужно знать, как генерируется идентификатор или хэш, но эти сервисы используются только для генерации данных, связанных с моделями домена (по крайней мере, в моем примере я не буду использовать эти 2 услуги ни за что). Имея это в виду, имеет ли смысл поместить реализацию этих двух сервисов на уровень предметной области? И если нет, то что может быть примером службы на уровне предметной области? - person G. Oliveira; 16.08.2019
comment
Когда вы думаете о том, относится ли концепция к доменному слою, вопрос, который я считаю полезным задать, что произойдет, если вы поменяете реализации? Изменится ли в вашем примере поведение и функциональность вашего домена, если вы измените идентификаторы с целых чисел на GUID или с Bcrypt на SHA256? Если ответ отрицательный, лучше оставить их вне доменного уровня. Я знаю, что заманчиво включить их в доменный уровень, но в долгосрочной перспективе вы получите выгоду от того, что оставите их снаружи. - person Subhash; 16.08.2019
comment
Службы на уровне предметной области редко касаются таких аспектов, связанных с реализацией. Один хороший пример, который я могу придумать, — это генерация уникального идентификатора заказа. У вас может быть идентификатор заказа, состоящий из отдельных частей: идентификатора магазина, даты и порядкового номера. Такая служба генерации должна быть частью уровня вашей предметной области, поскольку идентификатор заказа является понятием предметной области и связан с другими элементами предметной области. - person Subhash; 16.08.2019
comment
Еще раз спасибо за ответы! Ваш второй комментарий вызвал у меня этот вопрос: разве идентификатор заказа не совпадает с рассматриваемым идентификатором пользователя? Поскольку в обоих случаях генерируются уникальные идентификаторы для определенного элемента домена. - person G. Oliveira; 16.08.2019
comment
Извините, я мог предположить немного здесь. Если ваш UserID является текущим порядковым номером или GUID, то он не связан с доменом. Это произвольный уникальный идентификатор, сгенерированный без какой-либо помощи со стороны самого домена. Но OrderID использует другие концепции домена в процессе генерации, и в этом разница. Был ли я неправ в своем предположении о UserID? - person Subhash; 16.08.2019
comment
Аааа да, я понял, что вы имели в виду. И да, я использую IdGen от RobThree. - person G. Oliveira; 16.08.2019
comment
Ok. IdGen кажется детерминированным (а не случайным), но он по-прежнему не зависит от домена — ему не нужно ссылаться на другие концепции домена. Я бы посоветовал вам относиться к этому как к уникальному идентификатору и размещать его вне уровня домена. Если вы когда-нибудь захотите использовать другой генератор идентификаторов, это будет простой и простой обмен. - person Subhash; 16.08.2019
comment
Подробнее об инкапсуляции идентификаторов в виде объектов-значений: buildplease.com/pages/vo-ids - person Subhash; 16.08.2019
comment
Это было отличное чтение, я буду иметь это в виду. Поэтому у меня было время подумать, и я считаю, что даже интерфейсы двух упомянутых сервисов должны быть на уровне инфраструктуры, потому что они не «моделируют» поведение каких-либо элементов домена, в отличие, например, от интерфейсов репозиториев, где они « модельные операции над элементами предметной области. Но должен ли уровень инфраструктуры определять поведение внешних служб (в данном случае IdGen и BCrypt) И реализовывать их? - person G. Oliveira; 16.08.2019
comment
Да, лучше иметь интерфейсы снаружи на уровне инфраструктуры. Таким образом, ваш домен остается чистым. Да, уровень инфраструктуры должен справляться со сложностями, связанными с конкретными реализациями, и предотвращать их утечку на уровень вашей предметной области (инкапсулированный). - person Subhash; 16.08.2019
comment
Конечно, далее говорится, что уровень инфраструктуры также предоставляет разные интерфейсы для разных вариантов использования. Например, вы можете использовать IdGen для большинства ваших идентификаторов, но можете использовать GUID, скажем, для событий. Служба приложения Event будет использовать соответствующую конкретную реализацию. Но на уровне предметной области ваша модель Event будет иметь ту же концепцию в отношении кода — уникальный идентификатор. - person Subhash; 16.08.2019