[SGS] SERVER CREAM 개발기
===
###### tags: `20211213`
[TOC]
# 작업 스택
- user api - 1월 9일까지 마무리
- 유저 글로벌 필터 토큰 인증 기능
- redis 이용 토큰 작업 및 리프레시 토큰 기능 적용
- 유저 정보 반환, 로그아웃, 내 정보 수정
- 유레카 서버 구축, 클라이언트 등록
- email 인증 회원가입
- msa간 통신 환경 구성하기
- product api - 1월 16일까지 마무리
- 유저 인증서버 클라우드 환경에 구축하기
- 상품 데이터 베이스 구축 및 개발 환경 설정하기
- spring 다대일 모델 관련 조사 및 구현하기
- 상품 Read
- product + market api - 1월 24일까지 마무리
- 찜 기능 + 상품 반환시에 찜 여부
- 상품 검색 필터 제작하기
- 거래 데이터 베이스 구축 및 개발 환경 설정하기 (mongodb)
- 문서형 데이터 베이스 모델 제작하기
- 유저별 활동 데이터 저장하기
- 유저별 데이터 직렬화
- market api + recommend algorithm 1월 31일까지
- 경매 로직 및 가격 기능 구현하기
- 판매 입찰, 구매 입찰, 거래 기능
- 스케줄러를 통해 전날 가격 설정 구현
- 거래 생길 때마다 market도 업데이트 같이 쳐줘야함
- 가격 그래프를 위한 거래 내역 api 제작
- 내 판매, 구매 내역 및 기능 마무리
- 개인화 추천 알고리즘 철저히 조사 및 검증 테스트 코드 작성
- 실제 추천 알고리즘 구현
- recommend api + 마무리 - 2월 중순까지 마무리
- fast api 와 python을 이용한 추천 서버 구현하기
- 스케줄러를 이용한 개인화 추천 상품 계산 및 상품 서버와 연결하기
- 테스트 코드 작성하기
- container 기반 프로젝트 배포 하기
## 추가적으로 구현하면 좋은 것들
- SNS 연동 로그인
- 이메일, 비밀번호 및 param내용 검증 하는 로직이 추가적으로 있으면 좋겠다. (아니면 게이트웨이가 아니라면 request받지 않는 로직 추가하기.)
- 모바일 웹사이트 토큰 유효시간 다르게 설정하기
# 스프링과 코틀린에 대한 내용
## 스프링 시큐리티
### 시큐리티 동작 흐름
1. 요청
2. AuthenticationFilter를 거친다. 필터는 UsernamePasswordAuthenticationToken을 만들고 AuthenticationManager에 Autentication객체를 전달한다.
-> 이 부분에서 내가 작성한 필터가 동작한다. (명심 아직 필터 상태임)
3. authentication manager는 위에서 받은 인증 객체를 통해 authenticationprovider를 찾아 인증객체(위에서 만든 토큰)을 전달하고 인증요청
4. provider에서 Service를 호출하여 UserDetails를 통해 인증을 진행한다 (db 데이터 조회 및 검증) -> 여기서 아까 만든 토큰에 대해서 검증하나보다.
5. 인증 성공시 authentication 객체를 SecurityContextHolder에 저장하고 AuthenticationSuccessHandle을 실행 - 실패시 AuthenticationFailureHandler실행
## 디스패처 서블렛
## jpa
### getById vs findById
getById 같은 경우에는 실제 테이블을 사용하기 전까지 프록시 객체만 잠시 가져온다. 실제 사용되기 전까지 db를 조회하지 않는다.
findById 같은 경우에는 response 타입이 Optional이다.
## EntryPoint
## DeniedHandler
## exception 처리 하기
1. 에러코드 정리 및 enum 클래스로 작성
2. Exception 발생 시 응답하는 에러 정보 클래스 작성
3. 사용자 정의 Exception 클래스 작성
4. Exception 발생 시 전역으로 처리할 exception handler 작성
## Java에서의 Optional
타입의 null에 대해서 좀 쉽게 코드를 작성할 수 있게 만든 타입이고 8부터 추가되었다.
## 필터 vs 인터셉터의 차이
우선 어디에 포함되는지에서 부터 차이점이 있다.
필터 - 웹컨텍스트
인터셉터 - 스프링 컨텍스트
필터 -> 디스패처 서블렛 -> 인터셉터 -> 컨트롤러
웹컨텍스트 같은 경우에는 정말 모든 request, response에 일괄적으로 적용하거나 url을 변경한다거나 이런 전체적인 흐름에서 수정하는 일이 있다면 여기서 해결하면 될 거 같다.
인터셉터 같은경우에는 http메소드 별로 다르게 적용할 것들이 있거나 controller에 직접적으로 뭔가 분류할 일이 있을 때 쓰는 것이 좋은 것 같다.
## spring aop란?
aspect oriented programming 의 약자, 관점지향 프로그래밍이라는 의미.
이게 무슨 의미를 가지고 있는지 구체적으로 찾아보니 어떤 로직을 기준으로 핵심적인 관점, 부가적인 관점으로 나누어서 보고 그 관점을 기준으로 각각 모듈화 하겠다는 것.
내가 이해한 것으로는 클래스와 코드들을 나누어서 진행이 되는 시점으로 필요한 부분들을 나누어서 유연하게 쓴다는 의미인 것 같다.
그럼 이것을 왜쓸까? -> 코드 중복, 객체관 복잡성 증가 줄여줄 수 있다! (작성했던 것들을 나누어 다시 쓰기 때문)
## e-mail 인증
## EntryPoint와 DeniedHandler에서는 throw CustomException 던져도 왜 return 값이 올바르지 않을까?
필터에서는 throw를 해도 spring객체가 관리하지 않기 때문에 안되는 것이 아닐까? 그렇다면 어떻게 해결해야 할까?
## Kotlin 에서의 isEmpty와 isBlank의 차이점
빈 문자열인지 아닌지 확인하는 둘.. 과연 차이점이 뭘까
isEmpty 같은 경우에는 " " 이렇게 빈칸이 있어도 비어있다고 판단하지 않는다.
하지만 isBlank같은 경우에는 " " 빈칸만 있다면 true를 반환한다.
그래서 내가 사용하는 프로젝트에는 nullable한 파라미터를 써야 했기 때문에
isNullOrBlank를 사용하게 되었다.
## Kotlin에서의 data class
공식문서에서도 나온 데이터클래스의 용도는 간단하게 데이터를 가지고 있는 것이 목적인 class를 의미한다. getter, setter가 기본적으로 제공되기 때문에 lombok이 필요없고 기본 생성자를 반드시 사용해야 되기때문에 DTO로 사용하기에 좋다.
다만 엔티티에는 사용하면 안되는데 그 이유는 JPA를 사용하게 된다면 반드시 지켜야할 Java Class 속성이 있는데 확장성, 상속이 가능해야 한다는 점이다.
근데 코틀린에서는 그런 클래스를 선언하기 위해서는 open 키워드를 붙여줘야한다. 하지만 data class는 open 키워드를 붙여줄 수 없기 때문에 일반 클래스를 이용해서 Entity를 선언해줘야한다.
그래서 data 클래스는 꼭 dto에만 사용해야 한다.
## Kotlin에서의 3항 연산자
코틀린에서는 3항 연산자가 없다. if문이 컨디션이 아닌 expression이기 때문에 간단하게
```
//...
return if (a.isBlank) true else false
```
이런 식으로 작성하면 된다.
## stringfied json (string)을 어떻게 하면 배열 또는 객체로 만드는 방법
```
if (!ObjectMapper().readValue(product.sizes, ArrayList::class.java).contains(size)) {
throw BaseException(ErrorCode.INVALID_SIZE_FOR_PRODUCT)
}
```
object mapper를 이용해 쉽게 변환 할 수 있다.
다만 data class에서 기본 생성자에 default 값이 없는 경우 에러를 뿜는 경우가 있으니 주의해야한다.
https://eblo.tistory.com/62
# MSA와 백엔드 내용
## server간 통신 방법
### FeignClient
feign client를 사용하는 이유는 api를 호출하는 코드를 노가다 식으로 항상 만들어줬는데 이 부분들을 자동화해서 편하게 인터페이스만을 선언하고 어노테이션으로 쉽게 쓸 수 있게 추상화한 프로젝트 또한 로드밸런스까지 지원한다.
- 의존성
- Feign Client 역시 Spring Cloud 프로젝트 중 하나이기 때문에 다른 제품군과 호환성을 맞추어 주어야함, 이런 부분을 해결하는 Spring Cloud Release Train이란 것이 있다.
프로젝트를 시작하려면 Application 단에 @EnableFeignClient를 선언해 주어야 한다. 이 어노테이션을 통해 FeignClient가 있는 interface를 찾아서 등록해주는 역할을 한다. + 기본적으로 제공하는 정보가 있기 때문에 Configuration을 설정하지 않아도 바로 사용 할 수 있다.
@FeignClient
FeignClient라고 선언하는 어노테이션입니다.
- name 속성 : feignClient의 서비스 이름으로 필수 속성입니다, beanName과는 다릅니다.
- url : 해당 interface baseUrl입니다.
- qualifier : beanName입니다.
- configuration : 커스터마이징한 configuration을 넣을 수 있습니다.
- fallback : Hystrix fallback 메서드입니다.
## Mysql Json type column 해결 이슈
팀원들에게 물어보았을 때 그냥 Entity를 String으로 결정하는 것이 프론트도 서버에서도 좀 편한 것 같다라는 느낌을 많이 받았다. 실제로 그냥 String형태로 받아도 mysql에서 자동으로 json 데이터로 저장을 해주었다.
## put vs patch의 차이
put - 일부만 업데이트 한다.
patch - 리소스 모두를 업데이트 한다.
내가 변경하려는 유저 같은 경우에는 한번에 변경이 불가능 하기 때문에 patch에서 put으로 변경했다. 특히 gender나 age같은 경우에는 변경사항이 거의 없거나 알아서 업데이트 해주어야 하기 때문에 수정하지 않았다.
## jwt에서의 log out 방식
몇 블로그에서 찾아본 내용인데, token이 만료될때까지 효력을 막을 수 있게 blocklist를 redis에 만들어 두고 만약 여기에 있는 토큰을 사용한다면 요청을 막는 방식으로 사용하면 될 거 같다.
## varchar vs nvarchar
전에 직장에서 있었을 때에도 같은 궁금점을 가지고 조사한 적이 있었는데 기억이 가물가물해져서 다시 찾아보았다.
varchar - 1 byte for alphbet
nvarchar - 2 bytes for all character
그래서 만약 영어만 쓰이게 된다면 varchar에 아니라면 nvarchar를 사용하면 될거 같다.
## Spring cloud gateway에서의 에러 핸들링 방법
우선 Spring cloud gateway에서 필터의 동작을 살펴보고 return@gatewayfilter를 이용해 똑같이 처리를 하는 줄 알았지만, 그렇게 된다면 토큰은 없는데 routing을 통해 request가 다른 server로 전달되는 현상이 발생했다.
다른 곳으로 라우팅 되기 전에 response를 내려주고 싶었기에 방법을 찾아보았고 ErrorWeb
## Eureka서버와 GateWay서버 연결
상품, 유저, 경매 서버를 eureka 서버에 등록해 두었다.
이제 이 유레카 서버를 로드밸런싱과 naming discovery, 서버간 통신을 위해 게이트 웨이와 연결시키면 된다.
처리 순서는 게이트웨이 -> eureka server -> 게이트 웨이 -> 요청 주소 서버에서 처리
이런식으로 진행된다.
게이트웨이를 사용하는 이유는 보안적인 부분과 로드밸런싱을 통해 유저 요청을 분산시켜 효과적으로 처리하게 만들어 준다. 이를 위해 유레카 서버와 게이트웨이를 yml파일과 gradle 설정에 eureka server를 만들어 등록해 준다. spring cloud gateway는 round robin 알고리즘을 바탕으로 밸런싱을 한다.
특히 나중에 동일한 서버를 여러 대 띄워야 할 때에 유레카 서버가 없다면 랜덤한 포트번호를 사용할 수 없기 때문에 유레카 서버를 이용해 통신을 한다.
## error 처리에 대한 질문.. 한 서버에서 다른 서버에 요청을 날렸을 때에 에러가 났다면 어떻게 리스폰스를 내려줘야 할까?
# 개인화 추천 알고리즘 내용
추천시스템(RMSE)의 정확성.. -> 하지만 우리는 데이터가 정확하다는 보장이 없음
### CF
- 프로젝트에서의 장점
- 일단 가장 많이 쓰임
- 비교적 높은 정확도
- 설명이 가능한 부분이 꽤 많음 - 사용자 별 구매 경향 등
- 사용자 기반 CF가 아닌 상품 기반 CF를 사용하는 방법 (IBCF - 아마존 사용)
- 장점: 속도가 빠르다, 데이터가 적어도 어느정도 정확성을 유지한다.
- 단점: 정확도가 사용자 기반보다는 좀 떨어진다.
- 프로젝트에서의 단점
- 우리는 유저 데이터가 빈약하다라는 너무 큰 단점이 있다.
- 데이터 검증의 문제가 가장 큰 이슈
- CB를 쓸때에도 개인 유저의 데이터가 이슈가 있긴하지만 CF 만큼 큰 영향을 받지는 않을 것 같다. (하지만 IBCF를 사용하면 어느정도 해결 가능)
- cold user 추천이 cb보다는 어렵다
- 처음에 분석하는
- 인기 or 랜덤
- 연관분석
- cb와 연계
### MF -> CF의 한 기법 user-item의 매트릭스에 비어있는 요소를 채우는 기술
- 모델 기반 추천 방식
- 정체 사용자 방식으로 구하는 방식이라 우리 프로젝트에 오히려 안맞을듯..
### CB
- 프로젝트에서의 장점
- **추천을 한 이유가 타당하다**
- 상품 설명을 적어둔다면 text기반으로 좀 더 높은 추천 확률을 기대해 볼 수 있다.
- 사실상 유저 데이터없이도 가능함 - 개인별 데이터 트랙킹은 해야겠지만..
- 프로젝트에서의 단점
- CF 보다는 떨어지는 성능
- 필요요소
- Represented Items - raw data of products
- Profile Learner - user profile 분석 (머신러닝..?)
- Filtering Component - 위 두개 조합해 원하는 아이템을 추천
- weight rating - 평가가 많으면 평점이 낮을 수 밖에 없다, 이것을 좀 완화시켜주는 방법
### Hybrid
- Ensemble Method - 모델끼리 어떻게 조합해야하는 문제
- 가중합 두기
- 공부해야할 것
- 상관계수, 코사인 유사도, 자카드 계수
## 개인화 추천 검증 목표
- 선정한 개인화 추천 알고리즘 - CB(contents based filtering) + CF(collaborative filtering) = 하이브리드 알고리즘
- *RMSE: 추정 값 또는 모델이 예측한 값과 실제 환경에서 관찰되는 값의 차이를 다룰 때 흔히 사용하는 측도이다.
- CB 알고리즘 검증 방법
- 상품마다 가지고 있는 속성별 상관계수와 코사인 유사도를 바탕으로 추천 (카테고리, 브랜드, 컬러, 컬렉션)
- 속성마다 가중치를 다르게 두는 방법으로 최적의 RMSE(Root Mean Square Error) 값을 도출
- CF 알고리즘 검증 방법
- 상품 기반 CF 알고리즘을 이용, 상관계수와 코사인 유사도 바탕으로 추천
- 적은 유저 데이터로도 유의미한 추천 결과 도출 가능
- 유저별 신뢰도 가중치 변경으로 최적의 RMSE 도출
-> 추천 품목 마다 백로그를 기록, 상품의 추천 이유 검증 가능하게 만들기!
- 상품 데이터 베이스 테이블 기준으로 크롤링할 데이터 선정 및 속성 설정
- 상품, 유저 데이터 각각 1000개 제작 예정
- 상품: 데이터 크롤링을 이용한 상품 정보 (데이터 베이스 상품 테이블 속성 기준)
카테고리별 물품 설정 - 스니커즈(450), 의류(250), 패션 잡화(200), 라이프(80), 테크(20)
- 유저: 유저 활동데이터 수집 -> 구매(3점), 찜(2점), 클릭(1점) 활동들에 각각 점수를 부여해 상품별 선호 지수 도출
실제 유저 데이터 취합 후 일정 오차범위 내의 무작위 데이터 생성 예정 - 상품데이터 추출 및 결과별로 지속적으로 무작위 데이터 비율 업데이트 할 예정 (현재 20 실제 유저데이터 : 80 더미 데이터 )
- 콜드 유저에 대해서 다양한 방법을 도입 후 최적의 RMSE 결과 도출
- 인기 제품 추천, 설문, 랜덤 아이템 추천, 연관 분석 방법 중 택 1
- 개인화 추천 알고리즘 구현 계획
- python numpy를 이용해 구현, 추천 값들의 점수와 관련 이유들을 백로그를 통해 분석
- 흐름: Represented Items (상품 데이터 직렬화) - Profile Leaner (user 활동 데이터 분석) - Filtering Component (위 두개 알고리즘을 조합) - Weight rating (이후 값 변화를 이용한 최적화)
- Represented Items
- numpy를 이용한 수집한 유저, 상품 데이터 직렬화 (pycharm)
- Profile Leaner
- 유저 활동 데이터를 바탕으로 각 상품 마다 최대 6점, 최소 0점의 호감도 점수 부여, 테이블 제작 -> CF
- 유저 활동 데이터를 바탕으로
- Filtering Component
- Ensemble method를 이용 가중합을 어디에 더욱 둘지 최적의 비율 설정
- Weight rating
- 실제 도출된 값들을 바탕으로 CB에는 속석 별 가중치, CF에는 유저별 신뢰도 가중치를 더해가며 백로그 분석과 동시에 최적의 추천값 계산
# 참고 자료
코틀린 공식 문서
https://kotlinlang.org/docs/home.html
영속성 관련
https://velog.io/@roro/JPA-JPQL-update-%EC%BF%BC%EB%A6%AC%EB%B2%8C%ED%81%AC%EC%99%80-%EC%98%81%EC%86%8D%EC%84%B1-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8
https://huisam.tistory.com/entry/persistContext