본문 바로가기
📕 Spring Framework/Spring Project

코드 리팩토링 [1]

by GroovyArea 2022. 8. 3.
지난 1주일간 기존 프로젝트의 리팩터링을 위해 새로운 저장소를 생성했다.

아키텍처 부분 설계를 거의 2, 3일은 한 것 같다. 아직 수정이 필요하겠지만, 항상 고민해보자.

기존 프로젝트는 multi module로 진행했지만, 완벽한 멀티 모듈 프로젝트 이진 않았다. 
모놀리틱으로 갈지, MSA를 고려해 모듈간 분리를 할지가 계속 고민된다..

확장성을 위해 모듈, 패키지간 의존성 분리를 틈틈이 고려해봐야겠다.

기존 작성 코드를 옮기며 조금 더 클린한 객체지향적인 코드로 리뷰어님의 피드백을 통해 수정해나갔다.

많이 알고 있었다고 생각한 부분에서도 헛점이 있었고, 전혀 알지 못했던 디테일한 부분도 알아나가는 중이다.

인증 및 인가 부분은 기존 프로젝트에서는 손수 구현했지만, 이번에는 Spring Security를 이용해 볼 생각이다. 
한번 손수 경험한 것에 대한 보완점을 잘 만들어진 Framework를 사용함으로써 메꿔보고 싶다.

기존에 Persistence Layer를 Mybatis를 이용한 Sql Mapper를 이용해서 구현했다. 프로젝트 2개를 경험했었다.
이번엔 JPA를 이용해서 ORM으로 객체지향적 코드를 작성해보고 싶은 생각이 들었다.

리팩터링 한 부분과 새로 알게 된 점을 나열해보겠다.  

Model

객체는 Request, Response에 따라 책임을 나누어 같은 모델이라도 따로 나누어 생성하는 것이 좋다.

 > 이런 식으로 하나의 모델을 통합해서 사용하게 되면 변경사항이 생길 때 가변성을 띄게 될 수도 있다. 당연히 유지보수에 부적합하기 때문에 별개의 모델로 분리했다.

Utility

유틸리티로 사용할 클래스는 모든 도메인에서 이용이 가능해야 한다. 종속성을 띄면 안 된다.

public class CookieUtil {

    private static final String COOKIE_KEY = "Chicken";
    private static final String ENC_TYPE = "utf-8";

    private CookieUtil() {
    }

    /* 카트 쿠키 반환 메서드 */
    public static Optional<Cookie> getCartCookie(Cookie[] requestCookies) {
        return Arrays.stream(requestCookies)
                .filter(cookie -> cookie.getName().equals(COOKIE_KEY))
                .findFirst();
    }

    /* 카크 쿠키 값 디코딩 후 map 객체 반환 메서드 */
    public static Map<Integer, CartItemDTO> getCartItemDTOMap(Cookie responseCartCookie) throws UnsupportedEncodingException {
        String cookieValue = responseCartCookie.getValue();
        return JsonUtil.stringToMap(URLDecoder.decode(cookieValue, ENC_TYPE), Integer.class, CartItemDTO.class);
    }

    /**
     * 카트 안의 상품 번호 배열 반환 메서드
     *
     * @param responseCartCookie 응답 카트 쿠키
     * @return 상품 번호 배열
     * @throws UnsupportedEncodingException 인코딩 예외
     */
    public static Integer[] getItemNoArr(Cookie responseCartCookie) throws UnsupportedEncodingException {
        return Arrays.stream(getCartArr(responseCartCookie))
                .map(CartItemDTO::getProductNo)
                .toArray(Integer[]::new);
    }

    /**
     * 카트 안의 상품 이름 배열 반환 메서드
     *
     * @param responseCartCookie 응답 카트 쿠키
     * @return 상품 이름 배열
     * @throws UnsupportedEncodingException 인코딩 예외
     */
    public static String[] getItemNameArr(Cookie responseCartCookie) throws UnsupportedEncodingException {
        return Arrays.stream(getCartArr(responseCartCookie))
                .map(CartItemDTO::getProductName)
                .toArray(String[]::new);
    }

    /**
     * 카트 안의 상품 재고량 배열 반환 메서드
     *
     * @param responseCartCookie 응답 카트 쿠키
     * @return 상품 재고량 배열
     * @throws UnsupportedEncodingException 인코딩 예외
     */
    public static Integer[] getStockArr(Cookie responseCartCookie) throws UnsupportedEncodingException {
        return Arrays.stream(getCartArr(responseCartCookie))
                .map(CartItemDTO::getProductStock)
                .toArray(Integer[]::new);
    }

    /**
     * 카트 안의 상품 총 가격 반환 메서드
     *
     * @param responseCartCookie 응답 카트 쿠키
     * @return 상품 총 가격
     * @throws UnsupportedEncodingException 인코딩 예외
     */
    public static int getTotalAmount(Cookie responseCartCookie) throws UnsupportedEncodingException {
        return Arrays.stream(getCartArr(responseCartCookie))
                .map(CartItemDTO::getProductPrice)
                .mapToInt(Integer::intValue)
                .sum();
    }

    private static CartItemDTO[] getCartArr(Cookie responseCartCookie) throws UnsupportedEncodingException {
        Map<Integer, CartItemDTO> cartItemDTOMap = getCartItemDTOMap(responseCartCookie);
        Collection<CartItemDTO> values = cartItemDTOMap.values();
        return values.toArray(new CartItemDTO[0]);
    }
}

> 유틸리티 클래스이지만 CartItemDTO라는 모델에 종속적인 코드. 

 

@UtilityClass
public class CartCookieUtil {

    private static final String ENC_TYPE = "utf-8";

    /* 카트 쿠키 반환 메서드 */
    public static Optional<Cookie> getCartCookie(Cookie[] requestCookies, String cookieKey) {
        return Arrays.stream(requestCookies)
                .filter(cookie -> cookie.getName().equals(cookieKey))
                .findFirst();
    }

    /* 카트 쿠키 값 디코딩 후 map 객체 반환 메서드 */
    public static <V> Map<Integer, V> changeCookieToMap(Cookie responseCartCookie, Class<V> valueType) throws UnsupportedEncodingException {
        String cookieValue = responseCartCookie.getValue();
        return JsonUtil.stringToMap(URLDecoder.decode(cookieValue, ENC_TYPE), Integer.class, valueType);
    }

    /**
     *
     * @param responseCartCookie 응답용 장바구니 쿠키
     * @param clazz 변환 타입 clazz
     * @param <E> 변환 타입
     * @return 쿠키 값 리스트
     * @throws UnsupportedEncodingException 인코딩 예외
     */
    public static <E> List<E> getCookieValueList(Cookie responseCartCookie, Class<E> clazz) throws UnsupportedEncodingException {
        Map<Integer, E> map = changeCookieToMap(responseCartCookie, clazz);
        return new ArrayList<>(map.values());
    }
}

> 제네릭을 사용해 종속성을 없앴지만, 아직 좀 손 봐야 될 것 같다. 메서드명이나 파라미터명 등등

> 추후 서비스나 컴포넌트 빈을 이용해 장바구니 상세 로직을 구현해 보는 게 좋을 것 같다.

 

Mapper

매퍼와 DDL

> PK 가 없음

> PK를 직접 사용하지 않더라도 생성시키는 게 좋겠다. 

 

> 의미가 없는 스칼라 값 대신 자바의 enum을 사용하는 것이 좋겠다. 

> 또 디폴트를 하기보다 Not Null로 설정하여 Insert 시 한 번에 추가하는 게 좋을 것 같다. 변경 사항이 생길 시 좋지 않다.

 

> 금액 관련 부분은 충분히 큰 자료형을 사용하는 것이 좋겠다.

 

> 인덱스를 전혀 타지 않는 쿼리

> % 연산자가 맨 앞에 올 경우 풀스 캔이 필연적이다. 인덱스를 사용하더라도 풀스 캔을 하므로 조심하자.

 

 

Filter

> 이렇게 수많은 커스텀 예외를 만들기보다는 하나의 예외를 만들어 각기 다른 메시지를 파라미터로 받는 것도 좋은 방법 같다.

> 처리하지 못하는 예외는 Exception으로 처리했다.

> 스프링 시큐리티를 사용해서 예외 처리 필터를 만들어보자.

 

Intercepter

권한 처리를 인터셉터가? 

기존의 프로젝트는 애노테이션을 이용한 인터셉터 기반 인가 처리.

 

> 애노테이션을 정의해서 인터셉터에서 애노테이션 값을 읽어 권한 처리를 진행한다.

> 문제점 : 패키지 간 의존성이 심하게 증가한다. 객체지향적이지 못하다. 

> URL Paths를 따로 관리해 필터로 처리하는 방법도 좋을 것 같다.

 

=> 할 일

- ERD 모델

- JPA 세팅

- Spring security 적용

- 일단 모놀리틱 하게 본연의 코드에 집중하고 모듈 분리는 추후에 신경 쓰자.

 

반응형