caffeine-library / pro-spring-5

🌱 전문가를 위한 스프링5를 읽는 스터디
5 stars 0 forks source link

[keyword] 12장 스프링 리모팅 사용하기 키워드 정리 #81

Closed binchoo closed 2 years ago

binchoo commented 2 years ago

주제

12장 스프링 리모팅 사용하기를 읽고 중요✨ 하다고 생각하는 키워드와 선택한 이유에 대해서 코멘트로 달아주세요.

연관챕터

80

@caffeine-library/readers-pro-spring-5

JasonYoo1995 commented 2 years ago

12.4 스프링에서 JMS 사용하기

MQ 서버

JMS Template (=producer)

참고 : JMS / AMQP와 관련된 주제인 카프카에 대한 책 요약

https://oasis-cabin-8bb.notion.site/a118eea9f9bb4e26b7a591e0c9acd130

@caffeine-library/readers-pro-spring-5

binchoo commented 2 years ago

Service oriented architecture

서비스는 비즈니스 요구사항이 잘 정의되고 잘 분화된 독립적인 컴포넌트입니다. 그러므로 재사용과 컴포지션에 유용한 단위이고, 큰 서비스는 작은 서비스를 조합하여 사용합니다. image

클라이언트는 서비스 디렉토리를 조회하여 적합한 서비스를 찾아 호출할 수 있습니다. (일종의 메타 정보 제공처가 필요함을 의미합니다.) image

Remote procedure call

클라이언트의 코드는 마치 함수를 호출하는 것처럼 원격지 서비스를 호출합니다. 이를 위해 원격지 서비스에 대한 메타 정보를 활용하여 RPC 지원 라이브러리가 추상화를 제공해야 합니다.

Simple object access protocol + Web service description language

image

XML 기반의 메타 정보를 주고 받습니다. (WDSL, Envelop)

WSDL 파일 예제 https://github.com/sensepost/wikto/blob/master/Web%20References/com.google.api/GoogleSearch.wsdl

클라이언트 코드 예제
image

클라이언트 측 HTTP 헤더에 적재된 SOAP Envelop image

SOAP는 HTTP 프로토콜 위에 Envelop이라는 계층을 하나 더 얹은 구조입니다. Envelop 내용은 원격 프로시저의 시그니쳐 정보에 의존하기 때문에 수시로 바뀌지만 HTTP 패킷 구조는 변하지 않습니다. (요청 URL & HTTP Method는 POST로 일정함)

따라서 HTTP 요청의 단순함을 활용하지 못합니다.

REST & RPC Hybrid

SOAP Envelop 대신에 HTTP 요청 URL에 프로시저 시그니처를 담았습니다. REST의 이론에 어긋나는 설계이나, 실서비스 곳곳에서 발견되는 API입니다. image

해당 API가 REST 이론에 맞추어 http://flickr.com/photos/tags/penguin에 대한 GET 요청으로 바뀐다면 어떨까요?

Resource oriengted architecture

SOAP 클라이언트 원격 프로시저의 시그니쳐에 의존하여 머신 친화적인 아키텍처로 인식됩니다. 하지만 ROA는 클라이언트 서버 간 교환 대상이 되는 리소스에 집중하여 인간 친화적인 API를 제공받도록 합니다.

ROA 기반 REST

리소스는 URI를 통해 고유하게 식별됩니다. 리소스에 대한 연산(조회, 생성, 수정, 삭제)는 더 이상 Envelop이 아닌 HTTP의 기본 메서드들을 이용합니다.

덕분에 RESTful 웹 서비스의 요청 포맷은 매우 단순해졌습니다. 클라이언트와 서버는 리소스 객체를 직렬화한 JSON 또는 마샬링한 XML을 교환합니다. image

따라서 각 엔드포인트는 JSON 컨버터 및 마샬러를 활용해야 하며 12장에서 관련 빈들이 모두 설명되었습니다.

binchoo commented 2 years ago

REST와 @ResponseBody 어노테이션

스프링MVC에서 컨트롤러의 메서드는 "논리적인 뷰"를 반환하는 것으로 상정한다.

하지만 REST 서비스는 리소스를 반환해야하기 때문에 상성이 맞지 않다. 그럴 땐 컨트롤러 메서드에@ResponseBody 붙여준다.

@ResponseBody를 붙인 메서드의 반환은, 뷰가 되는 것이 아니며 즉시 HTTP 응답 스트림으로 사용된다.

반환된 값은 디스패쳐 서블릿에 등록된 메시지 컨버터 빈을 거쳐 JSON 및 XML로 변환되고 HTTP 응답 스트림에 쓰인다.

@Controller
@ResquestMapping(value="/singer")
public class SingerController {
  final Logger logger = LoggerFactory.getLogger(SingerController.class);
  @Autowired private SingerService singerService;

  @ResponseStatus(HttpStatus.OK)
  @RequestMapping(value = "/listdata", method = RequestMethod.GET)
  @ResponseBody
  public Singers listData() {
     return new Singers(singerService.findAll());
  }
}

스프링 4.0부터는 @RestController

@RestController 클래스 수준 어노테이션을 사용하면, @RequestMapping이 붙은 메서드에 자동으로 @ResponseBody가 적용된다. @GetMapping, @PostMapping 등은 @RequestMapping을 래핑한 버전이므로 역시 적용된다.

binchoo commented 2 years ago

RestTemplate을 사용하는 서비스 레이어 구조 및 구현

요약

서비스가 비즈니스 객체를 다른 곳에서 획득해 오는 경우가 있다. 그 출처는 DB가 될 수도 있고, 다른 웹 서비스가 되기도 한다. DB와 퍼시스턴스 레이어를 거쳐 객체를 뽑아오든, 웹 API를 호출하여 객체를 얻어오든 두 방식을 구현하는 코드 패턴은 스프링에서 거의 유사하다. 3티어 아키텍쳐로 책임을 나누게 되며. 티어 간 객체 변환이 공통적으로 요구되는 사항이다.

3티어 아키텍처 비교

DB에서 객체를 얻을 때

서비스는 레포지토리 레이어를 통해 DB 데이터에 접근한다. 레포지토리 단은 JPA 구현체 Hibernate 기술 등을 채용하여 ORM(객체-관계형 매핑)을 제공한다. ResultSet에 담긴 관계형 데이터를 자바 엔터티 객체로 변경하는 것은 중요한 요구사항이다.

서비스는 레포지토리 레이어를 통해 DB 데이터에 접근한다. 레포지토리 단은 JPA 구현체 Hibernate 기술 등을 채용하여 ORM(객체-관계형 매핑)을 제공한다. ResultSet에 담긴 관계형 데이터를 자바 엔터티 객체로 변경하는 것은 중요한 요구사항이다.

웹 서비스 호출로 객체를 얻을 때

가장 익숙하고 유명한 리모팅 프로토콜인 HTTP(S)를 사용하여 웹 서비스 API가 제공되었다고 가정하자. 서비스 단은 다시 웹 클라이언트에게 부탁하여 원격지 서비스의 API를 호출토록 한다. 그 결과로 돌아오는 HTTP(S) 응답 패킷의 Body에는 JSON 혹은 XML 포맷을 갖는 객체가 담겨있다. 따라서 JSON, XML 혹은 형식 없는 스트링을 자바 엔터티 객체로 변경하는 것은 중대한 요구사항이다.

가장 익숙하고 유명한 리모팅 프로토콜인 HTTP(S)를 사용하여 웹 서비스 API가 제공 되고 있다고 가정하자. 서비스 단은 웹 클라이언트 단에 부탁하여 해당 원격 서비스의 API를 호출토록 한다. 그 결과로 돌아오는 HTTP(S) 응답 패킷의 바디에는 JSON 혹은 XML 포맷을 갖는 객체가 담겨있다. 따라서 JSON, XML 혹은 형식 없는 스트링을 자바 엔터티 객체로 변경하는 것은 중대한 요구사항이다.

REST 클라이언트의 요구사항

엔터티 식별하기

비즈니스 엔터티

비즈니스에 중요한 정보와 로직을 캡슐화한 객체

@ToString
@EqualsAndHashCode
@Setter
@Getter
@NoArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
public class Movie {

    Long id;

    String title;

    String titleEng;

    @JsonFormat(pattern = "yyyy-MM-dd")
    DateTime date;

    Float userRating;

    Float audienceRating;

    Float reviewerRating;

    Float reservationRate;

    Long reservationGrade;

    Long grade;

    String thumb;

    String image;

    String photos;

    String videos;

    String outlinks;

    String genre;

    Long duration;

    Long audience;

    String synopsis;

    String director;

    String actor;

    Long like;

    Long dislike;
}

HTTP 엔터티

HTTP 요청하고 응답하기

[spring-web] RestTemplate API

요청 엔터티 작성 & 전송 & 응답 엔터티 수신 & 비즈니스 객체로 매핑

헬퍼 클래스들의 도움으로 HTTP 요청 패킷은 직관적으로 작성 가능하다.

RestTemplate API를 사용하면 요청 패킷 전송 & 응답 수신 & 비즈니스 객체 매핑이 진행된다.

UriComponentsBuilder로 요청 파라미터 삽입 시 주의사항

UriComponentsBuilderRestTemplate 과 같이 사용할 때 인코딩 쪽에 주의가 필요하다.

restTemplate의 API는 String URL 혹은 URI을 요구한다.

문자열 URL을 전달하게 되면, restTemplate의 내부에서 UTF-8로 인코딩을 진행하므로, 굳이 UriComponentsBuilder 로 URL 생성시 인코딩을 적용할 필요가 없다.

인코딩이 2번 진행되면 한글 표현 등 문제가 발생하니 유의.

String url = uriBuilder.toUriString(); // bad - 인코딩 진행함
String url = uriBuilder.build().encode().toUriString(); // bad - 인코딩 진행함
String url = uriBuilder.build().toUriString(); // good - 인코딩 진행 안 함

엔터티 간 매핑하기

HTTP 패킷은 문자열이다.

고로 자바 객체 - 문자열 포맷간 직렬화/역직렬화가 꼭 필요하지만, 엔터티 마다 이런 로직을 구현하기는 매우 까다롭다. 다행히도 RestTemplate에겐 MessageConverter가 존재하여 그런 역할을 수행해 주고 있다.

[jaskson-databind] Object Mapper 구성 예시

[jaskson-databind] 참고할 어노테이션

@JsonNaming(PropertyNamingStrategy.SankeCaseStrategy.class)
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonProperty("name")
@JsonFormat("yyyy-MM-dd")

[jaskon-datatype-joda] 간편하게 JodaTime 매핑 설정하기

@Bean
public ObjectMapper objectMapper() {
    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.registerModule(jodaModule());
    return objectMapper;
}

@Bean
public JodaModule jodaModule() {
    return new JodaModule();
}

---
@ToString
@EqualsAndHashCode
@Setter
@Getter
@NoArgsConstructor
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)
public class Movie {
    Long id;
    @JsonFormat(pattern = "yyyy-MM-dd")
    DateTime date; //joda의 DateTime
    ...
}
binchoo commented 2 years ago

디버깅

image

Fiddler4 프록시 소개

단말에서 송수신하는 HTTP(S) 패킷을 조회하는 데스크톱 앱. 브라우저 개발자 모드의 네트워크 탭의 기능과 거의 흡사합니다.

차이점은 해당 데스크탑을 프록시 서버로 만들 수 있다는 점.

FIddler4를 켜면 기본적으로 8888번 포트로 프록시 서버가 실행됩니다.

피들러를 켠 후 자기 스마트폰의 네트워크 설정에서 본인 데스크탑을 프록시로 지정하면, 내 스마트폰을 오고가는 HTTP(S) 패킷을 디버깅 할 수 있습니다. 이것이 제가 FIddler4를 쓰는 주 사용처.

한 편, 오고 가는 패킷을 인터셉트하여 커스터마이징 하는 기능도 제공하고 있습니다. 엄청 재밌는 기능이에요.

웹 페이지의 i18n을 테스트할 때 요청의 로케일을 `ko-KR`에서 `ja-JP`로 덮어 씌우는 등... 활용처가 있습니다.

이렇듯 Fidder4 프록시 서버에 JVM 발 요청이 지나다닐 수 있다면, 자바로 구현한 웹 API 클라이언트가 송수신하는 패킷을 들여다 볼 수 있습니다.

JVM 發 요청이 프록시를 거쳐가도록 설정

(링크 글의 내용을 옮겨왔습니다. )

피들러는 프로그램을 켜면 자동으로 8888번 포트에 프록시 서버를 엽니다. 그럼 바로 HTTP 패킷을 엿볼 수 있게 되는데요.

하지만 HTTPS 패킷을 보려면 인증서가 필요하죠?

그럼 일단 인증서를 발행해야하며, 대응되는 키는 클라이언트 쪽에서 보유해야합니다. 이를 위한 절차가 아래에 소개됩니다.

인증서와 키스토어 생성하기

  1. Fiddler4> Tools 메뉴> Options> HTTPS> Actions> Export Root Certificate to Desktop 클릭
  2. CMD 열기
  3. echo %JAVA_HOME%
  4. 자바 홈의 bin 폴더로 이동> keytool 명령어로 키스토어 생성 (1단계에서 만든 인증서 필요함)
  5. keytool.exe -import -file C:\Users\\\Desktop\\FiddlerRoot.cer -keystore FiddlerKeystore -alias Fiddler
  6. 키스토어 비밀번호 설정 후 인증서 신뢰 Y

JVM과 프록시 서버 간 연결 설정

방법1. 실행 옵션 넣어주기

JVM 실행 시에 실행 옵션을 주어 프록시 서버와의 연결을 설정을 할 수 있습니다. 보통 IDE의 실행 환경 설정에서 아래 옵션들을 입력해 주면 됩니다.

-DproxySet=true
-DproxyHost=127.0.0.1
-DproxyPort=8888
-Djavax.net.ssl.trustStore="path\to\java_home\bin\FiddlerKeyStore" 
-Djavax.net.ssl.trustStorePassword="password_used_during_keystore_creation"

방법2. System.property 호출로 넣어주기

자바 코드로 프록시 서버를 설정해 보겠습니다.

실행 옵션을 부여하는 것 대신, 자바 코드에서 System.setProperty() 를 호출하여도 됩니다.

스프링 프레임워크를 사용하는 앱에서 프록시를 설정 빈을 만들어 보겠습니다.

[ProxyEnabler.java] 파일

@Profile("proxy")
@Component
public class ProxyEnabler {

    @PostConstruct
    void enableProxy() {
        Properties props = new Properties();
        try {
            props.load(new ClassPathResource("proxy-config.properties").getInputStream());
            System.setProperties(props);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

스프링 컨텍스트가 부트스트랩 하면서 빈 ProxyEnabler를 생성하게 되면

프로퍼티 파일을 읽어와서 시스템 프로퍼티로 옮기게 됩니다.

그럼 JVM을 오가는 패킷을 Fiddler4가 볼 수 있습니다!

[proxy-config.properties] 파일

이 때, 프로퍼티 파일의 내용은 JVM 실행 옵션과 동일한 것들입니다.

http.proxyHost=127.0.0.1
https.proxyHost=127.0.0.1
http.proxyPort=8888
https.proxyPort=8888
javax.net.ssl.trustStore=C:\\Users\\wnwoq\\.jdks\\azul-1.8.0_275\\bin\\FiddlerKeystore
javax.net.ssl.trustStorePassword=password

설정 끝났습니다

이제 JVM 측이 생성하고 수신하는 HTTP 패킷은 Fiddler4를 거쳐갑니다.

한 번 RestTemplate로 웹 API 요청을 날려 보았습니다.

해당 요청들이 피들러 프록시에 캡쳐되는 것을 확인할 수 있습니다.

image