๐ ํ์ฌ ์ํฉ ๋ฐ ๋ฐฐ๊ฒฝ ์ค๋ช
๐ฝ DTO ํ๋ฆ
์ฝ๋์ ๊ฐ๋ ์ฑ๊ณผ ํ๋ฆ์ ๊น๋ํ๊ฒ ์ ์งํ๊ธฐ ์ํด ํด๋์ค ๊ฐ ์ ๋ณด๋ฅผ ์ ๋ฌํ ๋ DTO ํด๋์ค๋ฅผ ํ์ฉํ๊ณ ์๋ค. Swagger๋ฅผ ์ฌ์ฉํ๊ณ ์์ผ๋ฏ๋ก, DTO ํด๋์ค์ ๋ช ๋ช ๊ท์น์ ๋ฐฑ์๋ ๋ด๋ถ์ ์ผ๋ก ํต์ผํ์๋ค.
- ์ ๋ ฅ์ ์ ๋ฌํ๋ DTO: ~Request
- ์ถ๋ ฅ์ ์ ๋ฌํ๋ DTO: ~Response
- ๋ด๋ถ์ ์ผ๋ก ์ ๋ณด๋ฅผ ์ ๋ฌํ๋ DTO: ~Dto
โถ ๊ตฌํ ์ฝ๋
๐ฝ MemberRegisterRequest DTO
@Getter
@Builder
@RequiredArgsConstructor
public class MemberRegisterRequest {
@Schema(description = "ํ์์ ์ด๋ฆ", example = "์ด์์", required = true)
private final String name;
@Schema(description = "ํ์์ ๋๋ค์", example = "twocowsilver", required = true)
private final String nickname;
@Schema(description = "ํ์์ ์ง์ญ", example = "๋๋๋ฌธ๊ตฌ", required = true)
private final String location;
...
}
๐ฝ MemberInfoResponse DTO
@Getter
@Builder
@RequiredArgsConstructor
public class MemberInfoResponse {
@Schema(description = "ํ์์ Id", example = "1")
private final Long id;
@Schema(description = "ํ์์ ์ด๋ฆ", example = "์ด์์")
private final String name;
@Schema(description = "ํ์์ ๋๋ค์", example = "twocowsilver")
private final String nickname;
...
}
๐จ ๋ฌธ์ ์ํฉ
DTO์ ์ฌ๋ฌ ๊ฐ์ ํ๋๊ฐ ์์ ๋๋ ๋ฌธ์ ๊ฐ ์์ง๋ง, ๋จ์ผ ํ๋๋ฅผ ๊ฐ์ง DTO๋ง JSON parse error๊ฐ ๋ฐ์ํ๋ค.
๐ฝ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ DTO ํด๋์ค
@Getter
@Builder
@RequiredArgsConstructor
public class OrganizationJoinRequest {
@Schema(description = "๊ฐ์
ํ์ค ์๊ฐ", example = "์๋
ํ์ธ์", required = true)
private final String introduction;
}
@Getter
@Builder
@RequiredArgsConstructor
public class RefreshTokenRequest {
@Schema(description = "JWT ๊ฐฑ์ ์ ์ํ Refresh Token", example = "eyJ...", required = true)
private final String refreshToken;
}
๐ฝ ์๋ฌ ๋ก๊ทธ
โ๏ธ ์์ธ ๋ถ์
๊ตฌ๊ธ๋ง๊ณผ GPT๋ฅผ ํตํด ํ์ธํ ๊ฒฐ๊ณผ, ๊ธฐ๋ณธ ์์ฑ์ ์ด๋ ธํ ์ด์ @NoArgsConstructor์ ์ง์ ํด์ผ ํ๋ค๋ ๋ต๋ณ์ด ๋ง์๋ค. ๊ทธ๋ฐ๋ฐ ํ๋๊ฐ ์ฌ๋ฌ ๊ฐ์ธ DTO ํด๋์ค์๋ ๊ธฐ๋ณธ ์์ฑ์๋ฅผ ์ง์ ํ์ง ์์๋ ์ ์์ ์ผ๋ก ์๋ํ๋ ๋ฐ๋ฉด, ํ๋๊ฐ ํ๋์ธ DTO ํด๋์ค๋ง ์๋ฌ๊ฐ ๋ฐ์ํ๋ ๊ฒ์ด ์ด์ํ๋ค.
Jackson ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ GitHub ์ด์๋ฅผ ํตํด ๊ทธ ์์ธ์ ํ์ ํ ์ ์์๋ค.
๋จ์ผ ํ๋ DTO์ ๊ฒฝ์ฐ, Jackson์ ๊ธฐ๋ณธ์ ์ผ๋ก ๋จ์ผ-์ธ์ ์์ฑ์ ํธ์ถ์ ํตํด ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ ค ํ๋ค. ์ด ๊ณผ์ ์์ Jackson์ ๋ ๊ฐ์ง ๋ฐฉ์ ์ค ํ๋๋ฅผ ์ ํํด์ผ ํ๋ค.
- Delegating ๋ฐฉ์
- JSON ๊ฐ(๋จ์ผ ๊ฐ)์ด ๊ฐ์ฒด ์ ์ฒด์ ๋ฐ๋ก ๋งคํ๋ ๋
- ์: JSON "value" -> new Constructor("value")
- Properties ๋ฐฉ์
- JSON์ ์์ฑ-๊ฐ ์์ ํตํด ๊ตฌ์ฑ๋ ๋
- ์: JSON {"name": "value"} -> new Constructor(name = "value")
Jackson์ด ์๋ชป๋ ๋ฐฉ์์ ์ ํํ๋ฉด JSON parse error๊ฐ ๋ฐ์ํ๋ค. ํ๋๊ฐ ์ฌ๋ฌ ๊ฐ์ธ ๊ฒฝ์ฐ, Jackson์ ์๋์ผ๋ก Properties ๋ฐฉ์์ ์ ํํ๋ฏ๋ก ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ง ์๋๋ค. ํ์ง๋ง ํ๋๊ฐ ๋จ์ผ์ธ ๊ฒฝ์ฐ, Jackson์ Delegating ๋ฐฉ์๊ณผ Properties ๋ฐฉ์ ์ค ํ๋๋ฅผ ์ ํํ ์ ์๊ณ , ์๋ชป๋ ๋ฐฉ์์ ์ ํํ ๊ฒฝ์ฐ ์๋ฌ๊ฐ ๋ฐ์ํ๋ค.
ํ์ฌ ์ฌ๋ก์์๋ ๋จ์ผ ํ๋์ธ DTO ํด๋์ค์์ Jackson์ด JSON ๊ฐ์ Object ๊ฐ์ด ์๋ ์ฝ๋์ ์ ์ธ๋ String ๊ฐ์ผ๋ก ์ฒ๋ฆฌํ ๊ฒ์ผ๋ก ๊ธฐ๋ํ๊ณ Delegating ๋ฐฉ์์ ์ฌ์ฉํ๋ ค๊ณ ์๋ํ๋ค. ๊ทธ๋ฌ๋ ์ค์ JSON ๋ฐ์ดํฐ๋ Properties ๋ฐฉ์์ผ๋ก ์ฒ๋ฆฌ๋์ด์ผ ํ๋ ๊ตฌ์กฐ์๊ธฐ ๋๋ฌธ์, ์ด ๊ณผ์ ์์ ์๋ฌ๊ฐ ๋ฐ์ํ ๊ฒ์ด๋ค.
๐จ ํด๊ฒฐ ๋ฐฉ๋ฒ
โถ final ํค์๋ ์ญ์ (๋น์ถ์ฒ)
@Builder, @RequiredArgsConstructor, final ํค์๋๋ฅผ ์ ๊ฑฐํด Jackson์ด ๊ธฐ๋ณธ์ ์ผ๋ก ์ฌ์ฉํ ์ ์๋ ๊ตฌ์กฐ๋ก ๋ณ๊ฒฝํ๋ค.
ํ์ง๋ง DTO ํน์ฑ์, ์ ๋ณด๊ฐ ๋ณ๊ฒฝ๋์ง ์์์ผ ํ๋ค๋ ์ ์์ ์ข์ง ์์ ๋ฐฉ๋ฒ์ด๋ผ๊ณ ์๊ฐํ๋ค.
@Getter
public class OrganizationJoinRequest {
@Schema(description = "๊ฐ์
ํ์ค ์๊ฐ", example = "์๋
ํ์ธ์", required = true)
private String introduction;
}
โถ ๊ธฐ๋ณธ ์์ฑ์ ๊ฐ์ ์ ์ธ (๋น์ถ์ฒ)
๊ธฐ๋ณธ ์์ฑ์๋ฅผ @NoArgsConstructor(force = true)๋ฅผ ์ฌ์ฉํด ๊ธฐ๋ณธ ์์ฑ์๋ฅผ ๊ฐ์ ๋ก ์ถ๊ฐํ๋ค. @NoArgsConstructor๋ง ์ฌ์ฉํ ๊ฒฝ์ฐ final ํ๋๊ฐ ์ด๊ธฐํ๋์ง ์์ ๊ธฐ๋ณธ ์์ฑ์๊ฐ ์์ฑ๋์ง ์์ผ๋ฏ๋ก force = true ์ต์ ์ด ํ์ํ๋ค.
ํ์ง๋ง ์ด ๋ฐฉ์์ final ํ๋๊ฐ ๊ฐ์ ๋ก ๊ธฐ๋ณธ๊ฐ(null)์ผ๋ก ์ด๊ธฐํ๋๋ฏ๋ก, ๊ฐ์ฒด๊ฐ ๋ถ์์ ํ ์ํ๋ก ์์ฑ๋ ์ ์์ด ์ข์ง ์์ ๋ฐฉ๋ฒ์ด๋ผ๊ณ ์๊ฐํ๋ค.
@Getter
@Builder
@RequiredArgsConstructor
@NoArgsConstructor(force = true) // ๊ฐ์ ์ ์ธ
public class OrganizationJoinRequest {
@Schema(description = "๊ฐ์
ํ์ค ์๊ฐ", example = "์๋
ํ์ธ์", required = true)
private final String introduction;
}
๊ธฐ๋ณธ ์์ฑ์๊ฐ ์ถ๊ฐ๋๋ฉด ํด๊ฒฐ๋๋ ์ด์
Jackson์ด JSON ๋ฐ์ดํฐ๋ฅผ ์ญ์ง๋ ฌํ(Deserialize)ํ ๋ ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ ๋ฐฉ๋ฒ์ผ๋ก ๊ธฐ๋ณธ ์์ฑ์ ๋๋ ๋ช ์์ ์์ฑ์๋ฅผ ์ฌ์ฉํ๋ค. ๊ธฐ๋ณธ ์์ฑ์๊ฐ ์์ผ๋ฉด Jackson์ด ๊ฐ์ฒด๋ฅผ ์์ฑํ ์ ์์ด์ ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ค. ๋ฐ๋ฉด, ๊ธฐ๋ณธ ์์ฑ์๊ฐ ์ถ๊ฐ๋๋ฉด Jackson์ ์ด๋ฅผ ์ฌ์ฉํด ๊ฐ์ฒด๋ฅผ ์์ฑํ ๋ค, JSON ๋ฐ์ดํฐ์์ ์ถ์ถํ ์์ฑ-๊ฐ ์์ ํด๋น ๊ฐ์ฒด ํ๋์ ๋งคํํ๋ค.
โถ @JsonCreator๋ก ๋ช ์์ ๋ชจ๋ ์ค์ (์ถ์ฒ)
@JsonCreator(mode = JsonCreator.Mode.PROPERTIES)๋ฅผ ์ฌ์ฉํด Jackson์ด Properties ๋ฐฉ์์ ๋ช ์์ ์ผ๋ก ์ฌ์ฉํ๋๋ก ์ค์ ํ๋ค.
@Getter
@Builder
public class OrganizationJoinRequest {
@Schema(description = "๊ฐ์
ํ์ค ์๊ฐ", example = "์๋
ํ์ธ์", required = true)
private final String introduction;
@JsonCreator(mode = JsonCreator.Mode.PROPERTIES) // Properties ๋ชจ๋๋ก ์ค์
public OrganizationJoinRequest(@JsonProperty("introduction") String introduction) {
this.introduction = introduction;
}
}
โถ Jackson ์ ์ญ ์ค์ ๋ณ๊ฒฝ
Jackson 2.12 ์ด์ ๋ฒ์ ์์๋ ์ ์ญ์ ์ผ๋ก Properties ๋ชจ๋๋ฅผ ๊ธฐ๋ณธ๊ฐ์ผ๋ก ์ค์ ํ ์ ์๋ค.
@Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(MapperFeature.INFER_CREATOR_FROM_CONSTRUCTOR_PROPERTIES, true);
return mapper;
}
โถ @ConstructorProperties ์ฌ์ฉ
@ConstructorProperties๋ฅผ ์ฌ์ฉํด ์์ฑ์์ JSON ์์ฑ ๊ฐ์ ๋งคํ ์ ๋ณด๋ฅผ ์ ๊ณตํ์ฌ Jackson์ด Properties ๋ฐฉ์์ ์ฌ์ฉํ๋๋ก ์ ๋ํ๋ค.
@Getter
@Builder
@RequiredArgsConstructor
public class OrganizationJoinRequest {
@Schema(description = "๊ฐ์
ํ์ค ์๊ฐ", example = "์๋
ํ์ธ์", required = true)
private final String introduction;
@ConstructorProperties({"introduction"}) // ์์ฑ์ ๋งคํ ์ ๋ณด ์ ๊ณต
public OrganizationJoinRequest(String introduction) {
this.introduction = introduction;
}
}
๐ ๊ฒฐ๊ณผ ๊ด์ฐฐ
JSON ๋ฐ์ดํฐ๊ฐ ์ ์์ ์ผ๋ก ๋ฐํ๋๋ฉฐ, ์ ์์ ์ผ๋ก ์๋ํจ์ ํ์ธํ ์ ์์๋ค.

๐ก ๊ณ ์ฐฐ
์ฌ์ค ํ๋ก์ ํธ๋ฅผ ์งํํ๋ฉด์ ๋จ์ผ ํ๋ DTO๋ง ๊ธฐ๋ณธ ์์ฑ์๋ฅผ ์์ฑํด์ค์ผ ํ๋ค๋ ์ ์ด ์ด์ํ๋ค๊ณ ์๊ฐ์ ํ๊ณ ์์์ง๋ง, ์๊ฐ์ ์ซ๊ธฐ๋ค ๋ณด๋ final ํค์๋๋ฅผ ์ญ์ ํ๋ ๋ฐฉ๋ฒ์ผ๋ก ๊ธํ๊ฒ ์๋ฌ๋ฅผ ํด๊ฒฐํ์๋ค. ํ๋ก์ ํธ๊ฐ ๋๋ ํ, ๋น์ ๋๊ผ๋ ์๋ฌธ์ ์ ๋ฐํ์ผ๋ก ํธ๋ฌ๋ธ์ํ ์ ์งํํ๊ณ , ๋ฌธ์ ์ ์์ธ์ด ์๊ฐ๋ณด๋ค ์๋ฏธ ์๋ ๋ถ๋ถ์ ์์๋ค๋ ๊ฒ์ ์๊ฒ ๋์๋ค. ์ด ๊ณผ์ ์ ํตํด JSON์ด ๊ฐ์ฒด๋ฅผ ํ์ฑํ๋ ๋ฐฉ์์ ์ดํดํ ์ ์๋ ๊ธฐํ๊ฐ ๋์๋ค.