안녕하세요! 조엘입니다! 🤞🤞
우아한테크코스의 레벨3는 팀 프로젝트입니다!
저는 놀토라는 멋진 팀에서 "부담없이 자랑하는 작고 소중한 내 토이프로젝트"라는 목표를 가지고 개발하고 있는데요!
프로젝트 소개는 더 완성된 이후에 가져오도록 할게요 :)
먼저 궁금하신 분은 놀토 리포로 와주세용 🎺🎺
https://github.com/woowacourse-teams/2021-nolto
이번 레벨 역시 학습로그를 통해 배운 것들을 정리하였는데요.
저는 이번 레벨에 다음과 같은 주제를 공부했습니다!
JPA 기본
- 왜 필요한가?
- 객체를 관계형 DB에 저장하면서 발생하는 문제들
- SQL 중심의 개발로 변질
- 패러다임 불일치
- 상속 유무
- 연관 관계의 방향성(객체는 자유롭게 객체 그래프를 탐색할 수 있어야 함)
- 데이터 타입 불일치
- SQL 쿼리문으로 DB를 조회해 오는 것의 단점
- 수정의 포인트들이 너무 많아짐
- 신뢰성이 떨어지는 Dao
- 반복적인 비슷한 쿼리문
- 객체를 관계형 DB에 저장하면서 발생하는 문제들
- 장점
- 신뢰할 수 있는 Entity
- Collection을 사용하듯 DB에 저장 (객체지향적)
- JPA 성능 최적화 가능
- 1차 캐시, 쓰기 지연, 지연 로딩 등
- 영속성 컨텍스트
- 엔티티를 영구 저장하는 환경
- 하나의 트랜잭션 당 하나의 엔티티 매니저
- 엔티티 매니저로 엔티티 저장/조회 시 영속성 컨텍스트에 엔티티 보관/관리 (이를 통해 JPA의 기능 사용가능)
- 1차 캐시
- 동일성 보장
- 쓰기 지연
- 변경 감지
- 지연 로딩
- 일대다 연관 관계
- 일대다 관계에서 "다" 쪽이 FK를 가짐
- "다" 쪽이 연관 관계의 주인(외래키 관리자)으로써, 외래키를 등록/수정/삭제 가능
- 외래키를 조작할 때에는 양쪽의 관계를 신경쓰면서 코드를 작성할 것!
- 일대다 + 양방향 매핑 예시
-
@Entity public class Station { @Id @GeneratedValue private Long id; @Column(nullable=false) private String name; @ManyToOne @JoinColumn(name = "line_id") //FK로 사용할 칼럼의 이름 private Line line; } @Entity public class Line { @Id @GeneratedValue private Long id; @Column(nullable=false) private String name; @OneToMany(mappedBy = "line") private List<Station> stations = new ArrayList<>(); }
- 링크
깃 브랜치 전략
- 왜 필요한가?
- 하나의 브랜치로 관리하면 어디에서 에러가 터졌는지 추적이 힘듦
- 에러를 찾아도 그 이후 개발한 신규 기능 배포가 어려움
- 놀토의 깃 브랜치 전략
- main: 실제 제품 배포 브랜치
- release에서 넘어온 커밋에 태그를 붙여 정식 출시
- release: 이번 출시 버전 준비를 위한 브랜치
- 출시하기로한 기능까지 개발이 완료되면, develop에서 파생
- 실제 배포 환경과 유사한 환경에서 QA 진행
- 버그 발견 시 hotfix를 활용하여 수정
- 태그 붙여 main에 머지
- develop: 다음 출시 버전 개발을 위한 브랜치
- feature 브랜치를 머지
- feature: 신규 기능 개발
- feature/backend/
, feature/frontend/등으로 기능 개발
- feature/backend/
- hotfix: 에러 수정 브랜치
- release 브랜치의 QA 도중 에러 발생시 해당 브랜치에서 해결
- main: 실제 제품 배포 브랜치
- 프로젝트를 진행하며 변경된 전략
- develop에도 실제 배포환경과 유사하게 인프라 구축을 했었음
- OAuth 및 최신 기능을 프론트단에서 사용하기 위함
- develop에서 본의아니게 QA가 진행됨
- 여기서 발생한 버그를 hotfix로 파서 머지시키는 일이 계속 발생함
- release의 의미가 조금 퇴색되었음
- 그냥 tag를 붙이는 용도 정도?
- develop에도 실제 배포환경과 유사하게 인프라 구축을 했었음
- 머지의 종류
- Merge
- 머지 과정에서 개발 시 남긴 커밋을 참조하는 하나의 커밋 생성하고 머지
- Squash and Merge
- 머지 과정에서 개발할 시 남긴 커밋을 합쳐 새로운 하나의 커밋으로 만듦
- "feature -> develop" 개발에서 해당 머지를 사용
- Feature의 경우 기능개발이 끝나면 삭제하니, 커밋을 굳이 다 남길 필요가 없음
- Rebase and Merge
- 그 전까지 했던 작업 이후로 신규 커밋들을 붙임
- "develop -> release -> master" 개발에서 해당 머지를 사용
- 머지 과정에서 별도의 새로운 커밋을 생성하지 않아 좋음
- Merge
- 링크
웹훅
- 왜 필요한가?
- 웹훅은 웹앱에서 발생한 이벤트 정보를 "실시간"으로 제공하기 위해서 필요함
- 클라이언트 측에서 필요한 정보를 서버 측에 계속 물어보는 방식(Polling)이 아닌,
서버 측에서 클라이언트에게 필요한 정보가 도착하면 알려주는 방식(Webhooks)
- 사용처
- 깃헙에서 PR이 머지되면 젠킨스가 해당 리포를 새롭게 빌드하여 배포하도록 설정
- 젠킨스(클라이언트) <- 깃헙(서버)
- Error 레벨의 로그가 발생하면 슬랙에 알림 전송
- 슬랙(클라이언트) <- 놀토 EC2(서버)
- 깃헙에서 PR이 머지되면 젠킨스가 해당 리포를 새롭게 빌드하여 배포하도록 설정
- 링크
CI/CD
- 왜 필요한가?
- 서버에서 돌릴 수 있는 jar 파일로 만들어 배포를 해야함
- 하지만 해당 작업은 단순 반복이 많음
- 반복되는 과정의 자동화 필요 + 휴먼 에러를 없애야 함
- CI가 왜 필요한가?
- 팀원들이 나누어 개발하던 것을 하나로 합치는 과정에서 문제 발생할 수 있음
- 코드 작성이 완료되면 바로 합쳐 오류에 대한 피드백 받을 수 있음
- 애자일하게 기능 개발을 완료하는 개발 문화와 잘 어울림
- CD가 왜 필요한가?
- 실제로 배포하는 과정에서 휴먼 에러가 발생 가능
- 이를 잡아줌
- 서버에서 돌릴 수 있는 jar 파일로 만들어 배포를 해야함
- 젠킨스
- 자바 기반의 자동화 CI/CD 툴
- 다양한 플러그인을 활용해 효과적인 자동화 가능
- 놀토 젠킨스
- 도커에 젠킨스 설치하여 사용
- Freestyle Project로 구성
- 플러그인 설치
- git pull request builder (github 리포에서 코드 pull 받아 올 수 있도록 설정)
- publish over ssh (jar 파일을 WAS EC2에 전송)
- 깃헙에서 젠킨스 관련 필요 설정 해주기
- 젠킨스 workspace/repository/ <- 해당 디렉토리에서 빌드 작업 진행
- 이후 jar 파일을 WAS EC2에 전송
- WAS EC2에서 실행시킬 쉘 스크립트를 실행
- 링크
S3
- 왜 필요한가?
- 놀토 서비스 특성상 많은 사진들을 저장해야 했음
- 파일 서버의 역할을 하는 S3가 적격
- 파일 서버 트래픽 증가 시 장비 증설 대행
- 저장 파일의 갯수를 계속해서 늘릴 수 있음
- 다른 파일 서버들과 비교해보진 않았지만 다음과 같은 장점이 있다고 함
- 내구성이 좋음 (클라이언트 파일 유실 X)
- 저렴한 비용 (사용한 만큼만 지불)
- 가용성 (파일 서비스 중단 거의 없음)
- 보안 (SSL을 통한 안전한 운반)
- 놀토의 S3 사용법
- S3 버킷 만들기
- 유일한 이름으로 만들어야 함
- 퍼블릭으로 공개하지 않음
- 따라서 Cloudfront CDN을 통해 접근해야함
- 어떤 S3 버킷에 대한 CDN인지 설정
- S3 bucket access "Origin Access Identity"를 사용하여 Cloudfront에서는 S3에 접근 가능하게 설정
- bucket policy도 곁들여서 업데이트 해주자
- S3 버킷 만들기
- 스프링에서 S3 버킷에 접근하기
- access-key, secret-key를 발급하여 BasicAWSCredentials을 활용하여 S3에 접근 가능
- 하지만 이는 발급 금지
- WAS EC2에 IAM 권한을 부여하여 S3에 접근 가능하였음
- ec2-s3-deploy
- access-key, secret-key를 발급하여 BasicAWSCredentials을 활용하여 S3에 접근 가능
- 링크
OAuth
- 왜 필요한가?
- 패스워드 없이 사용자 인증이 가능함
- 놀토 서버에서 사용자의 비밀번호를 관리할 필요 없이 서비스 제공 가능
- 패스워드 없이 사용자 인증이 가능함
- 놀토의 OAuth 사용법
- 구글, 깃헙 OAuth를 활용
- 각각 총 3개의 OAuth 클라이언트를 만듦
- develop: localhost:9000으로 redirectUrl 전송 (프론트 개발용)
- qa: https://d2y0p6hv0hkdrd.cloudfront.net로 redirectUrl 전송 (QA 용)
- main: https://nolto.app로 redirectUrl 전송 (실 서비스용)
- 각각 총 3개의 OAuth 클라이언트를 만듦
- client_id와 client_secret은 민감정보이기에 깃헙에 올리지 않고 EC2 인스턴스 내부에서 yml파일로 관리
- 구글, 깃헙 OAuth를 활용
- OAuth 로직
- https://resource.server?client_id=1&scope=B,C&redirect_uri=https://client/callback 요청
- 사용자가 해당 resource.server 에서 로그인
- https://client/callback?code=authorization_code로 리다이렉트 됨
- authorization_code를 활용하여 https://resource.server/token에 필요한 정보를 담아 요청
- JSON body나, 쿼리스트링으로 정보를 담아 요청을 보냄
- resource.server에서 해당 사용자의 토큰이 발급됨
- 이제 해당 토큰으로 사용자의 필요한 정보를 조회해옴
- 링크
도커
- 왜 필요한가?
- snowflake server 이슈 방지
- 특정 환경에 종속되지 않은 어플리케이션을 띄우는 것이 목표
- 어플리케이션만 배포하면 되는데 OS 자체를 띄우는 VM을 설치하는 것은 오버 엔지니어링
- 링크
- https://github.com/joelonsw/woowacourse-TIL/blob/master/Level3/2021-08-15.md
- https://github.com/joelonsw/woowacourse-TIL/blob/master/Level3/2021-08-21.md
- https://github.com/joelonsw/woowacourse-TIL/blob/master/Level3/2021-08-22.md
- https://github.com/joelonsw/woowacourse-TIL/blob/master/Level3/2021-07-13.md#%EB%8F%84%EC%BB%A4
- https://github.com/joelonsw/woowacourse-TIL/blob/master/Level3/2021-07-14.md#%EB%8F%84%EC%BB%A4-%EC%84%A4%EC%B9%98-%ED%9B%84-%EC%BB%A8%ED%85%8C%EC%9D%B4%EB%84%88-%EC%8B%A4%ED%96%89
- https://github.com/joelonsw/woowacourse-TIL/blob/master/Level3/2021-07-17.md#%EB%8F%84%EC%BB%A4---%EC%9D%B4%EB%AF%B8%EC%A7%80-%EB%A7%8C%EB%93%A4%EA%B3%A0-%EB%B0%B0%ED%8F%AC%ED%95%98%EA%B8%B0
DB Migration
- 왜 필요하지?
- 데이터베이스의 스키마 변경 사항을 기록하여 버전관리
- DB의 변경이력을 남기면서 안전한 DB 변경 가능
- 기존의 데이터를 날리고 DB 스키마를 바꾸고 백업해 줄 필요가 없어짐
- 사용법
- 메타데이터 테이블을 만들어
- 버전을 추가하며 DB를 변경해 나감
- 꼬리에 꼬리를 무는 변경
- 링크
로깅
- 왜 필요하지?
- 디버깅을 하지 못하는 상황에서 로그를 남김
- 웹 기반은 사실 로깅에 의존해야하는 경우 다수
- 서버에 내 프로그램 올려서 디버깅을 할 수 있는 툴이 별로 없어
- 실서버는 로깅 보면서 해결해야 하는 경우가 다수
- 놀토의 로깅전략
- 로깅 레벨
- FATAL: 매우 심각한 에러, 프로그램 종료되는 경우
- ERROR: 에러가 발생하지만, 프로그램 종료되지 않는 경우
- ----------------------의도하지 않은 Exception-----------------------
- WARN: 에러가 될 수 있는 잠재적 가능성 있는 경우 (ex. 인메모리 캐시 용량 꽉차감, DB 커넥션 예상보다 오래걸림)
- INFO: 애플리케이션의 상태를 간결하게 보여주는 경우
- DEBUG: INFO 보다 더 자세한 정보가 필요한 경우
- TRACE: DEBUG 보다 더 자세한 정보가 필요한 경우, 최종 프로덕션이나 커밋에서 포함하지 말 것
- XML으로 로깅 설정 작성 후 yml에서 spring profile로 추가하여 사용
- profile별 어플리케이션 런타임 로깅 전략
- local: console에 INFO 이상의 로그
- dev: ERROR 레벨 이상의 로그, INFO 레벨 이상의 로그 각각 nolto-error.log, nolto-info.log에 저장
- prod: ERROR 레벨 이상의 로그, INFO 레벨 이상의 로그 각각 nolto-error.log, nolto-info.log에 저장
- dev/prod 환경에서 ERROR 레벨의 로그 찍히면 슬랙에 메시지 전송
- sql 쿼리 로그 찍기
- DB에 보낸 쿼리문을 nolto-sql.log에 저장
- dev 환경에서만 해당 파일 저장
- profile별 어플리케이션 런타임 로깅 전략
- 로깅 레벨
- 링크
트랜잭션
- 트랜잭션이란?
- 데이터베이스 트랜잭션은 일관성 있고 신뢰할 수 있는 방식으로 독립적이게 처리되는 작업 단위를 뜻한다.
- 데이터베이스 트랜잭션은 데이터베이스의 변화를 나타낸다. 데이터베이스 트랜잭션은 크게 두 가지의 목적을 가진다.
- 예상치 못한 에러가 발생해도 데이터베이스를 신뢰성 있는 상태로 만들 수 있도록 신뢰할 수 있는 작업 단위를 제공한다.
- 데이터베이스에 동시에 접근하는 경우 프로그램 간에 격리를 제공하여 에러를 방지한다.
- 트랜잭션의 성질
- 트랜잭션의 성질은 ACID로 대표됨
- [ Atomicity - 원자성 ]
- [ Consistency - 일관성 ]
- [ Isolation - 독립성 ]
- [ Durability - 지속성 ]
- 트랜잭션의 성질은 ACID로 대표됨
- 스프링에서의 트랜잭션
- AOP를 활용한 선언적 트랜잭션
@Transactional
- 여러가지 옵션을 어노테이션의 속성 값으로 명시함으로써 간편히 사용가능
- 트랜잭션 전파 설정
- REQUIRED, REQUIRES_NEW, MANDATORY, NESTED, NEVER, SUPPORTS, NOT_SUPPORTED
- 트랜잭션 고립 수준
- DEFAULT, READ_UNCOMMITED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE
- AOP를 활용한 선언적 트랜잭션
- 링크
빈 생명주기
- 빈 생명주기
- 스프링의 컨테이너는 빈 객체의 생성/소멸/주입/제공 등을 담당
- 생명주기는 다음과 같음
- 빈 인스턴스화 및 DI
- XML/자바 설정 클래스/어노테이션으로 빈 정의 스캔
- 빈 인스턴스 생성
- 빈 프로퍼티에 의존성 주입
- 빈이 인터페이스 구현시 호출
- 빈이 BeanNameAwate 인터페이스 구현 시 setBeanName() 호출
- 빈이 BeanClassLoaderAware 인터페이스 구현 시 setBeanClassLoader() 호출
- 빈이 ApplicationContextAware 인터페이스 구현시 setApplicationContext() 호출
- 빈 생성 생명주기 콜백
@PostConstruct
어노테이션 적용 메서드 호출- 빈이 InitializingBean 인터페이스 구현 시 afterPropertiesSet() 호출
- 빈이 init-method 정의하면 지정한 메서드 호출
- 빈 소멸 생성주기 콜백
@PreDestory
어노테이션 적용 메서드 호출- 빈이 DisposableBean 인터페이스 구현시 destory() 호출
- 빈이 destory-method 정의하면 지정한 메서드 호출
- 빈 인스턴스화 및 DI
- 링크
JPQL
- 왜 필요하지?
- 필요한 정보를 가져오기 위해 .get().get() 을 통해 가져오는 것에 대한 비효율성
- 필요한 데이터를 DB에서 가져오려면 검색 조건을 명시한 SQL 쿼리의 필요성 대두
- JPQL?
- JPA는 SQL을 추상화한 JPQL이란 객체 지향 쿼리 제공
- SQL과 문법이 유사
- JPQL은 엔티티 객체를 대상으로 쿼리를 질의하고, SQL은 데이터베이스 테이블 대상으로 쿼리를 질의
- 테이블이 아닌 객체를 대상으로 검색하는 객체지향 쿼리!
- SQL을 추상화했기에 특정 DB 벤더에 의존 X
- JPA는 SQL을 추상화한 JPQL이란 객체 지향 쿼리 제공
- 링크
자기참조 테이블
- 자기 참조 관계
- 하나의 엔티티가 다른 엔티티가 아니니 자기 자신과 관계를 맺는 타입
- 동일 엔티티 타입 내에서 자기가 자기의 인스턴스를 참조하는 구조
- 계층 구조를 나타내기에 유용
- 놀토에서는 댓글-대댓글에 사용
- FK로 연결하지 않고 상위 계층의 id는 자기 참조를 통해 나타냄
- 하나의 엔티티가 다른 엔티티가 아니니 자기 자신과 관계를 맺는 타입
- 링크
JPA N+1 문제
- 발생하는 경우
- fetchType.EAGER + findAll() 메서드 호출하는 경우 N+1 개 만큼 쿼리가 나감
- fetchType.LAZY + Loop으로 조회하는 경우 N+1 개 만큼 쿼리가 나감
- 해결 방법
- JPQL 조인 페치 사용
- 어떤 자식 엔티티까지 join해서 가져올지 명시
- JPQL 조인 페치 사용
- 링크
Spring EventListener & TransactionalEventListener
- 왜 필요하지?
- 복잡한 도메인 의존성을 줄이기 위함
- 직접적으로 연관이 있는 로직이 아님에도 빈을 주입받아 호출하는 것은 너무 강력한 연관관계를 맺어준 것
- ex. 댓글/좋아요가 달렸을 때 알림을 보내주는 것
- 복잡한 도메인 의존성을 줄이기 위함
- EventListener
- 호출 즉시 로직 시작
- TransactionalEventListener
- 해당 트랜잭션이 끝나면 로직 시작
- 링크
- https://github.com/joelonsw/woowacourse-TIL/blob/master/Level3/2021-08-04.md
- https://jobc.tistory.com/200
- https://javacan.tistory.com/entry/Handle-DomainEvent-with-Spring-ApplicationEventPublisher-EventListener-TransactionalEventListener
- https://engkimbs.tistory.com/827
- https://reiphiel.tistory.com/entry/understanding-of-spring-transaction-management-practice
반응형
'외부 활동 > 우아한테크코스' 카테고리의 다른 글
Level4 미션 정리 (0) | 2021.11.12 |
---|---|
[10분 테코톡] 조엘의 GC (6) | 2021.10.13 |
Level2 미션 + 학습 로그 정리 (2) | 2021.06.22 |
Level1 미션 + 학습 로그 정리 (1) | 2021.04.26 |
저는 모든 게 처음이라니까요? (2) | 2021.02.22 |
댓글