[Spring] 타 서버에 Request 하는 기능 구현하기

오늘은 Spring 서버에서 python 서버로 요청을 보내는 작업을 할 것이다.

혼자서 협업한다는 가정 하에... 시뮬레이션을 할 것이다!


🚗목차

  • 서버 간에 관계 파악하기

  • Git 브랜치 생성하기

  • Test 준비하기

  • ProductController Mvc 테스트

  • DBTest - Select 테스트

  • Git Merge


🧐서버 간에 관계 파악하기

먼저 서버들 간의 관계를 보자!

  1. 사용자가 검색을 한다.
  2. Spring Server는 DB를 검색 값에 맞추어 조회를 한다.
  3. DB에 데이터가 존재하면 그대로 사용자에게 응답한다. 없으면 Python Server로 Http 요청을 한다.
  4. 요청을 받은 Python Server는 데이터를 수집한다.
  5. Python Server는 수집한 데이터를 DB에 Insert한다.
  6. Insert가 완료되면, Java Server에 Json으로 "check: true"를 보낸다.
  7. Java Server는 다시 DB를 조회하여 사용자에게 데이터를 응답한다.

여기서 나는 DB를 조회하고 데이터가 없을 시, Python Server에 Http 요청을 날리는 작업을 할 것이다.


🧐Git 브랜치 생성하기

가장 먼저 Git 브랜치를 생성하자.

기능 구현이니, "features/request_to_python_server_with_http-ChaYoungHo"로 생성할 것이다.

🌈브랜치 생성하기

develop 브랜치로 가서, 브랜치를 생성하자.

🌈Upstream 설정

🌈git checkout

좋다. 이제 코딩 시작하자.


🧐Test 준비하기

먼저 어떤 것을 테스트를 통해 구현을 할 것인지 살펴보자!

  1. 사용자 요청이 Controller에 올바르게 전달이 되는지 테스트
  2. 사용자의 요청이 DB에서 정상적으로 Select가 되는지 테스트
  3. DB에 데이터의 유무에 따라 다른 로직이 돌아가는지 테스트

🌈test class 생성하기

* Test를 Controller 테스트, Select & Request 테스트 2개로 한다.
* Controller 테스트 : ProductControllerTest.java
* Select & Request 테스트     : DBTest.java

🌈test 정상 실행 확인하기!

*ProductController.java - void queryParameterTest(){}

![](https://media.vlpt.us/images/jkijki12/post/f3f32ac2-2d50-4d03-af59-ba83726576ae/1.PNG)

* DBTest.java - void getData(){}

![](https://media.vlpt.us/images/jkijki12/post/4d251785-d70f-41a8-b2d1-87568cedd0d0/3.PNG)

각각의 테스트를 차례대로 보자!


🧐ProductControllerTest

해당 테스트는 MVC를 구동을 해야한다. 그래서 MockMvc를 이용해보자!

코드는 다음과 같다.


@SpringBootTest
@AutoConfigureMockMvc
public class ProductControllerTest {


    @Autowired // Autowired 없으면 아래에 있는 BeforeEach로 MockMvc를 생성해주어야 한다.
    private MockMvc mockMvc;
    
    
    /*
    @BeforeEach로 MockMvc를 생성할 경우, 
    해당 컨트롤러에 존재하는 @Autowired 객체를 제대로 못읽어온다.
    */
    
    /*
    @BeforeEach //테스트 시작하기 전에 기초작업
    public void before(){  // 보통 before, setup을 메서드명으로 사용한다.

        // mock 컨트롤러를 생성해야한다.
        mockMvc =
                MockMvcBuilders
                .standaloneSetup(ProductController.class) // 테스트를 진행할 Controller를 입력
                .alwaysExpect(status().isOk()) // 필수 조건, 굳이 없어도 될 듯?
                .build();

    }
    */
    
    //본격 테스트 메서드
    @Test
    public void queryParameterTest() throws Exception{

        mockMvc.perform( // 컨트롤러를 호출해야한다.
                MockMvcRequestBuilders  // 보낼 Request를 생성한다.
                .get("/products?query=바나나우유") // get 메서드를 이용한다.
                .accept(MediaType.ALL)) // 모든 타입을 받는다.
                // 여기부터는 받는 값을 테스트한다. status : ok를 예상, 위에서 필수 조건으로 ok 넣어서 무조건 통과할 듯
        .andExpect(status().isOk())
                // 받는 Response의 body에 content를 json으로 고쳐서 받는 값이랑 비교한다.
                .andExpect(content().json("{\"query\":\"바나나우유\"}"));



    }
};

당연히 실패한다. 왜? (실제로는 주석빼서 코드를 제대로 나열해야한다.)
ProductController에 "/pruducts?query=바나나우유"에 매핑된 내용이 없으니까!

  • ProductControlelr에 매핑, 로직을 추가하자.
@RestController
@Slf4j
@RequiredArgsConstructor
public class ProductController {


    //이전 테스트에서 사용했던 Service
    @Autowired
    private final ProductService productService;



    @GetMapping("/products") // 해당 url로 get 매핑한다.
    @ResponseBody // body에 return 값을 입력하여 Response 한다.
    public HashMap<String, String> getProduct(@RequestParam(value = "query", required = false) String query){
        // 쿼리 파라미터로 넘어온 "query"를 @RequestParam으로 받는다.

        HashMap<String, String> map = new HashMap<>();
        map.put("query", query); //map으로 받는다.

        return map; // 그대로 리턴한다. -> jackson이 json으로 수정
    }
};

성공!!!
올바르게 json이 넘어왔다.


🧐DBTest - Select 테스트

이제 받은 검색어에 데이터를 select 하는 것을 테스트 할 것이다.
조금 더 알아야할 상황은 다음과 같다.

검색어를 받아서 DB에서 Select 한다.
Select를 했을 때, 데이터가 있으면 list는 NULL이 아니다.
Select를 했을 때, 데이터가 없으면 Http 메세지를 생성하여 타서버로
해당 검색어를 넘긴다. 그리고 타서버에서 {"check":"true"}를 응답한다.
(타 서버는 이미 만들어 놨음)-> 타 서버 생성 글

DBTest.java에 메서드를 생성하여 모든 것이 존재한다는 가정 하에 코드를 작성해보자!

🌈테스트 만들기

DBTest.java 생성 및 테스트 구현

@SpringBootTest
public class DBTest {

    @Autowired
    private ProductService productService;

    @Test
    public void getData(){

        //given
        String query = "바나나우유";

        List<Product> list = productService.findByDataType(query);

        //when

        if(list.isEmpty()){
            CommunicatePython communicatePython = new CommunicatePython();
            HashMap<String, String> map = communicatePython.createHttpRequestAndSend(query);
            Assertions.assertEquals("true", result);
        }else{
            Assertions.assertNotNull(list);
        }
    }

};

테스트를 살펴보자.
1. 검색어 query가 주어진다.
2. 검색어로 DB에서 Select를 하고, list에 담는다.
3. list가 NULL이면, Python 서버에 DB INSERT 요청을한다.
4. Python 서버가 INSERT를 완료하면 { "check":"true" }를 json으로 응답한다.
5. assertEquals로 "true"를 테스트한다.
6. list가 NULL이 아니면, assertNotNull로 list가 null이 아닌지 테스트한다.

given, when, then이 익숙하지 않다.. (국어를 못한다 난ㅜ)

당연히 실패한다. 에러의 이유는 Symbol이다.
그럼 필요한 객체들을 작성하러가자!!

🌈Go To Symbol 생성

🔎ProductService.java 수정

@Service
@RequiredArgsConstructor
public class ProductService {

    @Autowired
    private final ProductRepository productRepository;

    public List<Product> findByDataType(){
        List<Product> productList = productRepository.findByDataType();

        return productList;
    }
};

🔎CommunicatePython.java 생성 및 구현


public class CommunicatePython {

    public void  createHttpRequestAndSend(String query){

        //필요한 객체 생성
        RestTemplate restTemplate = new RestTemplate();

        //body 생성
        MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
        params.add("query", query);

        //header 생성

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
		
        // header, body 합치기
        HttpEntity<MultiValueMap<String,String>> entity = new HttpEntity<>(params, headers);
		
        // POST 요청 보내고, 응답값을 JSONObject로 받기
        JSONObject jsonObject = new JSONObject(restTemplate.postForObject("http://python서버 ip:포트번호/get_data", entity, String.class));
        
        // Json 파싱
        String result = jsonObject.getString("check");
        return result;
        
    }

};

HTTP를 생성하고 Python서버에 보낸다. 그리고 응답값을 받는다.

테스트를 해보자!

에러가 터진다.

당연하다.
Service에 존재하는 Repository에서 findByDataType() 메서드가 존재하지 않기 때문이다!!

그럼 Repository에 findByDataType()을 생성하러가자!

🔎ProductRepository.java 수정


@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {

    List<Product> findByDataType(String data_type);
};

약간의 에러가 있어서 메소드 명을 전부 수정했다.
Data_type -> DataType

Spring Data JPA를 참고했음!

다시 테스트 해보자.

잉? 에러가 난다.. 이유는?

해당 Request가 거절되었다.
타 서버를 안키고 돌렸다. 터 서버 실행하러 가즈앙

서버 실행해주고, 테스트!

성공 ㅋ

타 서버에도 query parameter가 올바르게 넘어갔다.

🔎데이터가 있는 경우

지금 mysql에 "녹차"라는 데이터는 존재한다.

test에 query를 수정해서 다시 한번 테스트하자!

테스트 통과!


🧐Git Merge

기능 구현이 완료되었다.

커밋, 푸쉬를 하고

develop으로 이동

git checkout develop

merge

git merge features/request_to_python_server_with_http-ChaYoungHo

merge 완료


📋마치며

서버 두 개를 테스트하면서 많은 에러와 만났다. 겪은 에러는 다음과 같다.

  • MockMvc의 Contructor 문제
  • Spring Data Jpa - Naming 문제(Camel -> Snake)

해당 에러에 대한 글을 올리도록 하자!!


🧷Reference

좋은 웹페이지 즐겨찾기