I’m new to Clean Architecture and EF Core, and I’m trying to learn by practicing and watching tutorials. I’ve been implementing a basic authentication flow and would appreciate some feedback on whether I’m structuring my code correctly according to Clean Architecture principles.
Here’s the structure of my code:
this is my controller:
[ApiController]
[Route("/auth")]
public class AuthController(IAuthService authService, IMapper mapper) : ControllerBase
{
[HttpPost("sign-up")]
[AllowAnonymous]
public async Task<IActionResult> RegisterUser([FromBody] EssentialSignUpCredentials registerCredentials)
{
var clientEmail = registerCredentials.email;
var clientUserName = registerCredentials.userName;
var ipAddress = HttpContext.Connection.RemoteIpAddress?.MapToIPv4();
var response=await authService.SignUpAsync(registerCredentials.email, registerCredentials.userName, ipAddress);
return response.Match<IActionResult>(Ok, BadRequest);
}
this is my auth service sign up method:
public async Task<Result> SignUpAsync(string email, string userName, IPAddress ipAddress)
{
await using var transaction = await dbContext.Database.BeginTransactionAsync();
var response = await userService.CreateUserAsync(email, userName, ipAddress);
return await response.MatchAsync<Result, User>(async success =>
{
var rolesResponse = await userService.AddRolesToUserAsync(success.Data);
if (!rolesResponse.isSuccess)
return Result.Failure(rolesResponse.FailureValue.errors.ToArray(),
rolesResponse.FailureValue.StatusCode);
await transaction.CommitAsync();
return Result.Success();
}, failure => Result.Failure(response.FailureValue.errors.ToArray(), response.FailureValue.StatusCode));
}
and lastly this is the user service :
public async Task<Result<User>> CreateUserAsync(string email, string userName,
IPAddress ipAddress)
{
if (string.IsNullOrEmpty(email) ||
string.IsNullOrEmpty(userName))
{
logger.LogInformation("user didnt fill credentials");
return Result<User>.Failure(["user didnt fill credentials".ToUpper()], StatusCodes.Status400BadRequest);
}
var userToBeRegistered = new User
{
Email = email, UserName = userName,
IpAddress = ipAddress.ToString()
};
var response = await userManager.CreateAsync(userToBeRegistered);
if (!response.Succeeded)
{
List<string> errorsList = [];
foreach (var responseError in response.Errors)
{
var userError = responseError.Code switch
{
"DuplicateUserName" => "the username is already in use please choose another one".ToUpper(),
"DuplicateEmail" => "the email address is already in use please choose another one".ToUpper(),
"PasswordTooShort" => "the password must contain at least 6 characters",
"PasswordRequiresNonAlphanumeric" => "please use both characters and numbers for your password",
_ => "An error occurred. Please try again"
};
errorsList.Add(userError);
}
logger.LogInformation(string.Join(", ", response.Errors));
return Result<User>.Failure(errorsList.ToArray(), StatusCodes.Status400BadRequest);
}
My Approach Controller: Thin controller with all the logic moved to AuthService. Service Layer: Contains business logic for signing up a user, ensuring database transaction management, and handling roles. UserService: Handles direct interaction with UserManager for user creation and error mapping.
Is my approach correct in terms of Clean Architecture principles (keeping the controller thin and moving logic to services)?
Am I managing responsibilities correctly by separating the AuthService for business logic and UserService for user-specific operations?
Are there any improvements you would recommend in terms of structure, error handling, or code practices?
Any advice or constructive feedback would be greatly appreciated!
Clean Code? \$\endgroup\$Clean Architecturethen you will need to provide more context, such as how the project is structured, and show some code that supports that structure. Currently your question lake that, so we can't review it. But we can review it asClean codefor that part only. \$\endgroup\$