Today I Learned
Chater 1. 과제 제출에 대한 튜터님 피드백을 받게 되었다
튜터님께서 해주신 피드백은 Layered Architecture Pattern에 대한 내용이었다
🔥 경력이 많은 선임 개발자가 내 코드를 보고 해주는 조언인 만큼 새겨듣고, 문제를 파악하고, 기록하며 더 좋은 코딩 습관을 만들도록 하자!!
[ Chapter 1. 과제 발제 ]
- MSA, 인메모리 저장소(Redis cache), 프로젝트 관리 심화과정을 응용해 보는 개인 프로젝트 과제입니다
패키지명 규칙과 포트 규칙 준수
- 패키지명은 com.sparta.msa_exam 으로 설정하고 유레카 서버는 19090 포트로 실행되도록 설정해주세요.
- 게이트웨이 서비스는 com.sparta.msa_exam.gateway 패키지로 추가하고 19091 포트로 실행되도록 설정해주세요.
- 상품 서비스를 com.sparta.msa_exam.product 패키지로 추가하고 19093 포트로 실행되도록 설정해주세요.
- 주문 서비스를 com.sparta.msa_exam.order 패키지로 추가하고 19092 포트로 실행하도록 설정해주세요.
- 인증 서비스를 com.sparta.msa_exam.auth 패키지로 추가하고 19095 포트로 실행하도록 설정해주세요.
모든 API 의 Response Header 에Server-PortKey 로 현재 실행중인 서버의 포트를 추가해주세요.
개발 환경의 세팅을 아래와 같이 설정 해주세요.
- Datasource
- H2 memory database를 이용하시는 경우 무관합니다.
- MySQL 로 생성하신 경우, dev 프로필의 설정하기 처럼 통일해주세요
- 설정하기: 포트: 3306 - 계정: root - 비밀번호: password - Database 이름: msa_exam
- Redis 설정
- 포트: 6379
- 계정: default
- 비밀번호: systempass
- Zipkin 설정
- 포트: 9411
- JWT 만기 처리
- JWT 토큰이 최소 1시간 이상 지속되도록 설정해 주세요. 과제 확인하는 도중에 토큰 만료로 인해 재발급받는 불편을 줄일 수 있습니다.
기본 API 구성하기
- POST /products 상품 추가 API
- 상품 Entity
- 상품 추가시 Request/Response 객체 구성은 자유입니다.
Key | Value |
product_id | Long (Primary, Auto Increment) |
name | String |
supply_price | Integer |
- Get /products 상품 목록 조회 API
- 응답 형태: List<응답 객체>
- 응답 객체
- 상품 목록 조회시 Request 객체 구성은 자유입니다.
Key | Value |
product_id | Long |
name | String |
supply_price | Integer |
- Post /order 주문 추가 API
- 주문 Entity
- Request/Response 객체 구성은 자유입니다.
Key | Value |
order_id | Long (Primary, Auto Increment) |
name | String |
product_ids | List<주문 매핑 상품 Entity> |
- 주문 매핑 상품 Entity
Key | Value |
id | Long (Primary, Auto Increment) |
order | 주문 Entity |
product_id | Long |
- PUT /order/{orderId} 주문에 상품을 추가하는 API
- 요청 Body
- Request/Response 객체 구성은 자유입니다.
Key | Value |
product_id | Long |
- GET /order/{orderId} 주문 단건 조회 API
- 응답 객체
Key | Value |
order_id | Long |
product_ids | List<Long> |
- GET /auth/signIn?user_id={string} 로그인 API
- DB 연결이 되지 않아도 됩니다. Gateway 서비스의 Filter 만 통과할 수 있도록 구성해주세요.
상품 서비스는 라운드로빈 형식으로 로드밸런싱 구성하기
- IntelliJ Configuration 을 이용하여 상품 서비스를 19094 포트로 하나 더 실행 해보세요.
- 라운드로빈 형식으로 로드밸런싱을 구현해서 상품 서비스가 호출될 때마다 두 서비스를 반복하며 호출되게 구성해 보세요.
- 상품 목록을 조회할 때마다 API 의 응답 헤더의 Server-Port 값이 19093 , 19094 로 변경되어야 합니다.
주문에 상품을 추가하는 API 만들 때 아래와 같이 구성해보세요
- FeignClient 를 이용해서 주문 서비스에 상품 서비스 클라이언트 연결
- 상품 목록 조회 API를 호출해서 파라미터로 받은 product_id 가 상품 목록에 존재하는지 검증
- 존재할경우 주문에 상품을 추가하고, 존재하지 않는다면 주문에 저장하지 않음.
분산추적 구현해보기
- 주문 서비스 와 상품 서비스 에 Zipkin 을 연동하고, 호출시 Zipkin 대시보드에 Duration 이 측정 되는지 확인해보세요.
캐싱 기능 구현하기
- 주문 조회 API 의 결과를 캐싱 처리하여 60초 동안은 DB 에서 불러오는 데이터가 아닌 메모리에 캐싱된 데이터 가 보여지도록 설정해주세요.
외부 요청 보호
- Oauth2,JWT 기반으로 인증/인가를 구성하여 인가 없이 상품 서비스, 주문 서비스를 호출할 때
401 HTTP Status Code를 응답하도록 설정해주세요.
캐시를 더 활용 해볼까요?
- 상품 추가 API 를 호출 할 경우 상품 목록 조회 API 의 응답 데이터 캐시가 갱신되도록 구현해주세요.
- 상품 추가 후 상품 목록 조회 API 호출 시 데이터가 변경 되는지 확인합니다.
피드백 내용
프로젝트에 Layered Architecture 를 적용 해보세요!
레이어드 아키텍처(Layered Architecture) 란, 하나의 소프트웨어를 계층으로 분리하여 역할을 분리시키는 아키텍처 패턴입니다.
JPA에서는 크게 비즈니스와 가장 크게 밀접한 연관을 갖는 Entity, Repository 를 도메인(Domain) 영역이라 부르고, 핵심 비즈니스 로직이 구현되는 Service 를 Application 계층이라 부르며, 사용자의 입력 값을 받고 응답을 리턴하는 Controller 를 유저 인터페이스(UI, UserInterface) 계층이라고 부릅니다.
이러한 분리를 하는 이유는, "단방향 의존성" 을 위함인데 이것은 UI -> Service -> Domain 계층으로는 의존이 가능하나, Domain -> Service -> UI 계층으로는 의존이 불가하게 설계하는 것입니다.예를들어, Service(Applicaiton) 단의 DTO 의 name 이 name2 로 변경되었을때, 만약 Entity(Domain) 에서 해당 Dto 를 파라미터로 받고 있고, name 값을 사용하고 있으면 어떻게 될까요 ? Entity 에서 변경되어야 하는 값은 전혀 없으나 DTO 로 인해 Entity 의 로직에 수정이 발생하는 경우가 생깁니다.
위와 같은 경우가 잦게 발생하면 예상치 못한 사이드이펙트가 발생할 수 있으며, 오류의 부분을 찾기 위해 전체 소스를 찾아보는 등 디버깅에 시간적인 리소스를 많이 소요하게 됩니다. 그래서 Entity 는 Entity 만의 변경에 대응하고 Service 는 Service 만의 변경에 대응하는 형태로 계층간의 분리를 이용한 아키텍처 방식이 레이어드 아키텍처 방식입니다.
병준님이 진행해주신 프로젝트에는 Entity Product.java 가 Application 레이어인 ProductRequestDto 를 참조하고 있어 이 부분부터 수정 진행하면 될 것 같습니다 ㅎㅎ
문제
- 도메인 계층인 Product(Entity)에서 서버 계층인 RequestDto를 사용
- UI -> Application -> Domain 계층 방향으로 흐르는 단방향 의존성 설계 실패
@Getter
@Entity
@NoArgsConstructor
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Long product_id;
private String name;
private Integer supply_price;
public Product(ProductRequestDto requestDto) {
this.name = requestDto.getName();
this.supply_price = requestDto.getSupply_price();
}
}
사실 피드백 내용이 더 있으나, 내용 중 한 부분만 가져왔다. 튜터님의 친절한 피드백에 사실 조금 감동받았다😭
튜터님께서 해주신 피드백 내용을 바탕으로 다음번에는 Layered Architecture Pattern에 대하여 공부하고, 공부한 내용을 바탕으로 프로젝트에 적용해 보도록 하자!!
읽어주셔서 감사합니다 😊
- Layered Architecture pattern 을 사용하여 프로젝트 계층 분리하기
'해피 코딩 > Today I Learned' 카테고리의 다른 글
[TIL 14] RabbitMQ 추가 실습 + JMeter (0) | 2024.08.18 |
---|---|
[TIL 12] RabbitMQ 이론 정복하기 (0) | 2024.08.16 |
[TIL 10] 대규모 스트림 처리 (0) | 2024.08.13 |
[TIL 9] 캐싱 개념과 캐싱 전략 이해하기 (0) | 2024.08.12 |
[TIL 8] @ManyToOne, @OneToMany 정복하기 (1) | 2024.08.10 |