10/18
·
TIL
Today I Learned오늘은 인증/인가 관련 구현을 하면서 여러가지 설계에 대한 고민을 해봤다. 1. 회원가입 시 UserRole 설정 전략회원가입 시 `UserRole`을 입력받는 방식은 보안상 위험할 수 있다고 생각했다.그래서 아래와 같이 일반 사용자 가입 시에는 `ROLE_USER`를 코드에서 자동 설정하는 방식으로 구현했다.public record SignupRequest( @NotBlank(message = "회원이름은 필수입니다.") String username, @Email(message = "올바른 이메일 형식이 아닙니다.") String email, @NotBlank(message = "비밀번호는 필수입니다.") ..
10/17
·
TIL
Today I Learned비밀번호 유효성 검증 방식 비교회원가입 시 비밀번호에 대소문자, 숫자, 특수문자를 각각 1개 이상 포함하도록 검증해야했다.1. @Pattern 방식 (정규식 기반)@Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\\\d)(?=.*[!@#$%^&*()_+\\\\-=]).{8,20}$", message = "비밀번호는 영문 대소문자, 숫자, 특수문자를 포함한 8~20자여야 합니다.")private String password;장점빠르게 적용 가능별도 클래스 없이 DTO에 바로 붙일 수 있음 단점정규식이 복잡하고 가독성이 떨어짐조건 추가/변경 시 유지보수가 어려움테스트나 디버깅이 불편함2. @ValidPassword 방식 (커스텀 ..
10/16
·
TIL
Today I Learned검색 API 설계와 QueryDSL 활용1. 검색 API 설계처음 검색 기능을 만들 때 가장 먼저 고민한 건 검색 조건이 없을 때 어떻게 동작해야 하는가였다.예를 들어 키워드, 날짜 범위 등 조건이 모두 비어 있으면 전체 데이터를 반환해야 할까? 아니면 예외를 던져야 할까? 내가 선택한 방식조건이 없으면 전체 데이터를 페이징하여 반환단, Pageable은 반드시 적용해서 성능 문제를 방지BooleanBuilder builder = new BooleanBuilder();if (StringUtils.hasText(condition.keyword())) { builder.and(...);}if (condition.fromDate() != null) { builder.and..
10/15
·
TIL
Today I Learned조회수 vs 좋아요: 동시성 처리 전략 비교와 설계 고민지난 프로젝트에서 좋아요 기능의 동시성 문제를 낙관적 락으로 해결한 경험이 있다. 당시에는 사용자마다 좋아요 상태를 정확히 관리해야 했기 때문에 낙관적 락이 적합하다고 판단했다. 그런데 이번에는 조회수 증가 로직을 설계하면서, 좋아요처럼 낙관적 락을 써야 하나? 라는 의문이 들었고, 두 기능의 성격 차이를 고민하게 되어 이렇게 정리해본다.조회수와 좋아요의 기능적 차이항목조회수좋아요성격단순 접근명시적 클릭취소 가능성없음있음정확성 요구대략적인 누적이면 충분누가 눌렀고 정확한 누적 숫자 파악 필요중복 허용허용제한조회수는 "얼마나 많은 사람이 봤는가"를 누적하는 지표이고, 좋아요는 "누가 좋아했는가"를 기록하는 지표이다. 따라서 ..
9/29
·
TIL
Today I Learned#리뷰 좋아요 API 구현In-Memory Cache 와 동시성 문제를 해결해보고자 하는 마음으로 리뷰 도메인에 좋아요 기능을 추가 구현했다.리뷰 좋아요는 리뷰에 대한 반응이므로 리뷰 도메인 속한다고 판단했고, 도메인 내부에 `like` 패키지를 만들어줬다. public class Review extends BaseEntity { @Column(nullable = false) private int likeCount = 0; public void incrementLikeCount() { this.likeCount++; } public void decrementLikeCount() { this.likeCount = Math.max..
9/28
·
TIL
Today's Codekataimport java.util.Stack;class Solution { public int solution(String s) { int count = 0; for (int i = 0; i stack = new Stack(); boolean isValid = true; for (char c : rotated.toCharArray()) { if (c == '(' || c == '[' || c == '{') { stack.push(c); } else { if (stack.isEmpty()) ..
9/24
·
TIL
백엔드의 역할에 대해최근 프로젝트를 진행하다 보면 프론트엔드와 백엔드의 경계가 생각보다 명확하지 않다는 걸 자주 느낀다.실사용자 경험이나 화면 로직에 대한 이해가 부족하다 보니 이건 백엔드가 해야 할 일인가 하는 의문이 생기곤 한다.그래서 이번 글에서는 백엔드의 역할과 프론트엔드와의 구분을 정리하면서,실제 협업 환경에서 어떤 기준으로 역할이 나뉘는지 정리해보려 한다.백엔드란?백엔드는 눈에 보이지 않는 시스템의 내부다.사용자가 화면에서 클릭한 행위가 실제로 어떤 데이터를 불러오고, 저장하고, 검증하는지를 담당한다.쉽게 말하면 서비스의 뇌와 혈관에 해당한다.클라이언트가 요청을 보내면 백엔드는 그 요청을 처리하고 결과를 다시 반환한다.이 과정에서 API 설계, 데이터베이스 연동, 인증과 인가, 비즈니스 로직 ..
9/23
·
TIL
Today I LearnedSpring 플러스 주차 개인 과제 #대용량 데이터 삽입 및 조회 성능 테스트이번에 구현한 기능은 대용량 데이터를 삽입하고, 그 데이터를 기반으로 검색 API의 성능을 테스트하는 작업이었다. 목표500만 건의 테스트 유저 데이터를 삽입검색 API의 응답 시간 측정 및 인덱스 최적화삽입/조회 성능 테스트 결과 기록 구현1. 삽입 코드 설계// 고유 닉네임 생성 메서드private String createUniqueNickname() { String nickname = "user_" + UUID.randomUUID().toString().substring(0, 8); // 중복인 경우 재생성 while (!nicknameSet.add(nickname)) { ..
9/22
·
TIL
Today's Codekata// 귤 고르기class Solution { public int solution(int k, int[] tangerine) { Map map = new HashMap(); // 귤 크기별 개수 세기 for (int size : tangerine) { map.put(size, map.getOrDefault(size, 0) + 1); } // 개수 내림차순 List list = new ArrayList(map.values()); list.sort(Collections.reverseOrder()); int sum = 0; int answer = 0; // 많이 나온..
9/21
·
TIL
스프링부트 자바 기초 되새기기트랜잭션스프링에서 트랜잭션은 데이터의 일관성과 무결성을 지키기 위한 핵심 개념이다.하나의 작업 단위를 여러 단계로 나눠 처리할 때, 도중에 오류가 발생하면 이전 작업까지 모두 되돌려야 한다.이 과정을 스프링에서는 선언적으로 관리할 수 있다. 트랜잭션을 직접 관리하려면 코드가 복잡해지지만, 스프링에서는 `@Transactional` 하나로 이를 간단히 제어할 수 있다.@Service@RequiredArgsConstructorpublic class OrderService { private final OrderRepository orderRepository; private final PaymentService paymentService; @Transactional ..