[241126 TIL]

    MSA 학습

    MSA 프로젝트 구현


    ProductRepositoryImpl 코드 분석

     

    • ProductRepositoryCustom 인터페이스를 구현하여, QueryDSL을 기반으로 복잡한 검색 로직 수행
    • 주요 기능-  동적 검색 조건정렬페이징 

    코드 전체 구조

    @RequiredArgsConstructor
    public class ProductRepositoryImpl implements ProductRepositoryCustom {
    
        private final JPAQueryFactory queryFactory;
    
        @Override
        public Page<ProductResponseDto> searchProducts(ProductSearchDto searchDto, Pageable pageable) {
            // Pageable 객체에서 정렬 조건 추출
            List<OrderSpecifier<?>> orders = getAllOrderSpecifiers(pageable);
    
            // QueryDSL로 동적 검색 수행
            QueryResults<Product> results = queryFactory
                    .selectFrom(product)
                    .where(
                            nameContains(searchDto.getName()),              // 이름 검색 조건
                            descriptionContains(searchDto.getDescription()), // 설명 검색 조건
                            priceBetween(searchDto.getMinPrice(), searchDto.getMaxPrice()), // 가격 범위 조건
                            quantityBetween(searchDto.getMinQuantity(), searchDto.getMaxQuantity()) // 수량 범위 조건
                    )
                    .orderBy(orders.toArray(new OrderSpecifier[0])) // 정렬 조건 추가
                    .offset(pageable.getOffset())                 // 시작 위치 설정
                    .limit(pageable.getPageSize())                // 페이징 크기 설정
                    .fetchResults();                              // 쿼리 실행
    
            // 검색 결과를 DTO로 변환
            List<ProductResponseDto> content = results.getResults().stream()
                    .map(Product::toResponseDto)
                    .collect(Collectors.toList());
    
            // 총 검색된 데이터 수
            long total = results.getTotal();
    
            // Page 객체로 결과 반환
            return new PageImpl<>(content, pageable, total);
        }
    
        // 이름 포함 조건
        private BooleanExpression nameContains(String name) {
            return name != null ? product.name.containsIgnoreCase(name) : null;
        }
    
        // 설명 포함 조건
        private BooleanExpression descriptionContains(String description) {
            return description != null ? product.description.containsIgnoreCase(description) : null;
        }
    
        // 가격 범위 조건
        private BooleanExpression priceBetween(Double minPrice, Double maxPrice) {
            if (minPrice != null && maxPrice != null) {
                return product.price.between(minPrice, maxPrice);
            } else if (minPrice != null) {
                return product.price.goe(minPrice);
            } else if (maxPrice != null) {
                return product.price.loe(maxPrice);
            } else {
                return null;
            }
        }
    
        // 수량 범위 조건
        private BooleanExpression quantityBetween(Integer minQuantity, Integer maxQuantity) {
            if (minQuantity != null && maxQuantity != null) {
                return product.quantity.between(minQuantity, maxQuantity);
            } else if (minQuantity != null) {
                return product.quantity.goe(minQuantity);
            } else if (maxQuantity != null) {
                return product.quantity.loe(maxQuantity);
            } else {
                return null;
            }
        }
    
        // 정렬 조건 생성
        private List<OrderSpecifier<?>> getAllOrderSpecifiers(Pageable pageable) {
            List<OrderSpecifier<?>> orders = new ArrayList<>();
    
            // Pageable의 정렬 조건을 QueryDSL의 OrderSpecifier로 변환
            if (pageable.getSort() != null) {
                for (Sort.Order sortOrder : pageable.getSort()) {
                    // 정렬 방향 결정 (ASC/DESC)
                    com.querydsl.core.types.Order direction = sortOrder.isAscending() 
                        ? com.querydsl.core.types.Order.ASC 
                        : com.querydsl.core.types.Order.DESC;
    
                    // 정렬 필드에 따른 OrderSpecifier 추가
                    switch (sortOrder.getProperty()) {
                        case "createdAt":
                            orders.add(new OrderSpecifier<>(direction, product.createdAt));
                            break;
                        case "price":
                            orders.add(new OrderSpecifier<>(direction, product.price));
                            break;
                        case "quantity":
                            orders.add(new OrderSpecifier<>(direction, product.quantity));
                            break;
                        default:
                            break;
                    }
                }
            }
    
            return orders;
        }
    }

    searchProducts 메서드

     

    1. 페이징 및 정렬 처리

     

    List<OrderSpecifier<?>> orders = getAllOrderSpecifiers(pageable);

     

     

    • getAllOrderSpecifiers(pageable):
      • QueryDSL의 정렬 객체(OrderSpecifier)를 생성하는 메서드
      • Pageable의 정렬 조건에 따라 QueryDSL에서 사용할 정렬 정보를 동적으로 생성
    private List<OrderSpecifier<?>> getAllOrderSpecifiers(Pageable pageable) {
        List<OrderSpecifier<?>> orders = new ArrayList<>();
    
        if (pageable.getSort() != null) {
            for (Sort.Order sortOrder : pageable.getSort()) {
                com.querydsl.core.types.Order direction = sortOrder.isAscending() 
                    ? com.querydsl.core.types.Order.ASC 
                    : com.querydsl.core.types.Order.DESC;
    
                switch (sortOrder.getProperty()) {
                    case "createdAt":
                        orders.add(new OrderSpecifier<>(direction, product.createdAt));
                        break;
                    case "price":
                        orders.add(new OrderSpecifier<>(direction, product.price));
                        break;
                    case "quantity":
                        orders.add(new OrderSpecifier<>(direction, product.quantity));
                        break;
                    default:
                        break;
                }
            }
        }
    
        return orders;
    }
    • Pageable 객체의 Sort 정보를 기반으로 QueryDSL의 OrderSpecifier 생성
    • 정렬 속성(property)에 따라 createdAt, price, quantity 필드의 정렬 조건 추가

     

    2. QueryDSL 검색 쿼리

    QueryResults<Product> results = queryFactory
            .selectFrom(product)
            .where(
                    nameContains(searchDto.getName()),
                    descriptionContains(searchDto.getDescription()),
                    priceBetween(searchDto.getMinPrice(), searchDto.getMaxPrice()),
                    quantityBetween(searchDto.getMinQuantity(), searchDto.getMaxQuantity())
            )
            .orderBy(orders.toArray(new OrderSpecifier[0]))
            .offset(pageable.getOffset())
            .limit(pageable.getPageSize())
            .fetchResults();

     

    • queryFactory.selectFrom(product)
      • QueryDSL로 Product 엔터티를 검색
    • where(...)
      • 검색 조건을 동적으로 추가
      • 각 조건은 BooleanExpression 객체로 반환되며, null인 경우 무시
    • orderBy(orders.toArray(...))
      • 정렬 조건 적용
    • offset(pageable.getOffset()) & limit(pageable.getPageSize())
      • 페이징 처리: 요청한 페이지의 시작 위치(offset)와 크기(limit)를 설정

    3. 결과 처리

    List<ProductResponseDto> content = results.getResults().stream()
            .map(Product::toResponseDto)
            .collect(Collectors.toList());
    long total = results.getTotal();
    
    return new PageImpl<>(content, pageable, total);
    
    • getResults(): 조회된 Product 객체 리스트.
    • toResponseDto(): 각 Product 엔터티를 ProductResponseDto로 변환.
    • getTotal(): 전체 검색 결과 수.
    • PageImpl:
      • Spring Data의 Page 구현체로, 결과 데이터(content), 페이징 정보(pageable), 총 데이터 수(total)를 포함.

    [241125 TIL]

    MSA 학습

    MSA 프로젝트 만들어보기

    • 강의 마무리
    • 프로젝트

    MSA 프로젝트

     

     

     

     


    [241122 TIL]

    MSA 학습

    • 보안구성
    • Config
    • 분산추적
    • 이벤트 드리븐

    보안구성

    • 마이크로서비스 아키텍처에서는 각 서비스가 독립적으로 배포되고 통신하기 때문에 보안이 매우 중요
    • 데이터 보호, 인증 및 권한 부여, 통신 암호화 등을 통해 시스템의 보안성을 확보해야 함

    ✏️

    jwt - 변조, 수정의 보안을 말하는거 즉, 데이터 무결성을 확인 페이로드 안에 데이터(정보) 확인을 못하게 암호화한다를 뜻하는 게 아님

    gateway 만 알고 나머지는 사용자가 접근못하게 방화벽 처리 19091로만 접속가능하게


    Spring Cloud Config

    • 분산 시스템 환경에서 중앙 집중식 구성 관리를 제공하는 프레임워크
    • 애플리케이션의 설정을 중앙에서 관리하고, 변경 사항을 실시간으로 반영
    • Git, 파일 시스템, JDBC 등 다양한 저장소를 지원
    • 중앙 집중식 구성 관리: 모든 마이크로서비스의 설정을 중앙에서 관리
    • 환경별 구성: 개발, 테스트, 운영 등 환경별로 구성을 분리하여 관리 가능
    • 실시간 구성 변경: 설정 변경 시 애플리케이션을 재시작하지 않고도 실시간으로 반영할 수 있음

    ✏️

    Config 서버가 native 프로필로 로컬 파일 시스템을 사용할 경우, 파일 변경을 자동으로 감지하지 않음

    그래서 config 가 변경되면 config 서버는 재실행해줘야함!


    분산 추적

    • 분산 시스템에서 서비스 간의 요청 흐름을 추적하고 모니터링하는 방법
    • 각 서비스의 호출 관계와 성능을 시각화하여 문제를 진단하고 해결할 수 있도록 도움
    • 트레이스(Trace) : 하나의 요청이 시작부터 끝까지 각 서비스를 거치는 전체 흐름을 나타냄
    • 스팬(Span) : 분산 추적에서 가장 작은 단위로, 특정 서비스 내에서의 개별 작업 또는 요청을 나타냄
    • 컨텍스트(Context): 요청이 서비스 간에 전달될 때 함께 전파되어, 각 서비스가 요청의 전체 흐름에 대한 정보를 가질 수 있게함
    • 필요성
      • 마이크로서비스 아키텍처에서는 여러 서비스가 협력하여 하나의 요청을 처리
      • 서비스 간의 복잡한 호출 관계로 인해 문제 발생 시 원인을 파악하기 어려울 수 있음
      • 분산 추적을 통해 각 서비스의 호출 흐름을 명확히 파악하고, 성능 병목이나 오류를 빠르게 진단할 수 있음

    이벤트 드리븐 아키텍처

    • 시스템에서 발생하는 이벤트(상태 변화나 행동)를 기반으로 동작하는 소프트웨어 설계 스타일
    • 이벤트는 비동기적으로 처리되며, 서비스 간의 느슨한 결합을 통해 독립적으로 동작할 수 있게함
    • 이벤트: 시스템 내에서 발생하는 상태 변화나 행동을 나타내는 메시지
    • 이벤트 소스: 이벤트를 생성하여 이벤트 버스에 전달하는 역할
    • 이벤트 핸들러: 이벤트를 수신하여 처리하는 역할
    • 이벤트 버스: 이벤트 소스와 이벤트 핸들러 간의 메시지 전달을 중개
    • 장점
      • 느슨한 결합
        • 서비스 간의 강한 종속성을 제거하여 독립적인 개발과 배포가 가능
        • 이벤트 기반 통신을 통해 서비스 간의 결합도 낮춤
      • 확장성
        • 수평 확장이 용이하여 대규모 시스템에서 유용
        • 이벤트 프로듀서와 컨슈머를 독립적으로 확장 가능
      • 비동기 처리
        • 이벤트를 비동기적으로 처리하여 시스템의 응답성 향상
        • 요청과 응답을 비동기적으로 처리하여 성능 최적화
    • 단점
      • 복잡성 증가
        • 이벤트 기반 통신으로 인해 시스템의 복잡성 증가 가능
        • 이벤트 흐름과 상태 관리를 체계적으로 설계 필요
      • 장애 전파
        • 이벤트 실패 시 다른 서비스로 장애가 전파될 수 있음
        • 이벤트 재처리 및 장애 복구 메커니즘 구현 필요

    [241121 TIL]

    MSA 학습

    • 서비스 디스커버리
    • 로드 밸런싱
    • 서킷 브레이커
    • API GW

    서비스 디스커버리

    • 마이크로서비스 아키텍처에서 각 서비스의 위치를 동적으로 관리하고 찾아주는 기능
    • 각 서비스는 등록 서버에 자신의 위치를 등록하고, 다른 서비스는 이를 조회하여 통신

    Eureka

    • 넷플릭스가 개발한 서비스 디스커버리
    • 모든 서비스 인스턴스의 위치를 저장하는 중앙 저장소 역할을 하며, 서비스 인스턴스의 상태를 주기적으로 확인하여 가용성 보장
    • 여러 인스턴스를 지원하여 고가용성 유지
    • Eureka Server
      • 설정 파일을 통해 서버를 구성하고, 클라이언트가 등록할 수 있도록 준비
    • Eureka Client
      • spring-cloud-starter-netflix-eureka-client 의존성 사용
    • 서비스 디스커버리
      • RestTemplate 사용 - @LoadBalanced
      • FeignClient 사용 - @EnableFeignClients, @FeignClient

    로드 밸런싱

    • 네트워크 트래픽을 여러 서버로 분산시켜 서버의 부하를 줄이고, 시스템의 성능과 가용성을 높이는 기술
    • 서버 간 트래픽을 고르게 분배하여 특정 서버에 부하가 집중되는 것을 방지
    • 클라이언트 사이드 로드 밸런싱
      • 클라이언트가 직접 여러 서버 중 하나를 선택하여 요청을 보내는 방식
      • 클라이언트는 서버 목록을 가지고 있으며, 이를 바탕으로 로드 밸런싱 수행

     

    FeignClient

    • Spring Cloud 에서 제공하는 HTTP 클라이언트로, 선언적으로 RESTful 웹 서비스를 호출할 수 있음
    • Eureka 와 같은 서비스 디스커버리와 연동하여 동적으로 서비스 인스턴스를 조회하고 로브 밸런싱 수행
    • 선언적 HTTP 클라이언트: 인터페이스와 어노테이션을 사용하여 REST API 를 호출할 수 있음
    • Eureka 연동: Eureka 와 통합하여 서비스 인스턴스 목록을 동적으로 조회하고 로드 밸런싱 수행
    • 자동 로드 밸런싱: Ribbon 이 통합되어 있어 자동으로 로드 밸런싱 수행

     

    Ribbon

    • 넷플릭스가 개발한 클라이언트 사이드 로드 밸런서
    • 다양한 로드 밸런싱 알고리즘 지원하며, Eureka 와 같은 서비스 디스커버리와 연동하여 사용
    • 서버 리스트 제공자: Eureka 등으로부터 서비스 인스턴스 리스트를 제공 받아 로드 밸런싱에 사용
    • 로드 밸런싱 알고리즘: 라운드 로빈, 가중치 기반 등 다양한 로드 밸런싱 알고리즘 지원
    • Failover: 요청 실패 시 다른 인스턴스로 자동 전환

    ✏️ Ribbon 기본이 라운드 로빈 방식인듯!


    서킷 브레이커

    • 마이크로서비스 간의 호출 실패를 감지하고 시스템의 전체적인 안정성을 유지하는 패턴
    • 외부 서비스 호출 실패 시 빠른 실패를 통해 장애를 격리하고, 시스템의 다른 부분에 영향을 주지 않도록 함
    • 상태 변화: 클로즈드 -> 오픈 -> 하프 오픈

     

    Resilience4j

    • 서킷 브레이커 라이브러리
    • 상태
      • 클로즈드(Closed)
        • 기본 상태로 모든 요청 통과, 호출 실패시 실패 카운터 증가
        • 실패율이 설정된 임계값을 초과하면 서킷 브레이커가 오픈 상태로 전환
      • 오픈(Open)
        • 오픈 상태로 전환시, 모든 요청을 즉시 실패 처리, 바로 에러 응답 반환
        • 설정된 대기 시간 경과 후, 하프-오픈 상태로 전환
      • 하프-오픈(Half-Open)
        • 오픈 상태에서 대기 시간이 지나면 하프-오픈 상태로 전환
        • 제한된 수의 요청을 허용하여 시스템이 정상 상태로 복구되었는지 확인
        • 요청 성공시, 클로즈드 상태로 전환 / 요청 실패시, 오픈 상태로 전환
    • Fallback: 호출 실패 시 대체 로직을 제공하여 시스템 안정성 확보
    • 모니터링: 서킷 브레이커 상태를 모니터링하고 관리할 수 있는 다양한 도구 제공

    API GW

    • 클라이언트의 요청을 받아 백엔드 서비스로 라우팅하고, 다양한 부가 기능을 제공하는 중간 서버
    • 클라이언트와 서비스 간의 단일 진입점 역할을 하며, 보안, 로깅, 모니터링, 요청 필터링 등을 처리

     

    Spring Cloud Gateway

    • Spring 프로젝트의 일환으로 개발된 API 게이트웨이
    • 동적 라우팅: 요청의 URL 패턴에 따라 동적으로 라우팅
    • 필터링: 요청 전후에 다양한 작업을 수행할 수 있는 필터 체인 제공
      • Global Filter: 모든 요청에 대해 작동하는 필터
      • Gateway Filter: 특정 라우트에만 적용되는 필터
      • PreFilter: 요청이 처리되기 전에 실행
      • PostFilter: 요청이 처리된 후, 응답이 반환되기 전에 실행
    • 모니터링: 요청 로그 및 메트릭을 통해 서비스 상태 모니터링
    • 보안: 요청의 인증 및 권한 검증

     

    ✏️ gw 가 없으면 사용자가 다 다른 호스트로 api 요청해야함

    ✏️ gw 가 있으면 gw 로 요청하면 gw 가 알아서 라우팅!


    [241120 TIL]

    MSA 학습

    • 코드카타
    • MAS 강의

    모놀리식 아키텍처(Monolithic Architecture: MA)

     

    • 하나의 큰 코드베이스로 구성된 애플리케이션
    • 모든 기능이 하나의 애플리케이션 내에 포함
    • 장점
      • 간단한 배포: 모든 코드가 하나의 코드베이스에 포함되어 있어 배포가 단순
      • 단일 데이터베이스: 하나의 데이터베이스를 사용하여 데이터 일관성을 쉽게 유지
    • 단점
      • 확장성 부족: 특정 기능을 확장하려면 전체 애플리케이션을 확장해야 함
      • 긴 개발 주기: 작은 변경 사항에도 전체 애플리케이션을 다시 배포해야 함
      • 유연성 부족: 새로운 기술 도입이 어렵고, 특정 모듈에 종속적
      • ex) 회원에서 기능 변경 및 확장이 생기면, 상품/주문 서비스에서는 해당 사항이 없어도 전체 애플리케이션을 확장/배포해야 함

    MSA(Microservice Architecture)

    • 여러 개의 독립적인 서비스로 구성된 애플리케이션
    • 각 서비스는 특정 비즈니스 기능을 수행
    • 장점
      • 확장성: 특정 서비스만 확장 가능, 특정 기능에 대한 성능 최적화가 용이
      • 독립적 배포: 개별 서비스의 변경 사항을 독립적으로 배포할 수 있음 -> 배포 주기 단축
      • 유연성: 다양한 기술 스택을 사용하여 서비스별 최적화 가능
      • 작은 구성: 서비스별 작은 팀으로 구성되어 민첩한 개발 가능
    • 단점
      • 복잡성 증가: 서비스 간 통신, 데이터 일관성 유지, 트랜잭션 관리 등의 복잡성 증가
      • 운영비용 증가: 각 서비스의 모니터링, 로깅, 장애 대등 등을 개별적으로 관리해야 함
      • 데이터 관리: 분산된 데이터베이스로 인해 데이터 일관성 유지가 어려울 수 있음
      • 네트워크 지연: 서비스 간의 통신이 네트워크를 통해 이루어지므로 지연 시간이 발생할 수 있음

    Spring Cloud

    • 마이크로서비스 개발을 위해 다양한 도구와 서비스를 제공하는 스프링 프레임워크의 확장
    • 마이크로서비스 아키텍처를 쉽게 구현하고 운영할 수 있도록 도움

     

    주요 모듈

    • 서비스 등록 및 디스커버리
      • Eureka: 넷플릭스가 개발한 서비스 디스커버리 서버로, 마이크로서비스 아키텍처에서 각 서비스의 위치를 동적으로 관리
    • 로드 밸런싱
      • Ribbon: 넷플릭스가 개발한 클라이언트 사이드 로드 밸런서로, 서비스 인스턴스 간의 부하를 분산
    • 서킷 브레이커
      • Hystrix: 넷플릭스가 개발한 서킷 브레이커 라이브러리로, 서비스 간의 호출 실패를 감지하고 시스템의 전체적인 안정성 유지
      • Resilience4j: 자바 기반의 경량 서킷 브레이커 라이브러리로, 넷플릭스 Hystrix 의 대안으로 개발

     

    구성 요소의 활용

    • API 게이트웨이
      • Zuul: 넷플릭스가 개발한 API 게이트웨이로, 모든 서비스 요청을 중앙에서 관리
      • Cloud Gateway: 스프링 클라우드에서 제공하는 API 게이트웨이로, 마이크로서비스 아키텍처에서 필수적인 역할
    • 구성 관리
      • Spring Cloud Config: 분산된 환경에서 중앙 집중식 설정 관리를 제공


     


    [241105 TIL] 

    My Select Shop 프로젝트

    • 구현 기능 점검
    • 카카오 로그인 구현
    • Spring AOP, Timer 적용
    • 예외처리

    구현 기능 점검

    ⚠️ 트러블슈팅 ⚠️

     

    1. 문제 정의

    • 관심 상품 폴더 기능 점검중, 폴더 추가가 제대로 이뤄지지 않는 문제 발견
      • 관심 상품에서 폴더 추가 버튼을 누르면 작동하지 않음
      • 폴더 추가를 하면 중복하지 않는 폴더명임에도 중복된 폴더라는 알림이 뜨지만, 폴더 추가는 됨

     

    2. 원인 추론

     

    ERROR

    org.thymeleaf.exceptions.TemplateInputException: Error resolving template [api/folders], template might not exist or might not be accessible by any of the configured Template Resolvers

     

     

     

    1. thymeleaf exception 이 발생했기에 front 문제인가?

    • console 오류를 확인해보니 basic.js 파일의 folders.map 이 제대로 동작하지 않음을 확인
    • front 의 문제는 아니고, folder 를 제대로 넘겨받지 못하는 문제인듯함

     

    2. FolderController 에 API 를 잘못 작성했나?

    • 구글링해보니 API 주소를 잘못 적은 경우가 다반사이길래 확인해보니, 모두 정확하게 적혀있음

     

    3. FolderService 에 폴더 추가 함수 자체가 잘못 작성됐나?

    • 조회 함수에서 print를 찍어보니 folderList 가 제대로 생성되지 않음을 확인

    • 추가 함수에서 print를 찍어보니 print 안됨 -> addFolders 함수 안으로 들어가지 못함

     

    ➡️  folder 가 제대로 넘어오지 못함

     

     

    4. FolderController 에서는 folder 가 제대로 생성되는지?

    • PostMapping 에서 print를 찍어보니 print 안됨

     

    ➡️ @RequestBody 가 제대로 동작하지 않음

     

     

    5. @RequestBody 사용을 위한 애노테이션이 제대로 작성되어 있는지?

    • @Controller 만 작성되어 있음을 확인!!

     

    3. 해결 방안 및 결과 

    • @Controller -> @RestController
      • @RequestBody 를 사용하려면 @RestController 혹은 @Controller + @ResponseBody 가 명시되어야 함!!
    • @RestController 변경 후 폴더 추가가 제대로 동작됨을 확인

     


    카카오 로그인 구현

    ⚠️ 트러블슈팅 ⚠️

     

    1. 문제 정의

    • 카카오 로그인 앱 관리자 설정 오류

     

    2. 원인 추론

     

    1. Redirect URI 가 잘못 작성되었나?

    • 카카오에 등록한 Redirect URI : http://localhost:8080/api/user/kakao/callback
    • KakaoService 에 작성한 Redirect URI : http://localhost:8080/api/user/login-page/api/user/kakao/callback

     

    3. 해결 방안 및 결과

    • redirect_uri 수정
      • http://localhost:8080/api/user/login-page/api/user/kakao/callback
        -> http://localhost:8080/api/user/kakao/callback
    • 수정 후 카카오 로그인 성공!

     

    + Recent posts