스프링부트 멀티모듈 구성하기(3) - API 모듈 작성하기
module-api에서module-core를 잘 호출하는지 확인 할 수 있는 간단한 API를 만들어보자module-core에서Customer와Order엔티티를 선언했는데, 이에 맞게module-api에서는Customer와Order읽어오기 / 쓰기 API를 만들어 보자.- 전체 적인 구조를 보았을 때, 나중에 가서는 역할이 달라 질 수 있겠지만(활용하기 나름…)
Customer와Order를 간단히 사용 해 보자
API Module 만들기 1 - 의존성 추가
- 나의
module-api의build.gradle.kts에는 아래와 같이 비어있다.plugins{ } dependencies{ } module-api에서 혹시 다른 디펜던시를 사용하고싶다면,build.gradle.kts의dependencies에 추가 해 주자
API Module 만둘기 2 - 패키지 세팅하기
module-core에서 말했듯이, 멀티모듈을 사용하려면 base package 이름이 같아야한다.com.wool패키지를 생성하고, 하위에 사용 할 패키지들을 따로 생성 해 주자- (패키지는 아니지만…)
ModuleApiApplication.kt를 생성하고 스프링 부트 어플리케이션을 선언 해 주자package com.wool import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication @SpringBootApplication class ModuleApiApplication fun main(args: Array<String>) { runApplication<ModuleApiApplication>(*args) } com.wool.controller: API Controller, 외부 Request를 받아 Response 해주는 역할com.wool.repository: API에서 사용 할 Repository,module-core에서 선언한 엔티티를 불러와서 Repository에서 사용com.wool.service: API에서 사용 할 Service,repository에서 불러온 데이터를 가공하여controller에게 전달
- (패키지는 아니지만…)
- 이 외에도 각각의 dto들이 있을 수 있다. 이건, 생성하면서 같이 보기로 하자.
API Module 만들기 3 - Repository 만들기
com.wool.repository패키지를 생성하고,ModuleCoreCustomerRepository.kt와ModuleCoreOrderRepository.kt를 생성하자- 나는
module-core에서 엔티티가 왔다는 것을 기억하고싶어서, 레포지토리 이름 앞에ModuleCore를 붙여주었다. - 신기한점은,
module-core에서 선언한 엔티티를module-api에서 사용 할 수 있다는 것이다. module-core라는 모듈명이 붙지 않아도, 그대로 사용이 가능하다.
Repository 만들기 1 - ModuleCoreCustomerRepository.kt
- JPA를 사용하기 위해
JpaRepository를 상속받는다.// com.wool.repository.ModuleCoreCustomerRepository.kt package com.wool.repository import com.wool.entity.Customer import org.springframework.data.jpa.repository.JpaRepository interface ModuleCoreCustomerRepository : JpaRepository<Customer, Long> { }
Repository 만들기 2 - ModuleCoreOrderRepository.kt
- 마찬가지로 JPA를 사용하기때문에
JPARepository를 상속받는다.package com.wool.repository import com.wool.entity.Customer import org.springframework.data.jpa.repository.JpaRepository interface ModuleCoreCustomerRepository:JpaRepository<Customer, Long> { }
API Module 만들기 4 - Service 만들기
- 이제 방금 만든
Repository를 주입해서 사용 할Service를 만들어보자 service패키지에는,Controller에서 들어온 데이터를 가지고Repository와 소통 해 주어야 하기 때문에dto를 만들어준다customer는CustomerDto를,order는OrderDto와OrderRequestDto를 만들어준다OrderRequestDto: 컨트롤러에서 받아주는 데이터 형태.OrderDto에 매핑되어있는Customer작업을 위해customer_id를 만들어 준다OrderDto:OrderRequestDto에서 받아온 customer 데이터를 가지고,Customer를 찾아서Order에 매핑해주는 역할을 한다

Service Dto 만들기 1 - CustomerDto.kt
- Customer 데이터를 엔티티로 만들어주는 역할을 하는 data클래스이다
package com.wool.service.dtos.customer import com.wool.entity.Customer data class CustomerDto( val customerNickName: String, val customerAddress: String, ) { fun toEntity() = Customer( customerNickName = this.customerNickName, customerAddress = this.customerAddress ) }toEntity():CustomerDto를Customer엔티티로 만들어주는 역할을 한다
Service Dto 만들기 2 - OrderRequestDto.kt
- Controller에서 데이터를 받아오는
OrderRequestDto.kt를 만들어준다package com.wool.service.dtos.order data class OrderRequestDto( val orderStoreName: String, val orderStoreAddress: String, val orderItem: String, val orderPrice: Int, val customerId: Long, )customerId:OrderRequestDto에서 추출해CustomerRepository를 통해Customer를 찾기 위한 값
Service Dto 만들기 3 - OrderDto.kt
OrderRequestDto를 통해Customer객체를 받아 온 후에OrderDto에 매핑되어 값을 받아오도록 하는 data클래스package com.wool.service.dtos.order import com.wool.entity.Customer import com.wool.entity.Order data class OrderDto( val orderStoreName: String, val orderStoreAddress: String, val orderItem: String, val orderPrice: Int, val customer: Customer, ){ fun toEntity() = Order( orderStoreName = this.orderStoreName, orderStoreAddress = this.orderStoreAddress, orderItem = this.orderItem, orderPrice = this.orderPrice, customer = this.customer ) }
Service 만들기 1 - CustomerService.kt
CusomterDto와 함께ModuleCoreCustomerRepository에서 통신한 데이터를 조합하고 Controller로 넘겨주는 역할package com.wool.service import com.wool.repository.ModuleCoreCustomerRepository import com.wool.service.dtos.customer.CustomerDto import org.springframework.stereotype.Service @Service class CustomerService( private val customerRepository: ModuleCoreCustomerRepository ) { fun getCustomers() = customerRepository.findAll() fun saveCustomers(customerDto: CustomerDto) { this.customerRepository.save(customerDto.toEntity()) } }getCustomers():ModuleCoreCustomerRepository에서findAll()을 통해 모든Customer를 가져온다saveCustomers():CustomerDto를Customer엔티티로 만들어ModuleCoreCustomerRepository에 저장한다
Service 만들기 2 - OrderService.kt
Controller에서 받아온OrderRequestDto를 통해Customer를 찾아OrderDto에 매핑하여 값을 불러오고 저장하는 역할package com.wool.service import com.wool.entity.Customer import com.wool.repository.ModuleCoreCustomerRepository import com.wool.repository.ModuleCoreOrderRepository import com.wool.service.dtos.order.OrderRequestDto import com.wool.service.dtos.order.OrderDto import org.springframework.stereotype.Service @Service class OrderService( private val orderRepository: ModuleCoreOrderRepository, private val customerRepository: ModuleCoreCustomerRepository ) { fun getOrders() = orderRepository.findAll() fun saveOrder(orderRequestDto: OrderRequestDto) { val customer: Customer = customerRepository.findById(orderRequestDto.customerId).get() //customer가 있을 경우 if (customer != null) { val orderDto = OrderDto( orderStoreName = orderRequestDto.orderStoreName, orderStoreAddress = orderRequestDto.orderStoreAddress, orderItem = orderRequestDto.orderItem, orderPrice = orderRequestDto.orderPrice, customer = customer ) orderRepository.save(orderDto.toEntity()) } else { //customer가 없을 경우 throw Exception("customer가 없습니다.") } } }getOrders():ModuleCoreOrderRepository에서findAll()을 통해 모든Order를 가져온다saveOrder():OrderRequestDto를 통해Customer를 찾아OrderDto에 매핑하여 값을 불러오고 저장한다
API Module 만들기 5 - Controller
- 외부의 요청을 받아 줄 수 있는
Controller를 만든다 - 자세하게 만든 기능이 아니기 때문에, 컨트롤러는 Dto 설정 외에 크게 볼 것이 없는 것 같다

Controller 만들기 1 - CustomerController.kt
package com.wool.controller
import com.wool.service.CustomerService
import com.wool.service.OrderService
import com.wool.service.dtos.customer.CustomerDto
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RestController
@RestController
class CustomerController(
private val customerService: CustomerService
) {
@PostMapping("/customers")
fun saveCustomers(@RequestBody customerDto: CustomerDto) {
return this.customerService.saveCustomers(customerDto)
}
@GetMapping("/customers")
fun getCustomers() = this.customerService.getCustomers()
}
saveCustomers():CustomerDto를CustomerService에 넘겨주어 저장한다getCustomers():CustomerService에서Customer를 가져와 반환한다
Controller 만들기 2 - OrderController.kt
package com.wool.controller
import com.wool.service.OrderService
import com.wool.service.dtos.order.OrderRequestDto
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RestController
@RestController
class OrderController(
private val orderService: OrderService
) {
@GetMapping("/orders")
fun getOrders() = orderService.getOrders()
@PostMapping("/orders")
fun saveOrders(@RequestBody orderRequestDto: OrderRequestDto) {
//save order
return orderService.saveOrder(orderRequestDto)
}
}
getOrders():OrderService에서Order를 가져와 반환한다saveOrders():OrderRequestDto를OrderService에 넘겨주어 저장한다
API Module 설정추가 - application.yml
- 서버를 동작시키는 여러 설정값들은
application.yml에서 관리하자 application.yml에 적은 값은, docker-compose로 실행한 postgresql 정보를 가져와 작성했다resources패키지 아래에application.yml을 만들고 아래와 같이 적는다spring: datasource: hikari: pool-name: HikariCp maximum-pool-size: 2 minimum-idle: 2 username: wool password: password1234 url: jdbc:postgresql://localhost:9876/wooldb driver-class-name: org.postgresql.Driver jpa: hibernate: ddl-auto: update show-sql: true properties: hibernate: format_sql: true default_schema: springtest jackson: serialization: fail-on-empty-beans: false
API Module 마무리
- 이번 글에서는
module-core에 작성한 엔티티를 가져와module-api에서 사용 해 보았다 - 실제로 서버를 실행해서 테스트 해 보면 데이터 조회 / 저장 이 잘 된다
ModuleApiApplication.kt를 실행 한 후 아래의 API 테스트의 데이터로 테스트한다
API 테스트하기
Customer 조회
- URL :
http://localhost:8080/customers - Method :
GET - Response
[ { "customerId": 1, "customerNickName": "올리버", "customerAddress": "서울특별시에서 살고싶음", "createdAt": "2022-11-20T00:00:00", "updatedAt": "2022-11-20T00:00:00" }, { "customerId": 2, "customerNickName": "미래", "customerAddress": "정자동에서 사는 성공한 삶", "createdAt": "2022-11-21T00:00:00", "updatedAt": "2022-11-21T00:00:00" } ]
Customer 생성
- URL :
http://localhost:8080/customers - Method :
POST - Body :
application/json{ "customerNickName": "미래", "customerAddress": "정자동에서 사는 성공한 삶" }
Order 조회
- URL :
http://localhost:8080/orders - Method :
GET - Response
[ { "orderId": 2, "orderUUID": "014e018a-3391-4c54-a913-3230e65a0013", "orderStoreName": "얌얌김밥", "orderStoreAddress": "서울특별시 얌얌로 김밥동", "orderItem": "불고기참치김밥", "orderPrice": 6500, "customer": { "customerId": 1, "customerNickName": "올리버", "customerAddress": "서울특별시에서 살고싶음", "createdAt": "2022-11-20T00:00:00", "updatedAt": "2022-11-20T00:00:00" }, "createdAt": "2022-11-21T00:00:00", "updatedAt": "2022-11-21T00:00:00" }, { "orderId": 3, "orderUUID": "caf72de8-7174-48d9-9e06-d5665ca2225e", "orderStoreName": "얌얌김밥", "orderStoreAddress": "서울특별시 얌얌로 김밥동", "orderItem": "불고기참치김밥", "orderPrice": 6500, "customer": { "customerId": 2, "customerNickName": "미래", "customerAddress": "정자동에서 사는 성공한 삶", "createdAt": "2022-11-21T00:00:00", "updatedAt": "2022-11-21T00:00:00" }, "createdAt": "2022-11-21T00:00:00", "updatedAt": "2022-11-21T00:00:00" } ]
Order 생성
- URL :
http://localhost:8080/orders - Method :
POST - Body :
application/json{ "orderStoreName": "얌얌김밥", "orderStoreAddress": "서울특별시 얌얌로 김밥동", "orderItem": "불고기참치김밥", "orderPrice": 6500, "customerId": 2 }
- Let’s create a simple API to verify that
module-apiproperly callsmodule-core - Since we declared
CustomerandOrderentities inmodule-core, let’s create read/write APIs forCustomerandOrderinmodule-apiaccordingly. - Looking at the overall structure, roles might change later (depending on usage…), but let’s simply use
CustomerandOrder
Creating API Module 1 - Adding Dependencies
- My
module-api’sbuild.gradle.ktsis empty as shown below.plugins{ } dependencies{ } - If you want to use other dependencies in
module-api, add them todependenciesinbuild.gradle.kts
Creating API Module 2 - Package Setup
- As mentioned in
module-core, to use multi-module, the base package name must be the same. - Create the
com.woolpackage and create the packages to be used underneath- (Not a package, but…) Create
ModuleApiApplication.ktand declare the Spring Boot applicationpackage com.wool import org.springframework.boot.autoconfigure.SpringBootApplication import org.springframework.boot.runApplication @SpringBootApplication class ModuleApiApplication fun main(args: Array<String>) { runApplication<ModuleApiApplication>(*args) } com.wool.controller: API Controller, responsible for receiving external Requests and sending Responsescom.wool.repository: Repository used by API, imports entities declared inmodule-corefor use in Repositorycom.wool.service: Service used by API, processes data fromrepositoryand passes it tocontroller
- (Not a package, but…) Create
- There may also be various DTOs. Let’s look at these as we create them.
Creating API Module 3 - Creating Repository
- Create the
com.wool.repositorypackage, and createModuleCoreCustomerRepository.ktandModuleCoreOrderRepository.kt - I prefixed the repository names with
ModuleCoreto remember that the entities came frommodule-core. - The interesting thing is that entities declared in
module-corecan be used inmodule-api. - They can be used as-is without the
module-coremodule name attached. 
Creating Repository 1 - ModuleCoreCustomerRepository.kt
- Inherit from
JpaRepositoryto use JPA.// com.wool.repository.ModuleCoreCustomerRepository.kt package com.wool.repository import com.wool.entity.Customer import org.springframework.data.jpa.repository.JpaRepository interface ModuleCoreCustomerRepository : JpaRepository<Customer, Long> { }
Creating Repository 2 - ModuleCoreOrderRepository.kt
- Similarly, inherit from
JPARepositorysince we’re using JPA.package com.wool.repository import com.wool.entity.Customer import org.springframework.data.jpa.repository.JpaRepository interface ModuleCoreCustomerRepository:JpaRepository<Customer, Long> { }
Creating API Module 4 - Creating Service
- Now let’s create a
Servicethat will inject and use theRepositorywe just created - In the
servicepackage, we createdtos because we need to communicate withRepositoryusing data fromController customergetsCustomerDto,ordergetsOrderDtoandOrderRequestDtoOrderRequestDto: The data format received from the controller. Createscustomer_idforCustomeroperations mapped toOrderDtoOrderDto: A data class that receives customer data fromOrderRequestDto, findsCustomer, and maps it toOrder

Creating Service Dto 1 - CustomerDto.kt
- A data class responsible for converting Customer data to an entity
package com.wool.service.dtos.customer import com.wool.entity.Customer data class CustomerDto( val customerNickName: String, val customerAddress: String, ) { fun toEntity() = Customer( customerNickName = this.customerNickName, customerAddress = this.customerAddress ) }toEntity(): ConvertsCustomerDtoto aCustomerentity
Creating Service Dto 2 - OrderRequestDto.kt
- Create
OrderRequestDto.ktthat receives data from Controllerpackage com.wool.service.dtos.order data class OrderRequestDto( val orderStoreName: String, val orderStoreAddress: String, val orderItem: String, val orderPrice: Int, val customerId: Long, )customerId: Value extracted fromOrderRequestDtoto findCustomerthroughCustomerRepository
Creating Service Dto 3 - OrderDto.kt
- A data class that receives a
Customerobject throughOrderRequestDtoand maps values toOrderDtopackage com.wool.service.dtos.order import com.wool.entity.Customer import com.wool.entity.Order data class OrderDto( val orderStoreName: String, val orderStoreAddress: String, val orderItem: String, val orderPrice: Int, val customer: Customer, ){ fun toEntity() = Order( orderStoreName = this.orderStoreName, orderStoreAddress = this.orderStoreAddress, orderItem = this.orderItem, orderPrice = this.orderPrice, customer = this.customer ) }
Creating Service 1 - CustomerService.kt
- Responsible for combining data communicated from
ModuleCoreCustomerRepositorywithCustomerDtoand passing it to Controllerpackage com.wool.service import com.wool.repository.ModuleCoreCustomerRepository import com.wool.service.dtos.customer.CustomerDto import org.springframework.stereotype.Service @Service class CustomerService( private val customerRepository: ModuleCoreCustomerRepository ) { fun getCustomers() = customerRepository.findAll() fun saveCustomers(customerDto: CustomerDto) { this.customerRepository.save(customerDto.toEntity()) } }getCustomers(): Gets allCustomers throughfindAll()fromModuleCoreCustomerRepositorysaveCustomers(): ConvertsCustomerDtoto aCustomerentity and saves it toModuleCoreCustomerRepository
Creating Service 2 - OrderService.kt
- Responsible for finding
CustomerthroughOrderRequestDtoreceived fromController, mapping it toOrderDto, and savingpackage com.wool.service import com.wool.entity.Customer import com.wool.repository.ModuleCoreCustomerRepository import com.wool.repository.ModuleCoreOrderRepository import com.wool.service.dtos.order.OrderRequestDto import com.wool.service.dtos.order.OrderDto import org.springframework.stereotype.Service @Service class OrderService( private val orderRepository: ModuleCoreOrderRepository, private val customerRepository: ModuleCoreCustomerRepository ) { fun getOrders() = orderRepository.findAll() fun saveOrder(orderRequestDto: OrderRequestDto) { val customer: Customer = customerRepository.findById(orderRequestDto.customerId).get() //if customer exists if (customer != null) { val orderDto = OrderDto( orderStoreName = orderRequestDto.orderStoreName, orderStoreAddress = orderRequestDto.orderStoreAddress, orderItem = orderRequestDto.orderItem, orderPrice = orderRequestDto.orderPrice, customer = customer ) orderRepository.save(orderDto.toEntity()) } else { //if customer doesn't exist throw Exception("Customer not found.") } } }getOrders(): Gets allOrders throughfindAll()fromModuleCoreOrderRepositorysaveOrder(): FindsCustomerthroughOrderRequestDto, maps it toOrderDto, and saves
Creating API Module 5 - Controller
- Create a
Controllerthat can receive external requests - Since the functionality isn’t detailed, there isn’t much to see in the controller beyond DTO configuration

Creating Controller 1 - CustomerController.kt
package com.wool.controller
import com.wool.service.CustomerService
import com.wool.service.OrderService
import com.wool.service.dtos.customer.CustomerDto
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RestController
@RestController
class CustomerController(
private val customerService: CustomerService
) {
@PostMapping("/customers")
fun saveCustomers(@RequestBody customerDto: CustomerDto) {
return this.customerService.saveCustomers(customerDto)
}
@GetMapping("/customers")
fun getCustomers() = this.customerService.getCustomers()
}
saveCustomers(): PassesCustomerDtotoCustomerServiceto savegetCustomers(): GetsCustomerfromCustomerServiceand returns it
Creating Controller 2 - OrderController.kt
package com.wool.controller
import com.wool.service.OrderService
import com.wool.service.dtos.order.OrderRequestDto
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RestController
@RestController
class OrderController(
private val orderService: OrderService
) {
@GetMapping("/orders")
fun getOrders() = orderService.getOrders()
@PostMapping("/orders")
fun saveOrders(@RequestBody orderRequestDto: OrderRequestDto) {
//save order
return orderService.saveOrder(orderRequestDto)
}
}
getOrders(): GetsOrderfromOrderServiceand returns itsaveOrders(): PassesOrderRequestDtotoOrderServiceto save
API Module Configuration - application.yml
- Various configuration values for running the server are managed in
application.yml - The values in
application.ymlwere written using postgresql information from docker-compose - Create
application.ymlunder theresourcespackage and write as followsspring: datasource: hikari: pool-name: HikariCp maximum-pool-size: 2 minimum-idle: 2 username: wool password: password1234 url: jdbc:postgresql://localhost:9876/wooldb driver-class-name: org.postgresql.Driver jpa: hibernate: ddl-auto: update show-sql: true properties: hibernate: format_sql: true default_schema: springtest jackson: serialization: fail-on-empty-beans: false
Wrapping Up API Module
- In this post, we imported entities written in
module-coreand used them inmodule-api - When you actually run the server and test, data retrieval/storage works well
- After running
ModuleApiApplication.kt, test with the API test data below
API Testing
Customer Retrieval
- URL :
http://localhost:8080/customers - Method :
GET - Response
[ { "customerId": 1, "customerNickName": "Oliver", "customerAddress": "Want to live in Seoul", "createdAt": "2022-11-20T00:00:00", "updatedAt": "2022-11-20T00:00:00" }, { "customerId": 2, "customerNickName": "Future", "customerAddress": "Successful life in Jeongja-dong", "createdAt": "2022-11-21T00:00:00", "updatedAt": "2022-11-21T00:00:00" } ]
Customer Creation
- URL :
http://localhost:8080/customers - Method :
POST - Body :
application/json{ "customerNickName": "Future", "customerAddress": "Successful life in Jeongja-dong" }
Order Retrieval
- URL :
http://localhost:8080/orders - Method :
GET - Response
[ { "orderId": 2, "orderUUID": "014e018a-3391-4c54-a913-3230e65a0013", "orderStoreName": "Yam Yam Gimbap", "orderStoreAddress": "Gimbap-dong, Yamyam-ro, Seoul", "orderItem": "Bulgogi Tuna Gimbap", "orderPrice": 6500, "customer": { "customerId": 1, "customerNickName": "Oliver", "customerAddress": "Want to live in Seoul", "createdAt": "2022-11-20T00:00:00", "updatedAt": "2022-11-20T00:00:00" }, "createdAt": "2022-11-21T00:00:00", "updatedAt": "2022-11-21T00:00:00" }, { "orderId": 3, "orderUUID": "caf72de8-7174-48d9-9e06-d5665ca2225e", "orderStoreName": "Yam Yam Gimbap", "orderStoreAddress": "Gimbap-dong, Yamyam-ro, Seoul", "orderItem": "Bulgogi Tuna Gimbap", "orderPrice": 6500, "customer": { "customerId": 2, "customerNickName": "Future", "customerAddress": "Successful life in Jeongja-dong", "createdAt": "2022-11-21T00:00:00", "updatedAt": "2022-11-21T00:00:00" }, "createdAt": "2022-11-21T00:00:00", "updatedAt": "2022-11-21T00:00:00" } ]
Order Creation
- URL :
http://localhost:8080/orders - Method :
POST - Body :
application/json{ "orderStoreName": "Yam Yam Gimbap", "orderStoreAddress": "Gimbap-dong, Yamyam-ro, Seoul", "orderItem": "Bulgogi Tuna Gimbap", "orderPrice": 6500, "customerId": 2 }
댓글남기기