728x90
반응형

springboot 14

도메인이 다른 서비스 간 SSO 구현기

최근 회사에서 기존 메인 서비스(A) 외에 새로운 보너스 성격의 서비스(B)를 런칭하게 되었습니다. 이때 "A 서비스에 로그인한 유저가 B 서비스로 넘어갈 때, 다시 로그인하지 않게(SSO) 해주세요"라는 요구사항을 받았어요.문제는 두 서비스의 도메인이 완전히 다를 예정이라는 점이었습니다.도메인이 다르면 브라우저 보안 정책 때문에 기존의 HttpOnly 쿠키(RefreshToken)를 넘겨줄 수가 없거든요. "그럼 URL 파라미터로 AccessToken을 찔러 넣어줄까?" 고민도 했지만, 이는 보안상 절대 해서는 안 될 위험한 행동이었습니다.오늘 포스팅에서는 이 문제를 일회용 티켓(One-Time Ticket) 방식을 통해 어떻게 안전하고 효율적으로 해결했는지 그 설계와 구현 과정을 공유해보려고 합니다...

[Spring] P6Spy로 일관된 쿼리 로깅 환경 구축하기

실무에서 프로젝트를 진행하다 보면 JPA만 단독으로 쓰기보다는, 복잡한 통계 쿼리를 위해 QueryDSL을 섞어 쓰거나 레거시 코드로 인해 MyBatis, JdbcTemplate 등을 한 서비스 안에서 혼용하는 경우가 꽤 많죠.저도 이런 환경에서 개발하다 보니, 각 기술마다 쿼리가 찍히는 로깅 포맷이 달라서 디버깅할 때마다 로그 읽기가 너무 고통스럽더라고요.그래서 이 파편화된 로깅 방식을 한 번에 통일해서 잡아주는 P6Spy를 도입하게 되었고, 그 과정과 실무 적용 팁을 공유해 보려고 합니다.1. 왜 P6Spy를 도입해야 할까요?하나의 프로젝트에서 JPA, MyBatis, JdbcTemplate 등을 함께 사용하면 어떤 문제가 발생할까요?JPA(Hibernate): spring.jpa.show-sql=t..

Backend/Spring 2026.03.06

[Spring] WebClient 동기 처리 시 필수! TCP & Block 이중 타임아웃으로 안정성 확보하기 (RestClient 못 쓸 때)

최근 운영 중인 서비스에서 등에 식은땀이 흐르는 경험을 했습니다.잘 돌아가던 Quartz 스케줄러 Job이 어느 순간부터 로그 하나 없이 '멈춤' 상태가 되어버린 것이죠.에러 로그도 없고, 스레드는 살아있는데 작업이 진행되지 않는 좀비 상태였습니다.원인을 파헤쳐 보니 범인은 WebClient의 block() 메서드였습니다.외부 API 통신 중 예상치 못한 네트워크 이슈가 발생했는데, 타임아웃 설정이 제대로 되어 있지 않아 스레드가 무한 대기 상태에 빠진 것이었죠."WebClient는 비동기인데 왜 block을 써?"라고 하실 수 있지만, 레거시 시스템이나 비즈니스 로직상 반드시 동기 처리가 필요한 경우가 있잖아요?게다가 Spring 버전 문제로 최신 RestClient를 도입할 수도 없는 상황이었습니다...

Backend/Spring 2026.02.12

[Spring Cloud] 설정 파일 지옥 탈출: Config Server로 서비스 한방에 관리하기

"DB 비밀번호가 변경되었습니다. 각 서비스 담당자분들은 application.yml 수정해서 배포해주세요."서비스가 3개일 땐 괜찮았습니다.그런데 MSA 전환 후 서비스가 10개가 넘어가니, 저 메신저 알림이 공포로 다가왔습니다.10개 프로젝트의 코드를 다 수정하고, 빌드하고, 배포하는 데만 반나절이 걸리니까요.실수로 하나라도 빼먹으면 장애로 이어집니다."설정 파일만 따로 모아서 관리할 수는 없을까?"이런 고민을 해결해 주는 것이 Spring Cloud Config입니다.모든 마이크로서비스의 설정(yml)을 Git 저장소 한곳에서 관리하고, 서버 재시작 없이 설정을 변경하는 마법 같은 방법을 공유합니다.1. Spring Cloud Config의 핵심 원리구조는 생각보다 단순합니다.Git Reposito..

[Spring Cloud] 모든 요청은 이곳을 통한다: Gateway 기본 구축 가이드

지난 시간, 우리는 Eureka를 통해 흩어져 있는 마이크로서비스들이 서로를 찾을 수 있게 만들었습니다.이제 우리끼리는 통신이 됩니다.하지만 클라이언트(웹, 앱 사용자) 입장에서는 어떨까요?서비스가 10개라고 해서 사용자에게 "회원가입은 192.168.0.1:8081로 가시고, 주문은 192.168.0.5:8082로 가세요"라고 할 수는 없습니다. 보안상으로도 내부 IP를 노출하는 건 위험하죠.이럴 때 필요한 것이 단일 진입점(Single Entry Point), 바로 API Gateway입니다.오늘은 Spring Boot 진영의 표준인 Spring Cloud Gateway(SCG)를 사용해, 외부의 요청을 적절한 서비스로 토스해주는 '교통경찰' 역할을 만들어보겠습니다. 1. 왜 Spring Cloud ..

[Spring] @Controller vs @RestController 차이점과 REST API 구축 시 선택 가이드

pring Boot로 개발을 하다 보면 가장 먼저 마주치는 어노테이션 중 하나가 바로 컨트롤러 관련 어노테이션입니다.처음 공부할 때는 책이나 강의에 나오는 대로 Controller를 썼다가, 어느 순간부터 RestController를 자연스럽게 쓰게 되는데요."그냥 요즘은 다 RestController 쓰는 거 아니야?" 라고 생각하고 넘어가기엔, 이 둘의 동작 방식에는 꽤 중요한 차이가 숨어 있습니다.특히 View(화면)를 렌더링 할지, Data(JSON)를 반환할지에 따라 명확하게 구분해서 사용해야 하죠.오늘은 실무에서 API를 개발할 때 왜 @RestController를 사용하는 것이 표준이 되었는지, 그리고 내부적으로 어떤 마법(?)이 숨어있는지 정리해 보려고 합니다.1. 전통적인 강자, @Con..

Backend/Spring 2026.02.03

[Spring Cloud] MSA의 전화번호부, Eureka Server & Client 구축 가이드

지난 글에서 우리는 모놀리식을 쪼개어 MSA로 가기로 결심했습니다.그런데 막상 서비스를 A, B, C로 나누고 나니 황당한 문제가 생깁니다. "A 서비스가 B 서비스를 호출해야 하는데, B 서비스 IP가 뭐지?"로컬(localhost:8080)에서야 포트만 외우면 되지만, AWS나 Docker/K8s 환경에서는 배포할 때마다 IP가 동적으로 바뀝니다.그때마다 소스 코드에 박힌 IP(192.168.x.x)를 수정해서 재배포할 수는 없겠죠.이럴 때 필요한 것이 바로 Service Discovery입니다.오늘은 MSA의 '전화번호부' 역할을 하는 Netflix Eureka를 구축하고, 흩어진 서비스들을 하나로 묶어보겠습니다.1. Service Discovery가 왜 필요한가요?쉽게 비유하자면 '114 전화번호..

JPA 대량 데이터 동기화 성능 최적화: Bulk Update

현재 진행 중인 프로젝트에서 'Product(상품)' 데이터를 'Section(규칙)' 정보와 비교하여 동기화(Sync)하는 배치 작업을 구현하고 있었습니다.초기 로직은 단순했습니다.JPA로 Product와 Section 데이터를 모두 조회한다.Java 애플리케이션 메모리 상에서 비교 로직을 수행한다.변경된 데이터는 saveAll(products)로 저장한다.하지만 데이터 양이 늘어나면서 심각한 성능 저하가 발생했습니다.🚨 주요 원인JPA의 N+1 쿼리 발생: saveAll()을 호출해도 내부적으로는 merge()가 동작하면서, 각 엔티티마다 DB에 존재하는지 확인하는 SELECT 쿼리가 발생했습니다. 그 후 UPDATE 쿼리가 건건이 나가는 최악의 상황이었습니다.비효율적인 문자열 파싱: 비교해야 할 ..

Backend/Spring 2026.02.01

[Spring] 다국어 처리 끝판왕: DB + Redis 하이브리드 아키텍처 (feat. Service 순수성 지키기)

지난 글에서는 비즈니스 예외(422)와 시스템 에러(500)를 분리하는 전략을 다뤘습니다.오늘은 그 연장선에서 에러 메시지와 UI 텍스트를 효율적으로 관리하는 '다국어(i18n) 시스템'을 구축해 보겠습니다.보통 messages.properties 파일로 다국어를 관리하지만, 저희 실무 환경에선 이 방법이 더 좋았습니다. 특히 관리자들(개발자 아님)이 소스 파일을 보지 않고, 다국어 정보를 필요로 할때, 직접 DB로 조회하거나 혹은 DB 조회한 데이터를 쉽게 전달받아 관리할 수 있었습니다.그래서 저는 "관리는 DB에서 쉽게(기획자 친화적), 조회는 Redis에서 빠르게(성능 최적화), 코드는 깔끔하게(관심사의 분리)" 하는 하이브리드 전략을 사용합니다.1. 왜 DB에 저장하는가? (협업 관점)단순히 개발..

Backend/Spring 2026.01.31

[Spring] 실무 예외 처리: 500 에러와 비즈니스 예외(422) 완벽 분리하기 (feat. 하이브리드 패턴)

지난 글에서는 @Valid와 @Validated를 비교하고 ResponseEntityExceptionHandler를 활용해 요청 값의 형식을 검증하는 방법을 다뤘습니다.하지만 실무 개발은 여기서 끝나지 않습니다.입력 값의 형식은 맞지만, 비즈니스 규칙 때문에 거절해야 하는 상황이 훨씬 더 많기 때문이죠."사용자 ID 형식은 맞는데(400 아님), 이미 탈퇴한 회원이라 로그인이 안 돼." "결제 요청 양식은 완벽한데(400 아님), 잔액이 부족해서 결제할 수 없어." 이걸 단순히 400으로 퉁치자니 모호하고, 그렇다고 500을 뱉으면 모니터링 알람이 빗발칩니다.오늘은 비즈니스 예외를 422(Unprocessable Entity)로 명확히 분리하고, 시스템 장애(500)와 공존하며 유연하게 관리하는 실무 패..

Backend/Spring 2026.01.30