오늘 할 일

  • jpa강의 기본편
    • 다양한 연관 관계 매핑
    • 고급 매핑
    • 프록시와 연관관계 관리
  • 알고리즘 1문제
  • 오브젝트

 

'회고 > 우아한테크코스' 카테고리의 다른 글

2023.06.14 일일 회고  (0) 2023.06.15
2023.06.13 일일 회고  (0) 2023.06.14
[레벨 2] 레벨 인터뷰 회고  (0) 2023.06.09
[레벨 2] 레벨 로그  (0) 2023.06.08
[레벨 2] 쇼핑 장바구니 미션 회고  (0) 2023.06.01

질문 1. 스프링의 장점

- 사용자가 많기 떄문에 커뮤니티가 거대하다. 학습이 수월하고 문제를 같이 고민할 사람이 많다.

- 기업에서 돈을 받는 개발자들이 유지보수를 담당하고 있기 지속적으로 업데이트되어 보다 안정적이고 신뢰성이 있다.

질문 2. 레벨 2에서 겪었던 문제.

- 복잡한 비즈니스로직을 만나서 이를 해결하는 것이 어려웠다. 처음에는 로직을 서비스에서 다 구현하다 보니 서비스의 책임이 너무 커졌다. 이를 해결하기 위해 도메인 객체들에게 책임을 분리하였다. 또한, dao를 사용해서 서비스에서 db에 접근하는 로직들이 섞여있었는데, repository를 도입하여 서비스 로직을 객체들의 협력으로 구성하도록 리팩토링했다.

질문 2.1 repository와 dao의 의존관계는 어떻게 되는지

repository가 dao를 의존하도록 했다.

질문 2.2 repository를 레이어드 아키텍처 상에서 어느 레이어에 위치하게 했는가

repository의 구현체를 Data access layer에 두고, repository의 인터페이스를 도메인 레이어에 두었다. repository는 객체의 컬렉션으로 도메인 로직의 일부로 사용하기 때문에 서비스가 도메인 레이어에 속한 repository의 인터페이스를 의존하기 때문에 비즈니스 로직을 객체간의 협력으로 구성할 수 있었다.

질문 2.3 복잡한 비즈니스 요구사항을 해결하기 위해 어떻게 문제를 해결했는지

지하철 미션에서 거리별 요금을 구하는 비즈니스 요구사항이 있었다. 기본 요금, 10km~50km, 50km이상 이렇게 요금을 따로 구해야 했는데, if문 분기로 구하기 보다 다르게 해결할 수 있는 방법을 많이 고민했다. 처음에는 자신의 범위의 요금만 계산하고 다음 객체에게 책임을 넘기는 책임-연쇄 패턴으로 구현을 했다. 책임-연쇄 패턴은 순서를 신경써야 하는데 거리별 요금 계산은 순서가 중요하지 않아서 컴포지트 패턴으로 변경하였다. 컴포지트 패턴을 학습하기 전에는 컴포지트 패턴과 비슷하게 구현했으나 각 객체에서 구한 거리별 요금을 모두 합하는 bulk연산이 캡슐화되지 않았었다. 컴포지트 패턴을 학습하고 나서 해당 bulk 연산을 하는 객체도 같은 인터페이스를 구현하도록 만들어 bulk연산도 캡슐화할 수 있었다.

질문 3. @Transactional을 붙였을때 어떻게 트랜잭션이 적용되는지 과정

스프링이 해당 클래스의 프록시 객체를 만들어서 빈으로 등록한다. 프록시 객체는 데코레이터 패턴을 통해 트랜잭션 경계 설정하는 코드를 포함하게 된다. 트랜잭션을 설정하기 위한 코드는 각 db마다 다른데, JDBC의 경우에는 커넥션을 얻어오고, 오토커밋을 끄고, 성공하면 커밋, 실패하면 롤백하고, 커넥션을 반환한다. 스프링에서는 이렇게 db마다 다른 트랜잭션을 PlatformTransactionManager로 추상화하여 간편하게 트랜잭션을시작하고, 트랜잭션을 커밋, 롤백만하여 쉽게 트랜잭션을 설정하도록 되어있다. 이 코드 조차 우리에게 보이지 않도록 하기 위해 프록시 객체로 만들어서 빈으로 등록하여 사용하는 것이다. 이를 통해 우리가 작성하는 로직에는 트랜잭션과 관련한 코드 없이 트랜잭션 설정을 할 수 있다.

질문 3.1 트랜잭션을 보장하는 프록시객체를 누가 만들고, 어느 시점에 만들어지는지?

- 빈으로 등록해서 사용하기 때문에 빈 생성 시점에 될거라고 생각하는데 누가 만드는지는 모르겠습니다. 

-> TransactionInterceptor가 이를 담당하고 있는데 이 과정을 자세히 알아보자.

'회고 > 우아한테크코스' 카테고리의 다른 글

2023.06.13 일일 회고  (0) 2023.06.14
2023.06.12 일일 회고  (0) 2023.06.13
[레벨 2] 레벨 로그  (0) 2023.06.08
[레벨 2] 쇼핑 장바구니 미션 회고  (0) 2023.06.01
2023.05.31 일일 회고  (0) 2023.06.01

Dependency Injection

  • 필드 주입
    스프링 버전 5.1 이전에는 빈 생성 시점에 순환 참조 문제를 발견할 수 없다.
    • 테스트 작성시 의존성을 주입하기 어렵다.
  • setter 주입
    • 불변을 보장하지 않는다. 객체 생성시 필요한 의존 관계를 쉽게 파악하기 어려워 코드를 이해하기 어렵고 불변성을 보장하지 않아 의도치 않은 문제를 유발할 수 있다.
  • 생성자 주입
    빈 생성 시점에 순환 참조를 발견할 수 있다.
    생성자 주입은 객체 생성시 필요한 모든 의존 관계를 쉽게 파악할 수 있다.

스프링의 장점

  • 사용자가 많아서 커뮤니티가 거대하다. 커뮤니티가 있기 떄문에 학습하기 수월하고 문제를 공유할 수 있는 사람이 많다.
  • 기업에서 돈을 받는 개발자들이 유지보수를 담당하고 있기 지속적으로 업데이트되어 보다 안정적이고 신뢰성이 있습니다.

스프링의 특징

  • IoC / DI(Inversion of Control / Dependency Injection)
    스프링을 사용하면 빈으로 관리하는 객체들은 IoC컨테이너가 의존 관계를 알아서 주입해주기 때문에 코드상에서 개발자가 직접 객체를 생성하여 초기화하는 로직이 사라진다. 객체 생성과 관련된 로직이 사라지기 때문에 비즈니스 로직에 집중할 수 있다.
  • AOP(Aspect Oriented Programming)
    비즈니스 로직과 거리가 있는 중복되는 코드들을 AOP에 맡겨 중복을 줄이고 응집도를 높여 비즈니스 로직에 집중할 수 있다. 실제로 ACID를 보장하기 위해서 트랜잭션을 사용하는 경우 트랜잭션 경계선을 설정하고, try-catch로 로직을 묶어서 성공하면 commit, 실패하면 rollback을 해주는 로직을 작성해야 하는데 이런 중복되고 비즈니스 로직과 거리가 있는 코드를 @Transactional 어노테이션만 붙이면 눈에 보이지 않고 AOP가 처리해준다.
  • PSA(Potable Service Abstraction)
    다른 라이브러리나 기술에 의존하는 서비스를 추상화하여 쉽게 사용하고 교체할 수 있도록 합니다. 예를 들어서, 사용하는 DB기술마다 트랜잭션을 보장하는 방법이 다릅니다. JDBC의 경우 커넥션을 얻어오고, 오토커밋을 끄고, 성공하면 커밋, 실패하면 롤백하고 finally로 커넥션을 반환하는 코드를 작성해야 합니다. 이렇게 기술에 의존적인 코드를 트랜잭션 매니저로 추상화하여 쉽게 트랜잭션을 얻어오고, 커밋과 롤백하는 코드만 작성하도록 할 수 있습니다. 트랜잭션을 보장하는 코드를 추상화 함으로써 다른 db기술을 사용하더라도 트랜잭션 매니저의 구현체만 변경하면 되기 때문에 변경에 쉽게 대응할 수 있습니다

트랜잭션

가장 작은 작업의 단위. 가장 작은 단위로 만들기 위해 ACID라는 특징을 가짐.

  • Atomicity - 이 작업 묶음이 실패하면 모두 실패하거나, 성공하면 모두 성공해야 한다
  • Consistency - 트랜잭션의 이전, 이후 데이터베이스의 상태는 유효해야 한다.
  • Isolation - 모든 트랜잭션은 다른 트랜잭션으로부터 고립되어야 한다.
  • Durability - 하나의 트랜잭션이 성공했다면 이후에 어떤 오류가 발생하더라도 해당 기록은 영구적이어야 한다.

왜 서비스에서 트랜잭션을 관리하는가?

  • 서비스의 책임은 하나의 작업 묶음을 관리한다. 때문에 서비스에서 이 작업 묶음에 대한 트랜잭션을 관리한다.

@Transactional을 붙였을 때 스프링에서 어떻게 트랜잭션을 만들어 주는가?

@Transactional이 붙어있는 클래스의 프록시 객체를 만든다. 스프링에서 트랜잭션을 만들기 위해서 트랜잭션 매니저로부터 트랜잭션 경계선을 설정하고, 로직을 try-catch로 감싸서 성공하면 commit, 실패하면 rollback을 하는 코드를 데코레이터 패턴으로 프록시 객체에 추가한다. 이렇게 생성된 프록시 객체를 빈으로 등록하여 사용한다.

트랜잭션의 전파

propagation 옵션을 통해 설정할 수 있다.

  • Required - 부모 트랜잭션이 있으면 포함된다
  • Supports - 부모 트랜잭션이 있으며 포함되고 없으면 트랜잭션을 보장하지 않음
  • Mendetory - 부모 트랜잭션이 있으면 포함되고 없으면 IllegalTransactionStateException
  • Requires_New - 새로운 트랜잭션을 생성한다.
  • Not_Supported - 부모 트랜잭션이 있으면 트랜잭션을 보류시키고 트랜잭션없이 진행한다.
  • never - 부모 트랜잭션이 있으면 IllegalTransactionStateException, 없으면 트랜잭션 없이 진행
  • nested - 부모 트랜잭션이 없으면 새로운 트랜잭션을 생성하고 있으면 중첩 트랜잭션을 생성한다.중첩 트랜잭션이 롤백되어도 부모 트랜잭션은 상관없지만 부모 트랜잭션이 롤백되면 중첩 트랜잭션도 롤백된다. db드라이버에 따라 지원하지 않을 수 있음(Jpa는 안함)

MVC

  1. DispatcherServlet이 request 요청을 받는다.
    dispatcherServlet은 요청을 가장 먼저 받는 프론트 컨트롤러
  2. HandlerMapping에서 적절한 핸들러를 찾는다.
    보통은 @Controller의 @RequestMapping정보를 활용하여 핸들러를 찾도록 함.
  3. 요청을 HandlerAdapter에 넘김
    컨트롤러의 구현방식이 다양하기 때문에 어댑터 패턴을 적용하여 컨트롤러의 구현 방식에 관계 없이 요청을 위임할 수 있도록 함.
  4. HandlerAdapter가 컨트롤러로 요청을 위임한다.
    RequestParamMethodArgumentResolver, PathVariableMethodArgumentResolver과 같은 argumentResolver들이 @RequestParam, @PathVariable같은 파라미터를 파싱하여 메서드에 바인딩한다.
  5. 비즈니스 로직을 처리한다
  6. 컨트롤러가 결과를 반환한다
    주로 ResponseEntity를 반환하거나 string을 반환하여 뷰의 이름을 반환할 수 있다.
  7. 핸들러 어댑터가 반환값을 처리한다
    어댑터가 반환값에 대한 후처리를 진행하고 결과를 디스패처 서블릿에 반환한다. 만약 컨트롤러가 뷰의 이름을 반환하면 뷰 리졸버를 통해 뷰를 반환한다.
  8. 응답을 클라이언트로 반환한다.

HTTP Basic 인증

Authorization header에 email과 password를 base64인코딩하여 전송한다.
base64는 단순 인코딩이기 때문에 평문으로 전송하는 것과 보안 수준이 같다.
즉, basic 인증 방식은 password를 평문으로 전송하기 때문에 HTTPS / TLS 상에서 통신해야 안전하다.
장점
email과 password만 요구하는 간편한 인증 방식이기 때문에 구현하기 쉽다.
단점
서버에 email과 password를 저장해야 한다.

Session

사용자가 인증에 성공하면 서버는 세션을 생성하여 저장하고, 클라이언트에게 쿠키로 저장할 수 있도록 세션ID를 반환한다.
basic인증은 authorization header에 인증 정보를 담고 있기 때문에 탈취당하면 위험하지만 세션은 의미없는 값이기 때문에 세션을 사용할수는 있으나 더 안전하다.
서버에 정보를 저장하기 때문에 서버 부하의 원인이 될 수 있다.
서버를 여러 대 사용하는 경우 서버들간에 저장하고 있는 세션을 동기화해주어야 하는 문제가 있다.

JWT Token

Json Web Token의 줄임 말로써 토큰 자체에 json 형식으로 데이터를 담고 이를 해시나 비대칭키로 서명하여 토큰을 발급한다. 서버의 키가 노출되지 않는 이상 악의적인 사용자가 토큰을 만들더라도 서버에서 검증할 수 있다. 서버에 클라이언트의 정보를 저장하지 않기 때문에 서버의 부하가 적다.
payload자체는 암호화되어있는 것이 아니기 때문에 민감한 정보를 담으면 안된다.
세션은 세션Id만 가지고 통신하는데 비해 토큰은 토큰에 인증 정보, 발급 시간, 만료 시간, 토큰 id등 여러 정보가 들어있기 때문에 네트워크 트래픽이 더 많이 발생한다.
토큰이 만료되면 작성중인 글을 제출했을 때 날라가는 등의 문제가 발생할 수 있다. sliding session으로 서비스를 사용중인 사용자에게 토큰 만료 기한을 계속 늘려주거나 refresh token을 사용하도록 하여 refresh token이 유효하면 새로 access token을 발급해줄 수 있다. 하지만 refresh token은 단지 기간만 긴 토큰이고 관리가 필요하다는 단점이 있다.

put vs patch

PUT은 target이 이미 존재하면 수정하고, 존재하지 않으면 새로 생성한다. 생성하면 201, 수정하면 200또는 204를 응답한다. Put의 표현이 target 자원과 일치하지 않는다면 일관성을 유지하기 위해 서버가 변환해주거나 409(conflict) or 415(Unsupported meida type)을 반환한다. 예를 들어, 자원이 항상 text/html Content-type을 가져야 하는데 put의 표현이 image/url 이라면 415를 반환한다.

PATCH는 자원의 일부만 수정할 때 사용한다.

CORS

cross-origin-resource-sharing의 줄임말로 브라우저단에서 지원한다. 다른 애플리케이션의 출처에서의 자원 접근 권한을 관리한다. SOP(same-origin-policy)는 같은 출처에서만 리소스를 공유할 수 있음을 의미.
cors동작 과정 : prefilght요청을 보낸다. Http의 Option 메서드와 함께 두개의 요청 헤더인 실제 요청 메서드정보와 실제 요청의 추가 헤더에 대한 정보를 담아 보낸다. 서버는 cors정책을 허가하는 출처와 허가 메서드, 헤더 등의 정보를 반환한다.

'회고 > 우아한테크코스' 카테고리의 다른 글

2023.06.12 일일 회고  (0) 2023.06.13
[레벨 2] 레벨 인터뷰 회고  (0) 2023.06.09
[레벨 2] 쇼핑 장바구니 미션 회고  (0) 2023.06.01
2023.05.31 일일 회고  (0) 2023.06.01
2023.05.30 일일 회고  (0) 2023.05.31

※ 이 글은 개인의 주관이 많이 섞여 있습니다.

장바구니 웹 어플리케이션을 구현하면서 다음과 같은 요구사항이 존재했다.

  1. 사용자의 장바구니의 모든 상품을 주문한다
  2. 장바구니의 상품을 주문하면 사용자의 장바구니의 상품을 모두 삭제한다

따라서 다음과 같이 주문을 하는 OrderService의 save 메서드를 구현했다.

public Order save(final Long memberId) {  
	final List<CartItem> cartItems = cartItemService.findAllByMemberId(memberId);  
  
	final Order order = Order.createFromCartItems(cartItems, memberId);  
  
	cartItemService.deleteByMemberId(memberId);  
	return orderRepository.save(order);  
}

첫 줄의 사용자에 해당하는 장바구니 아이템을 불러오는 코드는 괜찮지만

cartItemService.deleteByMemberId는 cartItem의 데이터를 직접 변경시키기 때문에 OrderService의 책임을 벗어난다고 볼 수 있다.

OrderService는 주문을 하는 데에만 관심이 있고, 주문을 함으로써 일어나는 side-effect까지 OrderService가 책임지는 것은 당장은 괜찮아 보이지만  side-effect가 많아지면 이를 관리하기 어려울 수 있다.

예를 들어서, 주문을 했을 때 사용자의 장바구니에 담긴 상품도 삭제하고, 점주에게 알림 메시지를 보내고, 가게에 수수료를 부과한다고 가정해보자.

그렇다면 코드가 다음과 같이 변경될 것이다.

public Order save(final Long memberId) {  
	final List<CartItem> cartItems = cartItemService.findAllByMemberId(memberId);  
  
	final Order order = Order.createFromCartItems(cartItems, memberId);  
  
	cartItemService.deleteByMemberId(memberId);  
	shopService.sendOrderMessage(order); // 가게에 주문 메시지를 보냄
	commissionService.charge(order);	// 가게에 수수료를 부과함
	return orderRepository.save(order);  
}

이렇게 주문을 함으로써 일어나는 side-effect들이 절차지향적으로 쌓이고, 주문 로직이 변경되지 않더라도 OrderService가 변경되어야 할 것이다. (서비스간의 결합도가 높다는 문제도 있다.)

이는, OrderService의 변경의 원인이 하나가 아니게 되어 SRP를 위반하고,

주문 외 기능이 확장되었을 때 OrderService에 변경이 생긴다.

이 문제를 해결하기 위해 스프링의 이벤트를 사용할 수 있다.

이벤트란?

스프링에서 디자인 패턴의 옵저버 패턴을 구현한 하나의 방법.

이벤트를 발행하면(notify), 이를 구독하고 있는 Observer들이 이벤트를 전달받고, 각자의 로직을 수행한다.

스프링의 이벤트는 다음과 같이 3가지 요소로 이루어져 있다.

1. 이벤트를 발행하는 Publisher

2. 이벤트 발행시 전달되는 이벤트 객체

3. 이벤트를 전달받는 EventListener

이벤트 객체는 스프링 4.2 버전 이전에는 ApplicationEvent를 상속한 객체만 가능했지만 그 이후에는 모든 객체를 이벤트 객체로 사용할 수 있다.

하지만, 도메인 객체를 직접 이벤트 객체로 사용하기 보다 이벤트가 발생한 시점의 도메인 객체의 스냅샷을 이벤트 객체로 사용하는 것이 좋다. 다음 코드에서는 `OrderedEvent.from()`에서 Order의 스냅샷을 저장하여 이벤트를 발행하도록 했다. 

public Order save(final Long memberId) {  
	final List<CartItem> cartItems = cartItemService.findAllByMemberId(memberId);  
  
	final Order order = Order.createFromCartItems(cartItems, memberId);  
  
	applicationEventPublisher.publishEvent(OrderedEvent.from(order));	// 이벤트 발행
	return orderRepository.save(order);  
}

OrderedEvent는 스냅샷을 저장하고 있는 DTO라고 볼 수 있다.

OrderService는 주문을 생성하여 저장하고, 주문을 완료했다는 이벤트만 발행하고 있다.

이 이벤트를 Listener를 구현하여 이벤트가 발행되면 그 이후의 연관되는 작업을 수행할 수 있다.

@Component
public class OrderedEventHandler {

    private final CartItemService cartItemService;

    public OrderEventHandler(CartItemService cartItemService) {
        this.cartItemService = cartItemService;
    }

    @EventListener
    public void deleteCartItems(OrderedEvent event) {
        cartItemService.deleteByMemberId(event.getMemberId());
    }

    // 가게에 메시지를 보내는 리스너
    // 가게에 수수료를 부과하는 리스너
}

주문이 발생했을 때 수행되는 추가적인 작업들을 이 Listener에 모두 구현하면 된다.

OrderService는 주문과 이벤트를 발행만 함으로써 주문에 따른 side-effect에 대한 책임을 eventListener에게 넘겨 SRP를 만족 하고, 서비스간의 결합도를 줄일 수 있었다.

 

참고 자료

- https://docs.spring.io/spring-framework/reference/core/beans/context-introduction.html#context-functionality-events 

- https://www.baeldung.com/spring-events

-https://ko.wikipedia.org/wiki/%EC%98%B5%EC%84%9C%EB%B2%84_%ED%8C%A8%ED%84%B4

학습 목표


  • ☑ 여러 요청에 대한 API를 설계하고 구현한다.
    • ☑ HTTP 프로토콜의 HTTP 메소드, 요청과 응답 헤더, 상태 코드 등의 요소가 무엇인지 안다.
    • ☑ Spring MVC가 제공하는 어노테이션을 사용해서 API 설계에 맞는 요청을 받고 응답할 수 있다.
  • ☑ 자주 사용되는 사용자 인증 방식이 무엇인지 알고, 이를 코드에 적용할 수 있다.
    • ☑ HTTP Basic 인증이 무엇인지 설명할 수 있다.
    • ☑ (심화) Token, Session 기반의 인증 방식이 무엇인지 설명할 수 있다.
    • ☑ Spring이 제공하는 HandlerInterceptor, HandlerMethodArgumentResolver의 구현체를 만들어 보고 이 인터페이스를 어떤 상황에 사용할 수 있는지 이해한다.
  • ☑ 서로 다른 View Type으로 응답할 수 있다.
    • ☑ 템플릿 엔진을 사용해 렌더링한 페이지를 응답할 수 있다.
  • ☑ 테스트 도구를 사용해서 웹 요청/응답에 대한 검증을 할 수 있다.

 

HTTP Basic 인증

https://datatracker.ietf.org/doc/html/rfc7617

Authorization header에 다음과 같이 email과 password를 base64인코딩하여 전송한다.

Authorization: Basic base64(email:password)

base64는 단순 인코딩이기 때문에 평문으로 전송하는 것과 보안 수준이 같다.

즉, basic 인증 방식은 password를 평문으로 전송하기 때문에 HTTPS / TLS 상에서 통신해야 안전하다.

장점 

email과 password만 요구하는 간편한 인증 방식이기 때문에 구현하기 쉽다.

단점

서버에 email과 password를 저장해야 한다.

 

Session

사용자가 인증에 성공하면 서버는 세션을 생성하여 저장하고, 클라이언트에게 쿠키로 저장할 수 있도록 세션ID를 반환한다. 

basic인증은 authorization header에 인증 정보를 담고 있기 때문에 탈취당하면 위험하지만 세션은 의미없는 값이기 때문에 세션을  사용할수는 있으나 더 안전하다.

서버에 정보를 저장하기 때문에 서버 부하의 원인이 될 수 있다.

서버를 여러 대 사용하는 경우 서버들간에 저장하고 있는 세션을 동기화해주어야 하는 문제가 있다.

JWT Token

Json Web Token의 줄임 말로써 토큰 자체에 json 형식으로 데이터를 담고 이를 해시나 비대칭키로 서명하여 토큰을 발급한다. 서버의 키가 노출되지 않는 이상 악의적인 사용자가 토큰을 만들더라도 서버에서 검증할 수 있다. 서버에 클라이언트의 정보를 저장하지 않기 때문에 서버의 부하가 적다.

payload자체는 암호화되어있는 것이 아니기 때문에 민감한 정보를 담으면 안된다.

세션은 세션Id만 가지고 통신하는데 비해 토큰은 토큰에 인증 정보, 발급 시간, 만료 시간, 토큰 id등 여러 정보가 들어있기 때문에 네트워크 트래픽이 더 많이 발생한다.

토큰이 만료되면 작성중인 글을 제출했을 때 날라가는 등의 문제가 발생할 수 있다. sliding session으로 서비스를 사용중인 사용자에게 토큰 만료 기한을 계속 늘려주거나 refresh token을 사용하도록 하여 refresh token이 유효하면 새로 access token을 발급해줄 수 있다. 하지만 refresh token은 단지 기간만 긴 토큰이고 관리가 필요하다는 단점이 있다.

PUT vs PATCH

PUT은 target이 이미 존재하면 수정하고, 존재하지 않으면 새로 생성한다. 생성하면 201, 수정하면 200또는 204를 응답한다. Put의 표현이 target 자원과 일치하지 않는다면 일관성을 유지하기 위해 서버가 변환해주거나 409(conflict) or 415(Unsupported meida type)을 반환한다. 예를 들어, 자원이 항상 text/html Content-type을 가져야 하는데 put의 표현이 image/url 이라면 415를 반환한다.

PATCH는 자원의 일부만 수정할 때 사용한다. 

Junit 테스트 메서드의 접근 제어

public 접근제어자가 없어도 되고, 일반적으로는 생략하는 것을 권장하고 있다

  • 테스트를 실행하기 위해서는 리플렉션을 사용한다.
  • JDK1.1에서는 리플렉션에서 public 메소드만 접근을 허용 했기 때문에 테스트는 모두 public 이어야했다.
  • JDK1.2부터 리플렉션으로 모든 접근 레벨을 허용하여 public이 아니어도 접근 가능하지만 Junit4까지는 기존 관례를 유지하기 위해 테스트 클래스와 메서드는 public이어야 했다.
  • Junit5부터는 public을 생략하도록 한다.

왜 생략하는 것이 좋은가?

  • public으로 접근제어를 열어두면 다른 테스트에서 접근이 가능하고 이는 모듈성이 떨어진다.
  • 모듈성이 떨어지면 독립적으로 개발, 배포,유지보수가 힘들어질 수 있다.

https://junit.org/junit5/docs/current/user-guide/#writing-tests-classes-and-methods

 

View Controller

호출되자마자 즉시 뷰를 전달하는 ParameterizableViewController 를 정의하는 방법.
뷰를 생성할 때 추가적인 로직이 필요하지 않은 경우 Java Controller를 사용하지 않고 처리하는 방법.

WebMvcConfigurer를 구현하는 Configuration에서 addViewControllers() 에서 경로에 따라서 어느 뷰의 이름을 지정해준다.

Handler Interceptor

로깅, 인증 확인과 같은 코드의 반복을 줄이기 위해 사용한다.

Handler Mapping이 실행할 컨트롤러를 찾아주면, request를 처리하기 전에 DispatcherServlet이 Interceptor를 호출할 수 있다.

  • prehandle() – 실제 핸들러가 실행되기 전에 호출됨. HandlerMapping 이 적절한 핸들러 객체를 선택한 후에 실행됨.
  • postHandle() – 실제 핸들러가 실행된 후에 호출됨. 핸들러 실행이 끝난 후에 인터셉트함. HandlerAdapter 가 핸들러를 호출한 후, DispatcherServlet이 뷰를 렌더하기 전에 인터셉트함.
  • afterCompletion() – request가 끝나고 뷰가 생성된 후에 호출됨

 

Handler Method ArgumentResolver

Spring MVC가 컨트롤러 메서드를 호출할 때, 요청 파라미터와 HTTP 헤더 등의 값을 해당 메서드의 매개변수에 바인딩하는 데 사용된다. 즉, 이 인터페이스를 구현한 객체는 컨트롤러 메서드에서 사용할 수 있는 객체를 생성하거나 변환하는 기능을 제공한다.

먼저 사용할 어노테이션을 정의해준다. 컨트롤러의 파라미터에 이 어노테이션을 붙이면 ArgumentResolver를 통해 완성된 객체를 파라미터를 전달받을 수 있을것이다.

 

  • @Import - import 할 하나 이상의 @Configuration 컴포넌트 클래스들을 명시한다. 
  • @ExtendWith - 테스트 클래스나 메서드에서 Junit jupiter API의 확장 기능을 사용할 수 있도록 한다. 스프링은 보통 @ExtendWith(SpringExtenstion.class)를 사용하여 SpringExtension의 확장 기능을 사용한다. SpringExtension은 테스트 클래스의 Application Context를 관리하여 테스트에서 스프링 컨테이너를 사용할 수 있도록 한다.테스트 인스턴스에 대한 DI를 가능하게 하며 @Transactional 을 통한 트랜잭션 관리를 지원한다.
  • @ContextConfiguration - 테스트에서 ApplicationContext에 대한 설정을 로드하는데 사용한다.
  • @WebMvcTest - Spring MVC에 필요한 빈들을 로드하여 실제로 동작하는 MVC환경에서 테스트할 수 있도록 한다. 
  • @SpringBootTest - 스프링부트 기반 테스트를 할 때 사용한다. loader를 설정해주지 않으면 기본 ContextLoader로 SpringBootContextLoader를 기본으로 사용한다. 웹 환경을 Mock, Random Port, Defined Port, None으로 지정할 수 있다. Mock으로 지정시 웹 환경을 따로 설정해주어야 하며 @AutoConfigureMockMvc를 사용할 수 있다.
  • @JdbcTest - datasource설정을 위한 @AutoConfigureTestDatabase, Jdbc 설정을 위한 @AutoConfigureJdbc와 각 테스트마다 트랜잭션을 보장하고, 롤백을 하기 위한 @Transactional을 포함하고 있다.
  • @MockBean - Application Context에 가짜 객체를 빈으로 등록한다. 각 테스트 메서드 이후에 reset한다.
  • @Inject Mocks - 대상 클래스의 의존성을 클래스에서 선언한 @Mock 객체들로 주입한다. 실제 객체가 필요한 경우에는 @Spy 객체를 주입하여 모의할 메서드만 스터빙 할 수 있다.

'회고 > 우아한테크코스' 카테고리의 다른 글

[레벨 2] 레벨 인터뷰 회고  (0) 2023.06.09
[레벨 2] 레벨 로그  (0) 2023.06.08
2023.05.31 일일 회고  (0) 2023.06.01
2023.05.30 일일 회고  (0) 2023.05.31
2023.05.25 일일 회고  (0) 2023.05.26

오늘 할 일

  • 테스트
    • 도메인
    • 컨트롤러
    • 서비스
    • 레포지토리
    • dao
  • Money 분리
  • 연관관계 그림 그리기
    - 글쓰기 미션
  • 근로 회의

 

장바구니 협업 미션을 제출했다.

뒤늦게 테스트를 작성하면서 많은 버그들을 발견하고, Api명세와 어긋나는 부분들이 많이 나왔다. 도메인 로직에서 Api에서 필요하지 않은 부분을 계산하는 등의 쓸데없는 로직도 있었고, request를 잘못 이해한 코드도 있었다.

또한 뒤늦게 수십개의 테스트를 작성하려고 하니 너무 힘들다.

다음부터는 꼭 atdd로 시작하고 tdd로 개발하자

주문 도메인 의존성

MeberCoupon의 패키지를 coupon에 둘지 member에 둘지 고민이었다.

Order가 이미 member패키지를 의존하고 있고 직접 Coupon 패키지를 의존할 필요가 없어서 member패키지에 위치하도록 했다.

이렇게 설계했을 때 Order가 의존하는 패키지의 수를 줄일 수 있다.

'회고 > 우아한테크코스' 카테고리의 다른 글

[레벨 2] 레벨 로그  (0) 2023.06.08
[레벨 2] 쇼핑 장바구니 미션 회고  (0) 2023.06.01
2023.05.30 일일 회고  (0) 2023.05.31
2023.05.25 일일 회고  (0) 2023.05.26
2023.05.24 일일 회고  (0) 2023.05.26

오늘 할 일

  • 인수 테스트 작성
  • 프론트와 api 회의
  • 바뀐 api 명세대로 코드 수정

 

우아한 객체지향

우연히 조영호님의 우아한 객체지향 영상을 보게 되었다.https://www.youtube.com/watch?v=dJ5C4qRqAgA

이전 미션까지는 모든 객체가 객체를 참조하도록 설계를 했다.

그것이 OOP라고 생각했고, 객체가 다른 객체의 id를 가지는 것은 이상하다고 생각했다.

이 방식의 단점은 Repository에서 객체를 만드는 과정이 너무 어려웠다. 참조하고 있는 모든 객체를 완성해야 하기 때문이다.

이를 해결하기 위해 Aggregate를 나누고, 다른 Aggregate는 id를 참조하고 서비스에서 이 id를 통해 객체를 만들어 와서 협력하도록 설계할 수 있다.

다음은 Aggregate를 나누는 간단한 규칙이다.

  1. 함께 생성되고 저장되는 객체끼리 묶는다.
  2. 도메인 제약사항을 공유하는 객체끼리 묶는다.
  3. 가능하면 분리한다.

만약 연관 관계가 강하게 결합되어 있어 객체를 직접 참조하고 있다면, 모든 객체들이 연결되고, 생성 주기가 다른데 트랜잭션으로 묶여서 성능 이슈가 발생할 수 있다.

예를 들어, 주문, 결제, 배달은 서로 주기가 다른데 트랜잭션으로 묶인다면 불필요한 락을 오랫동안 걸게 되어 성능에 문제가 생긴다.

 

설계를 하다 보면 패키지간의 의존성 사이클이 생기는 경우가 있다.

이런 의존성  사이클은 나중에 시스템을 분리하기 어렵게 만든다. 

또한, 의존성 사이클이 있다는 것은 결합도가 높고 응집도가 떨어진다는 것을 의미하기도 한다.

의존성 사이클을 해결하는 방법은 3가지가 있다.

  1. 중간 객체 생성
  2. DIP
  3. 클래스를 나누고 패키지를 분리한다.

 

정말 좋은 내용이지만 너무 어려워서 간단하게 이해만 했다. 영상을 다시 보면서 더 깊게 이해할 필요가 있을 것 같다.

'회고 > 우아한테크코스' 카테고리의 다른 글

[레벨 2] 쇼핑 장바구니 미션 회고  (0) 2023.06.01
2023.05.31 일일 회고  (0) 2023.06.01
2023.05.25 일일 회고  (0) 2023.05.26
2023.05.24 일일 회고  (0) 2023.05.26
2023.05.23 일일 회고  (0) 2023.05.24

오늘 할 일

  • 테코톡
    • 공지
    • 진행
  • 장바구니 협업
    • 기존 코드 api명세에 맞게 수정
    • 스웨거를 통한 api문서 자동화

 

정신 없는 하루...

데일리 미팅에서 벌써 내 차례가 돌아왔다.

레벨 1때에는 2번 밖에 안했고 레벨 2에서는 이미 2번을 해서 더 이상 차례가 돌아오지 않을거라 생각해서 적잖이 당황했다.

준비해둔게 없어서 어제 저녁을 먹으면서 크루들과 이야기했던 표창게임이 생각나서 표창게임을 하자고 했다.

반응은 싸늘했고 다른 게임으로 바꾸자는 이야기가 나왔다.

게임을 추천받았는데 이것도 반응이 싸늘했다.

결국 진진가를 하자고 해서 어찌 어찌 시간을 때우면서 데일리를 마쳤다.

 

테코톡을 진행하는데 테코톡을 진행할 때 사용하는 맥북 프로가 켜지지 않았다.

충전을 해도 계속 안켜져서 10분 정도 지체됐는데 알고 보니 맥북 프로는 충전기의 어댑터가 다르다고 한다😔

(맥북 프로를 안써봤는데 내가 어떻게 아냐고요😭)

맥북 프로용 충전기를 빌려서 맥북을 키고 식은땀을 줄줄 흘리면서 셋팅을 시작했다.

수많은 크루들이 기다리고 있었기 때문에 빠르게 셋팅을 했다.

발표자에게 화면 공유를 부탁하고 바로 시작을 했는데 여기서 또 사고가 일어났다.

발표 중간 쯤 알아챘는데 화면 공유가 제대로 되고있지 않았다.

화면 공유가 포함된 영상을 유튜브에 올리는데 캠 화면만 녹화되었다.

테코톡이 끝난 후 발표자에게 상황을 설명하고 원하시면 ppt화면도 편집으로 넣어줄 수 있다고 했지만 괜찮다고 해주셨다.

테코톡은 잘 마무리 되어서 한 숨 놓을 수 있었다.

 

정신없는 하루를 끝내고 집에 돌아오는 버스에서 정신없이 잤다.

잠에서 덜 깬채로 버스에서 내려서 걷다가 핸드폰을 떨어뜨렸고 액정이 깨졌다...😱

핸드폰을 손에 들고 다니지 말고 주머니나 가방에 잘 넣고다니자...

'회고 > 우아한테크코스' 카테고리의 다른 글

2023.05.31 일일 회고  (0) 2023.06.01
2023.05.30 일일 회고  (0) 2023.05.31
2023.05.24 일일 회고  (0) 2023.05.26
2023.05.23 일일 회고  (0) 2023.05.24
2023.05.18 일일 회고  (0) 2023.05.19

+ Recent posts