전체 글

TL;DR상품 목록 조회 시 좋아요 수 정렬을 위해 Like 테이블과 JOIN하니 인덱스가 적용되지 않아 성능이 나빴다. Product 테이블에 likeCount 컬럼을 비정규화하고 인덱스를 추가해 성능을 개선했다. 읽기가 쓰기보다 압도적으로 많은 상황에서는 정합성 동기화 비용보다 조회 성능 개선이 더 중요하다고 판단했다.문제 상황커머스 서비스에서 상품 목록 API에 다음 기능을 추가하려고 했다.요구사항브랜드별 필터링좋아요 수 기준 정렬로컬에서 소량 데이터로 테스트할 땐 괜찮았는데, 10만 건 데이터를 넣고 테스트하니 성능이 나빴다.AS-IS: 초기 구현 (인덱스 없음)좋아요 수로 정렬하기 위해 Like 테이블과 LEFT JOIN 후 COUNT를 했다.SELECT p.id, p.name, ..
TL;DR재고, 포인트, 쿠폰처럼 충돌 빈도가 높고, 실패 시 이미 수행한 작업을 롤백하고 처음부터 다시 시도해야 하는 경우 비관적 락이 더 적합하다고 판단했다. 낙관적 락은 충돌이 드물고, 실패해도 해당 작업만 다시 시도하면 되는 상황에 더 맞지 않을까 생각한다. 문제 상황커머스 서비스에서 주문 기능을 구현하면서, 동시성 이슈를 마주했다.@Transactionalfun createOrder(userId: Long, items: List, couponId: Long? = null): Order { val orderProducts = deductStock(items) // 1. 재고 차감 val totalAmount = calculateTotalAmount(orderProducts) ..
모든 걸 mock 하면 진짜 로직은 누가 검증하는가?TL;DR단위 테스트에서 모든 의존성을 mock 처리하면 테스트는 통과하지만, 정작 중요한 도메인 로직은 검증되지 않는다. StockService를 mock에서 실제 구현체로 전환하며 느낀 "단위 테스트의 진짜 목적"에 대한 이야기. 문제를 발견한 순간주문 기능의 단위 테스트를 작성하던 중, 이런 코드를 작성했다.class OrderFacadeTest { private val stockService: StockService = mockk() @Test fun throwsException_whenStockIsInsufficient() { val currentStock = 50 val requestQuantity =..
TL;DR요구사항 명세서는 백엔드 개발자끼리 보는 기술 문서가 아니다.비개발 직군과의 소통 도구이며, "이거 제가 원한 게 아닌데요?"라는 말을 듣지 않기 위해, 그리고 놓친 케이스를 함께 찾아내기 위해 작성하는 문서다. 그래서 BAD_REQUEST 대신 "재고가 부족합니다"라고 써야 한다.시작하며이번 주 e-commerce 프로젝트를 시작하면서 가장 먼저 작성한 건 요구사항 명세서였다.사실 처음엔 이런 생각이 들었다.“이거 꼭 써야 하나? 어차피 코드로 구현하면 되는데... 문서 작성에 시간을 쓰는 게 비효율적인 거 아닌가?”하지만 막상 써보니 생각이 완전히 달라졌다.작성하는 과정 자체가 팀 전체의 이해를 맞추는 과정이었다.이 문서는 누구를 위한 것인가?처음엔 요구사항 명세서를 개발자를 위한 설계 문서..
TL;DRRepository 메서드명을 getXXX()대신 findByXXXOrNull()로 작성한 이유는, 메서드명만 보고도 null 가능성을 인지하고 안전하게 처리하도록 유도하기 위함이었다.# Repository 네이밍 컨벤션## 조회 메서드- findByXxxOrNull(): null을 반환할 수 있는 조회- getByXxx(): 없으면 예외를 던지는 조회## 예시- findByUsernameOrNull("user123") → User? 반환- getByUsername("user123") → User 반환 또는 예외자연스럽게 작성했던 getUser()interface UserRepository { fun getUser(username: String): User?}"유저를 가져온다(get)" -..
·백엔드/Kotlin
개요이 글에서는 runBlocking, launch, async 등 주요 코루틴 빌더를 직접 실습하며, 그 동작 원리와 예외 처리, 취소 등 실무에서 반드시 숙지해야 할 코루틴 사용법을 하나씩 짚어본다. 목표코루틴의 정의와 개념 이해runBlocking, launch, async 등 다양한 코루틴 빌더의 사용법 습득코루틴의 취소, 예외 처리, 병렬 처리 등 실무 활용 방법 이해 코루틴이란'Coroutine'은 'co-operative routine', 즉 협력하는 루틴이라는 의미이다. 코틀린(Kotlin)의 'Ko'가 아니라, 여러 루틴이 협력하여 실행 흐름을 제어한다는 의미의 'co'가 붙은 것이다. 코루틴은 멀티스레드 환경에서 발생할 수 있는 복잡성과 자원 소모를 줄이며, 경량화된 비동기 처리를 가능..
소개글로벌 플랫폼에서는 각기 다른 시간대를 사용하는 다양한 국가의 사용자들이 서비스를 이용합니다. 이러한 환경에서는 시간 데이터를 어떻게 저장하고 제공하는지가 사용자 경험에 큰 영향을 미칩니다. 이번 글에서는 한국 시간(KST)으로만 관리되던 시간 데이터를 사용자 시간대에 맞게 변환하는 과정을 소개하고, 이를 구현한 기술적 접근 방식을 공유합니다.  문제초기 환경(KST 기반 데이터 관리)처음에는 한국 서버에서만 운영되었기 때문에 모든 시간 데이터가 KST(한국 표준시)로 관리되었습니다. 하지만 플랫폼이 성장하며 미국, 몽골, 베트남 등 다양한 국가의 사용자들이 늘어나자, 동일한 시간 데이터가 사용자마다 다르게 해석되는 문제가 발생했습니다. 사용자 혼란 사례예를 들어, 2024년 10월 30일 08:00..
·백엔드/Spring
이 글에서는 JWT(Json Web Token) 기반 인증 시스템에서 발생할 수 있는 리프레시 토큰 탈취 문제를 해결하기 위한 실질적인 방법을 제시합니다.탈취된 토큰이 악용되는 상황을 방지하고, 리프레시 토큰 로테이션과 플랫폼별 고유 토큰 관리를 통해 보안을 강화합니다.  프로젝트 환경 요약Spring Boot 3.3.4Java 21JJWT 라이브러리 버전 0.12.3Redis를 활용한 리프레시 토큰 관리Spring Security  의존성 설정JWT와 Redis를 사용하기 위해 다음과 같은 의존성을 추가합니다.dependencies { // JWT implementation("io.jsonwebtoken:jjwt-api:0.12.3") implementation("io.jsonwebto..
·백엔드/JPA
소개JPA를 사용하여 데이터베이스와 객체를 연결할 때, 부모-자식 1:N 관계에서 자식 엔티티가 부모 엔티티와의 관계에서 벗어났을 때 어떻게 처리할지를 결정하는 orphanRemoval 속성의 사용법에 대해 설명하는 글입니다.orphanRemoval의 true와 false 설정에 따른 동작 차이를 알아보고, 이를 사용방법을 설명하겠습니다.    도메인 모델 정의하나의 게시글(Board)에 대해 국가별로 보기 권한(BoardCountry)을 설정할 수 있다고 가정해보겠습니다.이 예제에서는 Board 엔티티가 특정 국가에서만 볼 수 있는 게시글을 나타내며, BoardCountry 엔티티가 그 국가 정보를 담고 있습니다.  Board 엔티티@Getter@Entity@Builder@AllArgsConstruct..
·백엔드/Test
Testcontainers를 이용한 애플리케이션 통합 테스트 환경 구축이 글에서는 Testcontainers를 활용하여 애플리케이션 통합 테스트 시 데이터베이스 환경을 어떻게 구성할 수 있는지에 대해 설명합니다.Testcontainers 공식 문서를 참고하여 현재 버전에 맞게 직접 테스트해보며 작성했습니다.  서론테스트 코드를 작성할 때, Docker를 사용해 테스트용 데이터베이스를 직접 띄우거나 H2 메모리 데이터베이스를 활용할 수 있습니다. 하지만 Docker로 테스트용 DB를 직접 실행하고 종료하는 과정은 번거로울 수 있으며, H2 메모리 DB는 설정이 간편하고 테스트 속도가 빠르지만 운영 환경과 다른 DB를 사용할 때 문제가 발생할 가능성이 있습니다. 이는 테스트에서 문제가 없더라도 실제 운영 환..
dami97
시공의개발자