Луковая архитектура - DTO в интерфейсах

Я новичок в Onion Architecture и пытаюсь применить ее в проекте ASP.NET. В настоящее время я реализую ASP.NET Identity и хочу использовать его класс UserManager для хранения пользователей.

Из того, что я понял из Onion Architecture, я должен создать интерфейс на уровне домена, который можно использовать для обертывания UserManager.

public interface IUserManager
{
    public Task CreateAsync(string username, string password);
}

А на уровне инфраструктуры я бы затем реализовал свой собственный UserManager, который является оболочкой для ASP.NET Identity UserManager.

using Microsoft.AspNetCore.Identity;

public class MyUserManager : IUserManager
{
    private readonly UserManager<IdentityUser> _userManager;
    
    public UserManager(UserManager<IdentityUser> userManager)
    {
        _userManager = userManager;
    }

    public async Task CreateAsync(string username, string password)
    {
        var user = new IdentityUser { UserName = username }; 
        await _userManager.CreateAsync(user, password);
    }
}

Проблема

Моя проблема в том, что я также хотел бы возвращать возможные ошибки из UserManager.CreateAsync(...) вместо того, чтобы иметь тип возвращаемого значения void. Выглядит примерно так:

public class IdentityResult
{
    public bool Succeeded { get; set; }
    public IEnumerable<string> Errors { get; set; } // Some Error type instead of a string would be better
}

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


person octagon_octopus    schedule 19.07.2020    source источник


Ответы (1)


С точки зрения архитектуры программного обеспечения, Интерфейс - это не только фактическое определение interface (т.е. ваше IUserManager), но и все, что является частью контракта между двумя компонентами, например входные и выходные DTO функций и исключения, которые реализация может бросить, и вызывающий должен уметь поймать.

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

person Francesc Castells    schedule 19.07.2020
comment
Ваше объяснение DTO имеет большой смысл, спасибо. Итак, вы также говорите, что я должен определить исключения, которые реализация интерфейса может вызывать как часть домена? Я бы подумал, какие исключения могут быть созданы - это зависит от реализации. Скажем, у меня есть функция GetUserById(int id), я бы не хотел определять что-то вроде FileNotFoundException в домене, но, возможно, определение UserNotFoundException в домене имело бы смысл (конечно, все гипотетически), хотя я мог бы также оставить это на усмотрение реализации. - person octagon_octopus; 20.07.2020
comment
Исключения часто упускаются из виду. Подумайте о предоставлении XML-документации для ваших интерфейсов. Для такого метода, как GetUserById, вы документируете, что он делает и что возвращает, когда работает. Но вам также необходимо предоставить информацию о том, что произойдет, если пользователь не существует. Вы вернете null? вы вернете Result ‹User› со свойством перечисления Error с ошибкой NotFound? или вы выбросите NotFoundException? Если вы генерируете исключение, вам нужно указать, какое исключение, и убедиться, что у потребителя этого исключения есть ссылка на него. - person Francesc Castells; 20.07.2020
comment
Вы не собираетесь указывать каждое исключение, которое может вызвать ваш метод, например сетевые ошибки, нехватку памяти и тому подобное. Вы будете документировать и публиковать только те, которые являются частью вашего языка интерфейса. - person Francesc Castells; 20.07.2020
comment
Вот как я это действительно понял. Сделать исключения частью контракта (C # interface) будет невозможно, но достаточно указать исключения в документации XML. Спасибо за вашу помощь! - person octagon_octopus; 20.07.2020