0

I'm new to Onion Architecture and am trying to apply it in an ASP.NET project. I'm currently implementing ASP.NET Identity and want to use its UserManager class to store users.

From what I understood from Onion Architecture is that I should create an interface in the domain layer that can be used to wrap the UserManager.

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

And in the infrastructure layer I would then implement my own UserManager that wraps the 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);
    }
}

Problem

My issue with this is that I would also like to return possible errors from UserManager.CreateAsync(...) instead of having a void return type. Looking something like this:

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

I'd have to define this DTO in the domain layer since they have to be part of the IUserManager interface, but I'm not sure if that's the right approach. From the open source projects that I've found I see none of them using DTOs in the domain layer and people seem to be saying that DTOs are generally an application concern, but I could just be waaay overthinking it. Maybe I'm already taking the wrong approach with the way I'm currently doing it?

octagon_octopus
  • 1,194
  • 7
  • 10

1 Answers1

1

From a software architecture point of view, an Interface is not only the actual interface definition (ie. your IUserManager) but everything that is part of the contract between two components, like the functions' input and output DTOs and the exceptions that the implementation might throw and the caller has to be able to catch.

In .NET you define all these in the same project so that you can distribute it as a package if necessary. In onion-architecture, technically these are part of the core, but that doesn't mean that the whole core needs to be defined in a single project. If you separate the core implementation from the contracts, you can distribute the contracts without distributing also the implementations.

Francesc Castells
  • 2,692
  • 21
  • 25
  • Your explanation about the DTOs makes a lot of sense, thanks. So what you are also saying is that I should define the exceptions that the implementation of the interface might throw as part of the domain? I would've thought that it is up to the implementation what kind of exceptions might be thrown. Say I have the function `GetUserById(int id)`, I wouldn't want to define something like a `FileNotFoundException` in the domain, but maybe defining a `UserNotFoundException` in the domain would make sense (all hypothetical of course), although I could also leave that to the implementation. – octagon_octopus Jul 20 '20 at 11:53
  • The exceptions is something that many times get overlooked. Think about providing XML documentation of your interfaces. For a method like GetUserById, you'll document what it does and what it returns when it works. But you also need to provide information about what will happen if the user does not exist. Will you return null? will you return a Result with an Error enum property with error NotFound? or will you throw a NotFoundException? If you throw an Exception you'll have to specify which exception and make sure that the consumer of that exception has a reference to it. – Francesc Castells Jul 20 '20 at 14:07
  • You are not going to specify every single exception that your method can throw, like network errors, out of memory and what not. You will document and publish only the ones that are part of your interface language. – Francesc Castells Jul 20 '20 at 14:09
  • That's how I understood it indeed. It's going to be impossible to make the exceptions part of the contract (the C# `interface`), but providing the exceptions through the XML documentation should be sufficient. Thanks for your help! – octagon_octopus Jul 20 '20 at 14:15