2019.11.27
한양대학교 - 김상욱 교수
- 추천 시스템을 적용해서 성공한 대표적인 기업
- 아마존, 넷플릭스, 유튜브, 인스타그램 등
- 추천 시스템의 기반 기술
- Content-based approach
- 아이템의 컨텐츠와 사용자 히스토리의 컨텐츠를 매칭
- Trust-based approach
- 많은 사용자들의 신뢰도 기반
- Collaborative Filtering (CF) approach
- 협업 필터링
- 나와 취향이 비슷한 이웃들에 의해 많이 추천된 아이템을 추천한다.
- Content-based approach
NHN Edu 서버개발팀 - 신승엽
- Git flow
- 항상 존재하는 브랜치
- master 브랜치
- develop 브랜치
- 서포팅 브랜치
- 필요할 때 생성 후 삭제하는 브랜치
- feature 브랜치
- 특정 feature의 개발이 완료되면 다시 develop으로 merge 이때, fast forward 하지 않도록 주의
- release 브랜치
- feature들이 포함된 develop 에서 따고, 릴리즈 후엔 master와 develop에서 merge
- hotfix 브랜치
- master에서 출발
- 항상 존재하는 브랜치
- GitHub flow
- Git glow는 대부분의 케이스에서는 너무 복잡하다
- 사람들이 워크 플로우를 이해하기 쉽게 되어 실수가 없어지고 헤메지 않게 되었다고 설명함
- Master 브랜치가 항상 stable한 상태여야 한다. 현재 master 그대로 배포되어도 이상하지 않은 상태로 유지해야 한다.
- 새로운 기능을 개발할 때는, Topic 브랜치를 master 브랜치에서 딴다.
- 기능 개발
- Pull request 개설
- 코드 리뷰 / 논의
- 완료된 토픽 브랜치를 그대로 배포한다.
- 토픽 브랜치 배포시 반드시 CI 빌드를 통과해야 하며
- 락이 가능하다
- Master 브랜치의 최신 커밋이 존재하는지 확인해서 충돌을 막는다.
- 배포 후 이상없다면, master에서 해당 토픽 브랜치 merge
- 이때 배포 락이 해제 된다.
- GitLab flow
- git flow는 너무 복잡하며, github flow는 너무 간단하다
- 지속적인 배포가 어려울 때
- github flow는 마스터를 실서버에 그대로 배포하는 컨셉인데, 대부분의 회사에서 불가능할 수 있다.
- 이럴경우 production 브랜치를 관리하고, production에서 master를 merge해서 배포한다.
- 배포버전 관리에 용이하다.
- 환경별 배포가 필요할 때
- master는 staging에 자동 배포되고
- Pre-production, production 브랜치 두개를 관리할 수 있다.
- 릴리즈 소프트웨어일 때
- 마스터에서 버전별 stable 브랜치를 따서 배포한 후
- 핫픽스의 경우 master에서 수정하고 각 버전의 stable 브랜치로 cherry pick 한다.
- 이것은 마치 리눅스에서 hotfix 하고, 각 배포판에 적용하는 것과 같은 원리이다.
-
브랜치 전략
- 상황
- 단기간의 배포 일정인 경우
- 배포 날짜로 관리. develop-20191121 등
- 장기간의 배포 일정인 경우
- 코드네임으로 관리. develop-tomato 등
- 단기간의 배포 일정인 경우
- git flow base
- develop 브랜치에서 위의 상황에 맞는 각 sub develop 브랜치를 따고
- sub develop 브랜치 개발이 완료되면 QA를 진행한다.
- 사실 이 정책의 경우 릴리즈 브랜치가 필요 없다. sub develop이 명확하다.
- sub develop 개발 후 QA가 끝나서 배포가 완료되면, master와 develop에서 merge한다.
- 나머지 sub develop을 현재 develop에 rebase 한다.
- 연속되는 충돌이 있을 경우 -> 영상 참조
- 같은 이름의 sub develop 브랜치를 새로 따고, 체리픽 활용
- 핫픽스는 master에서 따서 완료후 master, develop merge
- 핫픽스 브랜치 merge 후에 sub develop들을 develop에 다시 merge.
- 현재 포털개발팀 TV줌파트에서 쓰는 방식과 매우 유사함. (릴리즈 브랜치 관리는 협업 환경에 따라 다르다고 생각함)
- 상황
-
개발 플로우
NHN Dooray 개발실 - 정명주
'현대의 웹 애플리케이션은 여러 서브 도메인으로 이루어져 있습니다. 여기에서 가장 중요한 핵심 도메인은 그에 걸맞은 방법론이 필요합니다. 이 복잡성의 끝에서 만날 수 있는 DDD-Lite를 인기 있는 Spring 프레임워크를 통해서 알아보겠습니다.'
- 복잡성과 위기
- 지식 탐구: 위키
- 구현: Model-Driven VS Data-Driven
- 아키텍처와 모듈
- 결론
- 복잡성
- 우리가 해결해야 할 문제 자체의 복잡성
- 우리가 사용하는 기술과 도구의 복잡성
- 위기
- 빠르고 간단하게 구축(일정 드리븐 개발)
- 문제의 출발점
- 새로운 요구사항
- 복잡성 증가
- 가파른 비용증가, 급격한 생산성 감소
- 위기를 맞고
- 차세대 개편 + 레거시 의존
- 잘못된 레거시에 의존하기 때문에 계속되서 악순환이 일어난다.
- 빠르고 간단하게 구축(일정 드리븐 개발)
- 시간이 지날수록 복잡성과 비용은 폭발적으로 우상향하게 된다.
- DDD로 이 위기를 극복해보자
- 해결해야할 문제 = 문제 공간
- 이것을 해결 공간으로 바꿔야 한다.
- 이해 당사자의 해결해야 할 문제를 도메인 지식을 결합해서 해결 공간을 뽑아 내려고 하는 것이 DDD의 전술적 패턴이다.
- 전술적 패턴 - 큰 그림
- 전략적 패턴 - 세부 그림
- DDD 전술적 패턴 일부를 적용하는 것을 DDD-Lite라고 한다.
- 주의할 점.
- 엔티티는 행위가 우선한다.
- 필드는 행위를 하는데 필요하는 경우에만 명세한다.
- 기존에 DB 스키마 만들고 엔티티에 필드 명세하면 끝나는게 도메인 엔티티 생성이 아니다.
- 예를 들면, create() 메서드를 먼저 생성하고 메서드에 필요한 필드를 추가 한다.
- 연관관계도 마찬가지다. 필요한 경우에만 단방향으로 매핑하자.
- 중복 필드는 Value Object로 해결할 수 있다.
- Value Object에도 행위를 명시할 수 있기 때문에 도메인이 풍부해 질 수 있다.
- Data Driven vs Domain Driven 차이점 중요.
- 도메인 서비스는 무상태.
- 헥사고날 아키텍처
- 기술보단 도메인이 먼저다.
NHN 클라우드 프레임워크 개발팀 - 최강훈
웹 개발은 백엔드와 프런트엔드로 나눠서 전문성을 가지고 개발하는 게 트렌드입니다. 그러나, 이미 서비스 중인 덩치 큰 일체형 웹 서비스를 나누는 건 막막하고 두려운 일입니다. 이 세션에서는 서버 개발자 관점에서 SPA를 도입하여 레거시 웹 서비스라는 괴물을 길들였던 경험과 고민을 공유하고자 합니다.
- 프롤로그: 무엇이 문제인가?
- 1막: 워밍업
- 2막: 본격 분리 작업
- 에필로그: 1년간 운영해보니...
- 일체형 웹 서비스(with MVC) 방식은 기능과 화면이 적을 땐 괜찮았다.
- 점점 비대해지는 웹 서비스
- 모두가 꺼리게 된 자바스크립트 코드
- 페이지는 무거워지고 서스테이닝은 점점 어려워 진다.
- 어떻게 하면 될까요?
- SPA를 도입하자.
- 웹 페이지 구현에 필요한 모든 정적 리소스를 최초 한번 다운 받고
- 이후 필요한 데이터는 그때그때 비동기로 받아서 화면을 만든다.
- 하나의 페이지만 존재하고 페이지 전환 및 구성을 자바스크립트로 구현한다.
- 장점
- 성능 개선
- 불필요한 네트워크 통신이 없어 진다
- 단점
- 검색엔진 최적화에 어려움이 있다
- 언제 쓰면 좋은가?
- UI 구성이 복잡하고, 데이터가 많이 필요한 웹 페이지
- 페이지 내에 정적인 요소보다 동적인 요소가 많은 경우
- 로그인을 필수로 해야되는 경우(검색엔진 최적화가 불필요한 경우)
- 개편에 앞서 동료와 협업 부서 설득하기.
- 장점
- 무기를 고르자
- 앵귤러, 리액트, vue.js
- 뭐가 제일 좋을까?(X)
- 뭐가 우리 조직에 잘 맞을까?(O)
- 앵귤러
- 모든걸 갖췄지만 러닝커브가 높다
- 리액트
- 강력한 기능을 가지고 있지만, 더 필요한 도구는 필요에 따라서 선택해야 한다.
- vue.js
- 최소한의 있을건 다 있고, 배우기 쉬우며, 빠르게 만들어 낼 수 있다.
- 앵귤러, 리액트, vue.js
- 우리 팀은 어떤가?
- 자바 개발자가 많고, 자바스크립트 개발 비중은 상대적으로 적은 편
- Vue.js 를 골랐다
- 러닝커브가 가장 낮고, 가이드 문서도 잘 되어 있다.
- single file component로 개발하면 하나의 파일에서 컴포넌트 단위로 작업할 수 있다.(퍼블리싱 팀과 협업시 용이)
- 많이 변해버린 자바스크립트
- ES6 문법이다.
- let, const
- Arrow function
- import, export
- class
- 등 구글링해서 10분만 보자.
- webpack 설정은 어떻게?
- 프론트엔드 프레임워크를 도입할때 최대 고비.
- gradle 학습할때와 비슷한 느낌으로 공부하자..
- ES6 문법이다.
- 프로젝트 구성
- Back-end, Front-end 어떻게 구성할까?
- 어떻게든 둘을 합쳐보자
- Spring-boot-vuejs
- 부트와 vuejs를 maven으로 한번에 빌드
- Spring-boot-vuejs
- 빌드 프로세스
- 프론트 엔드 빌드
- 백엔드 리소스 디렉토리에 copy
- 백엔드 빌드
- 모든 요청을 was에서 해결하기
- static 파일인데도 was로 서비스 해야되나?
- UI 변경이 잦은 편인데, 매번 백엔드 포함하서 빌드/배포 해야되나?
- 전과 차리가 없는데?
- UI요청과 API 요청을 구분하자
- 리소스 요청은 웹서버에서
- 나머지는 was에서
- 결론
- 굳이 둘을 섞어서 프로젝트를 구성할 필요가 없었다.
- 모놀리스 개발을 하다보니 합치는 것에 강박이 있었던 것 같다.
- 백엔드 프로젝트와 프론트엔드 프로젝트는 과감하게 나누자
- API 호출 방식
- 상태 관리
- 프론트엔드의 복잡한 컴포넌트 간 데이터 전달
- 컴포넌트간 의존 관계가 복잡하다.
- 컴포넌트 통신이나 이벤트 버스로는 벅차다
- 중략 ...
- 상태 관리 라이브러리 vuex 사용하자는 결론
- vuex 사용 팁
- api 호출 횟수 줄여보기
- lodash의 debounce를 적용하여 여러 번 호출해도 한 번만 호출되도록 구성
- 이미 로드한 데이터라면 추가로 호출되지 않도록
- 데이터 가공은 어떻게?
- RxJS에는 데이터 가공을 위한 유용한 연산자가 많이 존재함
- vuex의 state, getter는 컴포넌트뿐만 아니라 다른 곳에서도 호출이 가능하다.
- router에서 사용하면 데이터 로딩이 완료된 다음에 화면을 노출할 수 있음(서버 렌더링 처럼 보이는 효과)
- api 호출 횟수 줄여보기
- 결론
- 상태관리 라이브러리 사용해서 중앙 집중식 데이터 관리 하자
- 다국어 처리
- Spring MVC에서 다국어 처리
- Spring MessageSource 사용
- 서버 렌더 방식이다 보니 언어 변경을 하려면 반드시 새로 고침
- 이제 다국어 처리도 프론트엔드에서
- vue-i18n 플러그인 사용
- 새로고침 없이도 언어 변경 가능
- 아직 남아있는 문제점
- 문구 수정 요청이 잦다
- 문구 수정을 위해서는 여전히 빌드/배포가 필요하다.
- 해결 책
- 메시지 서비스를 만들자
- 다국어 메세지를 관리하는 통합 메세지 서비스
- 동일한 key를 사용하여 4개 국어 저장
- 빌드 배포를 하지 않더라도 메세지 서비스에 요청해서 처리할 수 있다.
- Spring MVC에서 다국어 처리
- 배포 환경에 따른 설정 값 처리
- 프론트엔드에서는 배포 환경에 따라 설정값을 어떻게 처리?
- development
- production
- 실무에서는?
- 팀, 프로젝트마다 사용중인 환경설정이 제각각
- webpack 설정 파일의 분리
- 각 환경별로 webpack 설정 파일 분리
- DefinePlugin 사용하여 각 환경별로 다른 설정값 추가
- package.json에서 각 환경에 맞게 빌드 명령어 구성
- 프론트엔드에서는 배포 환경에 따라 설정값을 어떻게 처리?
- 성능 측면
- 확실히 줄어든 API 호출 횟수
- 웹 리소스 최적화
- 웹팩 빌드 최적화 되면서 리소스 용량 작아짐
- 운영 측면
- 빌드/배포는 꼭 필요할 때 필요한 모듈만
- 개발 측면
- 프론트엔드 프레임워크의 도입
- 규칙과 일관성이 생긴 코드
- ESLint가 많이 도와줌
- 가독성 증가
- 재사용 가능한 컴포넌트 개발 지향
- 백엔드는 데이터에만 집중하기
- 서버 렌더링이 없으니 데이터만 생각하면 됨
- API Gateway의 역할에 집중
- 프론트엔드 프레임워크의 도입
NHN Dooray개발실 - 신동민
- Spring JPA의 사실과 오해
- 연관 관계 맵핑에 대한 모든 것
- Spring Data JPA Repository의 숨겨진(?) 기능
- 연관관계 매핑
- 사실상 단방향 매핑만으로 연관관계 매핑은 이미 완료
- 대개의 경우 단방향 매핑이면 충분하다.
- 하지만, 일대다 단방향 연관관계 매핑에서 영속성 전이(cascade)를 사용할 경우 양방향으로 변경하자
- 추가 update 쿼리 방지
- Spring Data JPA Repository
- JpaRepository 상속하면 웬만한 CRUD, Paging, Sorting 메서드 사용가능
- 메서드 이름 규칙을 통한 쿼리 생성 가능
- 이름 규칙에 따라 interface에 메서드 선언만 하면 쿼리 생성
- JPA Repository 메서드로도 JOIN 쿼리 수행 가능
- 이름 규칙에 따라 Entity 내 연관관계 필드 탐색함
- JPA Repository 메서드에서도 다양한 DTO Projection 지원
- Dynamic DTO Projection도 가능하다.
NHN 이병찬
- Kafka 메시지를 비동기로 처리하는 방법
- ReactiveX에서 제공하는 연산자를 활용하는 사례
- Project Reactor의 내부 구조(Publisher-Subscriber 간 처리 흐름)
예제 코드 저장소 - https://github.com/EleganceLESS/nhn-forward-2019
- 가장 인기 있고 대중적인 스트리밍 플랫폼
- Spring에서 Kafka 쓰는 방법이 매우 간단해 졌다. JMS 리스너와 유사.
- 제약 조건
- 1스레드당 1개의 일감을 처리할 수 있는데, 일감이 오래걸리면 병목이 발생한다.
- 병렬로 처리할 순 있으나 동일한 문제는 여전히 존재 한다.
- 스트리밍 플랫폼의 본질?
- producer의 publish
- consumer의 subscribe
- 그 사이의 stream
- Kafka를 reactive하게 사용하기 위해 Reactor Kafka 또는 Spring Kafka 사용
- producer가 publish 할 때, Flux를 만들어서 stream에 밀어 넣는다.
- consumer도 subscribe 할 때, Flux로 가져와서 처리한다.
- 서버 모니터링 시스템에서 reactive kafka를 활용했다
- 기본 로직은 서버에서 메트릭 정보를 가져와서 메트릭DB에 넣고 그것을 보여준다.
- 그 사이에서 Detector(관찰자)가 존재하여 메트릭 정보를 활용해 사용자에게 Event를 전달한다.
- 이 때 Detector는 수 많은 서버에서 오는 메트릭을 다 관장해야 한다. 커버링 범위가 매우 크다.
- 1000 대 이상의 서버에서 동시다발적으로 이벤트가 감지되면?
- 해당 이벤트가 짧은 간격으로 수차례 반복하면?
- 어쨋든 이상 이벤트 통지에 지연이 발생해서는 안되며, 각 이벤트는 상호 독립성이 보장되어야 한다.
- 이벤트가 발생하면 Detector는 그 정보를 메세지로 만들고 스트림에 집어 넣는다.
- 이벤트 프로세서에서는 메세지를 읽고 DB에 기록 후, 필요하면 메세지 발송 요청을 보내자.
- 서버에서 Dector에게 데이터 보내고, 문제가 있으면 Event에 집어 넣는다.
- 우리는 Detector가 매우 중요하므로 HA 구성을 하게 된다.
- 2대의 Detector는 2개의 input과 2개의 ouput이 발생한다. 이벤트 중복을 제거하자.
- Flux Operator의 sampleFirst(), groupBy()를 이용해서 처리
- 최악의 상황의 경우? 동시다발적 또는 많은 양의 메세지 발생
- 기준 시간 동안 발생한 여러 이벤트는 하나의 메세지로 모아서 통지하자.
- 발생하는대로 메세지를 보내게 되면 1000건 발생하면 1000개의 메세지를 받게 된다.
- ex) 30초 단위로 버퍼에 쌓아서 1건으로.
- Flux Operator의 buffer() 이용해서 처리 가능하다.
public void process() {
consume().flatMap(this::recordToNotifyObject)
.groupBy(Message::key)
.flatMap(flux -> flux.buffer(Duration.ofSeconds(30))) //버퍼링 - 30초
.flatMap(this::notify)
.flatMap(this::saveResult)
.subscribe();
}
- 우리가 잘 만들었다고 쳐도, 이 시스템을 사용하는 연관 시스템이 해당 API Call을 견딜 수 있는지 확인해야 한다.
- API 서버도 reactive하게 만들었다. 해결 됐지 않나?
- 결국 그 요청은 DB가 다 받는다.
- 발송 시스템의 경우에도 과도하게 요청을 다 보내면 과금이 어마어마하게 늘어난다.
- Custom Subscriber를 만들어서 subscribe할 때, hookOnSubscribe() 를 정의할 수 있다. (정해진 양 만큼 발송)
- subscribe 시작시점에는 순차지만, 비동기기 때문에 끝나는 시점에 순서를 보장할 수 없다.
- request(4)로 요청해서 1243 순서로 끝났는데 서버가 뻗었다.
- 그 다음 요청은 3부터 시작해서 onNext() 4567을 처리하게 된다.
- 해결 - offset이 증가하는 경우에만 commit을 하자.
- 코드는 추후 제공되는 영상 참조
- 많이 공부하고, 많이 고민하고, 적게 코딩하자