You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
@Getter@AllArgsConstructorpublicenumErrorCode{
NOT_FOUND_DIARY(HttpStatus.NOT_FOUND,"error","존재하지 않는 Diary 입니다."),
;
@JsonIgnoreprivatefinalHttpStatushttpStatus;
privatefinalStringcode;
privatefinalStringmessage;
}
enum 클래스에 에러 코드를 정의했습니다. 후에 다른 여러가지 에러코드들을 추가할 수 있습니다.
추후 상황에 따라 ErrorCode enum 클래스 또한 NotFoundErrorCode, IllegalArgumentErrorCode, UnAuthorizedErrorCode 등의 여러 enum 클래스로 유형별로 분리하여 사용할 수도 있습니다. (현재는 너무 작은 프로젝트이기 때문에 ErrorCode 로 정의했습니다)
@JsonIgnore : HttpStatus JSON 응답에 표현되지 않게 해서 불필요한 정보를 클라이언트에게 굳이 제공하지 않도록 했습니다.
현 과제에서는 사용되지 않았으나, 후에 success 요청에도 활용할 수 있겠습니다! 이를 통해 실패, 성공 응답 모두 일관된 형식으로 클라이언트에게 반환할 수 있게 됩니다.
// 후에 아래처럼 success 에 대해서도 공통된 응답 형식을 반환할 수 있도록 하면 좋은 코드가 될 것 같다.publicstatic <T> ResponseDto<T> success(finalTdata) {
returnnewResponseDto<>("success", data, null);
}
🧐 중복 제목을 검사하는 역할이 “일기”에게 있는지, “서버(util)”에게 있는지 고민했습니다.
✅ 제목이 중복되었는지 확인하는 역할의 책임은 “일기 본인”에게 있다고 생각했습니다. 따라서 중복을 확인하는 로직을 “일기”라는 객체 안에 두었습니다.
포럼 진행 후 소감
🍃 더 정교한 코드를 작성하기 위해서는 보다 더 명확하게 기능 명세서를 분석할 필요가 있다고 생각하게 되었습니다.
🍃 좋은 코드의 기준으로 “예기치 못한 변화에 대해 유연하게 대처할 수 있는 코드”를 더 생각하게 되었습니다. 예기치 못한 오류가 발생하거나, 기획자의 의도와 다르게 코드를 작성했더라도 역할 분리를 잘 하는 등 유연하게 이에 대처할 수 있도록 코드를 작성하는 것이 중요할 것 같다고 생각하게 되었습니다.
여러가지 구현작업을 진행하면서 각 객체들을 쪼개서 역할, 책임을 최대한 맡길 수 있는 구조로 만들어보려고 하였습니다.
Transactional 어노테이션을 활용하여서 여러가지 변경 사항들이 일어났을 경우 이에 대한 처리가 잘 수행될 수 있도록 구현하려고 하였습니다.
카테고리는 비워있으면 안되나?
카테고리를 구현하는 과정속에서 다른 데이터들은 요청에 비워져있으면 모르겠지만 카테고리는 사실 비워있을 수도 있어서 따로 Enum의 ETC 와 같은 지정한 카테고리가 아닌 경우들에 대해서는 기타로 처리하는 작업을 진행하였습니다.
@component라는 어노테이션에게 이름을 부여해 Custom하게 사용하기! ex) Validator
이전 과제부터 계속 구현을 하면서 역할이 너무 확실하게 있었던 Validator 묶음의 친구들이 있었는데 Validator라는 이름으로 넣어 진행하면 더 코드를 보는 입장에서 한 눈에 어떤 역할인지 더 이해할 수 있도록 하였습니다.
최대한 Entity 사용하지 않고 진행하기
entity 를 다루다가 물론 사용자에 따라 다르겠지만 변동사항이 일어나면 dirty checking 을 활용할 때가 있는데 사람들의 실수로 인해 코드가 변경되거나 하는 부분들이 있을거라 생각하여 로직을 다룰때는 웬만하면 entity → domain으로 변경하여 진행하려고 하였습니다.
또한 entity를 계속 이용하다 변경사항이 일어나면 db의 테이블에도 어떤 영향이 있을 것이라고 생각하였습니다.
DIP 라는 객체지향 원칙 지켜보기!
service라는 패키지와 api 라는 패키지를 철저하게 분리를 하였을 경우를 생각해봤습니다. 이렇게 구성을 하였을 경우 언젠가 혹시 multi module로 진행한다고 하여도 service의 변경사항이 인터페이스에서만 끝나게 되고 service의 구현과는 전혀 의존하지 않을 수 있도록 할 수 있기 때문에 DIP라는 원칙을 지켜볼 수 있도록 하였습니다.
포럼 진행 후 소감
🍃 다른 조원들이 치열하게 조사한 것을 보면서 조금 더 api 를 더욱 깔끔하고 일관성 있을 수 있는 형식인 봉투 패턴을 활용을 하는 방식으로 앞으로 진행할 때 변경해봐야겠고 순수한 Jpa를 어떻게 하면 더 잘 활용할 수 있는지 배울 수 있었습니다.
🍃 또한 비동기, 동기 방식으로 처리하는 것에 대해서 알게 되어서 나중에 많은 사람들이 동시에 요청을 하게 되는 경우 여러가지 동시성 문제에 대해서 생각해볼 수 있었기 때문에 조금 더 보는 시야가 넓혀질 수 있는 시간이었습니다.
@ExceptionHandler(HttpMessageNotReadableException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ResponseBodypublicStringhandleDefaultException(HttpMessageNotReadableExceptione) {
return"올바르지 않은 값이 전달되었습니다. 요청 형식을 확인해주세요.";
}
• 타입 에러 등, 요청 body를 파싱할 수 없는 에러의 경우 HttpMessageNotReadableException를 발생시키기 때문에, GlobalExceptionHandler 측에서 해당 Exception을 처리하도록 하였다.
1주차 과제의 경우, 역할 분리가 주제인 과제라고 이해했기 때문에, 사용자 요청 형식에 대한 검증은 크게 생각하지 않았다.
2주차 과제는 실제로 API 요청을 받는 과제이기 때문에, 사용자의 요청을 어떻게 검증할지, 어떻게 저장할지 더 고민하며 구현하였다.
예은
고민 과정
유효성 검사를 진행한 후 어떤 응답코드를 내려주는 것이 적합할까 ? 에 대해 초점을 맞추어 구현했습니다.
201 Created : 새로운 자원이 생성된 경우 사용 (주로 POST 요청 성공 시 사용)
204 No Content : 요청이 성공적으로 처리되었으나 반환할 데이터가 없을 때 사용(주로 수정 및 삭제 API에서 사용)
400 Bad Request : 클라이언트가 잘못된 데이터를 보냈을 때 사용 (유효성 검사 실패 시)
404 Not Found : 클라이언트가 요청한 자원이 존재하지 않을 때 사용(ID 기반 조회, 수정, 삭제 시 해당 ID가 없을 경우)
✅ 유효성 검사를 어느 계층에서 수행할 것인가 ?
Controller에서 수행 ****
장점 : controller는 클라이언트의 요청을 직접 처리하므로 요청을 받자마자 바로 유효성 검사를 수행하므로 이해가 쉽다
단점 : 비지니스 로직이 혼합되어 관심사 분리가 적절하게 수행되지 않음 , 유효성 검사를 재사용할 수 없음 (코드 중복 발생)
Service 계층에서 수행
장점 : 비지니스 로직의 일관성, 다시 사용 가능
단점 : 예외를 반환하는 Controller의 작업이 필요
Validator 만들어서 수행
장점 : Bean Validation 기능을 통해 유효성 검사가 자동으로 이루어지므로 코드가 더 깔끔하고 간결
단점 : 비즈니스 규칙과의 혼합 가능성
→ 다음을 놓고 고민하던 차에, 유효성 검사 진행 후 가시적으로 어떤 응답코드를 반환하는지에 집중하기 위해 Controller에 구현을 진행했습니다.
But 코드토크 이후, Validator를 만들어서 유효성 검사를 진행하고 싶다는 생각이 들어서 이후 수정해나갈 예정입니다.
Pageable을 사용하면 간단한 코드로 페이징과 정렬을 처리할 수 있기 때문에 Pageable 을 사용했습니다. JPA는 Pageable을 통해 기본적으로 필요한 LIMIT, OFFSET, ORDER BY를 자동으로 처리하므로, 페이징을 위한 복잡한 쿼리문을 직접 작성할 필요가 없어서 JPA가 제공하는 기능을 최대한 활용했습니다.
✅ 30자를 넘기면 어떤 응답코드를 내려주면 좋을지 고민해주세요.
기획서를 바탕으로 일기의 글자수는 30자로 제한했고, 함께 약속한 규칙을 어겼으므로, 클라이언트에서 잘못된 요청을 보냈다는 응답코드 400 Bad Request 이 적절하다고 생각했습니다.
400 Bad Request 란 ?
클라이언트에서 잘못된 요청을 보낸 경우에 반환되는 응답 코드
✅ 5분에 하나만 작성되게 해주세요!
(5분 기준은 마지막 일기 작성 시간 기준으로 해주세요)
if (lastDiaryCreatedAt != null && lastDiaryCreatedAt.plusMinutes(5).isAfter(LocalDateTime.now())) {
thrownewIllegalArgumentException("5분에 하나의 일기만 작성할 수 있습니다.");
}
lastDiaryCreatedAt.plusMinutes(5) : 마지막 일기가 작성된 시간으로부터 5분 후의 시간
.isAfter(LocalDateTime.now()) : 현재 시간 (LocalDateTime.now())이 이 5분 후의 시간보다 이전인지 확인
만약 true 라면, 현재 시간이 마지막 일기 작성 후 5분이 지난 시간임을 의미합니다.
✅ 5분 안에 일기가 작성되면 어떻게 응답되면 좋을까요? 함께 고민해주세요.
400 Bad Request
기획서를 바탕으로 5분 안에 1개의 일기만 작성하기로 약속했으므로, 5분안에 일기가 또 작성되면 이는 클라이언트 요청이 잘못되었다고 생각했다. 따라서 클라이언트에서 잘못된 요청을 보낸 경우 반환되는 응답코드인 400 Bad Request가 반환되어야 한다고 생각합니다.
✅ 중복되는 제목은 없게 해주세요.
비지니스 로직에 해당되므로 Service Layer의 역할이라고 생각했습니다.
▶️ Service 코드
Optional<DiaryEntity> existingDiary = diaryRepository.findByName(name);
if (existingDiary.isPresent()) {
thrownewIllegalArgumentException("중복된 이름입니다.");
}
Optional 을 사용하여 null 값이 발생할 가능성이 있는 데이터를 처리할 때, 명시적으로 이를 표현하여 코드의 안전성을 향상시켰습니다. null을 직접적으로 반환하지 않고 Optional.empty()를 반환합니다.
🍃 예외처리를 할 때 공통 응답 형식을 보내기 위해 봉투패턴을 사용한 것이 클라이언트의 입장에서 일관된 형태의 응답구조를 받을 수 있기 때문에 클라이언트 친화적인 api라고 생각 되었습니다. 따라서 api를 작성할 때 클라이언트의 입장에서 선호할만한 api는 무엇인가에 대해 고민하면서 api를 만들어야겠다는 생각이 들었습니다..
🍃 최대한 각 객체는 하나의 일을 하도록 구현한 코드를 보면서 1주차 과제의 강조점이었던 각 클래스 간의 책임과 역할에 대해 다시 한 번 복습할 수 있는 시간이 되었다.
⭐️ 서술 과제
Switch?
⏩ OSI 7계층?4계층?
OSI 7계층 은 개념적으로 사용되는 표준 모델이고, TCP/IP 4계층 은 현실에서 실질적인 통신을위해 사용하는 더 실용적인 모델이다!
전송 계층 (~L4)
수신지와 목적지를 감독하면서 데이터가 오류 없이 도착하는 것을 담당
TCP(연결형), UDP(비연결형)
응용 계층 (~L7)
사용자와 통신할 수 있는 응용 서비스를 제공한다.
웹사이트 접속(HTTP), 파일 전송(FTP), 메일 송수신(SMTP, POP3), 이름 해석(DNS), SMTP(네트워크 구성, 성능, 장비, 보안 관리) 등…
⏩ IP
**인터넷 계층(~L3)**에서 사용하는, 네트워크 통신 시 데이터를 전송하는 경로 및 주소 지정을 담당하는 프로토콜이다.
IP는 데이터를 패킷 단위로 분할해서 전송하게 되는데, 패킷에는 출발지/목적지 IP주소를 가지고 있다.
IP는 각 패킷의 출발지/목적지 IP 주소를 이용해서 데이터를 목적지로 전달한다.
⏩ 캡슐화/역캡슐화
미미나 자료에서 발췌
그림을 살펴보면 My Phone(송신측)에서 캡슐화해서 전달한 데이터가 Instagram(수신측)에서 역캡슐화 된다.
송신측 : 상 → 하위 계층
캡슐화 계층마다 필요한 정보(헤더)를 붙여서 다음 계층으로 보낸다.
데이터 링크 계층에서는 트레일러(데이터를 전달할 때 데이터에 마지막에 추가하는 정보)도 추가
수신측 : 하 → 상위 계층
역캡슐화 받는 쪽에서 헤더를 하나씩 제거한다.
⏩ Routing
네트워크 상에서 데이터 패킷을 출발지에서 목적지로 전송하는 과정으로, 네트워크 상의 여러 경로들 중에서 최적의 경로를 선택하여 패킷을 전달한다.
IP 주소와 같은 네트워크 주소 정보를 기반으로 트래픽을 어디로 보내야 할지 결정한다.
⏩ L4, L7 Switch 역할_Load Balancing의 차이?
**로드밸런싱(Load Balancing)**이란,
여러 개의 서버나 프로세서가 있을 때, 각 서버에 적절히 작업을 분배해서 한쪽 서버에 트래픽이 몰리지 않도록 만드는 기술이다. 이 역할을 하는 것을 로드밸런서(Load Balancer)라고 한다.
☁️ 로드밸런서인 L4, L7에 대해서 자세하게 알아보자 !! ☁️
L7과 L4의 로드 밸런싱은 모두 트래픽을 여러 서버에 나누어 주는 역할을 하지만, 처리하는 방식이 다르다.
❗일단 결론부터 말하자면, 요청의 내용을 얼마나 깊이 분석하느냐? 의 차이 ❗
L4는 패킷의 내용은 분석하지 않고, 단순히 네트워크 흐름을 처리한다.
L7는 URL, 헤더, 쿠키 등 패킷의 내용을 분석하여 트래픽을 세밀하게 분배한다.
L4 스위치
IP, TCP/UDP **포트 번호를 기준(포트 기반 라우팅)**으로 클라이언트 요청을 여러 서버에 분배한다. 즉, HTTP(포트 80)와 HTTPS(포트 443)와 같은 요청을 구분할 수 있다
→ 어떤 요청이 어떤 서버에 가는지에 대한 관리는 어렵다.
L7 스위치
요청의 내용을 기반으로 하여 트래픽을 분배한다.
아래와 같이, 클라이언트가 요청한 URL, HTTP 헤더, 쿠키, API 경로 등을 분석하여 적절한 서버에 요청을 전달함으로써 더 세밀하게 로드 밸런싱한다.
예를 들어, /images 경로는 이미지 서버로, /api 경로는 API 서버로 라우팅할 수 있다.
비용
앞서 살펴 본 내용을 토대로.. 당연히 L7이 더 비싸다.
사용상황
L4 로드 밸런서
“단순한 부하 분산”
단순하게 모든 클라이언트 요청을 여러 서버에 균등하게 나누고 싶을 때
서버는 각 요청이 어떤 데이터를 가지고 있는지 신경 쓰지 않고, 단순히 IP 주소와 포트 정보를 기준으로 빠르게 트래픽을 분배
L7 로드 밸런서
“결제만 담당하는 서버, 회원가입만 담당하는 서버 등.. 작은 단위로 요청을 각각의 서버에 분산”
특정 사용자와 서버의 연결(세션)을 일정 기간 유지하는 기능이 필요할 때
(ex) 쇼핑몰 사이트에서 사용자가 로그인하거나 장바구니에 상품을 추가할 때, 다른 서버에 접속되어 데터에 차이가 생기지 않도록 그 세션을 같은 서버로 유지한다.
→ HTTP 쿠키나 세션 데이터를 분석해, 사용자가 처음 접속한 서버로 계속 요청을 보내어 일관된 세션 관리를 가능하게 한다.
⏩ Server가 위의 내용들을 알아야 하는 이유는? ~~
(ex :: L4) AWS의 Elastic Load Balancer(ELB)
여기서 백엔드 개발자는 특정 포트로 들어오는 트래픽을 어떻게 분배할지 정의한다.
(ex :: L7) Nginx
http {
upstream app1 {
server app1.example.com;
}
upstream app2 {
server app2.example.com;
}
server {
location /app1/ {
proxy_pass http://app1;
}
location /app2/ {
proxy_pass http://app2;
}
}
}
⏩ 미미나 리마인드 : 면접 Tip
🧔🏻 OSI 7계층에 대해서 설명해 주세요
💬 실제로 현업에서 자주 사용되는 계층은 주로 4계층이다. 하위 계층으로 갈수록 더 단순하고 기본적인 작업을 처리해야 하기 때문에, 하위 계층은 비교적 “바보같이” 동작하는 것이 효율적입니다.
L4는 IP 주소와 포트 번호만을 기반으로 트래픽을 분배합니다. 이 계층은 IP 정보 외에 더 많은 정보를 알지 못하므로, 매우 단순하게 동작합니다.
L7은 앞 단에서 받은 데이터로부터 HTTP, URL, 쿠키와 같은 애플리케이션 데이터를 분석할 수 있으므로, 더 세밀한 방식으로 트래픽을 라우팅할 수 있습니다.
한 번에 하나의 그린 스레드만 처리할 수 있습니다. 따라서 이 모델은 다대일 모델로 간주됩니다. 이 때문에 그린 스레드는 멀티코어 프로세서에서 실행될 수 있지만 여러 코어를 활용할 수는 없습니다.
그린 스레드에서는 동기화와 리소스 공유가 더 쉽고, 따라서 실행 시간도 짧습니다.
Sun Solaris OS는 Green Thread 모델을 지원합니다.
네이티브 스레드 모델
이 모델의 스레드는 기본 OS 지원의 도움을 받아 JVM이 관리합니다 .
이러한 스레드는 OS 수준에서 구현되며(OS 멀티스레딩 API를 사용하여) 커널 공간에서 관리됩니다.
이를 비그린(커널 수준) 스레드라고도 합니다.
여러 네이티브 스레드가 공존할 수 있습니다. 따라서 다대다
모델 이라고도 합니다 . 이 모델의 이러한 특성 덕분에 멀티코어 프로세서를 최대한 활용하고 별도의 개별 코어에서 스레드를 동시에 실행할 수 있습니다.
이 모델은 여러 스레드가 동시에 실행될 수 있기 때문에 스레드 동기화와 리소스 공유가 복잡해집니다. 이로 인해 스레드의 실행 시간이 늘어납니다.
모든 윈도우 기반 OS는 네이티브 스레드 모델을 지원합니다.
Green Thread 모델은 제한 사항으로 인해 더 이상 사용되지 않습니다. Native Thread 모델은 Green Thread 모델을 대체했으며 오늘날 널리 사용됩니다. 이 외에도 Java의 Thread 클래스에는 더 이상 사용되지 않는 몇 가지 메서드가 있습니다.
⭐️ 코드 토크
영주
고민 과정
어떻게 하면 클라이언트에게 ‘언제나’ ‘일관된 응답’을 내려줄 수 있을까? 에 대해서 중점적으로 고민했다.
✅ 예외 처리 및 공통 응답 형식 (봉투패턴)
GlobalExceptionHandler
클래스에서 해당 예외를 처리할 수 있습니다.NotFoundErrorCode
,IllegalArgumentErrorCode
,UnAuthorizedErrorCode
등의 여러 enum 클래스로 유형별로 분리하여 사용할 수도 있습니다. (현재는 너무 작은 프로젝트이기 때문에 ErrorCode 로 정의했습니다)@JsonIgnore
: HttpStatus JSON 응답에 표현되지 않게 해서 불필요한 정보를 클라이언트에게 굳이 제공하지 않도록 했습니다.@RestControllerAdvice
: 전역 예외 처리, JSON 형식의 응답 반환에 적합→ 존재하지 않는 일기의 Id 로 검색할 경우 아래와 같은 응답이 반환됩니다.
❗여기서 잠깐 ❗
앞서 Controller에서
@Valid
어노테이션을 사용해서 입력한 제목과 내용의 글자수를 검증하는 과정을 진행했었습니다.@Valid
를 사용해서 예외가 발생했을 시에는 MethodArgumentNotValidException 가 발생하게 됩니다.그래서,
GlobalExceptionHandler
클래스에서MethodArgumentNotValidException.class
에 대한 예외 처리를 커스텀해서 ResponsEntity를 custom한 객체로 만들어서 보내주었습니다.GlobalExceptionHandler
ResponseDto
→ 일기의 제목, 내용 모두 지정된 글자수를 초과할 경우
→ 일기의 제목만 지정된 글자수를 초과할 경우
→ 일기의 제목을 쓰지 않았을(null) 경우
➕ TIP
현 과제에서는 사용되지 않았으나, 후에 success 요청에도 활용할 수 있겠습니다! 이를 통해 실패, 성공 응답 모두 일관된 형식으로 클라이언트에게 반환할 수 있게 됩니다.
포럼 진행 후 소감
작성예정
구현 과정
AND-SOPT-SERVER/yeongju.cho#10
의진
고민 과정
🧐 유효성 검사를 어느 layer에서 수행하는지 고민했습니다.
✅ 결론적으로 client에게서 받은 정보에 대한 유효성은 ‘controller’에서, 비즈니스 로직에 대한 유효성 검사는 ‘service’에서 수행하는 것으로 결정했습니다.
🧐 private final 키워드를 많이 사용하고 이를 쉽게 다룰 수 있도록 하고 싶었습니다.
✅ request, response에 사용되는 DTO를 Record 클래스로 사용하기로 했습니다.
🧐 JPA를 이용하거나 Pageable 중 하나를 이용하도록 했습니다.
✅ 결론적으로 JPA를 이용하기로 했습니다.
✅ ’페이지’라는 개념이 보이지는 않는 것 같아서 JPA를 선택했습니다.
🧐 DB에 저장되는 클래스와 비즈니스 로직에서 사용되는 클래스의 정보가 차이가 있을 것이라 생각했습니다.
✅ Domain과 Entity를 구별해보자고 결정했습니다.
🧐 DB 중 생성일기준으로 정렬한 후 가장 최근에 만들어진 일기를 가져와 현재 시간과 비교할지, 최근 업데이트 날짜를 객체로 따로 보관을 해야할 지 고민했습니다.
✅ 생성일 기준 최근에 만들어진 일기를 가져오는 방식을 택했습니다. 정렬 후 찾는 것이 비효율적이라고 생각이 들었지만, 최근 업데이트 날짜를 객체로 만드는 것이 더 공간을 낭비하고 비효율적이라고 생각이 들었습니다.
🧐 diary entity 내 속성을 이용해야 했기에 JPA 메소드로는 한계가 있다고 생각했습니다.
✅ @query를 이용하기로 했습니다.
🧐 중복 제목을 검사하는 역할이 “일기”에게 있는지, “서버(util)”에게 있는지 고민했습니다.
✅ 제목이 중복되었는지 확인하는 역할의 책임은 “일기 본인”에게 있다고 생각했습니다. 따라서 중복을 확인하는 로직을 “일기”라는 객체 안에 두었습니다.
포럼 진행 후 소감
🍃 더 정교한 코드를 작성하기 위해서는 보다 더 명확하게 기능 명세서를 분석할 필요가 있다고 생각하게 되었습니다.
🍃 좋은 코드의 기준으로 “예기치 못한 변화에 대해 유연하게 대처할 수 있는 코드”를 더 생각하게 되었습니다. 예기치 못한 오류가 발생하거나, 기획자의 의도와 다르게 코드를 작성했더라도 역할 분리를 잘 하는 등 유연하게 이에 대처할 수 있도록 코드를 작성하는 것이 중요할 것 같다고 생각하게 되었습니다.
구현 과정
[2차 세미나] : 과제 구현 by sansan20535 · Pull Request #7 · AND-SOPT-SERVER/uijin.kim
효준
고민 과정
가장 중요시했던 것은 바보(최대한 하나의 일을 하도록)로 만드는 과정이었습니다!
여러가지 구현작업을 진행하면서 각 객체들을 쪼개서 역할, 책임을 최대한 맡길 수 있는 구조로 만들어보려고 하였습니다.
포럼 진행 후 소감
🍃 다른 조원들이 치열하게 조사한 것을 보면서 조금 더 api 를 더욱 깔끔하고 일관성 있을 수 있는 형식인 봉투 패턴을 활용을 하는 방식으로 앞으로 진행할 때 변경해봐야겠고 순수한 Jpa를 어떻게 하면 더 잘 활용할 수 있는지 배울 수 있었습니다.
🍃 또한 비동기, 동기 방식으로 처리하는 것에 대해서 알게 되어서 나중에 많은 사람들이 동시에 요청을 하게 되는 경우 여러가지 동시성 문제에 대해서 생각해볼 수 있었기 때문에 조금 더 보는 시야가 넓혀질 수 있는 시간이었습니다.
구현 과정
AND-SOPT-SERVER/hyojun.kim#5
윤혁
고민 과정
✅ Controller, Service 검증 분리
• 타입 에러 등, 요청 body를 파싱할 수 없는 에러의 경우
HttpMessageNotReadableException
를 발생시키기 때문에,GlobalExceptionHandler
측에서 해당 Exception을 처리하도록 하였다.validate()
함수를 호출하도록 했다.createDiary
내부에서 검증하는 것으로 구현하였다.✅ Race Condition 고려 (제목 중복 값 검증)
DataIntegrityViolationException
오류를 일으키도록 구현하였다.✅ 비동기 구현 시도
포럼 진행 후 소감
🍃 조원들이 대부분 Spring Boot 사용 경험이 있었기에, 보다 더 일반적인(대중적인) 시각을 공유 받을 수 있어서 좋았다.
구현 과정
AND-SOPT-SERVER/yunhyuk.jeon#11
예은
고민 과정
유효성 검사를 진행한 후 어떤 응답코드를 내려주는 것이 적합할까 ? 에 대해 초점을 맞추어 구현했습니다.
201 Created
: 새로운 자원이 생성된 경우 사용 (주로 POST 요청 성공 시 사용)204 No Content
: 요청이 성공적으로 처리되었으나 반환할 데이터가 없을 때 사용(주로 수정 및 삭제 API에서 사용)400 Bad Request
: 클라이언트가 잘못된 데이터를 보냈을 때 사용 (유효성 검사 실패 시)404 Not Found
: 클라이언트가 요청한 자원이 존재하지 않을 때 사용(ID 기반 조회, 수정, 삭제 시 해당 ID가 없을 경우)✅ 유효성 검사를 어느 계층에서 수행할 것인가 ?
Controller에서 수행 ****
장점 : controller는 클라이언트의 요청을 직접 처리하므로 요청을 받자마자 바로 유효성 검사를 수행하므로 이해가 쉽다
단점 : 비지니스 로직이 혼합되어 관심사 분리가 적절하게 수행되지 않음 , 유효성 검사를 재사용할 수 없음 (코드 중복 발생)
Service 계층에서 수행
장점 : 비지니스 로직의 일관성, 다시 사용 가능
단점 : 예외를 반환하는 Controller의 작업이 필요
Validator 만들어서 수행
장점 : Bean Validation 기능을 통해 유효성 검사가 자동으로 이루어지므로 코드가 더 깔끔하고 간결
단점 : 비즈니스 규칙과의 혼합 가능성
→ 다음을 놓고 고민하던 차에, 유효성 검사 진행 후 가시적으로 어떤 응답코드를 반환하는지에 집중하기 위해 Controller에 구현을 진행했습니다.
But 코드토크 이후, Validator를 만들어서 유효성 검사를 진행하고 싶다는 생각이 들어서 이후 수정해나갈 예정입니다.
✅ 가장 최근 작성한 일기부터 10개를 보여줄 때 어떤 방식으로 구현할까 ?
Pageable
을 사용하면 간단한 코드로 페이징과 정렬을 처리할 수 있기 때문에Pageable
을 사용했습니다. JPA는Pageable
을 통해 기본적으로 필요한LIMIT
,OFFSET
,ORDER BY
를 자동으로 처리하므로, 페이징을 위한 복잡한 쿼리문을 직접 작성할 필요가 없어서 JPA가 제공하는 기능을 최대한 활용했습니다.✅ 30자를 넘기면 어떤 응답코드를 내려주면 좋을지 고민해주세요.
기획서를 바탕으로 일기의 글자수는 30자로 제한했고, 함께 약속한 규칙을 어겼으므로, 클라이언트에서 잘못된 요청을 보냈다는 응답코드
400 Bad Request
이 적절하다고 생각했습니다.400 Bad Request
란 ?클라이언트에서 잘못된 요청을 보낸 경우에 반환되는 응답 코드
✅ 5분에 하나만 작성되게 해주세요!
(5분 기준은 마지막 일기 작성 시간 기준으로 해주세요)
lastDiaryCreatedAt.plusMinutes(5)
: 마지막 일기가 작성된 시간으로부터 5분 후의 시간.isAfter(LocalDateTime.now())
: 현재 시간 (LocalDateTime.now()
)이 이 5분 후의 시간보다 이전인지 확인만약
true
라면, 현재 시간이 마지막 일기 작성 후 5분이 지난 시간임을 의미합니다.✅ 5분 안에 일기가 작성되면 어떻게 응답되면 좋을까요? 함께 고민해주세요.
400 Bad Request
기획서를 바탕으로 5분 안에 1개의 일기만 작성하기로 약속했으므로, 5분안에 일기가 또 작성되면 이는 클라이언트 요청이 잘못되었다고 생각했다. 따라서 클라이언트에서 잘못된 요청을 보낸 경우 반환되는 응답코드인
400 Bad Request
가 반환되어야 한다고 생각합니다.✅ 중복되는 제목은 없게 해주세요.
비지니스 로직에 해당되므로 Service Layer의 역할이라고 생각했습니다.
Optional
을 사용하여 null 값이 발생할 가능성이 있는 데이터를 처리할 때, 명시적으로 이를 표현하여 코드의 안전성을 향상시켰습니다. null을 직접적으로 반환하지 않고Optional.empty()
를 반환합니다.구현 과정
AND-SOPT-SERVER/yeeun.kim#6
포럼 진행 후 소감
🍃 예외처리를 할 때 공통 응답 형식을 보내기 위해 봉투패턴을 사용한 것이 클라이언트의 입장에서 일관된 형태의 응답구조를 받을 수 있기 때문에 클라이언트 친화적인 api라고 생각 되었습니다. 따라서 api를 작성할 때 클라이언트의 입장에서 선호할만한 api는 무엇인가에 대해 고민하면서 api를 만들어야겠다는 생각이 들었습니다..
🍃 최대한 각 객체는 하나의 일을 하도록 구현한 코드를 보면서 1주차 과제의 강조점이었던 각 클래스 간의 책임과 역할에 대해 다시 한 번 복습할 수 있는 시간이 되었다.
⭐️ 서술 과제
Switch?
⏩ OSI 7계층?4계층?
OSI 7계층
은 개념적으로 사용되는 표준 모델이고,TCP/IP 4계층
은 현실에서 실질적인 통신을위해 사용하는 더 실용적인 모델이다!⏩ IP
⏩ 캡슐화/역캡슐화
미미나 자료에서 발췌
그림을 살펴보면 My Phone(송신측)에서 캡슐화해서 전달한 데이터가 Instagram(수신측)에서 역캡슐화 된다.
캡슐화
계층마다 필요한 정보(헤더)를 붙여서 다음 계층으로 보낸다.역캡슐화
받는 쪽에서 헤더를 하나씩 제거한다.⏩ Routing
⏩ L4, L7 Switch 역할_Load Balancing의 차이?
로드밸런싱(Load Balancing)
**이란,로드밸런서(Load Balancer)
라고 한다.☁️ 로드밸런서인 L4, L7에 대해서 자세하게 알아보자 !! ☁️
L7과 L4의 로드 밸런싱은 모두 트래픽을 여러 서버에 나누어 주는 역할을 하지만, 처리하는 방식이 다르다.
❗일단 결론부터 말하자면, 요청의 내용을 얼마나 깊이 분석하느냐? 의 차이 ❗
L4 스위치
IP, TCP/UDP **포트 번호를 기준(포트 기반 라우팅)**으로 클라이언트 요청을 여러 서버에 분배한다. 즉, HTTP(포트 80)와 HTTPS(포트 443)와 같은 요청을 구분할 수 있다
→ 어떤 요청이 어떤 서버에 가는지에 대한 관리는 어렵다.
L7 스위치
요청의 내용을 기반으로 하여 트래픽을 분배한다.
아래와 같이, 클라이언트가 요청한 URL, HTTP 헤더, 쿠키, API 경로 등을 분석하여 적절한 서버에 요청을 전달함으로써 더 세밀하게 로드 밸런싱한다.
예를 들어, /images 경로는 이미지 서버로, /api 경로는 API 서버로 라우팅할 수 있다.
비용
앞서 살펴 본 내용을 토대로.. 당연히 L7이 더 비싸다.
사용상황
L4 로드 밸런서
“단순한 부하 분산”
L7 로드 밸런서
“결제만 담당하는 서버, 회원가입만 담당하는 서버 등.. 작은 단위로 요청을 각각의 서버에 분산”
→ HTTP 쿠키나 세션 데이터를 분석해, 사용자가 처음 접속한 서버로 계속 요청을 보내어 일관된 세션 관리를 가능하게 한다.
⏩ Server가 위의 내용들을 알아야 하는 이유는? ~~
(ex :: L4) AWS의 Elastic Load Balancer(ELB)
여기서 백엔드 개발자는 특정 포트로 들어오는 트래픽을 어떻게 분배할지 정의한다.
(ex :: L7) Nginx
⏩ 미미나 리마인드 : 면접 Tip
🧔🏻 OSI 7계층에 대해서 설명해 주세요
💬 실제로 현업에서 자주 사용되는 계층은 주로 4계층이다. 하위 계층으로 갈수록 더 단순하고 기본적인 작업을 처리해야 하기 때문에, 하위 계층은 비교적 “바보같이” 동작하는 것이 효율적입니다.
L4는 IP 주소와 포트 번호만을 기반으로 트래픽을 분배합니다. 이 계층은 IP 정보 외에 더 많은 정보를 알지 못하므로, 매우 단순하게 동작합니다.
L7은 앞 단에서 받은 데이터로부터 HTTP, URL, 쿠키와 같은 애플리케이션 데이터를 분석할 수 있으므로, 더 세밀한 방식으로 트래픽을 라우팅할 수 있습니다.
참고문헌
What is RESTful?
공통된 의견을 정리했고 세부적인 내용은 개인 이슈를 참고해주세요 :)
1️⃣ 자원에 대한 행위는 HTTP Method 로 표현한다.
ex)
⇒ 일기 삭제 API : [DELETE] /diaries
⇒ 일기 조회 API : [GET] /diaries
2️⃣ URI는 정보의 자원을 표현해야 하고, 어순에 맞춰서 최대한 간단하게 한다.
서버 내부에서 일어나는 일들은 클라이언트가 알 필요가 없다!!
ex) 어순에 맞춰서 적는 것도 좋은 선택일 듯 !
⇒ ‘게시판’의 ‘키워드’들을 모두 조회하는 API (게시판 → 키워드) 처럼
⇒ /board/keywords
3️⃣ 소문자를 쓰도록 한다. - RFC3986 에 명시
URL은 대소문자를 구분하는 웹 표준을 따르기에, api path 에서 대소문자 사용으로 문제가 일어나지 않도록 클라이언트와 사전 논의가 필요할 것 같다.
L4, L7 / What is RESTful 개인 이슈
영주 : AND-SOPT-SERVER/yeongju.cho#8
의진 : AND-SOPT-SERVER/uijin.kim#8
효준 : AND-SOPT-SERVER/hyojun.kim#6
윤혁 : AND-SOPT-SERVER/yunhyuk.jeon#12
예은 : AND-SOPT-SERVER/yeeun.kim#5
⭐️ 챌린지 과제
☑️ 서버를 10개 띄울건데, 클라이언트는 하나의 주소로 우리 서버를 호출했으면 좋겠어요.
🧐 프록시(Proxy)란?
🧐 프록시의 종류
포워드 프록시
💡 클라이언트가 요청을 하는 경우 프록시 서버를 거치는 방식
장점
리버스 프록시
💡 서버에서 데이터를 응답하는 경우 프록시 서버를 거치는 방식
장점
☑️ 트래픽이 우루루 몰릴때, 10개의 서버가 균등하게 요청을 처리할 수 있을까요?
🧐 로드 밸런싱(Load Balancing)이란?
네트워크 트래픽을 균등하게 배포하는 방법
모든 리소스 서버가 동일하게 사용되도록 촉진
장점
로드밸런싱 알고리즘
1️⃣ 라운드로빈 : 서버에 들어온 요청 순서대로
2️⃣ 가중 라운드로빈 : 서버마다 가중치를 매기고 가중치가 높은 서버에 우선
3️⃣ IP 해시 : 클라이언트의 IP주소를 특정 서버에 매핑
4️⃣ 최소 연결 방식 : 가장 적은 연결상태의 서버 우선
5️⃣ 최소 응답 방식 : 서버의 현재 연결 상태와 응답시간 고려
☑️ 같은 형상의 서버를 n 개 (위에서는 10개) 띄우기위한 효과적인 방법은 무엇일까요?
🧐 도커(Docker)란?
애플리케이션을 신속하게 구축, 테스트 및 배포할 수 있는 소프트웨어 플랫폼
”컨테이너”라는 표준화된 유닛으로 패키징
⇒ 컨테이너 : 라이브러리, 시스템 도구, 코드, 런타임 등 소프트웨어 실행에 필요한 모든 것을 포함
장점
🧐 도커 이미지(Docker Image)란?
컨테이너를 실행할 수 있는 실행파일, 설정 값 들을 가지고 있는 것
같은 맛 서버 맥이기(docker)
Docker 를 활용해본다!
https://aws.amazon.com/ko/docker/
같은 서버에 Docker를 활용한다면 쉽게 같은 형상의 애플리케이션들을 띄우는 것이 가능합니다.
위 그림과 같이 예를 들어서 각 환경에서 mysql이라는 하나의 친구를 설치하기 위해서 여러가지 운영체제나 환경이 다르다면 설정값들이 다를 수 있습니다.
하지만 도커를 활용한다면 어떠한 환경이든 상관없이 Docker가 제공하는 컨테이너 환경속에서 일관된 버전을 갖출 수 있다고 생각합니다.
실제 현재 다른 프로젝트에서 저희 서버 개발자들과 같은 형상의 서버를 띄우고 환경을 제공하기 위해서 mysql, server Application을 docker container 를 활용하여 띄우는 작업을 진행하였습니다!
여기서 멈추지 않고 또한 docker container들이 많거나 하였을 때 한 번에 관리해주기 위한 docker-compose 라는 친구가 있다는 것을 알 수 있었습니다.
☑️ 특정 헤더를 인식하면, 정해진 도메인과 path 로 요청을 보내고싶어요 - L7 switch
서술 과제의
Switch
부분과 동일합니다 :)☑️Spring Boot Application 은 아무 설정하지 않는다면, 동시에 몇개의 요청을 처리할 수 있을까요?
🧐 톰캣(Tomcat)이란?
☑️→ 그렇다면 무작정 tomcat thread 를 늘리면 좋은거 아닌가요?
스레드를 생성하는데 비용
스레드 간의 컨텍스트 스위칭(Context Switching)이 발생하여 오버헤드
💡적정수의 thread 설정은 어떻게 하면 좋을까요?💡
Spring Boot Application 은 아무 설정하지 않는다면, 동시에 몇개의 요청을 처리할 수 있을까요? 200개 tomcat default thread connection pool 갯수
참고 링크
https://youtu.be/nZJJ5mylQas?si=QyZ1tBAa-1mG0rNN
https://velog.io/@sihyung92/how-does-springboot-handle-multiple-requests
그린 스레드 모델
네이티브 스레드 모델
이 모델의 스레드는 기본 OS 지원의 도움을 받아 JVM이 관리합니다 .
이러한 스레드는 OS 수준에서 구현되며(OS 멀티스레딩 API를 사용하여) 커널 공간에서 관리됩니다.
이를 비그린(커널 수준) 스레드라고도 합니다.
여러 네이티브 스레드가 공존할 수 있습니다. 따라서 다대다
모델 이라고도 합니다 . 이 모델의 이러한 특성 덕분에 멀티코어 프로세서를 최대한 활용하고 별도의 개별 코어에서 스레드를 동시에 실행할 수 있습니다.
이 모델은 여러 스레드가 동시에 실행될 수 있기 때문에 스레드 동기화와 리소스 공유가 복잡해집니다. 이로 인해 스레드의 실행 시간이 늘어납니다.
모든 윈도우 기반 OS는 네이티브 스레드 모델을 지원합니다.
Green Thread 모델은 제한 사항으로 인해 더 이상 사용되지 않습니다. Native Thread 모델은 Green Thread 모델을 대체했으며 오늘날 널리 사용됩니다. 이 외에도 Java의 Thread 클래스에는 더 이상 사용되지 않는 몇 가지 메서드가 있습니다.
참고링크
https://www.geeksforgeeks.org/green-vs-native-threads-and-deprecated-methods-in-java/
https://docs.oracle.com/cd/E19455-01/806-3461/6jck06gqk/index.html
☑️→ 적정수의 thread 설정은 어떻게 하면 좋을까요?
application.yml 이나 properties 의 설정들을 통해서 조절을 하게 되고 본인 서버 컴퓨터의 스펙이나 여러가지 자원들을 고려해서 진행해야한다.
The text was updated successfully, but these errors were encountered: