phantomjs 가 2018년 4월에 멈춰졌기로 확인해봤다니..
기존의 고생한 것도 있고. headless chrome 때문에 옮겼다고 한다.
https://groups.google.com/forum/#!topic/phantomjs/9aI5d-LDuNE
Hi,
Vitaly.
phantomjs 가 2018년 4월에 멈춰졌기로 확인해봤다니..
기존의 고생한 것도 있고. headless chrome 때문에 옮겼다고 한다.
https://groups.google.com/forum/#!topic/phantomjs/9aI5d-LDuNE
Hi,
Vitaly.
gradle 에러 내용은 다음과 같다.
난 여태 gradle은 순서에 상관없는 빌드를 지원하는 것으로 알고 있었다.
그런데 아래와 같은 에러가 발생한다.
아래와 같이 사용했더니
repositories {
mavenCentral()
}
plugins {
id 'org.springframework.boot' version '2.1.6.RELEASE'
id 'java'
}
아래와 같은 gradle 에러가 발생한다.
only buildscript {} and other plugins {} script blocks are allowed before plugins {} blocks, no other statements are allowed
https://docs.gradle.org/5.4.1/userguide/plugins.html 문서를 참고하면.
plugins (플러그인 바이너리) 스펙에 constraint를 설명하고 있다.
에러 문구 대로 plugins {} 구문의 앞에는 buildscript {}와 기타 plugin{}만 사용될 수 있다.
인터넷에 많이 공개된 Spring Rest Docs를 공부하고 적용해봤다.
* Spring Rest Docs가 Swagger2에 비해 갖는 장점
- 내가 원하는 형태로 문서화할 수 있다. (대신 단점도 됨)
- 테스트를 무조건 만들어야 하고 문서화를 진행해야 한다. (안전)
- Spring Controller의 지저분한 annotation이 깔끔해짐 (코드의 간결성)
- Swagger2는 2018년 중반 이후부터 더 이상 개발되지 않고 있다.
* Spring Rest Docs가 Swagger2에 비해 갖는 단점
- 처음에 손이 많이 간다.
asciidoc로 문서화를 수행해야 한다. 처음에는 디폴트가 먼저도 모름. 헤맴. 그리고 만들어진 html 문서를 resource static 문서로 복사해야 한다. gradle, maven 자동화가 필요하다.
- UI를 못하거나 문서화를 자동으로 해주는 swagger2가 적당한 선이 아직까지는 내게 어울림
- swagger2의 try it out 기능이 없는 것 같다(또는 어디에 있는지 못 찾겠다.)
swagger2의 try it out은 api 연동의 핵심이라.. 의사소통을 많이 줄여준다.
-> (개인적으로) 아직은 Swagger2가 훨씬 편한 듯 하다. 그러나 Spring Rest Docs의 철학이 아무 매력적이다. '테스트 없는 문서화가 의미가 있나?' 테스트를 강제하는 형태가 좋아보였다.
Spring Rest Docs를 사용하면 아래와 비슷하게 나온다. 귀찮아서 정리를 못했지만 손이 조금 간다. 꼼꼼한 사람들에게는 좋을 것 같다.
나머지는 코드이다.
https://github.com/knight76/springboot2-restdocs
build.gradle
buildscript {
repositories {
mavenCentral()
jcenter()
}
dependencies {
classpath 'org.asciidoctor:asciidoctor-gradle-plugin:1.5.9'
}
}
plugins {
id 'org.springframework.boot' version '2.1.6.RELEASE'
id "org.asciidoctor.convert" version "1.5.9"
}
apply plugin: 'org.asciidoctor.convert'
apply plugin : 'java'
apply plugin : 'io.spring.dependency-management'
apply plugin : 'org.asciidoctor.convert'
ext {
lombokVersion = "1.18.8"
swagger2Version = '2.9.2'
oldSwagger2Version = '1.5.21'
}
group = 'com.google.knight76'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'
ext {
snippetsDir = file('build/generated-snippets')
}
test {
outputs.dir snippetsDir
}
asciidoctor {
inputs.dir snippetsDir
dependsOn test
}
bootJar {
dependsOn asciidoctor
from ("${asciidoctor.outputDir}/html5") {
into 'static'
}
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation('org.springframework.boot:spring-boot-starter-web') {
exclude module: "spring-boot-starter-tomcat"
}
implementation "org.projectlombok:lombok:${lombokVersion}"
implementation 'org.springframework.boot:spring-boot-starter-undertow'
asciidoctor 'org.springframework.restdocs:spring-restdocs-asciidoctor'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation('org.springframework.restdocs:spring-restdocs-mockmvc')
}
src/doc/asciidoc/api-docs.adoc
:sectnums:
:sectnumlevels: 5
:toc: left
:toclevels: 3
:page-layout: docs
= Hello World
== Hello World (Plain)
include::{snippets}/hello-world-test/hello-world/http-request.adoc[]
include::{snippets}/hello-world-test/hello-world/curl-request.adoc[]
include::{snippets}/hello-world-test/hello-world/http-response.adoc[]
include::{snippets}/hello-world-test/hello-world/request-body.adoc[]
include::{snippets}/hello-world-test/hello-world/response-body.adoc[]
include::{snippets}/hello-world-test/hello-world/httpie-request.adoc[]
== Hello World (Json)
include::{snippets}/hello-world-test/hello-world-json/http-request.adoc[]
include::{snippets}/hello-world-test/hello-world-json/curl-request.adoc[]
include::{snippets}/hello-world-test/hello-world-json/http-response.adoc[]
include::{snippets}/hello-world-test/hello-world-json/request-body.adoc[]
include::{snippets}/hello-world-test/hello-world-json/response-body.adoc[]
include::{snippets}/hello-world-test/hello-world-json/response-fields.adoc[]
include::{snippets}/hello-world-test/hello-world-json/httpie-request.adoc[]
include::{snippets}/hello-world-test/hello-world-json/request-parameters.adoc[]
== City (add-city)
include::{snippets}/cities-controller-test/add-city/http-request.adoc[]
include::{snippets}/cities-controller-test/add-city/curl-request.adoc[]
include::{snippets}/cities-controller-test/add-city/http-response.adoc[]
include::{snippets}/cities-controller-test/add-city/request-body.adoc[]
include::{snippets}/cities-controller-test/add-city/response-body.adoc[]
include::{snippets}/cities-controller-test/add-city/response-fields.adoc[]
== City (delete-city)
include::{snippets}/cities-controller-test/delete-city/http-request.adoc[]
include::{snippets}/cities-controller-test/delete-city/curl-request.adoc[]
include::{snippets}/cities-controller-test/delete-city/http-response.adoc[]
include::{snippets}/cities-controller-test/delete-city/request-body.adoc[]
include::{snippets}/cities-controller-test/delete-city/response-body.adoc[]
include::{snippets}/cities-controller-test/delete-city/response-fields.adoc[]
== City (get-cities)
include::{snippets}/cities-controller-test/get-cities/http-request.adoc[]
include::{snippets}/cities-controller-test/get-cities/curl-request.adoc[]
include::{snippets}/cities-controller-test/get-cities/http-response.adoc[]
include::{snippets}/cities-controller-test/get-cities/request-body.adoc[]
include::{snippets}/cities-controller-test/get-cities/response-body.adoc[]
include::{snippets}/cities-controller-test/get-cities/response-fields.adoc[]
== City (get-city)
include::{snippets}/cities-controller-test/get-city/http-request.adoc[]
include::{snippets}/cities-controller-test/get-city/curl-request.adoc[]
include::{snippets}/cities-controller-test/get-city/http-response.adoc[]
include::{snippets}/cities-controller-test/get-city/request-body.adoc[]
include::{snippets}/cities-controller-test/get-city/response-body.adoc[]
include::{snippets}/cities-controller-test/get-city/path-parameters.adoc[]
include::{snippets}/cities-controller-test/get-city/response-fields.adoc[]
예시
@Test
public void getCity() throws Exception {
// given
String response = "{'id': 1,'name':'Bratislava','population':432000}";
// when
when(cityService.get(1L)).thenReturn( Optional.of(new City(1L, "Bratislava", 432000)));
ResultActions result =
mockMvc.perform(get("/api/1/city/{id}", 1L)
.contentType(MediaType.APPLICATION_JSON_UTF8_VALUE))
.andDo(print());
// then
result.andExpect(status().isOk())
.andDo(document.document(
pathParameters(
parameterWithName("id").description("도시의 id")),
responseFields(
fieldWithPath("id").description("도시의 id"),
fieldWithPath("name").description("도시 이름"),
fieldWithPath("population").description("도시 인구")
)))
.andExpect(jsonPath("id", is(notNullValue())))
.andExpect(content().json(response));
}
참고
http://woowabros.github.io/experience/2018/12/28/spring-rest-docs.html
https://docs.spring.io/spring-restdocs/docs/current/reference/html5/
보통은 org.springframework.web.HttpMediaTypeNotSupportedException 예외는
Rest api에서 허용할 수 있는 MediaType이 아니면 에러가 난다.
@RequestMapping(path="/api/1", consumes = MediaType.APPLICATION_JSON_UTF8_VALUE, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
ResultActions result =
mockMvc.perform(post("/api/1/city")
.contentType(MediaType.APPLICATION_JSON_UTF8_VALUE)
.content(requestJson))
.andDo(print());
그러나, 특별한 경우로
SpringBoot의 UI 모델 또는 DTO에 lombok, Jackson의 JsonCreator가 잘못 결합될 때. 이상한 에러가 난다.
그래서 몇시간동안 삽질, 주화 입마에 빠질 수 있다.
org.springframework.web.HttpMediaTypeNotSupportedException
문제가 되는 코드는 다음과 같다.
@RestController
@RequestMapping("/api/test")
public class CityController {
@PostMapping
public Product post(@RequestBody City city) {
.....
}
}
@Data
public class City {
@NonNull
private Long id;
@NonNull
private String name;
@NonNull
private Integer population;
@JsonCreator
public City(Long id, String name, Integer population) {
this.id = id;
this.name = name;
this.population = population;
}
}
또는 아래와 같이 AllArgsConstructor에 JsonCreator를 사용하면 안된다.
@Data
@Builder
@AllArgsConstructor(onConstructor = @__(@JsonCreator))
public class City {
@NonNull
private Long id;
@NonNull
private String name;
@NonNull
private Integer population;
}
HttpMediaTypeNotSupportedException가 발생한다.
따라서 아래와 같이 모델을 변경하니 잘 동작한다.
@Data
@AllArgsConstructor
@NoArgsConstructor
public class City {
@NonNull
private Long id;
@NonNull
private String name;
@NonNull
private Integer population;
}
이미 보고는 되어 있는데.. 해결 방안은 딱히 없으니. 알아서 잘 피해야 할 것 같다.
https://github.com/FasterXML/jackson-databind/issues/1239
https://github.com/spring-projects/spring-boot/issues/12568
에러
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframework.http.converter.HttpMessageConversionException: Type definition error: [simple type, class com.example.model.City]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.example.model.City` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
at [Source: (PushbackInputStream); line: 2, column: 3]
생성자에 Jackson의 @JsonCreator를 붙여 해결하고 싶겠지만. 결국 문제가 발생할 것이다.
@Data
public class City {
@NonNull
private Long id;
@NonNull
private String name;
@NonNull
private Integer population;
@JsonCreator
public City(Long id, String name, Integer population) {
this.id = id;
this.name = name;
this.population = population;
}
}
아래 org.springframework.web.HttpMediaTypeNotSupportedException 이 발생하게 된다...
최대한 Lombok을 잘 사용하는 것이 좋다.
sprin test를 사용하다가 로그에 request의 body(특정 json)가 안나오는 문제가 있다.
MockHttpServletRequest:
HTTP Method = POST
Request URI = /api/1/city/
Parameters = {}
Headers = [Accept:"application/json"]
Body = <no character encoding set>
Session Attrs = {}
그래서 코드에 characterEncoding을 추가했더니 문제가 발생하지 않았다.
ResultActions result =
mockMvc.perform(post("/api/1/city/", 10L)
.accept(MediaType.APPLICATION_JSON)
.characterEncoding("utf-8")
.content(requestJson))
.andDo(print());
다음과 같이 request의 body 정보가 출력되었다.
MockHttpServletRequest:
HTTP Method = POST
Request URI = /api/1/city/
Parameters = {}
Headers = [Accept:"application/json"]
Body = {
"id" : 10,
"name" : "Bratislava",
"population" : 432000
}
Session Attrs = {}
gradle, asciidoctor를 사용할 때
maven plugin 예제를 참고하다 잘 안되는 경우가 있다.
(디폴트로) maven의 adoc 파일을 생성하는 위치는 src/main/asciidoc 인 반면,
gradle 의 경우는 src/docs/asciidoc 이다.
gradle 사용자는 주의해야 한다.
Proxy를 사용해야 하는 환경에서
grails 2.x 컴파일을 하는데, 제대로 inhouse library를 다운받지 못한 현상이 발생했다.
마치 nonProxy 설정에 문제가 있다는 느낌..
grails add-proxy client --host=${PROXY_HOST} --port=${PROXY_PORT} --noproxy="${JAVA_NO_PROXY}"
grails set-proxy client
grails compile
grails dependency-report를 실행해보면, inhouse lib을 못가져온다.
grails compile 을 실행하니 제대로 동작한다.
예전에 2000년 쯤에 "구XX 커피"라는 회사에 몇 개월 알바를 한 적이 있다.
이때 오리온 동천동 물류 창고(현재 동천역)에 납품한 적이 있었는데. 엄청난 규모의 물류 창고 크기에 압도된 적이 있다.실제 재고량에 압도되었던 그 때의 기억이 난다.
최근에 물류 쪽 얘기를 들어보면서 모르는 단어에 대해 링크를 걸어본다.
https://m.blog.naver.com/jackshin01/220969531915
https://m.blog.naver.com/jackshin01/220982419044
DAS, PAS, DPS
https://m.blog.naver.com/jackshin01/221039015878
냉동 창고
http://www.kharn.kr/news/article.html?no=7900
라스트 밀과 풀필먼트
[리스크] querydsl 라이브러리 이슈. (0) | 2019.08.20 |
---|---|
무표 폰트 다운로드받는 곳 (0) | 2019.08.06 |
리눅스 서버의 한글이 잘 안보일 때, 내 환경을 다시 돌아봐야 겠다. (0) | 2019.06.18 |
펌) 마이그레이션 전략 (0) | 2019.04.24 |
구글의 원격 협업 관련 내용 (0) | 2019.04.05 |
인사관점에서 본 넷플릭스의 밝음과 어두움을 볼 수 있는 좋은 책이었다.
기억나는 부분
성장 과정을 추억한 것이 아니라
높은 성과를 내는 조직문화를 만들려면 어떻게 해야 할까를 안내하는 내용이다.
낡은 평가 시스템으로 인해 조직 전체가 느려지고 쓸떼 없는 경쟁력을 소진시키는 현실들..불필요한 절차를 제거했다.
처음부터 고성과자를 뽑고 맞는 일을 준다.
관리자는 직원에게 회사가 정확히 어디에 있는지 소개하고, 성취하려고 하는 것이 무엇인지 설명한다.
또한 당면과제와 비즈니스를 명확히 하고 소통하는데 시간을 많이 들여야 한다.
극도로 솔직하고 격렬히 토론한다.
직원의 가치만큼 보상한다.
(속이 다 후련한 문화이다..)
"쿠팡" - 우리가 혁신하는 이유 (0) | 2019.10.05 |
---|---|
구글을 움직이는 10가지 황금율 (0) | 2019.08.17 |
'플랫폼 제국의 미래'를 보고 (0) | 2019.06.24 |
소프트 스킬의 잼있는 부분 발췌 (0) | 2019.03.30 |
[추천] 신호와 소음 : 미래는 어떻게 당신 손에 잡히는가 (0) | 2016.09.22 |