The best way to handle Errors in Clean Arch

Hello, everything okay? I have a question. I want to know what is the best way to handle errors in my application using Clean Architecture. To put you in context, I am creating a layered API, that is, there is the Infrastructure that contains the repository and the application layer that contains the service. The logic goes in the service, for example, when a user registers and their username has at least 6 characters, then it throws an error that is ArgumentException with a message:
throw new ArgumentException("The Password must be at least 6 characters");
throw new ArgumentException("The Password must be at least 6 characters");
Now, in the controller, what is the best way to handle those errors? Why does it always return a status code of 500, while the error could be 401? Also, how do I resolve the message? Thank you so much. I leave you images so that you can put yourself in context.
21 Replies
peladodocinero
peladodocineroOP11mo ago
MemberService
No description
peladodocinero
peladodocineroOP11mo ago
Controller
No description
blueberriesiftheywerecats
You can create error handling middleware
blueberriesiftheywerecats
Example of my error handling You need to add this middleware before routing and just use throw And you better make your own exception with status code in it like i did
peladodocinero
peladodocineroOP11mo ago
THANKS
Pobiega
Pobiega11mo ago
Thats certainly one way, and its a good way to make sure no exceptions go unlogged (I use such a middleware to make sure any request->response pipeline that fails gets properly logged in Seq). It has a minor downside thou - now your business logic is suddenly HTTP aware. Personally, I like to keep my business logic away from protocol specific awareness when possible. An example is that your service method needs to know that it should throw a very specific exception that will leads to a 403. My personal solution to this is that my "services" (I don't use service objects, they are too similar to god-classes for my tastes) instead return stronly typed result objects, and the endpoints (controller actions or similar) handle the business error -> HTTP conversion.
blueberriesiftheywerecats
What do u do when u need to get authorised user id in application layer Do u use httpcontext accessor or pass id through request
Pobiega
Pobiega11mo ago
access the http context in your endpoint and pass the correct user object down into the handler
peladodocinero
peladodocineroOP11mo ago
Do not hear the moment you handle it and decide the statusCode. Siempre returned 500, no? . That's exactly what I mean. My service is in the Application layer, I don't want it to return a BadRequest, since that is from API and Http. I am a Typescript backend programmer in Nodejs, and before, I did what you mentioned, that is, returning an object with a message and its StatusCode. Is it functional? Does it belong to Clean Arch? Is there any other alternative? I'm reading the message again and you understood me perfectly lol Can we make a call so can you help me? I'm new and I can't figure out how to do it. Because I have many services, and for example, in the users service, I want to return a JSON object of this style: { statusCode: ..., message: ..., [MODEL] } The [MODEL] depends on the service. In the example I am giving you, the Model would be the UserDTO I don't know what would be the best way to make a general contract between all the services to return that JSON
blueberriesiftheywerecats
It returns 500 if thrown error wasn't ApiException Otherwise it will take code from the ApiException Do u have a github rep with example?
Pobiega
Pobiega11mo ago
Sadly not. But its fairly simple Exactly how to implement it depends on what Result library you use, since its not a standard thing. My personal preference is Remora.Result (made by our very own Jay), but OneOf is probably more familiar for a Typescript developer, and there is also ErrorOr and Rascal (made by our very own Thinker). https://github.com/Remora/Remora.Results https://github.com/mcintyre321/OneOf https://github.com/amantinband/error-or https://github.com/thinker227/Rascal Either of these are fine.
[HttpGet]
public async Task<ActionResult<TodoListDto>> Get(int id)
{
var query = new GetTodoListQuery()
{
Id = id
};

var result = await _mediator.Send(query);
if (!result.IsSuccess)
{
return result.Error switch
{
NotFoundError notFoundError => NotFound(notFoundError.Message),
ValidationError validationError => BadRequest(validationError.ValidationFailures),
_ => Problem(detail: result.Error.Message, title: "Internal Server Error", statusCode: 500)
};
}

return _mapper.Map<TodoListDto>(result.Entity);
}
[HttpGet]
public async Task<ActionResult<TodoListDto>> Get(int id)
{
var query = new GetTodoListQuery()
{
Id = id
};

var result = await _mediator.Send(query);
if (!result.IsSuccess)
{
return result.Error switch
{
NotFoundError notFoundError => NotFound(notFoundError.Message),
ValidationError validationError => BadRequest(validationError.ValidationFailures),
_ => Problem(detail: result.Error.Message, title: "Internal Server Error", statusCode: 500)
};
}

return _mapper.Map<TodoListDto>(result.Entity);
}
something like this, with Remora The pattern itself works fine without a mediator or a mapper, but those are things I personally like using
blueberriesiftheywerecats
So the object mediator return is something like Results<TResponse> where Results have properties like Error error bool isSuccess TResonse Entity?
Pobiega
Pobiega11mo ago
yeah, something like that https://github.com/thinker227/Rascal/blob/main/src/Rascal/Result.cs look here for an example this is Rascal, but the same concept applies
blueberriesiftheywerecats
solution seems pretty good
Pobiega
Pobiega11mo ago
yeah I like it you could replace the mediator.Send call with your service and method, if you prefer that pattern I strongly recomend using dtos and a mapper of some sort (manual is fine too) thou, to prevent oversharing
blueberriesiftheywerecats
I think i can combine both this and middleware
Pobiega
Pobiega11mo ago
you can. I use the middleware to log exceptions, and "this" to handle my errors unexpected exceptions sometimes leak through to the middleware, which is exactly what its purpose is
blueberriesiftheywerecats
Yeah, thanks for your help
FrannoINC
FrannoINC11mo ago
You just need to be careful of returning "just any" exception in your API responses. From a security point of view, that could give an attacker valuable information.
Pobiega
Pobiega11mo ago
your global exception handler should not return any information about the exception in a production setting, just "internal server error" or something similar only in debug/qa/local mode should it expose exception details
Want results from more Discord servers?
Add your server