ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Agarang] 지금까지 내가 잘 못 알고 있었던 점 (2) - 아키텍처 재구성
    개발 ing 2025. 3. 2. 15:43

    해당 글은 SSAFY12기 공통 프로젝트 Agarang 서비스를 만들면서 했던 내용이다

     

    일단 이 글의 목표는 확장성 있는 코드를 작성하는게 목표이다. 

     

    https://hyeonni.tistory.com/108

     

    [Agarang] 지금까지 내가 잘 못 알고 있었던 점 (1) - 문제제기

    해당 글은 SSAFY12기 공통 프로젝트 Agarang 서비스를 만들면서 했던 내용이다 이 글은 코드 작성 과정에서 내가 고민했던 점과 잘못 알고 있었던 개념을 정리한 글이다. 일단 코드를 한번 보자 Bab

    hyeonni.tistory.com

     

    이글은 위 포스트와 이어진다

     

    이제 문제점을 파악 했으니 해당 글에서는 이런 문제점을 어떤식으로 풀었는지에 관한 이야기를 할것이다

     

    일단 한마디로 말을 하자면 아키텍처를 수정했다

     

    Controller -> Service -> Repository  이 3계층을 깰것이다 

     

    그럼 어떤식으로 깰것이냐? 

     

    일단 객체지향적으로 생각을 해보자

     

    Controller 는 웹 계층으로 이 Controller 는 Request 와 Response 를 처리 하는 역할을 가지고 있는 클래스이다

     

    Repository 는 데이터베이스와 통신하는 역할을 가지고 있다

     

    일단 이 두 클래스는 각각의 역할을 가지고 있다

     

    그럼 남은건 Service 이다 

     

    Service는 원래는 비즈니스 로직을 처리하는 클래스이다 

     

    하지만 앞에 포스트에서 봤듯이 Service에 Business Logic 과 Implements Logic 이 섞여 있다

     

    Business Logic 의 뜻은 시스템이 해결해야 하는 핵심 규칙과 로직이다 

    다시 말해서 구현 로직들의 흐름을 처리하는 것이다 

     

    그래서 나는 기존의 Service 를 Business Service 와 Implements Service 계층으로 나누었다 

     

    Controller -> Business Service -> Implements Service -> Repository

    이런식으로 나누었다.

     

    Implements Service 는 곧 Business Service의 도구 클래스라고 생각하면 된다

     

    Business Service는 Implements Service들을 호출하여 비즈니스 로직을 처리하는 역할을 하는 클래스이다

     

    Implements Service는 구현을 하는 역할을 하는 클래스이다

     

    역할을 다시 정리르 해보자면

     

    1. Controller 

    • 웹 계층
    • request, response 처리 

     

    2. Business Service 

    •  비즈니스 로직 담당
    • 시스템이 해결해야 하는 핵심 규칙 즉 도메인 규칙, 흐름, 정책 등을 처리
    • 필요한 구현 로직은 Implements Service 에게 위임

     

    3. Helper Service

    • 세부 구현을 처리한다
    • Repository를 통해 DB에 접근

     

    4. Repository 

    • DB 접근 계층
    • 실제 CRUD 작업 수행 

     

    이렇게 분리를 함으로써

     

    가독성이 좋아지고 유지보수성이 용이해지는 장점이 있다 

     

    코드를 봐보자 

     

    /**
     * packageName    : com.example.dosirakbe.domain.user_activity.service<br>
     * fileName       : UserActivityService<br>
     * author         : Fiat_lux<br>
     * date           : 11/03/24<br>
     * description    : 사용자 활동 기록과 관련된 비즈니스 로직을 처리하는 서비스 클래스입니다.<br>
     * ===========================================================<br>
     * DATE              AUTHOR             NOTE<br>
     * -----------------------------------------------------------<br>
     * 11/03/24        Fiat_lux                최초 생성<br>
     */
    @Service
    @RequiredArgsConstructor
    @Transactional(readOnly = true)
    public class UserActivityService {
        private final UserActivityReader userActivityReader;
        private final UserActivityWriter userActivityWriter;
        private final UserReader userReader;
        private final UserActivityMapper userActivityMapper;
    
        /**
         * 특정 사용자의 지정된 월에 대한 활동 목록을 조회하여 {@link UserActivityResponse} Response DTO 리스트로 반환합니다.
         *
         * <p>
         * 이 메서드는 사용자의 ID와 조회할 월 정보를 기반으로 해당 사용자의 활동 기록을 조회합니다.
         * 지정된 월의 첫째 날과 마지막 날 사이의 활동을 생성일 기준 오름차순으로 정렬하여 반환합니다.
         * </p>
         *
         * @param userId 사용자의 고유 식별자 {@link Long}
         * @param month  조회할 월을 나타내는 {@link YearMonth} 객체 (선택 사항)
         * @return 조회된 사용자 활동을 포함하는 {@link List} 형태의 {@link UserActivityResponse} 객체 리스트
         * @throws ApiException {@link ExceptionEnum#DATA_NOT_FOUND} 예외 발생 시
         */
        public List<UserActivityResponse> getUserActivityList(Long userId, YearMonth month) {
            User user = userReader.findByUserIdButThrow(userId);
    
            LocalDate firstDay = month.atDay(1);
            LocalDate lastDay = month.atEndOfMonth();
    
            List<UserActivity> userActivitiesByDate = userActivityReader.getUserActivitiesByDate(user, firstDay, lastDay);
    
            return userActivityMapper.mapToUserActivityResponseList(userActivitiesByDate);
        }
    
        /**
         * 특정 사용자의 오늘 날짜에 대한 활동 기록을 생성하거나 기존 기록을 증가시킵니다.
         *
         * <p>
         * 이 메서드는 사용자의 ID를 기반으로 오늘 날짜에 대한 활동 기록을 조회합니다.
         * 만약 활동 기록이 존재하면 {@link UserActivity#addCommitCount()} 메서드를 통해 커밋 수를 증가시키고,
         * 존재하지 않으면 새로운 {@link UserActivity} 엔티티를 생성하여 저장합니다.
         * </p>
         *
         * @param userId 사용자의 고유 식별자 {@link Long}
         * @throws ApiException {@link ExceptionEnum#DATA_NOT_FOUND} 예외 발생 시
         */
        @Transactional
        public void createOrIncrementUserActivity(Long userId) {
            User user = userReader.findByUserIdButThrow(userId);
    
            userActivityReader.findTodayActivityByUser(user)
                    .ifPresentOrElse(userActivityWriter::addCommitUserActivity,
                            () -> userActivityWriter.createUserActivity(new UserActivity(user))
                    );
        }
    
    
    }

     

    위 코드를 리뷰해 보자면 

     

    위코드는 Business Service 클래스이다

     

    getUserActivityList() 메서드를 봐보자

     

    1) UserActivityReader

    • 역할 : UserActivity 와 관련된 조회 로직 

     

    2) UserActivityWriter

    • 역할 : UserActivity에 대한 작성 또는 갱신 로직 담당

     

    3) UserReader

    • 역할 : User 를 조회하는 책임, 존재하지 않으면 예외 처리

     

    4) UserActivityMapper

    • 역할 : entity <-> DTO 변환 로직

     

    이런식으로 Business Service는 흐름을 보여주고 있다 느낌이 교통 정리? 같은 느낌이다

     

    이런 아키텍처를 

    이런식으로 수정을 함으로써 좀 더 확장성 있고 가독성 높게 만들어줬다

     

    Service 계층을 Business Service, Helper Service 이런식으로 나누고 있는데 

    이걸 무지성으로 적용 시키는 것만은 아니다

    만약 Business Service 에 간단하게 직접 데이터를 가져올 경우에는 굳이 HelperService를 걸칠 필요 없다.  그래서 그때 상황에 맞게 판단해서 만들 생각이다 위에 그림은 그냥 대략적인 것이다.

     

    Helper Service 라고 했는데 비즈니스 로직을 만드는데에 도와주는 느낌 아닌가? 도구 느낌으로 생각해서 이름을 붙혀주었다.

    추후에는 만약 로직이 복잡하고 해당 도메인이 복잡하거나 하는 행동이 많게 되면 XXXGetHelper, XXXWriteHelper 이런식으로 ReadOnly 설정 기준으로 분리를 해 나갈 것이다.

     

     

    마무리

    이런식으로 하여 

    가독성, 유지보수성, 확장성을 좀 더 높일 수 있었다 

     

    앞으로 기능이 늘어나거나 정책이 변경되어도 도메인 규칙과 구현 세부사항을 따로 수정할 수 있어서 좀 더 유연성이 높다고 할 수 있다

     

    일단 이런식으로 개발을 이어갈 것다 

     

    그리고 앞으로의 방향성은 

     

    최근에 우리 프로젝트에 만약 JPA가 아닌 Mybatis로 수정을 한다면? ORM이 바뀌어 버린다면? 이라는 의문이 들기 시작했다 

    그럼 코드를 다시 다 작성해야하는데 이게 유연성이 높다고 말을 할 수 있을까? 라는 의문점이 들기 시작했다

     

    그리고 계층 구조에서의 dto, vo 를 명확히 한게 맞을까? 라는 생각이 들어 이런 방향으로 개선할 것 같다

     

    그리고 도메인 별로 현재 Repository와 Service가 구성이 되어 있는데 지금 이런 구조가 도메인 독립적인지도 생각을 해봐야 할 것 같고 

     

    멀티 모듈로 좀 더 쪼개면 어떨까? 라는 생각이 들게 되었다

     

     

    다시 말해 

     

    1. ORM 교체 한다면?

    2. DTO와 VO 정의

    3. 도메인 독립성

    4. 멀티 모듈화 

     

    이런 방향으로 고민하고 개선할 예정이다! 

Designed by Tistory.