κ°œλ°œν•˜λ©΄μ„œ 배운 Map vs DTO λ°©μ‹μ˜ 차이점

2022. 7. 31. 17:16ㆍBackend/🌿 Spring

λͺ©μ°¨

  • μ„œλ‘ 
    • λ„μž…
    • κΈ°λŠ₯ μš”κ±΄
    • 문제점

 

  • λ³Έλ‘ 
    • 초기 객체 ꡬ쑰(Map) - Bad
    • μ΅œμ’… 객체 ꡬ쑰 (DTO) - Good

 

  • κ²°λ‘ 

μ„œλ‘ 

λ„μž…

μ‚¬μ΄λ“œ ν”„λ‘œμ νŠΈμ—μ„œ "μ œλ‘œμ›¨μ΄μŠ€νŠΈ κ΄€λ ¨ μž₯μ†Œ"λ₯Ό 지도에 λ„μš°λŠ” μž‘μ—…μ„ λ‹΄λ‹Ήν–ˆλ‹€. "μ œλ‘œμ›¨μ΄μŠ€ κ΄€λ ¨ μž₯μ†Œ" (μ΄ν•˜ place) λŠ” λ‹€μ–‘ν•œ μΉ΄ν…Œκ³ λ¦¬λ‘œ λΆ„λ₯˜λ  수 μžˆλŠ”λ°, 기획 λ‹¨κ³„μ—μ„œ μ•„λž˜ μ΄λ―Έμ§€μ˜ ν•„ν„° λΆ€λΆ„ 처럼 μ •μ˜ν•˜μ—¬ μΉ΄ν…Œκ³ λ¦¬ν™” ν•˜μ˜€λ‹€.

μ•…ν•„ γ…ˆγ……

κΈ°λŠ₯ μš”κ±΄

μ‚¬μš©μžκ°€ μ›ν•˜λŠ” μΉ΄ν…Œκ³ λ¦¬μ˜ 상점을 선택할 수 μžˆλ„λ‘ 'ν•„ν„°'λ₯Ό κΈ°λŠ₯을 μ œκ³΅ν•˜λŠ” 것이 λͺ©ν‘œμ˜€λ‹€.

  1. 큰 μΉ΄ν…Œκ³ λ¦¬μ— ν•΄λ‹Ήλ˜λŠ” μ†ŒλΉ„λ₯Ό λ•λŠ”, μˆœν™˜μ„ λ•λŠ”, 보상을 μ£ΌλŠ” μΉ΄ν…Œκ³ λ¦¬λ₯Ό μš°μ„  λ§Œλ“ λ‹€.
  2. 각 μΉ΄ν…Œκ³ λ¦¬μ— μ†ν•˜λŠ” μ—¬λŸ¬ 개의 ν•„ν„°λ₯Ό λ„£λŠ”λ‹€.
  3. ν΄λΌμ΄μ–ΈνŠΈ λ‹¨μ—μ„œ 각각의 값을 μ•Œ 수 μžˆλ„λ‘ key, value ν˜•μ‹μœΌλ‘œ μ „μ†‘ν•œλ‹€. key 값을 μ‘°νšŒν•˜λ©΄ ν•΄λ‹Ή μΉ΄ν…Œκ³ λ¦¬μ˜ 영문 μ½”λ“œκ°€, value λ₯Ό μ‘°νšŒν•˜λ©΄ ν•΄λ‹Ή μΉ΄ν…Œκ³ λ¦¬μ˜ ν•œκΈ€ μ½”λ“œλ₯Ό μ‘°νšŒν•  수 μžˆλ„λ‘ ν•œλ‹€.

문제점

λ°±μ—”λ“œ 개발자인 ν•„μžλŠ” 이 'ν•„ν„°' λ₯Ό μ½”λ“œν™” ν•˜μ—¬ 화면단에 보내주어야 ν–ˆλ‹€. 이 κ³Όμ •μ—μ„œ ν•„μžλŠ” ν•„ν„°λ₯Ό map ν˜•νƒœλ‘œ κ΅¬μ„±ν•˜μ—¬ ν΄λΌμ΄μ–ΈνŠΈλ‘œ μ „μ†‘ν•œ μ‹€μˆ˜λ₯Ό λ²”ν–ˆλŠ”λ°, 1) 이게 μ™œ 잘λͺ»λœ 방식인지, 2) map λŒ€μ‹  Dto λ₯Ό μ‚¬μš©ν•΄μ•Ό ν•˜λŠ” μ΄μœ κ°€ 무엇인지 λ₯Ό μ€‘μ‹¬μœΌλ‘œ 글을 풀어보겠닀.

 

λ³Έλ‘ 

초기 객체 ꡬ쑰(Map) - Bad

μ•„λž˜ 객체 κ΅¬μ‘°λŠ” ν•„μžκ°€ 처음 μ„€κ³„ν•œ λ°©μ‹μœΌλ‘œ, Java 의 Map 을 μ΄μš©ν•΄ κ΅¬μ„±ν–ˆλ‹€.

μ΅œμƒμœ„ key κ°’μœΌλ‘œ RECYCLE, COMPENSATION, CONSUME 을 가지도둝 ν–ˆκ³ , 각각의 key 에 λŒ€μ‘λ˜λŠ” value λŠ” λ˜λ‹€λ₯Έ Key-value ꡬ쑰λ₯Ό 리슀트둜 받도둝 ν–ˆλ‹€.

{
    "RECYCLE": {
        "name": [
            "λ¬΄ν•΄ν•œ μˆœν™˜μ„ λ•λŠ”"
        ],
        "filters": [
            "μ•„μ΄μŠ€νŒ©μˆ˜κ±°ν•¨",
            "μ†Œν˜•κ°€μ „μˆ˜κ±°ν•¨",
            "λ™μ‚¬λ¬΄μ†Œ"
        ]
    },
    "COMPENSATION": {
        "name": [
            "보상을 μ£ΌλŠ”"
        ],
        "filters": [
            "수퍼빈",
            "λΆ„λ¦¬μˆ˜κ±°"
        ]
    },
    "CONSUME": {
        "name": [
            "λ¬΄ν•΄ν•œ μ†ŒλΉ„λ₯Ό λ•λŠ”"
        ],
        "filters": [
            "μ œλ‘œμ›¨μ΄μŠ€νŠΈμƒ΅",
            "λ¦¬ν•„μŠ€ν…Œμ΄μ…˜",
            "채식식당"
        ]
    }
}

사싀 μ§€κΈˆ 보면 μ–΄μ²˜κ΅¬λ‹ˆ μ—†λŠ” μ½”λ“œμΈλ°, λ‹Ήμ‹œμ—λŠ” 무슨 근거둜 μ΄λ ‡κ²Œ 객체λ₯Ό κ΅¬μ„±ν–ˆλŠ”μ§€ λͺ¨λ₯΄κ² λ‹€.

Map 을 μ‚¬μš©ν•΄ μœ„ 객체λ₯Ό μ–΄λ–»κ²Œ κ΅¬μ„±ν–ˆλŠ”μ§€ μ‚΄νŽ΄λ³΄μž.

μš°μ„  μ΅œμƒμœ„ key 에 λŒ€μ‘λ˜λŠ” value 의 νƒ€μž…λ„ Map 이 λΌμ•Όν•œλ‹€. (이미 μ—¬κΈ°μ„œλΆ€ν„° λ³΅μž‘ν•˜λ‹€)

μ™œλƒν•˜λ©΄ 각각의 객체 κ΅¬μ‘°λŠ” (name κ³Ό filters λŠ” λΌλŠ”) String key 에 λŒ€μ‘ν•˜λŠ” λ¬Έμžμ—΄ 리슀트λ₯Ό value 둜 λ°›κΈ° λ•Œλ¬Έμ΄λ‹€.

μ½”λ“œλ‘œ ν‘œν˜„ν•˜λ©΄ λŒ€κ°• 이런 λͺ¨μŠ΅μ΄λ‹€.

public Map<String, Map<String, List<String>>> category = new HashMap<>();

λ”± 보고 λ³΅μž‘ν•˜λ‹€~ μ‹ΆμœΌλ©΄ 잘λͺ»λμŒμ„ νŒŒμ•…ν•΄μ•Όν•œλ‹€.

λ‹Ήμ—°νžˆ μ½”λ“œλ¦¬λ·° λ•Œ νŒ€μ›μœΌλ‘œ λΆ€ν„° μ•„λž˜μ™€ 같은 ν”Όλ“œλ°±μ„ λ°›μ•˜λ‹€.

λΉ¨κ°„ κΈ€μ”¨λŠ” ν•„μžκ°€ 덧뢙여 μ“΄κ²ƒμž„μœΌλ‘œ μ˜€ν•΄ν•˜κΈ° μ—†κΉ…

νŒ€μ›λΆ„μ˜ λ§ˆμ§€λ§‰ μŠ€λ ˆλ“œλ₯Ό 보고, λ‚΄κ°€ μ§  객체 μ„€κ³„μ˜ 문제점이 이해됐닀.

μœ„ 처럼 μ„€κ³„ν•œ κ²ƒμ˜ 문제점

  1. ν΄λΌμ΄μ–ΈνŠΈ κ°œλ°œμžκ°€ κ³Όμ—° RECYCLE, COMPENSATION, CONSUME μ΄λž€ key 값을 λ‹€ μ•Œκ³  μžˆμ„κΉŒ?
  2. μ§€κΈˆμ€ 전체 μΉ΄ν…Œκ³ λ¦¬κ°€ 3개 λΏμ΄μ§€λ§Œ, 좔후에 μΉ΄ν…Œκ³ λ¦¬κ°€ μˆ˜μ‹­, 수백개둜 λŠ˜μ–΄λ‚œλ‹€λ©΄? κ·Έ λ•Œλ„ ν΄λΌμ΄μ–ΈνŠΈ κ°œλ°œμžκ°€ 일일이 key 값에 ν•΄λ‹Ήλ˜λŠ” 값을 λͺ¨λ‘ νŒŒμ•…ν•  수 μžˆμ„κΉŒ?

λ•Œλ¬Έμ— value 에 ν•΄λ‹Ήλ˜λŠ” RECYCLE, COMPENSATION, CONSUME 같은 값듀은 keyκ°€ μ•„λ‹Œ, value 의 μœ„μΉ˜μ— λ‘λŠ” 것이 μ˜³λ‹€.

이런 점을 μ—Όλ‘ν•˜μ—¬ 객체 ꡬ쑰λ₯Ό map μ—μ„œ DTO 둜 λ¦¬νŒ©ν† λ§ ν•΄λ³΄μž.

μ΅œμ’… 객체 ꡬ쑰 (DTO) - Good

{
    "categoryGroup": [
        {
            "key": "CONSUME",
            "value": "λ¬΄ν•΄ν•œ μ†ŒλΉ„λ₯Ό λ•λŠ”",
            "filters": [
                {
                    "value": "μ œλ‘œμ›¨μ΄μŠ€νŠΈμƒ΅",
                    "key": "ZEROWASTESHOP"
                },
                {
                    "value": "λ¦¬ν•„μŠ€ν…Œμ΄μ…˜",
                    "key": "REFILLSTATION"
                },
                {
                    "value": "채식식당",
                    "key": "VEGANSHOP"
                }
            ]
        },
        {
            "key": "RECYCLE",
            "value": "λ¬΄ν•΄ν•œ μˆœν™˜μ„ λ•λŠ”",
            "filters": [
                {
                    "value": "μ•„μ΄μŠ€νŒ©μˆ˜κ±°ν•¨",
                    "key": "ICEPACK"
                },
                {
                    "value": "μ†Œν˜•κ°€μ „μˆ˜κ±°ν•¨",
                    "key": "APPLIANCES"
                },
                {
                    "value": "행정볡지센터",
                    "key": "DONG"
                }
            ]
        },
        {
            "key": "COMPENSATION",
            "value": "보상을 μ£ΌλŠ”",
            "filters": [
                {
                    "value": "수퍼빈",
                    "key": "SUPERBEAN"
                }
            ]
        }
    ]
}

λ¦¬νŒ©ν† λ§ν•œ κ°μ²΄λŠ” DTO ν˜•νƒœλ‘œ μ •μ˜ν–ˆλ‹€.

categoryGroup μ΄λž€ key 에 λŒ€μ‘λ˜λŠ” value λŠ” λ™μΌν•œ DTO λ₯Ό 리슀트 ν˜•νƒœλ‘œ λ°›λŠ”λ‹€. 이 DTO λŠ” μ•„λž˜ μ½”λ“œμ—μ„œ CategoryDto 의 inner class 인 Response 에 ν•΄λ‹Ήλœλ‹€. Response ν΄λž˜μŠ€λŠ” key, value, filters λΌλŠ” 세가지 ν•„λ“œλ₯Ό 가지며, filters λŠ” Map 을 μ œλ„ˆλ¦­μœΌλ‘œ λ°›λŠ” 리슀트둜, 즉 μΉ΄ν…Œκ³ λ¦¬μ˜ ν•˜μœ„μ— μ†ν•˜λŠ” λ‹€μˆ˜μ˜ ν•„ν„° 값듀이 key-value 의 맡 ν˜•νƒœλ‘œ filters 에 ν¬ν•¨λ˜λŠ” ꡬ쑰닀.

CategoryDto 클래슀

@Data
@AllArgsConstructor
public class CategoryDto {
    private List<Response> categoryGroup;

    @Data
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    public static class Response {

        private CategoryGroups key;
        private String value;
        private List<Map<String, String>> filters;

        public static Response of(CategoryGroups categoryGroups) {
            List<Category> categoryList = CategoryGroups.getFilterList(categoryGroups);
            List<Map<String, String>> filters = new ArrayList<>();
            for (Category category : categoryList) {
                Map<String, String> map = new HashMap<>();
                map.put("value", category.getCategory());
                map.put("key", category.toString());
                filters.add(map);
            }
            return Response.builder()
                    .key(categoryGroups)
                    .value(categoryGroups.getCategory())
                    .filters(filters)
                    .build();
        }
    }

    public static CategoryDto response(List<Response> categoryGroup) {
        return new CategoryDto(categoryGroup);
    }
}

CategoryGroups Enum 클래슀

Category Enum 클래슀

Category 호좜 API

CategoryDto 클래슀 version 2

처음 μ§  CategoryDto ν΄λž˜μŠ€μ—” ν•œκ°€μ§€ λ¬Έμ œκ°€ μžˆλŠ”λ°, λ°”λ‘œ filters λ¦¬μŠ€νŠΈκ°€ map νƒ€μž…μ„ μ œλ„ˆλ¦­μœΌλ‘œ λ°›λŠ”λ‹€. 겉은 DTO ν˜•νƒœλ‘œ 받을지라도, κ·Έ λ‚΄λΆ€μ˜ 값을 map 으둜 λ°›λŠ” κΈ°μ΄ν•œ ν˜•νƒœμž„μ„ κΉ¨λ‹¬μ•˜κ³ , μ•„λž˜ μ½”λ“œμ²˜λŸΌ λ¦¬νŒ©ν† λ§ν–ˆλ‹€. κ·Έλ¦¬ν•˜μ—¬ filters 리슀트의 μ œλ„ˆλ¦­ νƒ€μž…λ„ FilterResponse λΌλŠ” DTO λ₯Ό 받도둝 λ¦¬νŒ©ν† λ§ν•  수 μžˆμ—ˆλ‹€.

@Data
@AllArgsConstructor
public class CategoryDto {
    private List<Response> categoryGroup;

    @Data
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    private static class FilterResponse{  // μΆ”κ°€λœ λΆ€λΆ„
        private Category key;
        private String value;
    }

    @Data
    @Builder
    @NoArgsConstructor
    @AllArgsConstructor
    public static class Response {

        private CategoryGroups key;
        private String value;
        private List<FilterResponse> filters; // λ³€κ²½λœ λΆ€λΆ„

        public static Response of(CategoryGroups categoryGroups) {
            List<Category> categoryList = CategoryGroups.getFilterList(categoryGroups);
            List<FilterResponse> filters2 = new ArrayList<>();
            for (Category category : categoryList) {

                // μΆ”κ°€ 및 λ³€κ²½λœ λΆ€λΆ„
                FilterResponse build = FilterResponse.builder()
                        .key(category)
                        .value(category.getCategory())
                        .build();
                filters.add(build);
            }
            return Response.builder()
                    .key(categoryGroups)
                    .value(categoryGroups.getCategory())
                    .filters(filters)
                    .build();
        }
    }
}

κ²°κ³Ό

μ΄λ ‡κ²Œ ν•˜μ—¬ 기쑴에 map ν˜•νƒœμ—μ„œ DTO 둜 Response λ₯Ό λ¦¬νŒ©ν† λ§ ν•  수 μžˆμ—ˆλ‹€.

 

map λŒ€μ‹  DTO λ₯Ό μ‚¬μš©ν•˜μ—¬ 얻을 수 μžˆλŠ” μž₯점은 무엇이 μžˆμ„κΉŒ?

1. 가독성이 크게 κ°œμ„ λœλ‹€. μœ„μ—μ„œ μ–ΈκΈ‰ν–ˆλ“―μ΄ μ€‘μ²©λœ map의 ν˜•νƒœλ₯Ό 보면 . . . κ·Έ λ‚΄λΆ€λ₯Ό νŒŒμ•…ν•˜λŠ” 것이 κ½€λ‚˜ λ³΅μž‘ν•˜λ‹€. μ•„λž˜ μ½”λ“œμ•Ό Map 내뢀에 map 이 ν•˜λ‚˜λ°–μ— μΆ”κ°€λ˜μ§€ μ•Šμ€ κ°„λ‹¨ν•œ μΌ€μ΄μŠ€μ§€λ§Œ, 쀑첩 레벨이 κΉŠμ–΄μ§„λ‹€λ©΄ μ½”λ“œ νŠΈλž˜ν‚Ήν•˜λŠ”λ° 더 λ§Žμ€ μ‹œκ°„μ΄ μ†Œλͺ¨λ  것이닀.

2. key 값에 String λŒ€μ‹  νŠΉμ • νƒ€μž…μ„ μ‚¬μš©ν•¨μœΌλ‘œ νƒ€μž… μ•ˆμ •μ„±μ„ 높일 수 μžˆλ‹€. μœ„ μ½”λ“œμ—μ„œλŠ” DTO μ‚¬μš©μ‹œ enum 을 key 둜 μ‚¬μš©ν•˜κΈ°μ—, μ˜€νƒ€λ‚˜ 잘λͺ»λœ λ¬Έμžμ—΄ μž…λ ₯μ‹œ κ³§λ°”λ‘œ 컴파일 μ—λŸ¬λ‘œ 문제λ₯Ό νŒŒμ•…ν•  수 μžˆλ‹€. -> ν•˜μ§€λ§Œ 이 뢀뢄은, map 을 μ‚¬μš©ν•  λ•Œλ„ key 뢀뢄에 String 이 μ•„λ‹Œ νŠΉμ • 데이터 νƒ€μž…μ„ λ„£μ–΄ νƒ€μž… μ•ˆμ •μ„±μ„ κ°•ν™”ν•  수 μžˆλ‹€κ³  μƒκ°ν•œλ‹€. 이게 κΌ­ DTO λ₯Ό μ‚¬μš©ν–ˆμ„ μ‹œμ˜ μž₯μ μΈμ§€λŠ” μ˜λ¬Έμ΄λ‹€. (λŒ“κΈ€λ‘œ λ‹€μ–‘ν•œ 의견 λΆ€νƒλ“œλ¦Όλ‹€)

3. 가독성과 μ—°κ΄€λœ 뢀뢄일 μˆ˜λ„ μžˆλŠ”λ°, ν΄λΌμ΄μ–ΈνŠΈλ‹¨μ—μ„œ μ›ν•˜λŠ” 데이터가 무엇인지 μ‰½κ²Œ νŒŒμ•…ν•  수 μžˆλ‹€. DTO 클래슀 내뢀에 μ •μ˜λœ 멀버 λ³€μˆ˜λ₯Ό 보면 ν΄λΌμ΄μ–ΈνŠΈ λ‹¨μœΌλ‘œ 보내야할 값이 어떀것인지 μ‰½κ²Œ νŒŒμ•…ν•  수 μžˆλ‹€. API 의 λͺ…μ„Έκ°€ 변경될 λ•Œμ—λ„, ν•΄λ‹Ή ν•„λ“œλ₯Ό λ³€κ²½ν•˜λ©΄ 되기 λ•Œλ¬Έμ— 변경에도 μœ μ—°ν•  것이라 μƒκ°ν•œλ‹€. 

 

 

κ²°λ‘ 

map λŒ€μ‹  DTO λ₯Ό μ“°μž. λ¬Όλ‘  μ½”λ“œμ–‘μ΄ λŠ˜μ–΄λ‚˜κ³  map 을 μ‚¬μš©ν•  λ•Œ 보닀 λ³΅μž‘ν•œ μ½”λ“œ ꡬ쑰λ₯Ό κ°–κ²Œ λ˜λŠ” 것이 λ‹¨μ μœΌλ‘œ λ°›μ•„λ“€μ—¬μ§ˆ 수 도 μžˆλ‹€. ν•˜μ§€λ§Œ 이런 λΆˆνŽΈν•¨μ„ κ°μˆ˜ν•΄μ„œλΌλ„, 개발의 μ•ˆμ •μ„±κ³Ό ν΄λΌμ΄μ–ΈνŠΈ κ°œλ°œμžμ™€μ˜ ν˜‘μ—… μΈ‘λ©΄μ—μ„œλŠ” DTO λ₯Ό μ‚¬μš©ν•˜μ—¬ μ„œλ‘œ ν˜‘μ˜λœ ν˜•νƒœλ‘œ 데이터λ₯Ό μ£Όκ³  λ°›λŠ” 것이 μ’‹λ‹€κ³  μƒκ°ν•œλ‹€.