Skip to content

Commit 46585e2

Browse files
committed
docs: Create README.md
1 parent 6573329 commit 46585e2

File tree

6 files changed

+207
-0
lines changed

6 files changed

+207
-0
lines changed

README.md

+84
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# API Response format 만들어 보기
2+
3+
Spring 과제를 하면서 팀원들과 과제 리뷰를 하던 중 팀원들마다 API response로 전달하는 데이터와 형식이 다 제각각인 것을 발견했다. 또 각 DTO마다 response의 형식이 달라진다는 문제점도 있었다. 지금은 한두명이서 과제를 하고 있지만, 어떤 형식으로 response를 보내줄 것인지는 형식을 정하는 것은 협업을 위해서 필수다.
4+
5+
많이 사용하는 API response 형식이 있다고 하지만, 공부도 하고 프로젝트에서 써볼 API response 형식을 만들어보기로 했다.
6+
7+
<br/>
8+
9+
## 설계를 할 때 고려한 사항
10+
11+
### 1. HTTP Status code는 반드시 보내준다.
12+
13+
몇몇 블로그에서는 HTTP Status code 없이 response를 보내는 것을 선호했는데 그 이유는 다음과 같다.
14+
15+
* HTTP 공식 사양에 41개의 너무 많은 statsu code가 존재해서 응답을 처리하는 경우의 수가 너무 많다.
16+
* HTTP는 브라우저를 대상으로 하는데 REST API의 대상은 브라우저만이 아니라 많은 클라이언트 프로그램을 포함한다.
17+
18+
위 말이 다 맞지만, HTTP Status code만으로도 요청/응답에 대한 충분히 많은 정보를 제공할 수 있다고 생각해서 포함시키기로 했다.
19+
20+
<br/>
21+
22+
### 2. Response의 구조는 최대한 동일하게 설계한다.
23+
24+
성공했을 때와 실패/에러의 경우 response의 형식을 동일하게 유지해야 하려고 했는데 문제가 있었다.
25+
26+
```json
27+
{
28+
"status": "string",
29+
"message": "string",
30+
"result": "object"
31+
}
32+
```
33+
34+
<br/>
35+
36+
위와 같은 방식으로 설계하면 성공/실패/에러 모든 경우에 동일한 형식으로 response를 전달할 수 있다. 하지만 성공했을 때와 실패/에러가 발생한 경우 result에 값이 들어가는게 뭔가 이상했다. 그래서 두 번쨰 방식을 만들어 봤다.
37+
38+
```json
39+
// success
40+
{
41+
"status": "string",
42+
"message": "string",
43+
"data": "object"
44+
}
45+
46+
// error
47+
{
48+
"status": "string",
49+
"message": "string"
50+
}
51+
```
52+
53+
<br/>
54+
55+
개인적으로는 두번째 방식이 더 마음에 드는데, 뭐가 더 좋은 방식인지는 아직 감이 잘 안 잡힌다.
56+
57+
더 많은 프로젝트를 해보고 실제로 사용해보면서 더 보완해나가자.
58+
59+
<br/>
60+
61+
## 보완해야 할 사항
62+
63+
- Response body로 전달되는 data에 front와 약속한 에러 코드를 보내주자.(5~6자리 숫자로)
64+
65+
- error와 fail을 명확히 구분해서 다시 구조를 잡자.
66+
67+
- BindingResult, FieldError, ObjectError 등 validation 관련 에러에 대해 공부하자.
68+
69+
<br/>
70+
<br/>
71+
72+
## Reference
73+
74+
[JSend](https://github.com/omniti-labs/jsend)
75+
76+
[rest-api-response-format](https://github.com/cryptlex/rest-api-response-format)
77+
78+
[REST API Response Body 형식에 대한 경험적 구조](https://blog.storyg.co/rest-api-response-body-best-pratics)
79+
80+
[API Response 포맷에 관한 고찰](https://blog.lyunho.kim/api-response)
81+
82+
[스프링 API 공통 응답 포맷 개발하기](https://velog.io/@qotndus43/%EC%8A%A4%ED%94%84%EB%A7%81-API-%EA%B3%B5%ED%86%B5-%EC%9D%91%EB%8B%B5-%ED%8F%AC%EB%A7%B7-%EA%B0%9C%EB%B0%9C%ED%95%98%EA%B8%B0)
83+
84+
[REST API Response Format, 응답 객체는 어떤 형식이 좋을까?](https://wildeveloperetrain.tistory.com/240)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.example.apiresponse;
2+
3+
import com.example.apiresponse.apiresponse2.ApiResponse2;
4+
import com.example.apiresponse.apiresponse2.ApiResponse2.SuccessBody;
5+
import com.example.apiresponse.apiresopnse1.ApiResponse1;
6+
import org.springframework.http.HttpStatus;
7+
import org.springframework.http.ResponseEntity;
8+
import org.springframework.web.bind.annotation.PostMapping;
9+
import org.springframework.web.bind.annotation.RequestBody;
10+
import org.springframework.web.bind.annotation.RestController;
11+
12+
@RestController
13+
public class ApiController {
14+
15+
@PostMapping("")
16+
public ResponseEntity<ApiResponse1> test(@RequestBody RequestDto responseDto) {
17+
ApiResponse1 apiResponse = ApiResponse1.builder()
18+
.result(responseDto)
19+
.message("hello")
20+
.status(responseStatus.SUCCESS.getStatus())
21+
.build();
22+
return ResponseEntity.status(HttpStatus.CREATED)
23+
.body(apiResponse);
24+
}
25+
@PostMapping("generic")
26+
public ResponseEntity<SuccessBody<?>> test2(@RequestBody RequestDto responseDto) {
27+
SuccessBody<Object> apiResponse = ApiResponse2.SuccessBody
28+
.builder()
29+
.message("커스텀 메세지")
30+
.data(responseDto)
31+
.status(responseStatus.FAIL.getStatus())
32+
.build();
33+
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
34+
.body(apiResponse);
35+
}
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.example.apiresponse;
2+
3+
4+
import lombok.Getter;
5+
6+
@Getter
7+
public class RequestDto {
8+
private String name;
9+
private int age;
10+
private String email;
11+
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.example.apiresponse.apiresopnse1;
2+
3+
import lombok.Builder;
4+
import lombok.Getter;
5+
6+
@Getter
7+
@Builder
8+
public class ApiResponse1 {
9+
10+
@Builder.Default
11+
private String status = "success";
12+
@Builder.Default
13+
private String message = "";
14+
private Object result;
15+
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package com.example.apiresponse.apiresponse2;
2+
3+
import lombok.Builder;
4+
import lombok.Getter;
5+
6+
public class ApiResponse2 {
7+
private static final String SUCCESS_STATUS = "success";
8+
private static final String FAIL_STATUS = "fail";
9+
private static final String ERROR_STATUS = "error";
10+
@Getter
11+
@Builder
12+
public static class SuccessBody<T> {
13+
@Builder.Default
14+
private String status = SUCCESS_STATUS;
15+
@Builder.Default
16+
private String message = "Success message";
17+
private T data;
18+
}
19+
20+
@Getter
21+
@Builder
22+
public static class ErrorBody {
23+
@Builder.Default
24+
private String status = ERROR_STATUS;
25+
@Builder.Default
26+
private String message = "Error message";
27+
}
28+
29+
@Getter
30+
@Builder
31+
public static class Fail<E> {
32+
@Builder.Default
33+
private String status = FAIL_STATUS;
34+
@Builder.Default
35+
private String message = "Fail message";
36+
private E data;
37+
38+
}
39+
40+
/*
41+
public static class FieldError {
42+
private String status;
43+
private String message;
44+
private List<Error>
45+
}
46+
*/
47+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.example.apiresponse;
2+
3+
import lombok.AllArgsConstructor;
4+
import lombok.Getter;
5+
6+
@Getter
7+
@AllArgsConstructor
8+
public enum responseStatus {
9+
SUCCESS("success"), ERROR("error"), FAIL("fail");
10+
11+
private final String status;
12+
}

0 commit comments

Comments
 (0)