프로그래머스 웹백엔드 스터디 7기 / 4주차 - 비즈니스 로직 2 댓글 구현과 S3 이미지 업로드 비동기 처리
03 Dec 2020 | Spring 스터디 프로그래머스 웹백엔드프로그래머스 웹백엔드 스터디 7기 활동을 하면서 공부한 내용과 기능 구현시 했던 고민들을 정리한 내용입니다.
Index
- 스터디 4주차
- 느낀점
- 고민
- 공부한 내용
- TODO 기능 구현
- References
스터디 4주차
비즈니스 로직 구현 2 - S3 이미지 업로드 비동기 처리와 댓글 구현
느낀점
이번주도 2주차처럼 기술적으로 어려운 미션들은 아니었지만, AWS S3 업로드 예외가 발생했을 때 어떻게 처리할 건지, 로그는 어떻게 어떤 내용을 남길 건지에 대한 고민을 하게 되었다. 여기서는 프로필 업로드를 회원 가입할 때 진행하였는데, 업로드 과정을 CompletableFuture를 이용해 비동기처리로 하는 과정이 흥미로웠다. 이외에도 수많은 메소드들이 있다고 리더분께서 말씀해주셨는데, 다음에 자바 비동기처리에 대해 좀 더 공부를 해봐야겠다.
고민
-
회원 가입시 필요한 프로필 사진 업로드. 업로드 실패시 예외를 무시하고 가입 처리 시켜야 하나? 아니면 가입 실패로 간주해서 예외를 던져야 하나?
- 수많은 SNS들을 생각해보면 기본 프로필 사진이라는게 존재한다. 즉, 프로필 이미지는 필수가 아니고 실패해도 비즈니스적으로 문제가 크지 않기 때문에 예외를 무시하고 회원 가입을 완료시켰다.
-
S3 업로드 중 발생하는 Exception은 어떻게 처리하고 로깅은 뭐로 남겨야하지?
- SdkClientException, AmazonServiceException 같이 AWS S3에서 오류가 발생한 경우 즉각 대응이 어렵고, 해당 서비스가 정상이 될때까지 기다려야 하므로, 로그 레벨 error보다는 warn이 맞을 꺼라 판단해서 warn으로 로깅을 남겼다. 그리고 예외는 try-catch를 이용했다
공부한 내용
- AWS S3(Simple Storage Service)
- 웹하드와 비슷. 인터넷상에 data를 올리고 다운 받는 곳.
- 웹하드와의 차이점
- Rest API로 저장/삭제/조회가 가능
- 저장 용량 제한 없음
- 99.99% 이상의 내구성 => 유실율 없음
- 크기가 매우 큰 파일 업로드시 사용 ex)대용량 로그파일
- 가격 싼 편
- 용어
- 객체(Object): S3에 데이터가 저장되는 최소 단위. 업로드되는 파일, 이미지
- 버킷(bucket): 업로드한 파일이 위치하게될 최상위 폴더(디렉토리)
- 리전(Region): 내가 올린 객체가 저장될 물리적 위치, 리전 간 객체 공유는 불가능
- ap-northeast-2: 한국 리전 의미
- AWS S3 환경 세팅
- pom.xml 파일에 dependency 추가 - aws-java-sdk
- AWS S3 인증 정보 configure 클래스 생성과 S3 이용을 위한 설정 파일 주입
- AWSConfigure 설정 클래스
- AWS S3 사용
- 객체 업로드
- PutObjectRequest 객체 사용해 업로드 요청
- 업로드 결과는 PutObjectRequest 객체 사용해 확인
- 객체 조회
- AWS SDK에서 제공하는 S3 클라이언트의 getObject 메소드 사용
- 또는 URL 사용해 조회
- 기본 URL + bucket 명 + 객체 Key
- https://s3.ap-northeast-2.amazonaws.com/버킷이름/key.jpeg
- 객체 업로드
- AWS S3 연동 예외처리
- S3란 네트워크 통해 업로드 하는 기능 -> 언제든 예외 발생 가능함을 인지해야함
- 그런데 네트워크 I/O와 파일 I/O 같은 I/O 작업은, 예외가 언제 발생할지 예측할 수 없는 근본적으로 막을 수 없는 작업!
- 다시 connection 맺어서 될때까지 retry 하는 정도의 대응 밖에 없음
- 그러므로 S3 연동시 반드시 방어 로직 추가해줘야함!!!
- 보통 RuntimeException을 발생시키기 때문에 try-catch 사용 안함
- AWS S3 작업으로 발생한 예외는 비즈니스 로직에 영향을 주지 않도록 하는 것이 중요
- 사용자 프로필 이미지 업로드 실패시 회원 가입 처리가 실패해야 하는가? - 무엇이 자연스러운 flow일까
- 예외처리 가이드
- 상황에 맞는 올바른 예외 클래스를 사용하자
- RuntimeExceptio(Unchecked Exception)
- try catch 할 필요 없음. 요즘에 이걸 더 선호함
- 단점. 익숙해지면 예외 잡아야하는 경우에도 안 잡고 넘어가게 될 수가 있음.
- return type, 파라미터에 무엇이 있고 언제 어떤 예외가 발생할 수 있는지 확인하는 습관 필요
- CheckedException
- try catch로 반드시 감싸야 하는 예외
- ex. JDBC 예외. 네트워크,파일 I/O 예외
- IDE에 빨간줄 안 떠도 예외처리는 미리 해줘야함. 언제 발생할지 모르니
- DB 관련 예외는 Repository 레이어에서, 비즈니스 로직 관련 예외는 Service 레이어에서 발생시키기
- RuntimeExceptio(Unchecked Exception)
- 예외 로깅
- 예외를 catch 했다면 반드시 로그 남기기. 어떤 로그 남길지 고민 필요
- 경우에 따라 예외 로깅 제외 가능 - ex. 디버깅이 필요없고 너무 많이 발생하는 경우
- 로깅 프레임워크 활용해 디버깅에 도움되는 정보와 함께 stack trace를 함께 남긴다.
- log.error(“Unexpected error from userId: {} : {}”, userId, e.getMessage(), e);
- 로그 파일 관리
- 생각 없이 로그를 파일에 남기기만 하면 DISK Full 장애가 날 수 있고, 하나의 로그 파일 크기가 너무 큰 경우 필요한 정보 찾는데 시간이 많이 걸림
- 따라서, 단일 로그 파일 크기를 적절한 크기로 제한하고, 보통 50~100MB, 오래된 건 지우고 최근 며칠 것 만 보관한다.
- 일반적으로 사용하는 로깅 프레임워크에서 관련 설정 모두 지원 - log4j2, logback, sl4j 등등
- 상황에 맞는 올바른 예외 클래스를 사용하자
-
Java에서 비동기 처리 방법 - CompletableFuture
- 굉장히 많은 메소드들이 있지만 그 중 CompletableFuture와 자주 사용하는 메소드에 대해 알아보겠다.
- CompletableFuture
- 함수형 프로그래밍 기법을 지원하는 비동기 병렬 프로그래밍 지원
- 명시적 Thread Pool 선언 필요 없음
- 함수형 프로그래밍 방식을 지원하므로 간결하고 가독성 좋은 코드 작성 가능
- 각 병렬 Task들의 손쉬운 결합. 예외처리 지원
-
CompletableFuture의 메소드
- supplyAsync
- static CompletableFuture
runAsync (Runnablerunnable) - 반환값이 필요 없을 때 사용
- static CompletableFuture
-
runAsync
- static <U> CompletableFuture <U> supplyAsync(Supplie<U> supplier)
- 반환값이 필요할 때 사용. 체이닝 가능
- thenApply
- 비동기 작업의 결과값을 새로운 값으로 변환하는 함수 실행
- <U> CompletableFuture<U> thenApply(Function<? super T, ? extends U> fn)
- T: 비동기 작업의 결과값, U: T를 U로 변환해서 반환
- thenAccept
- 비동기 작업의 결과값을 소비하고 끝
- CompletableFuture<Void> thenAccept(Consumer<? super T> action)
- exceptionally
- 체이닝된 구문에서 예외 발생시 수행되며, 결과값 T 생성
- CompletableFuture<T> exceptionally(Function<Throwable, ? extends T> fn)
- allOf
- 동시에 N개의 비동기 작업을 실행하고, N개 작업 모두 완료된 후에 진행
- static CompletableFuture<Void> allOf(CompletableFuture<?>..cfs)
- 동시에 N개의 비동기 작업을 실행하고, N개 작업 모두 완료된 후에 진행
- anyOf
- 동시에 N개의 비동기 작업을 실행하고, 하나라도 작업이 완료되면 진행
- static CompletableFuture<Object> anyOf(CompletableFuture<?>..cfs)
- 동시에 N개의 비동기 작업을 실행하고, 하나라도 작업이 완료되면 진행
- supplyAsync
TODO 기능 구현
1. User, ConnectedUser에 String타입의 profileImageUrl 필드 추가
2. 회원가입시 업로드한 프로필 이미지를 S3에 업로드(CompletableFuture를 사용해 비동기처리)
- S3에 업로드 후 오브젝트 접근을 위한 URL을 profileImageUrl에 추가
- S3 예외 발생에 대한 처리 및 그 이유 설명
3. Comment 관련 비즈니스 로직구현
- Comment 작성 API 및 로직 구현
- Comment가 작성되면 관련 post의 comments가 1 증가해야 함
- 특정 post에 대한 comment 목록 조회 API 및 로직 구현
- comment 관련 로직은 가능한 stream api를 활용해 fluent 스타일로 코드 작성
- 가능한 도메인 모델을 활용해 코드 작성
4. CommentService 테스트작성
5. 새로운 RESTAPI 및 관련 모델에 대해 Swagger를 통해 문서화
References
- 초보 웹 개발자를 위한 스프링 5 프로그래밍 입문
- 토비의 스프링 3.1
- 처음 배우는 스프링 부트 2