본문 바로가기
카테고리 없음

.NET CORE 백엔드 개발 (3) - 에러 핸들링

by 아마도개발자 2025. 10. 5.
반응형

에러 핸들링

 

애플리케이션을 운영하다 보면 예외(Exception)는 피할 수 없다. 단순한 에러부터 비즈니스 로직에 따른 예외까지, 예외가 발생했을 때 이를 일관된 방식으로 처리하지 않으면 유지보수성, 사용자 경험, 보안 측면에서 모두 문제가 생긴다.

특히 Clean Architecture에서는 Application, Domain, Infrastructure 등 레이어가 나뉘어 있기 때문에, 예외 처리 로직을 각 레이어에 흩뿌려두는 것은 적합하지 않다.
따라서 전역(Global) 에러 핸들링 미들웨어를 통해 예외를 한 곳에서 모아 관리하는 것이 효율적일 것이다.

 

글로벌 에러 핸들링의 필요성

  • 일관성: 클라이언트는 항상 정해진 포맷으로 에러 응답을 받을 수 있음.
  • 보안성: 스택 트레이스나 내부 DB 정보가 그대로 노출되는 것을 방지할 수 있음.
  • 유지보수성: 에러 처리 로직이 한 곳에 모여 있어 확장/수정이 용이.
  • 로깅: Serilog, ILogger 같은 로깅 프레임워크와 쉽게 통합할 수 있음.(불필요한 중복 방지)

 

에러 핸들링을 하기 전에 우선 미들웨어의 개념에 대해 알아야 한다. 미들 웨어(Middleware)서버의 요청(Request)과 응답(Response) 사이에서 특정 기능을 수행하는 함수이다. 
예를 들어, 모든 요청에 대해서 로그를 남기거나, API의 검증(Authentication & Authorization)을 원할 때 미들웨어를 사용할 수 있다. 또한 사용자가 웹 페이지에서 전송한 데이터를 일괄적으로 파싱하는 등의 역할도 한다. 즉 요청과 응답에서 일괄적인 처리를 가능하게 하는 함수이다.

 

여기서는 글로벌 예외처리를 위해 에러처리를 위한 미들웨어를 만들 것이다. 우선 Application레이어 에서 발생할 수 있는 커스텀 에러를 만들어 보자.

 

Application/Common/Exceptions/NotFoundException.cs

namespace TodoList.Application.Common.Exceptions
{
    public class NotFoundException : Exception
    {
        public NotFoundException(string entity, object key)
            : base($"{entity} ({key}) was not found.") { }
    }
}

 

Application/Common/Exceptions/ValidationException.cs

namespace TodoList.Application.Common.Exceptions
{
    public class ValidationException : Exception
    {
        public IDictionary<string, string[]> Errors { get; }

        public ValidationException(IDictionary<string, string[]> errors)
            : base("One or more validation failures have occurred.")
        {
            Errors = errors;
        }
    }
}

 

이제 위 에서 정의한 에러들을 일괄적으로 처리할 수 있는 미들웨어를 만들어준다.

 

Api/Middlewares/ErrorHandlingMiddleware.cs

using System.Net;
using System.Text.Json;
using TodoList.Application.Common.Exceptions;

namespace TodoList.Api.Middlewares
{
    public class ErrorHandlingMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly ILogger<ErrorHandlingMiddleware> _logger;

        public ErrorHandlingMiddleware(RequestDelegate next, ILogger<ErrorHandlingMiddleware> logger)
        {
            _next = next;
            _logger = logger;
        }

        public async Task InvokeAsync(HttpContext context)
        {
            try
            {
                await _next(context);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Unhandled exception occurred");

                var statusCode = ex switch
                {
                    NotFoundException => (int)HttpStatusCode.NotFound,
                    ValidationException => (int)HttpStatusCode.BadRequest,
                    _ => (int)HttpStatusCode.InternalServerError
                };

                var response = new
                {
                    error = ex.Message,
                    details = ex is ValidationException vEx ? vEx.Errors : null
                };

                context.Response.ContentType = "application/json";
                context.Response.StatusCode = statusCode;
                await context.Response.WriteAsync(JsonSerializer.Serialize(response));
            }
        }
    }
}

 

program.cs에 등록

// ✅ 글로벌 에러 핸들링 미들웨어
app.UseMiddleware<ErrorHandlingMiddleware>();

 

이 미들웨어는 에러가 발생했을 경우 에러 로그를 남겨주고, 발생한 에러에 따라 satusCode를 지정해준다. 또한 요청에 대한 응답(response)를 수정하여 요청자가 일괄된 형태의 응답을 받을 수 있도록 한다. 만약 이런 에러핸들링이 없었다면, db에서 발생한 오류문구가 그대로 노출되어 db스키마가 유출되거나 요청자가 대응할 수 없는 에러메시지를 뱉을 수도 있을 것이다.

 

이제 service의 GetTodoAsync 함수에 NotFoundException에러를 적용한 뒤, 유효하지 않은 요청을 날려보자.

public async Task<TodoDto> GetTodoAsync(int id)
{
    var todo = await _context.Todos.FindAsync(id);
    if (todo == null)
        throw new NotFoundException("Todo", id);

    return new TodoDto(todo.Id, todo.Title);
}

 

 

존재하지 않는 77번 id를 요청한 경우, Custom에러에서 뱉은 메시지인 "Todo (id) was not found"가 지정한 타입으로 잘 응답되고 있는 것을 볼 수 있다.

 

이처럼 글로벌 에러 핸들링은 Clean Architecture의 핵심 보조 장치다. 각 레이어의 예외를 한 곳에서 모아 처리하면 유지보수 시에 큰 도움이 되며 특히 로그 시스템과 함께 사용하면 강력한 모니터링이 가능하기 때문에 꼭 서비스에 추가를 해두는 것이 좋다.

 

소스코드

https://github.com/FreeBono/EasyCleanArchitecture

 

GitHub - FreeBono/EasyCleanArchitecture: This project is made with a clean architecture that is easy to follow.

This project is made with a clean architecture that is easy to follow. - FreeBono/EasyCleanArchitecture

github.com

 

반응형