DTO 클래스


공공데이터포탈의 공휴일정보를 얻어오는 API 의 DTO 와 내부에서 데이터 처리하는 DTO들이 섞여있다.

 

그러나 이렇게 보았을 때, DTO 의 구조파악이 안 되었다.

 

DTO 클래스를 만들 때마다 하나의 파일에 DTO를 전부 넣을 수는 없나라는 생각을 계속했다.

 

이것에 대해서 검색해 보니, Inner Class를 사용해서 간소화 하는 방법을 쓰거나 기존대로 Object 단위로 모두 분리해서 사용하는 것을 알게 되었다.

 

Inner Class 를 사용하면, 내부 클래스에 접근하는 코드가 복잡해지는 대신 눈으로 보는 코드상으로는 가독성이 좋아지는 장점이 보였다.

 

둘 중에 어떤 방법을 취할까 고민하다가 한 번도 사용해보지 않은 Inner Class를 적용해 보기로 했다.

 

스키마를 개별적인 파일로 따로 분리할 때는 디렉터리 구조에 신경을 써야 할 것 같다.

-> root 디렉터리를 만들어 관련 DTO를 최소한 분류를 해야겠다는 생각.

-> package 구조처럼 만들어 볼까 싶기도 하나 너무 번거롭거나 복잡하진 않나 싶기도 하다.

 

2023년 09월 특일정보 API 호출 결과이다

{
    "response": {
        "header": {
            "resultCode""00",
            "resultMsg""NORMAL SERVICE."
        },
        "body": {
            "items": {
                "item": [
                    {
                        "dateKind""01",
                        "dateName""추석",
                        "isHoliday""Y",
                        "locdate"20230928,
                        "seq"1
                    },
                    {
                        "dateKind""01",
                        "dateName""추석",
                        "isHoliday""Y",
                        "locdate"20230929,
                        "seq"1
                    },
                    {
                        "dateKind""01",
                        "dateName""추석",
                        "isHoliday""Y",
                        "locdate"20230930,
                        "seq"1
                    }
                ]
            },
            "numOfRows"100,
            "pageNo"1,
            "totalCount"3
        }
    }
}

Inner Class로 수정한 결과


package com.example.excelparser.dto.spcdeinfoapi;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class RestDeInfoDTO{
    private Response response;
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class Response {
        private Header header;
        private Body body;
    }
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class Header {
        private String resultCode;
        private String resultMsg;
    }

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class Body {
        private Object items;
        private String numOfRows;
        private String pageNo;
        private String totalCount;
    }

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class Item {
        private String dateKind;
        private String dateName;
        private String isHoliday;
        private String locdate;
        private String seq;
    }
}

5~6 개 되는 파일이 파일하나로 매우 간결해졌다. 객체 간 관계를 파악하기도 편해졌다.

 

특일정보 API DTO는 정리가 되었으나, 내부데이터 가공 용도로 만든 DTO 가 남았다.

 

OriginDTO - 엑셀에서 POI API로 읽어서 해당객체에 저장하려고 만든 DTO


package com.example.excelparser.dto.original;

import lombok.Data;

@Data
public class OriginDTO {
    //문서번호
    private String doc_num;

    //이름
    private String name;

    //로그인 아이디
    private String loginId;

    //직책
    private String position;

    //부재 항목
    private String absentCase;

    //일수
    private String days;

    //기간
    private String durations;

    //작성일
    private String requestDate;
}

 

살펴보았을 때, 클래스 이름만 바꾸어 주면 될 것 같다.

 

SourceExcelDataExtractorDTO.java로 변경 (원본엑셀데이터추출)

 

나머지는 추가적으로 파악이 필요할 것 같다.

 

코드 동작 순서가 파악이 되지 않음.. -> 지금 와서 보니 메서드를 연쇄 호출해서 순서를 파악하기 어려움!!

 

고민을 해본 끝에 디버깅을 해보았다.

 

최초에 호출되는 메서드를 기준으로 줄줄이 소세지로 메서드가 왔다갔다 실행이 된다.

 

변경 계획

  1. 서비스 클래스를 "결과를 추출하는 메서드" 하나로 만든다.
  2. 임시로 서비스 클래스를 생성하여, 디버깅하면서 주석으로 워크플로우를 기록
  3. 기록한 워크플로우를 기준으로 "결과 추출 메서드" 내부에 워크플로우 기준으로 "기능단위"로 유틸리티 클래스를 손본다.

 

 

서비스 클래스 수정 -

(기존에는 isHolidayCalculate 메서드 내부에 있는 1~5 주석들 각각 메서드가 개별적으로 있었기 때문에

워크플로우가 파악이 안되는 문제가 있었음)

package com.example.excelparser.service;

import com.example.excelparser.dto.MergeDTO;
import com.example.excelparser.dto.UserListDTO;
import com.example.excelparser.dto.original.MergeOriginWithDurationDTO;
import com.example.excelparser.dto.original.SourceExcelDataExtractorDTO;
import com.example.excelparser.util.excel.ExcelCreation;
import com.example.excelparser.util.excel.ExcelParserUtil;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

public class AbsenceCalculatorService implements AbsenceCalculator {
    @Override
    public void isHolidayCalculate(
            String years,
            String month,
            HttpServletResponse response) throws IOException {
        // 1. absence.xlsx 파일 읽기 - ExcelParserUtil.java readAbsenceTimeOffList()
        List<SourceExcelDataExtractorDTO> absence = ExcelParserUtil.readAbsenceTimeOffList();

        // 2. list.xlsx 파일 읽기 - ExcelParserUtil.java getUsers()
        List<UserListDTO> userList = ExcelParserUtil.getUsers();

        // 3. 날짜 데이터 변환 &  워크플레이스 아이디 기준으로 병합
        List<MergeOriginWithDurationDTO> merged = MergeOriginWithDurationDTO.convertTo(absence, userList);
        
        // 4. 3번 결과에서 월이 넘어가는 데이터가 있다면, 월을 나누는 작업
        List<MergeOriginWithDurationDTO> reform = MergeOriginWithDurationDTO.compareMonth(merged);
        
        // 5. 4번 결과에서 다시 년도와 월을 선택
        List<MergeOriginWithDurationDTO> result = MergeOriginWithDurationDTO.compareMonth(reform, years, month);
        
        // 6. 5번 결과 데이터를 토대로 엑셀 파일을 생성
        new ExcelCreation().createFile(response, MergeDTO.convert(result), years, month);
    }
}

 

Controller 도 조금 변경되었음

[기존]

@GetMapping("/files/download/result/{years}/{month}")
public void calculate(@PathVariable("years") String years,
                      @PathVariable("month") String month,
                      HttpServletResponse response) throws IOException {
    new ExcelCreation().createFile(response, MergeDTO.convert(dataRefactorService.isHolidayCalculate(years, month)), years, month);
}

[변경]

@GetMapping("/files/download/result/{years}/{month}")
public void calculate(@PathVariable("years") String years,
                      @PathVariable("month") String month,
                      HttpServletResponse response) throws IOException {
    new AbsenceCalculatorService().isHolidayCalculate(years,month,response);
}

유틸클래스 메서드를 Controller 에서 호출하지 않고, Service 단으로 이동함

함수의 파라미터로 함수를 넣는 복잡함이 사라지고 코드 가독성도 높아졌음.