Now Loading ...
-
-
-
Spring Boot + Maven 환경에서 커스텀 빌드하기 (3)
Spring Boot + Maven 환경에서 커스텀 빌드하기 (3)
CUSTOM REST API 예제 작성
고객사 전용 커스텀 예제를 작성한다.
마찬가지로 최대한 간단하게 작성한다. (이게 중요한 게 아니니까2)
Test Code
package com.toy.springbootmavencustombuild.site.customer;
import com.toy.springbootmavencustombuild.site.customer.rest.CustomerController;
import com.toy.springbootmavencustombuild.site.customer.service.CustomerService;
import org.junit.Before;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.restdocs.JUnitRestDocumentation;
import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders;
import org.springframework.restdocs.payload.JsonFieldType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.BDDMockito.given;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.*;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
import static org.springframework.restdocs.request.RequestDocumentation.pathParameters;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* CustomerController 테스트 코드
*
* @author readra
*/
@AutoConfigureRestDocs
@WebMvcTest(CustomerController.class)
public class CustomerControllerTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private WebApplicationContext webApplicationContext;
@MockBean
private CustomerService customerService;
@Before
public void setUp() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.webApplicationContext)
.apply(documentationConfiguration(new JUnitRestDocumentation()))
.build();
}
@Test
@DisplayName("Customer Hello World")
void helloWorldTest() throws Exception {
final int code = 100;
final String result = String.format(CustomerService.HELLO_WORLD, code);
// given
given(customerService.helloWorld(anyInt())).willReturn(result);
// when
ResultActions resultActions = mockMvc.perform(
RestDocumentationRequestBuilders.get("/v1/customer/hello-world/{code}", code)
.contentType(MediaType.APPLICATION_JSON)
);
// then
resultActions.andExpect(status().isOk())
.andDo(
document(
"Customer Hello World",
preprocessRequest(prettyPrint()),
preprocessResponse(prettyPrint()),
pathParameters(
parameterWithName("code").description("코드")
),
responseFields(
fieldWithPath("message").type(JsonFieldType.STRING).description("메시지"),
fieldWithPath("code").type(JsonFieldType.NUMBER).description("코드")
)
)
)
.andDo(print());
}
}
Controller
package com.toy.springbootmavencustombuild.site.customer.rest;
import com.toy.springbootmavencustombuild.site.customer.service.CustomerService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 고객사 전용 Controller Layer
*
* @author readra
*/
@RestController
@RequestMapping("/v1/customer/")
public class CustomerController {
private final CustomerService customerService;
@Autowired
public CustomerController(final CustomerService customerService) {
this.customerService = customerService;
}
/**
* 안녕하세요!
*
* @param code
* 코드
* @return
* 안녕하세요!
*/
@GetMapping("/hello-world/{code}")
public String helloWorld(@PathVariable int code) {
return customerService.helloWorld(code);
}
}
Service
package com.toy.springbootmavencustombuild.site.customer.service;
import org.springframework.stereotype.Service;
/**
* 고객사 전용 Service Layer
*
* @author readra
*/
@Service
public class CustomerService {
public static final String HELLO_WORLD = "{ \"message\" : \"Hello World My Customer\", \"code\" : %d }";
/**
* 안녕하세요!
*
* @param code
* 코드
* @return
* 안녕하세요!
*/
public String helloWorld(int code) {
return String.format(HELLO_WORLD, code);
}
}
REST Docs
= Rest Docs Customer Sample API Document
readra.github.io
:doctype: book
:icons: font
:source-highlighter: highlightjs
:toc: left
:toclevels: 2
:sectlinks:
[[introduction]]
== 소개
readra Spring Rest Docs API
---
=== 고객사 전용 안녕하세요 API
고객사 전용 안녕하세요 API를 소개합니다.
operation::Customer Hello World[snippets='http-request,path-parameters,http-response,response-fields,curl-request']
---
-
Spring Boot + Maven 환경에서 커스텀 빌드하기 (2)
Spring Boot + Maven 환경에서 커스텀 빌드하기 (2)
REST API 예제 작성
예제는 최대한 간단하게 작성한다. (이게 중요한 게 아니니까)
Test Code
package com.toy.springbootmavencustombuild.rest;
import com.toy.springbootmavencustombuild.service.HelloWorldService;
import org.junit.Before;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.restdocs.AutoConfigureRestDocs;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.restdocs.JUnitRestDocumentation;
import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders;
import org.springframework.restdocs.payload.JsonFieldType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.BDDMockito.given;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document;
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration;
import static org.springframework.restdocs.operation.preprocess.Preprocessors.*;
import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath;
import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields;
import static org.springframework.restdocs.request.RequestDocumentation.parameterWithName;
import static org.springframework.restdocs.request.RequestDocumentation.pathParameters;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* HelloWorldController 테스트 코드
*
* @author readra
*/
@AutoConfigureRestDocs
@WebMvcTest(HelloWorldController.class)
public class HelloWorldControllerTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private WebApplicationContext webApplicationContext;
@MockBean
private HelloWorldService helloWorldService;
@Before
public void setUp() {
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.webApplicationContext)
.apply(documentationConfiguration(new JUnitRestDocumentation()))
.build();
}
@Test
@DisplayName("Hello World")
void helloWorldTest() throws Exception {
final int code = 100;
final String result = String.format(HelloWorldService.HELLO_WORLD, code);
// given
given(helloWorldService.helloWorld(anyInt())).willReturn(result);
// when
ResultActions resultActions = mockMvc.perform(
RestDocumentationRequestBuilders.get("/v1/hello-world/{code}", code)
.contentType(MediaType.APPLICATION_JSON)
);
// then
resultActions.andExpect(status().isOk())
.andDo(
document(
"Hello World",
preprocessRequest(prettyPrint()),
preprocessResponse(prettyPrint()),
pathParameters(
parameterWithName("code").description("코드")
),
responseFields(
fieldWithPath("message").type(JsonFieldType.STRING).description("메시지"),
fieldWithPath("code").type(JsonFieldType.NUMBER).description("코드")
)
)
)
.andDo(print());
}
}
Controller
package com.toy.springbootmavencustombuild.rest;
import com.toy.springbootmavencustombuild.service.HelloWorldService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 예제 Controller Layer
*
* @author readra
*/
@RestController
@RequestMapping("/v1")
public class HelloWorldController {
private final HelloWorldService helloWorldService;
@Autowired
public HelloWorldController(final HelloWorldService helloWorldService) {
this.helloWorldService = helloWorldService;
}
/**
* 안녕하세요!
*
* @param code
* 코드
* @return
* 안녕하세요!
*/
@GetMapping("/hello-world/{code}")
public String helloWorld(@PathVariable int code) {
return helloWorldService.helloWorld(code);
}
}
Service
package com.toy.springbootmavencustombuild.service;
import org.springframework.stereotype.Service;
/**
* 예제 Service Layer
*
* @author readra
*/
@Service
public class HelloWorldService {
public static final String HELLO_WORLD = "{ \"message\" : \"Hello World\", \"code\" : %d }";
/**
* 안녕하세요!
*
* @param code
* 코드
* @return
* 안녕하세요!
*/
public String helloWorld(int code) {
return String.format(HELLO_WORLD, code);
}
}
REST Docs
= Rest Docs Sample API Document
readra.github.io
:doctype: book
:icons: font
:source-highlighter: highlightjs
:toc: left
:toclevels: 2
:sectlinks:
[[introduction]]
== 소개
readra Spring Rest Docs API
---
=== 안녕하세요 API
안녕하세요 API를 소개합니다.
operation::Hello World[snippets='http-request,path-parameters,http-response,response-fields,curl-request']
---
-
Spring Boot + Maven 환경에서 커스텀 빌드하기 (1)
Spring Boot + Maven 환경에서 커스텀 빌드하기 (1)
현재 나는 솔루션 회사에 근무하고 있다. 여러 업무 중 하나로 API 서버를 개발/유지보수하고 있다.
솔루션에는 대부분 기능의 표준이 존재하지만, 고객의 강력한 요청에 의해 표준의 경계가 흐려지거나 망가지는 케이스가 많다. (경험상)
API 서버라고 예외는 없다. 고객의 강력한 커스터마이징 요청이 있었고, 이를 대응하기 위해 하나의 프로젝트에서 고객사별로 커스텀 빌드가 가능하도록 처리한 내용을 정리하려고 한다.
개인적으로 하는 개발과 달리 다소 제약적인 회사 내, 개발 환경이라는 점 참고 바랍니다.
[AS-IS]
* 공통 코드는 하나의 단독 프로젝트로 관리한다.
* 커스터마이징 코드는 공통 코드와는 분리된 별도의 프로젝트로 관리하며, 저장소 위치도 다르다.
* 커스터마이징 개발의 경우, 커스터마이징 코드에 공통 코드를 Import Module 하여 개발한다.
나는 개인적으로 이런 낯선 개발 환경/방식을 스스로 풀어나가는 걸 좋아한다.
하지만, 문제는 새로 입사하신 분들 중에는 빠르게 정답만 알고 싶어하시는 분이 있다는 점이다.
그럴 때마다 듣는 사람도 심드렁하고, 알려주는 보람도 없는 일을 더 이상 하기 싫어졌다.
커스터마이징 코드 관리 전략을 스스로 생각하기에 좋은 패턴으로 개선한 내용을 소개하려고 한다.
[TO-BE]
* 공통 코드는 하나의 단독 프로젝트로 관리한다.
* 커스터마이징 코드는 공통 코드와 동일한 프로젝트에 관리하며, 저장소 위치도 동일하다.
* 커스터마이징 개발의 경우, 추가 작업 없이 바로 개발이 가능하다.
* 단, 커스터마이징 코드는 정해진 hierarchy 규칙에 따라 관리/개발한다.
AS-IS 구조의 개발이 안좋다고 말하려는 것이 아니다.
정답은 없다고 생각하며, 또 다른 하나의 방법을 제시하는 것이다.
글로만 전달하려고 하니, 괜한 오해가 있을까봐 말이 길어진다...
커스터마이징 너무 싫다... 조금만 줄이고 싶다...
프로젝트 준비
Common API 테스트 코드 작성 (+REST Docs)
Common API 테스트 코드 기반으로 실제 코드 작성
Customizing API 테스트 코드 작성 (+REST Docs)
Customizing API 테스트 코드 기반으로 실제 코드 작성
Common + Customizing API 패키징 (실제 코드 + 테스트 코드 + REST Docs)
위의 총 5-Step 에 걸쳐 정리할 예정이다.
다음 글에서는 간단한 Common API 테스트 코드 작성하여 REST Docs API 문서를 만들고, 이를 기반으로 실제 코드를 작성하도록 하겠다.
-
Touch background to close