SlideShare a Scribd company logo
1 of 107
Download to read offline
0
1
2
3
4
5
6
7
8
9
10
2017년1월
2017년2월
2017년3월
2017년4월
2017년5월
2017년6월
2017년7월
2017년8월
2017년9월
2017년10월
2017년11월
2017년12월
2018년1월
2018년2월
2018년3월
2018년4월
2018년5월
2018년6월
2018년7월
2018년8월
2018년9월
2018년10월
2018년11월
2018년12월
2019년1월
2019년2월
2019년3월
2019년4월
2019년5월
2019년6월
2019년7월
푸시 이메일 문자 카카오톡
0
1
2
3
4
5
6
7
푸시 이메일 문자 카카오톡 비즈
//	v1.0	Message
{
"id": "3bnKmSrLEdrckTlA",
"to": "jinho.shin",
"title": "제목",
"body": "내용",
"messageType": "AD",
"status": "FAILED",
"createdDateTime": "2019-11-10T14:48:42.846+09:00"
}
//	v1.1	Message
{
"id": "V9MdIGgVEMCIcs5g",
"to": "jinho.shin",
"title": "제목",
"body": "내용",
"messageType": "AD",
"status": "FAILED",
"statusMessage": "'jinho.shin'	is	not	registered",
"createdDateTime": "2019-11-10T14:51:04.262+09:00",
"updatedDateTime": "2019-11-10T14:51:04.262+09:00"
}
return	V1_0Message
.builder()
.id(messageEntity.getId())
.title(messageEntity.getTitle())
.body(messageEntity.getBody())
.messageType(messageEntity.getMessageType())
.status(messageEntity.getStatus())
.createdDateTime(messageEntity.getCreatedDateTime())
.build();
return	V2_0Message
.builder()
.id(messageEntity.getId())
.title(messageEntity.getTitle())
.body(messageEntity.getBody())
.messageType(messageEntity.getMessageType())
.status(messageEntity.getStatus())
.statusMessage(messageEntity.getStatusMessage())
.createdDateTime(messageEntity.getCreatedDateTime())
.updatedDateTime(messageEntity.getUpdatedDateTime())
.build();
2927
2846
88
11
0.23
처리량
@Mapper
public	interface MessageMapper {
MessageEntity toMessageEntity(V1_0SendMessage	v1_0SendMessage);
}
…
@Override
public MessageEntity toMessageEntity(V1_0SendMessage	v1_0SendMessage)	{
if (	v1_0SendMessage	== null	)	{
return	null;
}
MessageEntity messageEntity =	new MessageEntity();
messageEntity.setTo(	v1_0SendMessage.getTo()	);
…
return messageEntity;
}
@Mapper(componentModel =	"spring")	
public	interface MessageMapper {
…
@Mapping(source	= "id", target	= "message.id")
V1_0SendMessageResponse	toV1_0SendMessageResponse(MessageEntity messageEntity);
default V1_0GetMessageResponse	toV1_0GetMessageResponse(MessageEntity messageEntity)	{
…
}
}
$	curl	-i -X	POST 
-H	'X-Secret-Key:vOwlgJY4'	
-H	'Content-Type:application/json'	
-d	'{"to":"jinho.shin","title":"힌트는","body":"오타","messageType":"NOTIFIACITON	"}'	
'http://127.0.0.1/v1.0/messages'
{
"header": {
"isSuccessful": false,
"resultCode": -400,
"resultMessage": "Client	Error.	property	is	invalid."
}
}
$	curl	-i -X	POST 
-H	'X-Secret-Key:vOwlgJY4'	
-H	'Content-Type:application/json'	
-d	'{"to":"jinho.shin","title":"힌트는","body":"오타","messageType":"NOTIFIACITON	"}'	
'http://127.0.0.1/v1.0/messages’
{
"header": {
"isSuccessful": false,
"resultCode": -400,
"resultMessage": "messageType['NOTIFIACITON']	- must	match	"AD|NOTIFICATION""
}
}
public	boolean sendMessageV1_0(V1_0SendMessage	sendMessage)	{
if (StringUtils.isBlank(sendMessage.getTo()))	{
throw	new	IllegalArgumentException("to	is	blank");
}
if (!EmojiParser .parseToAliases(sendMessage.getTo()).equals(sendMessage.getTo()))	{
throw	new	IllegalArgumentException("to	contains	emoji");
}
if (!"NOTIFICATION".equals(sendMessage.getMessageType())	&&
!"AD".equals(sendMessage.getMessageType()))	{
throw	new	IllegalArgumentException("messageType[" + sendMessage.getMessageType()	+ …);
}
…
public	class V1_0SendMessage	{
@Length(max	=	128)	//	최대 길이 127
@NotBlank //	값이 없거나 빈 문자열은 안됨
private String	to;
@Length(max	=	256)
@NotBlank
private String	title;
@Length(max	=	1024)
@NotBlank
private	String	body;
@NotBlank
@Pattern(regexp =	"AD|NOTIFICATION")	//	‘AD’,	‘NOTIFICATION’	둘 중 하나
private String	messageType;
}
@RestController
public	class V1_0MessageController	{
…
@PostMapping("/v1.0/messages")
public	V1_0SendMessageResponse	sendMessage(@RequestBody @Valid V1_0SendMessage	sendMessage)	{
…
}
}
@Validated
@Service
public	class MessageService {
public MessageEntity send(@Valid	@NotNull MessageEntity messageEntity)	{
…
}
}
@ControllerAdvice
public	class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
@ExceptionHandler(value	=	ConstraintViolationException.class)
@ResponseBody
protected Response	handleException(ConstraintViolationException exception)	{
Response	response =	…
…
return response;
}
public	interface	ConstraintViolation<T>	{
String	getMessage(); //	생성된 메시지
Object	getLeafBean(); //	데이터 유효성 검사한 객체
Path	getPropertyPath(); //	데이터 유효성 검사가 실패한 속성
Object	getInvalidValue(); //	잘 못된 값
...
}
$	curl	-i -X	POST 
-H	'X-Secret-Key:vOwlgJY4'	
-H	'Content-Type:application/json'	
-d	'{"to":"jinho.shin","title":"힌트는","body":"오타","messageType":"NOTIFIACITON	"}'	
'http://127.0.0.1/v1.0/messages’
{
"header": {
"isSuccessful": false,
"resultCode": -400,
"resultMessage": "messageType['NOTIFIACITON']	- must	match	"AD|NOTIFICATION""
}
}
@Target({METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
@Retention(RUNTIME)
@Constraint(validatedBy =	NoEmojiValidator.class)
@Documented
public @interface NoEmoji {
String	message()	default "Emoji	is	not	allowed";
…
}
public	class NoEmojiValidator implements ConstraintValidator<NoEmoji, String>	{
@Override
public boolean isValid(String	value,	ConstraintValidatorContext context)	{
if (StringUtils.isEmpty(value)	==	true)	{
return	true;
}
return EmojiParser.parseToAliases(value).equals(value);
}
}
public	class	V1_0SendMessage	{
@NoEmoji
@Length(max	=	128)	//	최대 길이 127
@NotBlank //	값이 없거나 빈 문자열은 안됨
private String	to;
@Length(max	=	256)
@NotBlank
private String	title;
@Length(max	=	1024)
@NotBlank
private String	body;
@NotBlank
@Pattern(regexp =	"AD|NOTIFICATION")	//	‘AD’,	‘NOTIFICATION’	둘 중 하나
private String	messageType;
}
public	class V1_0SendMessage	{
@To
private String	to;
@Length(max	=	256)
@NotBlank
private String	title;
@Length(max	=	1024)
@NotBlank
private String	body;
@NotBlank
@Pattern(regexp =	"AD|NOTIFICATION")	//	‘AD’,	‘NOTIFICATION’	둘 중 하나
private String	messageType;
}
ValidationMessage.properties 파일
org.hibernate.validator.constraints.EAN.message = invalid	{type}	barcode
org.hibernate.validator.constraints.Email.message = not	a	well-formed	email	address
org.hibernate.validator.constraints.ISBN.message = invalid	ISBN
org.hibernate.validator.constraints.Length.message = length	must	be	between	{min}	and	{max}
org.hibernate.validator.constraints.CodePointLength.message = length	must	be	between	{min}	and	{max}
ValidationMessage_ko.properties 파일
org.hibernate.validator.constraints.EAN.message = 올바르지 않은 {type}	바코드입니다
org.hibernate.validator.constraints.Email.message = 올바른 형식의 이메일 주소여야 합니다
org.hibernate.validator.constraints.ISBN.message = 올바르지 않은 ISBN입니다
org.hibernate.validator.constraints.Length.message = 길이가 {min}에서 {max}	사이여야 합니다
org.hibernate.validator.constraints.CodePointLength.message = 길이가 {min}에서 {max}	사이여야 합니다
[2019] PaaS & API Experience: 좋은 API DX를 제공하기 위한 작은 걸음
[2019] PaaS & API Experience: 좋은 API DX를 제공하기 위한 작은 걸음
[2019] PaaS & API Experience: 좋은 API DX를 제공하기 위한 작은 걸음
[2019] PaaS & API Experience: 좋은 API DX를 제공하기 위한 작은 걸음
[2019] PaaS & API Experience: 좋은 API DX를 제공하기 위한 작은 걸음
[2019] PaaS & API Experience: 좋은 API DX를 제공하기 위한 작은 걸음
[2019] PaaS & API Experience: 좋은 API DX를 제공하기 위한 작은 걸음
[2019] PaaS & API Experience: 좋은 API DX를 제공하기 위한 작은 걸음
[2019] PaaS & API Experience: 좋은 API DX를 제공하기 위한 작은 걸음
[2019] PaaS & API Experience: 좋은 API DX를 제공하기 위한 작은 걸음
[2019] PaaS & API Experience: 좋은 API DX를 제공하기 위한 작은 걸음
[2019] PaaS & API Experience: 좋은 API DX를 제공하기 위한 작은 걸음
[2019] PaaS & API Experience: 좋은 API DX를 제공하기 위한 작은 걸음

More Related Content

More from NHN FORWARD

[2019] 아직도 돈 주고 DB 쓰나요 for DBA
[2019] 아직도 돈 주고 DB 쓰나요 for DBA[2019] 아직도 돈 주고 DB 쓰나요 for DBA
[2019] 아직도 돈 주고 DB 쓰나요 for DBANHN FORWARD
 
[2019] 비주얼 브랜딩: Basic system
[2019] 비주얼 브랜딩: Basic system[2019] 비주얼 브랜딩: Basic system
[2019] 비주얼 브랜딩: Basic systemNHN FORWARD
 
[2019] PAYCO 매거진 서버 Kotlin 적용기
[2019] PAYCO 매거진 서버 Kotlin 적용기[2019] PAYCO 매거진 서버 Kotlin 적용기
[2019] PAYCO 매거진 서버 Kotlin 적용기NHN FORWARD
 
[2019] 벅스 5.0 (feat. Kotlin, Jetpack)
[2019] 벅스 5.0 (feat. Kotlin, Jetpack)[2019] 벅스 5.0 (feat. Kotlin, Jetpack)
[2019] 벅스 5.0 (feat. Kotlin, Jetpack)NHN FORWARD
 
[2019] Java에서 Fiber를 이용하여 동시성concurrency 프로그래밍 쉽게 하기
[2019] Java에서 Fiber를 이용하여 동시성concurrency 프로그래밍 쉽게 하기[2019] Java에서 Fiber를 이용하여 동시성concurrency 프로그래밍 쉽게 하기
[2019] Java에서 Fiber를 이용하여 동시성concurrency 프로그래밍 쉽게 하기NHN FORWARD
 
[2019] PAYCO 쇼핑 마이크로서비스 아키텍처(MSA) 전환기
[2019] PAYCO 쇼핑 마이크로서비스 아키텍처(MSA) 전환기[2019] PAYCO 쇼핑 마이크로서비스 아키텍처(MSA) 전환기
[2019] PAYCO 쇼핑 마이크로서비스 아키텍처(MSA) 전환기NHN FORWARD
 
[2019] 비식별 데이터로부터의 가치 창출과 수익화 사례
[2019] 비식별 데이터로부터의 가치 창출과 수익화 사례[2019] 비식별 데이터로부터의 가치 창출과 수익화 사례
[2019] 비식별 데이터로부터의 가치 창출과 수익화 사례NHN FORWARD
 
[2019] 게임 서버 대규모 부하 테스트와 모니터링 이렇게 해보자
[2019] 게임 서버 대규모 부하 테스트와 모니터링 이렇게 해보자[2019] 게임 서버 대규모 부하 테스트와 모니터링 이렇게 해보자
[2019] 게임 서버 대규모 부하 테스트와 모니터링 이렇게 해보자NHN FORWARD
 
[2019] 200만 동접 게임을 위한 MySQL 샤딩
[2019] 200만 동접 게임을 위한 MySQL 샤딩[2019] 200만 동접 게임을 위한 MySQL 샤딩
[2019] 200만 동접 게임을 위한 MySQL 샤딩NHN FORWARD
 
[2019] 언리얼 엔진을 통해 살펴보는 리플렉션과 가비지 컬렉션
[2019] 언리얼 엔진을 통해 살펴보는 리플렉션과 가비지 컬렉션[2019] 언리얼 엔진을 통해 살펴보는 리플렉션과 가비지 컬렉션
[2019] 언리얼 엔진을 통해 살펴보는 리플렉션과 가비지 컬렉션NHN FORWARD
 
[2019] 글로벌 게임 서비스 노하우
[2019] 글로벌 게임 서비스 노하우[2019] 글로벌 게임 서비스 노하우
[2019] 글로벌 게임 서비스 노하우NHN FORWARD
 
[2019] 배틀로얄 전장(map) 제작으로 알아보는 슈팅 게임 레벨 디자인
[2019] 배틀로얄 전장(map) 제작으로 알아보는 슈팅 게임 레벨 디자인[2019] 배틀로얄 전장(map) 제작으로 알아보는 슈팅 게임 레벨 디자인
[2019] 배틀로얄 전장(map) 제작으로 알아보는 슈팅 게임 레벨 디자인NHN FORWARD
 
[2019] 위치 기반 빅 데이터의 시각화와 지도
[2019] 위치 기반 빅 데이터의 시각화와 지도[2019] 위치 기반 빅 데이터의 시각화와 지도
[2019] 위치 기반 빅 데이터의 시각화와 지도NHN FORWARD
 
[2019] 웹 프레젠테이션 개발기: Dooray! 발표 모드 해부하기
[2019] 웹 프레젠테이션 개발기: Dooray! 발표 모드 해부하기[2019] 웹 프레젠테이션 개발기: Dooray! 발표 모드 해부하기
[2019] 웹 프레젠테이션 개발기: Dooray! 발표 모드 해부하기NHN FORWARD
 
[2019] 레거시 웹 서비스 길들이기: 서버 개발자의 SPA 적용기
[2019] 레거시 웹 서비스 길들이기: 서버 개발자의 SPA 적용기[2019] 레거시 웹 서비스 길들이기: 서버 개발자의 SPA 적용기
[2019] 레거시 웹 서비스 길들이기: 서버 개발자의 SPA 적용기NHN FORWARD
 
[2019] 실용적인 프런트엔드 테스트 전략
[2019] 실용적인 프런트엔드 테스트 전략[2019] 실용적인 프런트엔드 테스트 전략
[2019] 실용적인 프런트엔드 테스트 전략NHN FORWARD
 
[2019] 점진적으로 프런트엔드 프레임워크 교체하기
[2019] 점진적으로 프런트엔드 프레임워크 교체하기[2019] 점진적으로 프런트엔드 프레임워크 교체하기
[2019] 점진적으로 프런트엔드 프레임워크 교체하기NHN FORWARD
 
[2019] 하이 벅스. 내 목소리 들리니? : DNN 보이스 트리거 개발기
[2019] 하이 벅스. 내 목소리 들리니? : DNN 보이스 트리거 개발기[2019] 하이 벅스. 내 목소리 들리니? : DNN 보이스 트리거 개발기
[2019] 하이 벅스. 내 목소리 들리니? : DNN 보이스 트리거 개발기NHN FORWARD
 
[2019] 처음 분위기 그대로: Music Mood Classification
[2019] 처음 분위기 그대로: Music Mood Classification[2019] 처음 분위기 그대로: Music Mood Classification
[2019] 처음 분위기 그대로: Music Mood ClassificationNHN FORWARD
 
[2019] 딥러닝을 이용한 가상 피팅 룸
[2019] 딥러닝을 이용한 가상 피팅 룸[2019] 딥러닝을 이용한 가상 피팅 룸
[2019] 딥러닝을 이용한 가상 피팅 룸NHN FORWARD
 

More from NHN FORWARD (20)

[2019] 아직도 돈 주고 DB 쓰나요 for DBA
[2019] 아직도 돈 주고 DB 쓰나요 for DBA[2019] 아직도 돈 주고 DB 쓰나요 for DBA
[2019] 아직도 돈 주고 DB 쓰나요 for DBA
 
[2019] 비주얼 브랜딩: Basic system
[2019] 비주얼 브랜딩: Basic system[2019] 비주얼 브랜딩: Basic system
[2019] 비주얼 브랜딩: Basic system
 
[2019] PAYCO 매거진 서버 Kotlin 적용기
[2019] PAYCO 매거진 서버 Kotlin 적용기[2019] PAYCO 매거진 서버 Kotlin 적용기
[2019] PAYCO 매거진 서버 Kotlin 적용기
 
[2019] 벅스 5.0 (feat. Kotlin, Jetpack)
[2019] 벅스 5.0 (feat. Kotlin, Jetpack)[2019] 벅스 5.0 (feat. Kotlin, Jetpack)
[2019] 벅스 5.0 (feat. Kotlin, Jetpack)
 
[2019] Java에서 Fiber를 이용하여 동시성concurrency 프로그래밍 쉽게 하기
[2019] Java에서 Fiber를 이용하여 동시성concurrency 프로그래밍 쉽게 하기[2019] Java에서 Fiber를 이용하여 동시성concurrency 프로그래밍 쉽게 하기
[2019] Java에서 Fiber를 이용하여 동시성concurrency 프로그래밍 쉽게 하기
 
[2019] PAYCO 쇼핑 마이크로서비스 아키텍처(MSA) 전환기
[2019] PAYCO 쇼핑 마이크로서비스 아키텍처(MSA) 전환기[2019] PAYCO 쇼핑 마이크로서비스 아키텍처(MSA) 전환기
[2019] PAYCO 쇼핑 마이크로서비스 아키텍처(MSA) 전환기
 
[2019] 비식별 데이터로부터의 가치 창출과 수익화 사례
[2019] 비식별 데이터로부터의 가치 창출과 수익화 사례[2019] 비식별 데이터로부터의 가치 창출과 수익화 사례
[2019] 비식별 데이터로부터의 가치 창출과 수익화 사례
 
[2019] 게임 서버 대규모 부하 테스트와 모니터링 이렇게 해보자
[2019] 게임 서버 대규모 부하 테스트와 모니터링 이렇게 해보자[2019] 게임 서버 대규모 부하 테스트와 모니터링 이렇게 해보자
[2019] 게임 서버 대규모 부하 테스트와 모니터링 이렇게 해보자
 
[2019] 200만 동접 게임을 위한 MySQL 샤딩
[2019] 200만 동접 게임을 위한 MySQL 샤딩[2019] 200만 동접 게임을 위한 MySQL 샤딩
[2019] 200만 동접 게임을 위한 MySQL 샤딩
 
[2019] 언리얼 엔진을 통해 살펴보는 리플렉션과 가비지 컬렉션
[2019] 언리얼 엔진을 통해 살펴보는 리플렉션과 가비지 컬렉션[2019] 언리얼 엔진을 통해 살펴보는 리플렉션과 가비지 컬렉션
[2019] 언리얼 엔진을 통해 살펴보는 리플렉션과 가비지 컬렉션
 
[2019] 글로벌 게임 서비스 노하우
[2019] 글로벌 게임 서비스 노하우[2019] 글로벌 게임 서비스 노하우
[2019] 글로벌 게임 서비스 노하우
 
[2019] 배틀로얄 전장(map) 제작으로 알아보는 슈팅 게임 레벨 디자인
[2019] 배틀로얄 전장(map) 제작으로 알아보는 슈팅 게임 레벨 디자인[2019] 배틀로얄 전장(map) 제작으로 알아보는 슈팅 게임 레벨 디자인
[2019] 배틀로얄 전장(map) 제작으로 알아보는 슈팅 게임 레벨 디자인
 
[2019] 위치 기반 빅 데이터의 시각화와 지도
[2019] 위치 기반 빅 데이터의 시각화와 지도[2019] 위치 기반 빅 데이터의 시각화와 지도
[2019] 위치 기반 빅 데이터의 시각화와 지도
 
[2019] 웹 프레젠테이션 개발기: Dooray! 발표 모드 해부하기
[2019] 웹 프레젠테이션 개발기: Dooray! 발표 모드 해부하기[2019] 웹 프레젠테이션 개발기: Dooray! 발표 모드 해부하기
[2019] 웹 프레젠테이션 개발기: Dooray! 발표 모드 해부하기
 
[2019] 레거시 웹 서비스 길들이기: 서버 개발자의 SPA 적용기
[2019] 레거시 웹 서비스 길들이기: 서버 개발자의 SPA 적용기[2019] 레거시 웹 서비스 길들이기: 서버 개발자의 SPA 적용기
[2019] 레거시 웹 서비스 길들이기: 서버 개발자의 SPA 적용기
 
[2019] 실용적인 프런트엔드 테스트 전략
[2019] 실용적인 프런트엔드 테스트 전략[2019] 실용적인 프런트엔드 테스트 전략
[2019] 실용적인 프런트엔드 테스트 전략
 
[2019] 점진적으로 프런트엔드 프레임워크 교체하기
[2019] 점진적으로 프런트엔드 프레임워크 교체하기[2019] 점진적으로 프런트엔드 프레임워크 교체하기
[2019] 점진적으로 프런트엔드 프레임워크 교체하기
 
[2019] 하이 벅스. 내 목소리 들리니? : DNN 보이스 트리거 개발기
[2019] 하이 벅스. 내 목소리 들리니? : DNN 보이스 트리거 개발기[2019] 하이 벅스. 내 목소리 들리니? : DNN 보이스 트리거 개발기
[2019] 하이 벅스. 내 목소리 들리니? : DNN 보이스 트리거 개발기
 
[2019] 처음 분위기 그대로: Music Mood Classification
[2019] 처음 분위기 그대로: Music Mood Classification[2019] 처음 분위기 그대로: Music Mood Classification
[2019] 처음 분위기 그대로: Music Mood Classification
 
[2019] 딥러닝을 이용한 가상 피팅 룸
[2019] 딥러닝을 이용한 가상 피팅 룸[2019] 딥러닝을 이용한 가상 피팅 룸
[2019] 딥러닝을 이용한 가상 피팅 룸
 

[2019] PaaS & API Experience: 좋은 API DX를 제공하기 위한 작은 걸음