Spring

연습용 주문 서버 만들기 03 메뉴 목록 조회 API

voider 2023. 1. 17. 20:45

연습용 주문 서버 만들기

등록 API는 따로 만들지 않고, 메뉴가 고정되어 있다고 가정하겠습니다.

  • 메뉴 조회는 누구나 가능하다.
  • 프로젝트 실행 시 기본 메뉴 생성

역시 매우 간단한 기능이므로 딱히 부연 설명할 게 없습니다.

메뉴 조회 API Controller

@RestController  
@RequestMapping(V1_API_PREFIX)  
class MenuViewApiController(  
    private val menuService: MenuService  
) {  

    @GetMapping("/menu")  
    fun getAllMenu(): ResponseEntity<List<MenuViewResponse>> {  
        val allMenu = menuService.getAllMenu();  

        return ResponseEntity(allMenu, HttpStatus.OK)  
    }  
}

메뉴 조회 GET 요청을 받으면 MenuService에서 모든 메뉴를 불러옵니다.

메뉴 조회 ResponseDTO

data class MenuViewResponse(  
    val id: Long,  
    val menu: String,  
    val price: Int  
) {  
    companion object {  
        internal fun of(menu: Menu) = MenuViewResponse(  
            id = menu.id,  
            menu = menu.menu,  
            price = menu.price  
        )  
    }  
}

반환은 이런 형태로 하게됩니다.

메뉴 조회 Service

interface MenuService {  

    fun getAllMenu(): List<MenuViewResponse>  

}

@Service  
internal class MenuDefaultService(  
    private val menuViewService: MenuViewService  
): MenuService{  

    @Transactional(readOnly = true)  
    override fun getAllMenu(): List<MenuViewResponse> {  
        return menuViewService.getAllMenu()  
    }  

}

@Service  
internal class MenuViewService(  
    private val menuRepository: MenuRepository  
) {  

    @Transactional(readOnly = true)  
    fun getAllMenu(): List<MenuViewResponse> {  
        return menuRepository.findAll()  
            .map { MenuViewResponse.of(it) }  
    }  
}
  • 읽기만 하기 때문에 트랜잭션을 읽기 전용으로 선언했습니다.

웹 레이어와 비즈니스 레이어를 분리하고 반드시 인터페이스를 통해서만 접근하도록 설계했습니다.
웹 계층에서는 비즈니스 계층에 있는 구현체를 참조할 수 없습니다. 따라서 구현체를 좀더 자유롭게 변경할 수 있습니다.

Repository/Entity

@Entity  
internal class Menu(  
    menu: String,  
    price: Int  
) {  

    @Id  
    @GeneratedValue(strategy = GenerationType.IDENTITY)  
    val id: Long = 0  

    val menu: String = menu  

    val price: Int = price  

    val createdAt: LocalDateTime = LocalDateTime.now()  
}

internal interface MenuRepository: JpaRepository<Menu, Long> {  
}

엔티티는 최대한 간단하게 메뉴 이름과 가격으로만 구성했습니다.
Id를 기본값을 0을 준 이유는 kotlin 문법을 우회하기 위해서입니다. JPA는 기본값이 설정되어 있어도 (어떻게 그러는지는 모르겠지만) Repository에 save할 때 id를 초기화합니다.

서버 실행 시 기본 메뉴 생성

@Component  
internal class MenuInitializer(  
    private val menuRepository: MenuRepository  
) {  

    @PostConstruct  
    fun init() {  
        if (menuRepository.count() > 0) return  

        val menuList = listOf(  
            Menu("불고기 샌드위치", 8000),  
            Menu("닭가슴살 샌드위치", 8000),  
            Menu("함박 샌드위치", 8000),  
            Menu("에그마요 샌드위치", 6000),  
            Menu("햄참치 샌드위치", 7000),  
            Menu("장조림 라이스 샐러드", 10000),  
            Menu("잠봉 라이스 샐러드", 10000),  
            Menu("연어 샐러드", 12000),  
            Menu("아메리카노", 3000),  
            Menu("카페라떼", 4000),  
            Menu("히비스커스티", 3000),  
            Menu("탄산음료", 2000),  
            Menu("생수", 1000),  
        )  

        menuRepository.saveAll(menuList)  

    }  
}

나중에 어떤 메뉴를 사면 음료를 할인해주는 정책 같은 것을 추가해봐도 될 것 같습니다.

Test

@AutoConfigureMockMvc  
@SpringBootTest  
internal class MenuViewApiControllerTest {  

    @Autowired  
    lateinit var mockMvc: MockMvc  

    @Test  
    fun `메뉴 목록 조회 200 OK`() {  
        mockMvc.get("$V1_API_PREFIX/menu")  
            .andExpect {  
                status { isOk() }  
                content {  
                    contentType(MediaType.APPLICATION_JSON)  
                }  
            }            .andDo { print() }  
    }  
}

사실 로직이라고 할 것이 마땅치 않아서 간단히 호출이 되고, 원하는 응답이 돌아오는지 정도만 확인했습니다.