λ³Έλ¬Έ λ°”λ‘œκ°€κΈ°
Spring Boot/μŠ€ν”„λ§ λΆ€νŠΈμ™€ AWS둜 혼자 κ΅¬ν˜„ν•˜λŠ” μ›Ή μ„œλΉ„μŠ€

2μž₯: ν…ŒμŠ€νŠΈ μ½”λ“œ μž‘μ„±ν•˜κΈ°

by oliviarla 2022. 6. 13.

Table of Contents

     

    ν…ŒμŠ€νŠΈ μ½”λ“œλž€

    TDD

    ν…ŒμŠ€νŠΈ μ½”λ“œλ₯Ό λ¨Όμ € μž‘μ„±ν•˜λŠ” ν…ŒμŠ€νŠΈ 주도 개발

    1. 항상 μ‹€νŒ¨ν•˜λŠ” ν…ŒμŠ€νŠΈ μ½”λ“œ μž‘μ„±

    2. ν…ŒμŠ€νŠΈκ°€ ν†΅κ³Όν•˜λŠ” ν”„λ‘œλ•μ…˜ μ½”λ“œ μž‘μ„±

    3. ν…ŒμŠ€νŠΈκ°€ ν†΅κ³Όν•˜λ©΄ ν”„λ‘œλ•μ…˜ μ½”λ“œ λ¦¬νŒ©ν† λ§

     

    μ΄λ ‡κ²Œ μ„Έ 단계λ₯Ό λŒμ•„κ°€λ©΄μ„œ κ°œλ°œν•˜λŠ” 과정을 κ±°μΉœλ‹€.

     

    λ‹¨μœ„ν…ŒμŠ€νŠΈ

    - κΈ°λŠ₯ λ‹¨μœ„μ˜ ν…ŒμŠ€νŠΈ μ½”λ“œλ₯Ό μž‘μ„±ν•˜λŠ” 것

    - ν•„μš”μ„±

    •  κ°œλ°œ 단계 μ΄ˆκΈ°μ— 문제 λ°œκ²¬ν•˜λ„λ‘ λ„μ™€μ€Œ
    • μΆ”ν›„ μ½”λ“œ λ¦¬νŒ©ν† λ§, 라이브러리 μ—…κ·Έλ ˆμ΄λ“œ λ“±μ—μ„œ κΈ°μ‘΄ κΈ°λŠ₯의 λ™μž‘μ„ 확인 κ°€λŠ₯
    • κΈ°λŠ₯에 λŒ€ν•œ λΆˆν™•μ‹€μ„± κ°μ†Œμ‹œν‚΄
    • μ‹œμŠ€ν…œμ— λŒ€ν•œ μ‹€μ œ λ¬Έμ„œλ‘œ 제곡 κ°€λŠ₯

    - μžλ°”μ—μ„œλŠ” JUnitμ΄λΌλŠ” ν…ŒμŠ€νŠΈ ν”„λ ˆμž„μ›Œν¬κ°€ μ‚¬μš©λ¨

    Controller μž‘μ„±

    일반적으둜 νŒ¨ν‚€μ§€λͺ…은 μ›Ή μ‚¬μ΄νŠΈ μ£Όμ†Œμ˜ μ—­μˆœμœΌλ‘œ μ„€μ •

    메인 클래슀

    package com.oliviarla.springboot;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
    
    @SpringBootApplication
    public class Application {
        public static void main(String[] args) {
            SpringApplication.run(Application.class, args);
        }
    }
    @SpringBootApplication
    - μŠ€ν”„λ§ λΆ€νŠΈμ˜ μžλ™ μ„€μ •, μŠ€ν”„λ§ Bean 읽기 및 생성을 μžλ™μœΌλ‘œ 섀정함
    - 메인 ν΄λž˜μŠ€κ°€ μžˆλŠ” μœ„μΉ˜λΆ€ν„° 섀정을 μ½μ–΄λ‚˜κ°€λ―€λ‘œ ν”„λ‘œμ νŠΈ μ΅œμƒλ‹¨μ— μœ„μΉ˜ν•΄μ•Όν•¨
    λ‚΄μž₯ WAS
    - λ³„λ„λ‘œ 외뢀에 WASλ₯Ό 두지 μ•Šκ³  μ• ν”Œλ¦¬μΌ€μ΄μ…˜ μ‹€ν–‰ μ‹œ λ‚΄λΆ€μ—μ„œ 싀행됨
    - μ–Έμ œ μ–΄λ””μ„œλ‚˜ 같은 ν™˜κ²½μ—μ„œ μŠ€ν”„λ§ λΆ€νŠΈ 배포 κ°€λŠ₯
    - μ™Έμž₯ WAS μ‚¬μš©ν•  경우 λͺ¨λ“  μ„œλ²„μ˜ WAS μ’…λ₯˜, 버전, 섀정을 μΌμΉ˜μ‹œμΌœμ•Ό ν•˜λ―€λ‘œ λ³΅μž‘ν•œ μž‘μ—… μ†Œμš”λ¨ -> λ‚΄μž₯ WAS둜 ν•΄κ²° κ°€λŠ₯

     

    컨트둀러 클래슀

    package com.oliviarla.springboot.web;
    
    import com.oliviarla.springboot.web.dto.HelloResponseDto;
    import lombok.RequiredArgsConstructor;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class HelloController {
    
        @GetMapping("/hello")
        public String hello(){
            return "hello";
        }
    }
    @RestController
    - JSON을 λ°˜ν™˜ν•˜λŠ” 컨트둀러둜 μ„€μ •
    @GetMapping
    - HTTP Get λ©”μ†Œλ“œμ˜ μš”μ²­μ„ 받을 수 μžˆλŠ” API λ©”μ†Œλ“œλ₯Ό λ§Œλ“€μ–΄ μ£ΌλŠ” μ–΄λ…Έν…Œμ΄μ…˜
    - localhost:8080/hello λΌλŠ” url둜 μš”μ²­μ΄ μ˜¨λ‹€λ©΄ "hello"λ₯Ό λ°˜ν™˜ν•˜λŠ” κΈ°λŠ₯을 μ œκ³΅ν•¨

     

    ν…ŒμŠ€νŠΈ 클래슀

    @RunWith(SpringRunner.class)
    @WebMvcTest(controllers = HelloController.class)
    public class HelloControllerTest {
    
        @Autowired //μŠ€ν”„λ§μ΄ κ΄€λ¦¬ν•˜λŠ” Bean μ£Όμž…λ°›μŒ
        private MockMvc mvc;
    
        @Test
        public void return_hello() throws Exception{
            String hello = "hello";
    
            mvc.perform(get("/hello"))
                    .andExpect(status().isOk())
                    .andExpect(content().string(hello));
        }
    }

    MockMvc 클래슀λ₯Ό μ‚¬μš©ν•΄ μ›Ή APIλ₯Ό ν…ŒμŠ€νŠΈν•˜λŠ” μ½”λ“œ

    @WebMvcTest: Web에 μ§‘μ€‘ν•˜μ—¬ ν…ŒμŠ€νŠΈ ν•  수 μžˆλŠ” μ–΄λ…Έν…Œμ΄μ…˜
    mvc.perform λ©”μ†Œλ“œμ—μ„œλŠ”...
    - HTTP Header의 statusκ°€ 200인지 확인
    - Hello μ»¨νŠΈλ‘€λŸ¬μ—μ„œ λ°˜ν™˜λœ contentκ°€ "hello"와 μΌμΉ˜ν•˜λŠ”μ§€ 확인

     

    λ¨Όμ € ν…ŒμŠ€νŠΈμ½”λ“œλ‘œ κ²€μ¦ν•œ ν›„ μ‹ λ’°κ°€ κ°€μ§€ μ•ŠλŠ”λ‹€λ©΄ ν”„λ‘œμ νŠΈλ₯Ό μ‹€ν–‰ν•΄ ν™•μΈν•˜λŠ” λ°©ν–₯으둜 κ°œλ°œν•΄μ•Ό 함!

    Lombok

    μžλ°” 개발 μ‹œ 자주 μ‚¬μš©ν•˜λŠ” Getter, Setter, κΈ°λ³Έ μƒμ„±μž, toString 등을 μ–΄λ…Έν…Œμ΄μ…˜μ„ 톡해 μžλ™ μƒμ„±ν•΄μ€Œ

     

    build.gradle에 μ˜μ‘΄μ„± μΆ”κ°€

    implementation 'org.projectlombok:lombok'

     

    +) 둬볡 ν”ŒλŸ¬κ·ΈμΈ μ„€μΉ˜λ₯Ό ν•  수 μžˆμœΌλ‚˜ ν”„λ‘œμ νŠΈ 진행해봀을 λ•Œ μ„€μΉ˜ν•˜μ§€ μ•Šμ•„λ„ λΆˆνŽΈν•¨ 없이 μ§„ν–‰λ˜μ—ˆμœΌλ―€λ‘œ μƒλž΅ν•©λ‹ˆλ‹€ πŸ™‚

     

    Lombok ν™œμš©ν•˜κΈ°

    둬볡을 μ μš©ν•΄ 클래슀λ₯Ό 생성해본닀.

     

    DTO 클래슀

    package com.oliviarla.springboot.web.dto;
    
    import lombok.Getter;
    import lombok.RequiredArgsConstructor;
    
    @Getter
    @RequiredArgsConstructor
    public class HelloResponseDto {
        private final String name;
        private final int amount;
    }
    @Getter: μ„ μ–Έλœ λͺ¨λ“  ν•„λ“œμ˜ get λ©”μ†Œλ“œ 생성
    @RequiredArgsConstructor: μ„ μ–Έλœ λͺ¨λ“  final ν•„λ“œκ°€ ν¬ν•¨λœ μƒμ„±μž 생성 (final μ—†λŠ” ν•„λ“œλŠ” ν¬ν•¨λ˜μ§€ μ•ŠμŒ)

     

    컨트둀러 클래슀 μˆ˜μ •

    package com.oliviarla.springboot.web;
    
    import com.oliviarla.springboot.web.dto.HelloResponseDto;
    import lombok.RequiredArgsConstructor;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class HelloController {
    
        @GetMapping("/hello")
        public String hello(){
            return "hello";
        }
    
        @GetMapping("/hello/dto")
        public HelloResponseDto helloResponseDto(@RequestParam("name") String name, @RequestParam("amount") int amount){
            return new HelloResponseDto(name, amount);
        }
    }

    νŒŒλΌλ―Έν„° κ°’μœΌλ‘œ μž…λ ₯된 name, amount 값을 μ‚¬μš©ν•œ HelloResponseDto 객체λ₯Ό 생성해 λ°˜ν™˜

    ex) "localhost:8080/hello/dto?name=kim&amount=1" λΌλŠ” URL μš”μ²­ μ‹œ HelloResponseDto(kim,1) μ΄λΌλŠ” 객체가 λ°˜ν™˜λ¨

     

     

    ν…ŒμŠ€νŠΈ ν΄λž˜μŠ€μ— λ©”μ†Œλ“œ μΆ”κ°€

    @Test
        public void return_helloDto() throws Exception{
            String name = "hello";
            int amount = 1000;
    
            mvc.perform(get("/hello/dto")
                    .param("name", name)
                    .param("amount", String.valueOf(amount)))
                    .andExpect(status().isOk())
                    .andExpect(jsonPath("$.name", is(name)))
                    .andExpect(jsonPath("$.amount", is(amount)));
        }

    νŒŒλΌλ―Έν„° κ°’μœΌλ‘œ nameκ³Ό amount λ³€μˆ˜μ˜ 값을 μ „λ‹¬ν•˜κ³ , GET으둜 얻은 JSON 응닡 κ°’κ³Ό μΌμΉ˜ν•˜λ©΄ ν…ŒμŠ€νŠΈκ°€ ν†΅κ³Όν•˜κ²Œ λœλ‹€.

     

    MVC의 param 값은 String만 ν—ˆμš©λ˜λ―€λ‘œ int, date ν˜•μ‹μ„ μ‚¬μš©ν•˜λ €λ©΄ String으둜 ν˜•λ³€ν™˜ ν›„ μ‚¬μš©ν•΄μ•Ό 함.

     

     

    마치며

    보톡 μžλ°”μ—μ„œ ν…ŒμŠ€νŠΈ μ½”λ“œλ₯Ό μž‘μ„±ν•  λ•Œ Assertj의 AssertThat λ©”μ†Œλ“œλ₯Ό 많이 μ‚¬μš©ν•˜μ§€λ§Œ, μŠ€ν”„λ§ λΆ€νŠΈμ—μ„œ ν…ŒμŠ€νŠΈ μ½”λ“œλ₯Ό μž‘μ„±ν•  λ•ŒλŠ” API ν…ŒμŠ€νŠΈμ— μ ν•©ν•œ MockMvc의 λ©”μ†Œλ“œλ₯Ό μ‚¬μš©ν•˜μ—¬ νŽΈλ¦¬ν•˜κ²Œ λ‹¨μœ„ν…ŒμŠ€νŠΈλ₯Ό μ§„ν–‰ν•  수 μžˆλ‹€!

     

     

    λŒ“κΈ€