소개
호주에 살고 있는 소프트웨어 개발자입니다. 30년간 다양한 분야의 시스템과 서비스를 개발해본 경험이 있습니다.
스프링 프레임워크와 관련 기술을 좋아하고 JVM 기반 언어를 주로 사용합니다.
한국스프링사용자모임(KSUG)을 설립하고 활동했고, 토비의 스프링이라는 책을 쓰기도 했습니다.
개발과 관련된 다양한 주제에 관해 이야기하는 것을 좋아합니다.
강의
전체3수강평
- 좋은 강의 감사합니다. 잘 들었습니다.
김태민
2024.10.07
1
- 이제야 이해가 되어요
neobns.jwlee
2024.10.03
1
- 감사합니다.
is kim
2024.09.30
1
게시글
질문&답변
2024.10.04
애플리케이션 예외 사용에 대한 질문
날카로운 질문을 해주셨네요. 지금 예로 드신 InsufficientBalanceException을 복구가능한 정상적인 작업흐름, 그러니까 비즈니스적으로 의미있는 설계된 플로우의 하나로 볼 것인가, 또 그게 어느 정도의 비율로 나타날 것인가 등에 따라서 사실 접근 방법은 조금씩 다른 듯합니다. InsufficientBalanceException을 정상적인 비즈니스 로직의 케이스의 하나로 보지만 극히 드물게 일어난다고 가정을 해보죠. 예를 들어 프론트에서 이미 잔고를 확인한 상태에서만 이체 요청을 한다거나 결제를 하도록 만들었는데, 하지만 그 순간 다른 결제가 돌아가면서 잔액이 부족해지는 경우가 있습니다. 아주 드물지만 없지 않죠. 비율이 얼마나 될까요. 전체 이체 요청 만 건당 하나라고 보면, 이건 예외적인 상황이 맞습니다. 그래서 이런 건 시스템 장애나 버그에 의한 예외는 아니지만 극히 예외적인 상황이라서 Exception을 사용하고 싶은 거죠. 그러면서 이런 시도를 했다는 기록을 DB에 남기고 싶다면, 체크예외를 쓸 수 있습니다. 그러면 이체 자체를 제외한 나머지 DB 작업은 트랜잭션이 커밋되면서 남게 되고, 이 예외를 앞의 Controller가 받아서 이 경우에 사용자에게 보내줄 알림을 전해줄 수 있겠죠. 그런데 이런 0.01% 수준의 예외적인 케이스를 위해서 리턴 값에 의미를 부여해서 잔액 부족은 -1. 이런식으로 정해서 이걸 if로 잡는 코드를 넣으면, 이를 처리하는 코드가 뭔가 메인이 되는 느낌이죠. 게다가 응답 값에 의미를 부여하는 것은 자칫 버그가 발생하기 쉽죠. 어떤 개발자는 이 서비스 코드를 호출하면서 0보다 작으면 에러인가 이럴 수도 있고, 어떤 개발자는 이거 상수로 정의해야 하는 거 아닌가 해서 INSUFFICIENT_BALANCE = -1 이렇게 정의해서 쓸 수도 있고, 나중에 예외 값은 -100으로 하자고 고치게 되면 로직이 틀어지겠죠. enum으로 정의할 수도 있지만, 다른 응답 값이 있는 경우에 예외 경우를 체크하기 위한 enum을 같이 리턴하려면 또 클래스로 만들어야 하죠. 그래서 이때는 체크 예외를 만들어서 이런 경우가 있을 수도 있다는 걸 사용하는 컨트롤러 등의 코드에 명확하게 알려주고, 이때 대응하는 코드를 만들도록 강제할 수 있습니다. 아마 책에서는 이걸 강조해서 설명했던 것 같습니다. 강의에서 이 케이스를 다루지는 않았지만, 핵심은 복구 불가능한 예외는 예외로 처리하고 굳이 핸들링하지 않는게 맞다는 것을 이야기했습니다. 이 잔액 부족은 복구 가능한 것일까요? 여기서 예외를 프론트엔드까지 다루는 시나리오에 따라서 여러 판단이 있을 수 있을 듯합니다. 이걸 의미있게 처리하기 위해 정상 플로우의 특별한 상황으로 정의하고 API에서 특정 응답을 주도로 만들고, 프론트는 이런 잔액부족 상황을 알게 되면 다시 최신 잔액을 확인하는 API를 호출하게 만들고, 안내를 주도록 할 수 있죠. 또는 이걸 응답에서 그래서 부족한 이유와 잔액을 이 케이스에서는 API 응답으로 만들 수도 있습니다. 또는 이건 어쨌든 처리할 수 없는 예외상황이니 메시지만 딱 보여주고 말겠다면, 서비스에서부터 런타임 에러를 던지고, @ControllerAdvice에서 그 Exception에 담긴 에러 메시지를 담아서 리턴하고, 프론트는 에러 메시지를 띄워주고, 사용자는 다시 잔액 확인하러 뒤로 가고.. 등등의, 정말 예외적인 케이스니까 굳이 이걸 처리하는 코드를 복잡하게 만들지 않도록 할 수 있습니다. 어쨌든 예외 상황을 리턴 값으로 다루지는 마시고, 복구 가능한 예외인지 아니면 일반적인 정말 예외 상황인지에 따라서 예외 타입을 결정해서 사용하시면 될 듯합니다.
- 0
- 1
- 25
질문&답변
2024.09.23
프로퍼티 빈의 후처리기 도입 AnnotationUtils의 사용
오, 이건 제가 몰랐던 방법이네요. 이렇게 annotation.prefix()를 바로 이용하는 게 더 간단하겠네요.
- 0
- 2
- 34
질문&답변
2024.09.20
SimpleCacheConfiguration과 빈 등록
스프링 부트 내부의 자동구성 설정은 꽤 복잡하게 다양한 옵션을 적용할 수 있도록 구성되어 있습니다. 특히 Customizer가 붙은 것들은 프로퍼티 파일과 결합해서 다양한 방식으로 기본 구성의 변경이 필요할 때 그 책임을 맡기는 용도로 사용되어지는데요. 말씀하신 것처럼 conditional 조건에 부합된 @Configuration 클래스 내부에 @Bean 팩토리 메소드가 있는데 거기 파라미터로 넘겨 받는 다른 오브젝트가 존재하지 않으면 실행될 때 에러가 나겠죠. 아마 기본 SimpleCacheConfiguration 외에 다른 자동 구성 클래스 어딘가에서 해당 프로퍼티와 커스토마이저 빈이 등록되도록 설정된 곳이 있을 겁니다. 디버거를 걸어서 @Bean 메소드 실행시점을 잡아서 보면 아마 그런 오브젝트가 들어와 있을 것입니다. 이게 어느 설정에서 만들어졌는지를 추적하려면 스프링 부트 소스코드 전체를 가져다가 검색을 해보면서 따라가봐야 하는데 꽤 복잡한 작업이라, 일단 어디선가 만들어지는 곳이 있다고만 설명을 드릴게요. 🙂
- 0
- 2
- 38
질문&답변
2024.09.20
paymentService를 호출 할때 new로 생성한다면
@Autowired 같은 것은 스프링 컨테이너가 new Paymentservice() 등을 호출해서 의존관계를 맺을 때 참고하도록 넣어주는 정보입니다. 스프링이 하는 일을 직접 코드로 만들어 본 것이 강의의 Client 클래스의 main() 메소드 코드입니다. 이건 내부에서 어떻게 오브젝트가 만들어지고 의존관계가 맺어지는지를 이해하도록 코드를 직접 넣은 것일 뿐입니다. 실제로 스프링 애플리케이션을 띄우면 이런 작업을 스프링 컨테이너가 직접 합니다. 그리고 많은 요청을 처리할 때 매번 PaymentService를 새로 만들 필요가 없기 때문에 한번 만들어진 PaymentService 오브젝트를 계속 재사용합니다. 그래서 불필요하게 오브젝트를 많이 만드는 일은 일어나지 않습니다. 일단 스프링 내부의 동작 방식을 이해하셨다면 개발자가 직접 new PaymentService() 등을 호출해서 사용할 일은 없습니다. 이런 작업을 대신 해주고, 한번 만든 오브젝트를 재사용하도록 관리해주는 등의 책임을 맞기기 위해서 스프링과 같은 프레임워크를 사용하는 것이죠.
- 0
- 2
- 66
질문&답변
2024.09.08
인터페이스는 사용하는 클래스에 가장 가까이 두는 걸로 이해했습니다. 하지만 그 인터페이스를 사용하는 클라이언트가 많다면 어떻게 해야할까요?
인터페이스를 구현과 분리하는 방식을 쓸 때, 기본적으로는 인터페이스의 클라이언트와 인터페이스를 가까이 둡니다. 그런데 인터페이스를 사용하는 클라이언트가 여러개라면? 이 때는 별도의 위치로 분리하는 수 밖에 없죠. 중요한 건 모듈 레벨에서, 혹은 계층 레벨에서 클라이언트와 같은 레벨에 두는 겁니다. 구현과 함께 또는 가까운 쪽이 아니고요. 패키지는 분리될 수 있겠지만요.
- 0
- 2
- 76