ํ‹ฐ์Šคํ† ๋ฆฌ ๋ทฐ

๐Ÿค”
DTO์™€ ์—”ํ‹ฐํ‹ฐ ์ค‘ validation์„ ์–ด๋””์— ๊ฑธ์–ด์•ผ ํ• ๊นŒ?

 

DDD๋ฅผ ๋„์ž…ํ•˜๋Š” ์ดˆ๊ธฐ ๋‹จ๊ณ„์—ฌ์„œ ๊ทธ๋Ÿฐ์ง€ ์œ„๊ฐ™์€ ์งˆ๋ฌธ์ด ๊ณ„์†ํ•ด์„œ ๋“ค์—ˆ๋‹ค.

์ฑ…์—์„œ ๋ฐฐ์šด ๊ฑธ ๋– ์˜ฌ๋ ธ์„ ๋•Œ์—๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

  • ํƒ€์ž…์— ๋Œ€ํ•œ ๊ฒ€์ฆ์€ ์ปจํŠธ๋กค๋Ÿฌ ์ด์ „์ธ DTO์—์„œ ์ฒ˜๋ฆฌ๋˜์–ด์•ผ ํ•œ๋‹ค.
  • ๋น„์ฆˆ๋‹ˆ์Šค์ ์ธ ์œ ํšจ์„ฑ ๊ฒ€์ฆ์€ domain ์—”ํ‹ฐํ‹ฐ์—์„œ ์ฒ˜๋ฆฌ๋˜์–ด์•ผ ํ•œ๋‹ค.

ํ•˜์ง€๋งŒ ์•„๋ž˜ ๋‚˜์™€์žˆ๋‹ค์‹œํ”ผ ๋น„์ฆˆ๋‹ˆ์Šค ๊ทœ์น™์„ ๊ฒ€์ฆํ•˜๊ธฐ๊ฐ€ ์—ฌ์˜์น˜ ์•Š๋‹ค๋ฉด ๋„๋ฉ”์ธ ์—”ํ‹ฐํ‹ฐ ์‚ฌ์šฉ์ „์— ์‚ฌ์šฉํ•ด๋„ ๋œ๋‹ค.

๊ทธ๋ž˜์„œ ๊ฒฐ๋ก ์„ ๋‚ด๋ฆฐ ๊ฒŒ ์ž…๋ ฅ๋ช… ๊ธธ์ด๋‚˜ ํ•„์ˆ˜๊ฐ’์„ DTO์—์„œ ์ฒ˜๋ฆฌํ•˜๊ธฐ๋กœ ํ–ˆ๋‹ค.

๋‹ค์Œ์€ ์ƒ์ ์„ ๋งŒ๋“œ๋Š” DTO ์˜ˆ์‹œ ์ฝ”๋“œ์™€ ๊ฐ™๋‹ค.

import lombok.Builder;
import lombok.Getter;
import net.logstash.logback.util.StringUtils;

import javax.validation.constraints.*;

@Getter
public class StoreCreateDto {
    @NotBlank(message = "[์ƒ์ ๋ช…]์€ ํ•„์ˆ˜๊ฐ’์ž…๋‹ˆ๋‹ค.")
    @Size(max = STORE_NAME_MAX_LENGTH, message = "[์ƒ์ ๋ช…]์€ 50์ž๋ฅผ ์ดˆ๊ณผํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.")
    private final String name;
    @Pattern(regexp = PHONE_NUMBER_PATTERN, message = "์˜ฌ๋ฐ”๋ฅธ ์ „ํ™”๋ฒˆํ˜ธ ํ˜•์‹์ด ์•„๋‹™๋‹ˆ๋‹ค. 02-1234-1234์™€ ๊ฐ™์€ ํ˜•์‹์œผ๋กœ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”.")
    private final String contact;
    @NotNull(message = "[์ƒ์ ์œ ํ˜•]์€ ํ•„์ˆ˜๊ฐ’์ž…๋‹ˆ๋‹ค.")
    private final StoreType type;
    private final StoreAddress address;

    @AssertFalse(message = "[์šฐํŽธ๋ฒˆํ˜ธ]๋Š” ํ•„์ˆ˜๊ฐ’์ž…๋‹ˆ๋‹ค.")
    private boolean isNullZipcode() {
        return StringUtils.isBlank(address.getZipcode());
    }

    @AssertFalse(message = "[์ฃผ์†Œ]๋Š” ํ•„์ˆ˜๊ฐ’์ž…๋‹ˆ๋‹ค.")
    private boolean isNullMainAddress() {
        return StringUtils.isBlank(address.getMainAddress());
    }

    @AssertFalse(message = "[์ฃผ์†Œ]๋Š” 100์ž๋ฅผ ์ดˆ๊ณผํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.")
    private boolean isValidMainAddress() {
        return address.getMainAddress().length() > MAIN_ADDRESS_MAX_LENGTH;
    }

    @AssertFalse(message = "[์ƒ์„ธ์ฃผ์†Œ]๋Š” 60์ž๋ฅผ ์ดˆ๊ณผํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.")
    private boolean isValidSubAddress() {
        return !StringUtils.isBlank(address.getSubAddress())
                && address.getSubAddress().length() > SUB_ADDRESS_MAX_LENGTH;
    }
}
๐Ÿค”
DTO ํƒ€์ž…์˜ List์— ๋Œ€ํ•œ validation์€ ์–ด๋–ป๊ฒŒ ํ•ด์•ผ ํ• ๊นŒ?

 

๋‘ ๋ฒˆ์งธ ๋‹ค๋ค„๋ณผ ๋‚ด์šฉ์€ DTO ํƒ€์ž…์˜ List์— ๋Œ€ํ•œ validation์€ ์–ด๋–ป๊ฒŒ ํ• ๊นŒ์ด๋‹ค.

validationํ•˜๋Š” ์„ธ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์„ ์†Œ๊ฐœํ•˜๋ ค๊ณ  ํ•œ๋‹ค.

[1]. inner static class๋กœ ์ด์šฉ

๋‚ด๋ถ€ ํด๋ž˜์Šค DTO๋ฅผ ๊ฐ์‹ธ๋Š” List<DTO>๋ฅผ ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค.

@Getter
@NoArgsConstructor
public class StoreUpdateListDto {
    List<@Valid StoreUpdateDto1> listDto;
    
    public static class StoreUpdateDto1 {
        // ...
    }
}

์žฅ์ 

  • DTO ๊ฐœ์ˆ˜๋ฅผ ์ค„์ผ ์ˆ˜ ์žˆ๋‹ค.

๋‹จ์ 

  • ์ดํŽ™ํ‹ฐ๋ธŒ ์ž๋ฐ”์—์„œ๋Š” ๋‚ด๋ถ€ ํด๋ž˜์Šค๋Š” ๋˜๋„๋ก static์œผ๋กœ ๋งŒ๋“ค๋ผ๊ณ  ๋‚˜์™€์žˆ๊ธด ํ•œ๋ฐ, ์ค‘์ฒฉ ํด๋ž˜์Šค๋Š” ์ž์‹ ์„ ๊ฐ์‹ผ ๋ฐ”๊นฅ ํด๋ž˜์Šค์—๋งŒ ์“ฐ์—ฌ์•ผ ํ•˜๋ฉฐ, ๊ทธ ์™ธ์˜ ์“ฐ์ž„์ƒˆ๊ฐ€ ์žˆ๋‹ค๋ฉด ํ†ฑ๋ ˆ๋ฒจ ํด๋ž˜์Šค๋กœ ๋งŒ๋“ค์–ด์•ผ ํ•œ๋‹ค๊ณ ๋Š” ๋‚˜์™€์žˆ๋‹ค.
    • ์ฆ‰, ๋‚˜์ค‘์— ์œ ์ง€๋ณด์ˆ˜๊ฐ€ ํž˜๋“ค์–ด์ง„๋‹ค.

[2]. DTO 2๊ฐœ ์ƒ์„ฑ

DTO ํ•œ๊ฐœ, ๊ทธ DTO๋ฅผ ๊ฐ์‹ธ๋Š” List<DTO> ์ด ๋‘ ๊ฐœ๋ฅผ ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค.

@Getter
@NoArgsConstructor
public class StoreUpdateListDto {
    List<@Valid StoreUpdateDto> listDto;
}
@Getter
public class StoreUpdateDto {
    // ...
}

์žฅ์ 

  • DTO์˜ ์‚ฌ์šฉ ๋ชฉ์ ์„ ๋ถ„๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.
    • ์š”์ฒญ์œผ๋กœ ๋ฐ›์€ StoreUpdateListDto๋Š” ์š”์ฒญ ์ŠคํŽ™์œผ๋กœ์„œ์˜ ์—ญํ• 
    • StoreUpdateDto๋Š” ์œ„์—์„œ ๊ฒ€์ฆ๋œ ๊ฑธ ์‚ฌ์šฉํ•˜๋Š” DTO๋กœ์„œ์˜ ์—ญํ• 

๋‹จ์ 

  • DTO ๊ฐœ์ˆ˜๊ฐ€ ๋งŽ์•„์งˆ ์ˆ˜ ์žˆ๋‹ค.

[3]. List๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ValidList ์ƒ์„ฑ

validation์ด ํ•„์š”ํ•œ List์—๋Š” List๊ฐ€ ์•„๋‹Œ ValidList๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค.

List์— Valid ์–ด๋…ธํ…Œ์ด์…˜๊ณผ Delegate ์–ด๋…ธํ…Œ์ด์…˜์„ ๋ถ™์ธ ValidList๋ฅผ ๋งŒ๋“ ๋‹ค.

@Getter
public class ValidList<E> implements List<E> {
    @Valid
    @Delegate
    private List<E> list;

    public ValidList() {
        this.list = new ArrayList<>();
    }

    public ValidList(List<E> list) {
        this.list = list;
    }
}
@PutMapping("")
public ResponseEntity<?> updateStore(@Valid @RequestBody ValidList<StoreUpdateDto> storeDtos) {
    List<String> ids = storeService.updateStores(storeDtos);
    return ResponseEntity.ok().body(new CommonResponse(new DataResponse<>(ids)));
}

์žฅ์ 

  • DTO ๊ฐœ์ˆ˜๋ฅผ ์ค„์ผ ์ˆ˜ ์žˆ๋‹ค.

๋‹จ์ 

  • list๊ฐ€ ์•„๋‹ˆ๋ผ map์ด๋‚˜ set์œผ๋กœ ๋ฐ›๊ฒŒ ๋˜๋ฉด ๊ทธ๊ฒƒ๋„ Valid-๋ฅผ ๋‹ค ๋งŒ๋“ค์–ด์•ผ ํ•จ.