0308 - 0314
# 0308 - 0314
# 0308 - pull request
- Fork
- clone, remote
- branch
- add, commit, push
- pull request
- merge pull request
# 0309 - markdown ํ์ฉ - readme.md / git profile
# ๋งํฌ๋ค์ด ์น์๋ํฐ
- https://dillinger.io/
# HITS
- https://hits.seeyoufarm.com/
# Badge
- https://shields.io/
- https://simpleicons.org/
# Table Generator
- https://www.tablesgenerator.com/markdown_tables
# Git-Hub Stats
- https://github.com/anuraghazra/github-readme-stats
# Emoji
- https://www.webfx.com/tools/emoji-cheat-sheet/
# 0310 - mockMvc
์ค์ ๊ฐ์ฒด์ ๋น์ทํ์ง๋ง ํ ์คํธ์ ํ์ํ ๊ธฐ๋ฅ๋ง ๊ฐ์ง๋ ๊ฐ์ง ๊ฐ์ฒด๋ฅผ ๋ง๋ค์ด์ ์ดํ๋ฆฌ์ผ์ด์ ์๋ฒ์ ๋ฐฐํฌํ์ง ์๊ณ ๋ ์คํ๋ง MVC ๋์์ ์ฌํํ ์ ์๋ ํด๋์ค๋ฅผ ์๋ฏธํฉ๋๋ค.
- Controller์ ๋ํ Testํด๋์ค ์์ฑ
- ํ ์คํธ ๋ฉ์๋ ์คํ์ ์ ์ ๋ฉ์๋
private MockMvc mockMvc;
@Autowired
private WebApplicationContext ctx;
@BeforeEach
public void setup(){
this.mockMvc = MockMvcBuilders.webAppContextSetup(ctx).build();
}
// ํํฐ ์ถ๊ฐ ๋๋ alwayDo ์ต์
์ถ๊ฐ
// this.mockMvc = MockMvcBuilders.webAppContextSetup(ctx)
// .addFilters(new CharacterEncodingFilter("UTF-8", true))
// .alwaysDo(print())
// .build();
}
- MockMvc ๋ฉ์๋ ํ์ธ
- .perform()
- get(),post()
- header
- param()
- accept()
- contentType()
- ResultActions
- andExpert : ์์๊ฐ ๊ฒ์ฆ
- andDo : ์์ฒญ์ ๋ํ ์ฒ๋ฆฌ
- andReturn : ํ ์คํธํ ๊ฒฐ๊ณผ ๊ฐ์ฒด๋ฅผ ๋ฐ์ ๋
- .perform()
@Test
public void getStudyTest() throws Exception {
mockMvc.perform(get(BASE_URL+"/{seq}", 1L)
.header("Authorization", "Bearer " + loginedToken)
.accept(MediaTypes.HAL_JSON_VALUE)
.contentType(MediaTypes.HAL_JSON_VALUE))
.andDo(print())
.andExpect(status().isOk())
.andDo()
}
# 0311 - Spring REST Docs
API๋ฌธ์ ์์ฑ ์๋ํ ๋๊ตฌ
๋ํ์ ์ผ๋ก Swagger, Spring Rest Docs
# Asciidocor
Adoc ํ์ผ์ ํ์ฉํ์ฌ html ๋ฌธ์๋ฅผ ๋ง๋ค์ด์ฃผ๋ ๋๊ตฌ
- ์์กด์ฑ ๋ฐ ํ๋ฌ๊ทธ์ธ ์ถ๊ฐ
<plugin>
<groupId>org.asciidoctor</groupId>
<artifactId>asciidoctor-maven-plugin</artifactId>
<version>1.5.8</version>
<executions>
<execution>
<id>generate-docs</id>
<phase>prepare-package</phase>
<goals>
<goal>process-asciidoc</goal>
</goals>
<configuration>
<backend>html</backend>
<doctype>book</doctype>
</configuration>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.springframework.restdocs</groupId>
<artifactId>spring-restdocs-asciidoctor</artifactId>
<version>${spring-restdocs.version}</version>
</dependency>
</dependencies>
</plugin>
# ์์
- ํ ์คํธ ์ฝ๋
@WebMvcTest(PostController.class)
class PostControllerTest {
@Autowired
private MockMvc mvc;
@MockBean
private PostService postService;
@Test
public void create() throws Exception {
final PostResponse post = PostResponse.builder()
.id(1L)
.title("first_post")
.content("hello_my_world")
.build();
given(postService.createPost(any())).willReturn(post);
mvc.perform(post("/posts")
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON)
.content("{\"title\":\"first_post\",\"content\":\"hello_my_world\"}"))
.andExpect(status().isCreated())
.andExpect(header().stringValues("location", "/posts/" + 1L))
.andDo(print());
}
...
}
- Documentation ์์
- ํ ์คํธ ๋ชจ๋์์ ๋ณ๋์ ํจํค์ง ์์ฑํ๊ณ Documentation์ ์( ํ ์คํธ ์ฝ๋์ ์ด์ด ์์ฑํด๋ ๋์ง๋ง, ๋ฐ๋ก ์์ฑํ์ฌ ๊ด๋ฆฌ)
public class PostDocumentation {
public static RestDocumentationResultHandler createPost() {
return document("posts/create",
requestFields(
fieldWithPath("title").type(JsonFieldType.STRING).description("This is post title."),
fieldWithPath("content").type(JsonFieldType.STRING).description("This is post content")
)
);
}
...
}
- mockMvc์ Documentation ์ฌ์ฉ ํํฐ ์ถ๊ฐ
@BeforeEach
public void setUp(WebApplicationContext webApplicationContext, RestDocumentationContextProvider restDocumentation) {
mvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
.addFilter(new ShallowEtagHeaderFilter())
.apply(documentationConfiguration(restDocumentation))
.build();
}
// ๊ฐ ํ
์คํธ ๋ง์ง๋ง์ ๋ค์๊ณผ ๊ฐ์ด .andDo(PostDocumentation.xx)๋ฅผ ํธ์ถํด์ค๋ค.
@Test
public void create() throws Exception {
...
mvc.perform(post("/posts")
.accept(MediaType.APPLICATION_JSON)
.contentType(MediaType.APPLICATION_JSON)
.content("{\"title\":\"first_post\",\"content\":\"hello_my_world\"}"))
.andExpect(status().isCreated())
.andExpect(header().stringValues("location", "/posts/" + 1L))
.andDo(print())
.andDo(PostDocumentation.createPost()); // ์ถ๊ฐ
}
- ๋ฌธ์ ์คํ ์ ์๋ฅผ api-guide.adoc ํ์ผ์ ์ถ๊ฐ
- src ํด๋ ์๋์
documentation
>asccidoc
>api-guide.adoc
ํ์ผ ์ถ๊ฐ - api-guide.adoc ์์
- src ํด๋ ์๋์
ifndef::snippets[]
:snippets: ../../../build/generated-snippets
endif::[]
:doctype: book
:icons: font
:source-highlighter: highlightjs
:toc: left
:toclevels: 2
:sectlinks:
:operation-http-request-title: Example Request
:operation-http-response-title: Example Response
[[resources]]
= Resources
[[resources-posts]]
== Post
[[resources-posts-create]]
=== ํฌ์คํธ ์ถ๊ฐ
operation::posts/create[snippets='http-request,http-response,request-fields,request-body']
[[resources-posts-getAll]]
=== ํฌ์คํธ ์ ์ฒด ์กฐํ
operation::posts/getAll[snippets='http-request,http-response,response-body']
[[resources-posts-get]]
=== ํฌ์คํธ ์กฐํ
operation::posts/get[snippets='http-request,http-response,response-body']
[[resources-posts-update]]
=== ํฌ์คํธ ์์
operation::posts/update[snippets='http-request,http-response,request-fields,request-body']
[[resources-posts-delete]]
=== ํฌ์คํธ ์ญ์
operation::posts/delete[snippets='http-request,http-response']
- build test
# 0312 - Spring HATEOAS
Hypermedia As The Engine Of Application State
RESTful API๋ฅผ ์ฌ์ฉํ๋ ํด๋ผ์ด์ธํธ๊ฐ ์ ์ ์ผ๋ก ์๋ฒ์ ๋์ ์ธ ์ํธ์์ฉ์ด ๊ฐ๋ฅํ๋๋ก ํ๋ ๊ฒ
ํด๋ผ์ด์ธํธ๊ฐ ์๋ฒ๋ก๋ถํฐ ์ด๋ ํ ์์ฒญ์ ํ ๋, ์์ฒญ์ ํ์ํ(์์กด๋๋) URI๋ฅผ ์๋ต์ ํฌํจ์์ผ ๋ฐํ
# Content Type
- ContentType์ application/hal+json์ผ๋ก ์ ๋ฌํ๋ฉด ํด๋ผ์ด์ธํธ์์๋ _links ํ๋์ ๋งํฌ ์ ๋ณด๊ฐ ์๋ค๊ณ ์์ํ ์ ์๋ค.
# ์์
@PostMapping("/employees")
ResponseEntity<?> newEmployee(@RequestBody Employee newEmployee) {
EntityModel<Employee> entityModel = assembler.toModel(repository.save(newEmployee));
return ResponseEntity //
.created(entityModel.getRequiredLink(IanaLinkRelations.SELF).toUri()) //
.body(entityModel);
}
https://spring.io/guides/tutorials/rest/
# 0313 - Reverse Proxy
์ปดํจํฐ ๋คํธ์ํฌ์์ ๋ฆฌ๋ฒ์ค ํ๋ก์๋ ํ๋ ์ด์์ ์๋ฒ๋ก ๋ถํฐ ํด๋ผ์ด์ธํธ๋ฅผ ๋์ ํด์ ๋ฆฌ์์ค๋ฅผ ๊ฒ์ํ๋ ํ๋ก์ ์๋ฒ, ์๋ฒ๊ฐ ํด๋ผ์ด์ธํธ์๊ฒ ์ ์ดํ๋๊ฒ์ ์ค๊ฐ
- Load Balancing
- Web acceleration
- Security and anonymity
# 0314 - API ๋ช ์ธ์ ์๋ ์์ฑ
Spring Rest Docs
- ํ ์คํธ ์ผ์ด์ค ์์ฑ
- ํ ์คํธ ์คํ
- ์์ฑ๋ snippets ๋ฌธ์ ํ์ธ
curl-request.adoc : ํธ์ถ์ ๋ํ curl ๋ช
๋ น์ ํฌํจ ํ๋ ๋ฌธ์
httpie-request.adoc : ํธ์ถ์ ๋ํ http ๋ช
๋ น์ ํฌํจ ํ๋ ๋ฌธ์
http-request.adoc : http ์์ฒญ ์ ๋ณด ๋ฌธ์
http-response.adoc : http ์๋ต ์ ๋ณด ๋ฌธ์
request-body.adoc : ์ ์ก๋ http ์์ฒญ ๋ณธ๋ฌธ ๋ฌธ์
response-body.adoc : ๋ฐํ๋ http ์๋ต ๋ณธ๋ฌธ ๋ฌธ์
request-parameters.adoc : ํธ์ถ์ parameter ์ ๋ํ ๋ฌธ์
path-parameters.adoc : http ์์ฒญ์ url ์ ํฌํจ๋๋ path parameter ์ ๋ํ ๋ฌธ์
request-fields.adoc : http ์์ฒญ object ์ ๋ํ ๋ฌธ์
response-fields.adoc : http ์๋ต object ์ ๋ํ ๋ฌธ์
- API ๋ช ์ธ์ ์์ฑ
- ์ค๋ํซ ๋ฌธ์ ๊ฒฝ๋ก ์ง์
ifndef::snippets[]
:snippets: ../../../target/generated-snippets
endif::[]
- ์ํ๋ ์ค๋ํซ ์ถ๊ฐ
include::{snippets}/user-controller-test/get-list/curl-request.adoc[]
- maven install
- html๋ก ๋ฐํ / api ๋ช ์ธ์ ๋ณต์ฌ ์ค์
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
...
<build>
<plugins>
...
<!-- 1. API ๋ช
์ธ์๋ฅผ html๋ก ๋ณํ -->
<plugin>
<groupId>org.asciidoctor</groupId>
<artifactId>asciidoctor-maven-plugin</artifactId>
<version>1.5.3</version>
<executions>
<execution>
<id>generate-docs</id>
<phase>prepare-package</phase>
<goals>
<goal>process-asciidoc</goal>
</goals>
<configuration>
<backend>html</backend>
<doctype>book</doctype>
</configuration>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.springframework.restdocs</groupId>
<artifactId>spring-restdocs-asciidoctor</artifactId>
<version>2.0.2.RELEASE</version>
</dependency>
</dependencies>
</plugin>
<!-- 2. html ๋ก ๋ณํ๋ API ๋ช
์ธ์๋ฅผ static ์์ญ์ผ๋ก ๋ณต์ฌ -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.1</version>
<executions>
<execution>
<id>copy-resources</id>
<phase>prepare-package</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>
${project.build.outputDirectory}/static/docs
</outputDirectory>
<resources>
<resource>
<directory>
${project.build.directory}/generated-docs
</directory>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
...
</plugins>
</build>
...
</project>
- html ๋ฌธ์ ํ์ธ