ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Spring과 Abstract Factory Pattern
    Spring 2023. 2. 1. 13:57
    반응형

    추상 팩토리

    이번 주제는 프로세스 로직을 추상 팩토리 패턴을 적용하여 Spring에서 어떻게 사용할 수 있을지에 대해서 작성하려고 합니다.

    추상 팩토리 패턴이란?

    개인적인 견해로는 추상 팩토리 패턴은 팩토리 메서드 패턴보다 결합도를 낮추고 응집도를 올려주는 패턴이라고 생각합니다. 왜 결합도를 낮추고 응집도를 높히는지 알려면 먼저 팩토리 메서드 패턴 구조를 알아야 하는데 이 부분은 이전 글인 https://devssul.tistory.com/26를 참고하시면 되겠습니다.

    팩토리 메서드는 프로세스 구현 객체를 반환 받기 위해 추상 클래스(혹은 부모 클래스)와 상속 받는 구현체 구조를 가지게 되고 반환 받은 프로세스 구현 객체를 추상 클래스(혹은 부모 클래스)에서 제어하기 때문에 추상 클래스(혹은 부모 클래스)에서 제어 부분을 변경하게 되면 상속 받은 모든 자식 클래스에 영향이 전파 되는 결합이 생깁니다.

    추상 팩토리 패턴은 프로세스 구현 객체 반환 부분이 메서드 오버라이드 구조가 아닌 완전한 객체 생성 구조를 가지고 있기 때문에 변경 시 발생 되는 영향에 전파가 없으며 클래스로 프로세스 처리 구현 객체들을 생성 관리하기 때문에 응집도를 높힐 수 있습니다.

    더 자세한 내용은 추상 팩토리 패턴 (refactoring.guru)에서 확인 하시면 되겠습니다.

    예제

    예제는 https://devssul.tistory.com/26을 기준으로 하겠습니다. 이전 글에서는 회원가입만 있고 검증 하는 부분이 빠져있어 회원가입 & 검증을 추상 팩토리 패턴을 적용하도록 하겠습니다.

    큰 흐름의 클래스 다이어그램을 살펴보면 MemberJoinAbsFactory 인터페이스에서 회원가입 프로세스 구현체를 반환하는 createMemberJoin 메서드와 검증 프로세스 구현체를 반환하는 createMemberJoinValidate 메서드가 선언 되어 있고 카카오톡 회원가입 프로세스 구현체들을 반환하는 KakaoMemberJoinAbsFactory 구현체와 기본 회원가입 프로세스 구현체들을 반환하는 DefaultMemberJoinAbsFactory 구현체가 존재합니다.

    https://i.ibb.co/89mcfw0/Member-Join-Abs-Factory-1.png

    카카오톡 회원가입 추상 팩토리 패턴

    카카오톡 회원가입 프로세스 추상 팩토리 패턴 부분만 상세 클래스 다이어그램으로 보면 아래와 같습니다.

    https://i.ibb.co/H7F4qqS/Member-Join-Abs-Factory.png

    회원가입 프로세스 구현체

    /**
     * <pre>
     *     카카오톡 회원가입 처리 프로세스 클래스
     * </pre>
     */
    public class KakaoMemberJoinAbsFactoryProcessImpl implements MemberJoinAbsFactoryProcess {
        private final KakaoMemberJoinEntity kakaoMemberJoinEntity;
    
        public KakaoMemberJoinAbsFactoryProcessImpl(KakaoMemberJoinEntity kakaoMemberJoinEntity) {
            this.kakaoMemberJoinEntity = kakaoMemberJoinEntity;
        }
    
        /**
         * {@link MemberJoinAbsFactoryProcess#join()}
         * 카카오톡 회원가입인 경우엔 응답 받은 카카오톡 전문에서 특정 데이터만 추출하여
         * 기본 회원가입 처리도 동시 처리 진행한다.
         */
        @Override
        public void join() {
            KakaoMemberJoinWriteImpl kakaoMemberJoinWriteImpl = MemberJoinFactory.getInstance(KakaoMemberJoinWriteImpl.class);
    
            MemberJoinEntity memberJoinEntity = new MemberJoinEntity(this.kakaoMemberJoinEntity.getKakaoAccountEmail()
                    , UUID.randomUUID().toString().replace("-", "").substring(0, 10)
                    , MemberJoinEntity.MemberJoinType.KAKAO);
    
            kakaoMemberJoinWriteImpl.kakaoJoin(this.kakaoMemberJoinEntity);
            kakaoMemberJoinWriteImpl.join(memberJoinEntity);
        }
    }

    회원가입 검증 프로세스 구현체

    /**
     * <pre>
     *     카카오톡 회원가입 예외 검증 구현체
     * </pre>
     */
    public class KakaoMemberJoinValidateAbsFactoryProcessImpl implements MemberJoinValidateAbsFactoryProcess {
    
        private final KakaoMemberJoinEntity kakaoMemberJoinEntity;
    
        public KakaoMemberJoinValidateAbsFactoryProcessImpl(KakaoMemberJoinEntity kakaoMemberJoinEntity) {
            this.kakaoMemberJoinEntity = kakaoMemberJoinEntity;
        }
    
        /**
         * <pre>
         *     {@link MemberJoinValidateAbsFactoryProcess#validate()}
         * </pre>
         */
        @Override
        public void validate() {
            KakaoMemberJoinEntity findKakaoMemberJoinEntity = MemberJoinFactory.getInstance(KakaoMemberJoinReadImpl.class)
                    .findKakaoMember(this.kakaoMemberJoinEntity.getKakaoAccountEmail());
    
            if(findKakaoMemberJoinEntity.getId() != 0L) {
                throw new IllegalStateException("존재하는 회원 입니다.");
            }
        }
    }

    추상 팩토리 패턴 구현체

    /**
     * <pre>
     *     카카오톡 회원가입 추상 팩토리 클래스
     * </pre>
     */
    public class KakaoMemberJoinAbsFactory implements MemberJoinAbsFactory {
    
        /**
         * <pre>
         *     {@link MemberJoinAbsFactory#createMemberJoin(Object)}
         * </pre>
         */
        @Override
        public <T> MemberJoinAbsFactoryProcess createMemberJoin(T joinEntity) {
            return new KakaoMemberJoinAbsFactoryProcessImpl((KakaoMemberJoinEntity) joinEntity);
        }
    
        /**
         * <pre>
         *     {@link MemberJoinAbsFactory#createMemberJoinValidate(Object)}
         * </pre>
         */
        @Override
        public <T> MemberJoinValidateAbsFactoryProcess createMemberJoinValidate(T validateEntity) {
            return new KakaoMemberJoinValidateAbsFactoryProcessImpl((KakaoMemberJoinEntity) validateEntity);
        }
    }

    기본 회원가입 추상 팩토리 패턴

    기본 회원가입 추상 팩토리 패턴 클래스 다이어그램은 아래를 참고하시면 되겠습니다.

    https://i.ibb.co/LYZ5vG9/Member-Join-Validate-Abs-Factory-Process-Impl.png

    회원가입 프로세스 구현체

    /**
     * <pre>
     *     기본 회원가입 처리 프로세스 클래스
     * </pre>
     */
    public class MemberJoinAbsFactoryProcessImpl implements MemberJoinAbsFactoryProcess {
        private final MemberJoinEntity memberJoinEntity;
    
        public MemberJoinAbsFactoryProcessImpl(MemberJoinEntity memberJoinEntity) {
            this.memberJoinEntity = memberJoinEntity;
        }
    
        /**
         * {@link MemberJoinAbsFactoryProcess#join()}
         */
        @Override
        public void join() {
            MemberJoinFactory.getInstance(MemberJoinWriteImpl.class)
                    .join(memberJoinEntity);
        }
    }

    회원가입 검증 프로세스 구현체

    /**
     * <pre>
     *     기본 회원가입 예외 검증 구현체
     * </pre>
     */
    public class MemberJoinValidateAbsFactoryProcessImpl implements MemberJoinValidateAbsFactoryProcess {
    
        private final MemberJoinEntity memberJoinEntity;
    
        public MemberJoinValidateAbsFactoryProcessImpl(MemberJoinEntity memberJoinEntity) {
            this.memberJoinEntity = memberJoinEntity;
        }
    
        /**
         * <pre>
         *     {@link MemberJoinValidateAbsFactoryProcess#validate()}
         * </pre>
         */
        @Override
        public void validate() {
            MemberJoinEntity findMemberJoinEntity = MemberJoinFactory.getInstance(MemberJoinReadImpl.class)
                    .findMember(this.memberJoinEntity.getMemberId());
    
            if(!"".equals(findMemberJoinEntity.getMemberId())) {
                throw new IllegalStateException("존재하는 회원 입니다.");
            }
        }
    }

    추상 팩토리 패턴 구현체

    /**
     * <pre>
     *     기본 회원가입 추상 팩토리 클래스
     * </pre>
     */
    public class DefaultMemberJoinAbsFactory implements MemberJoinAbsFactory {
    
        /**
         * <pre>
         *     {@link MemberJoinAbsFactory#createMemberJoin(Object)}
         * </pre>
         */
        @Override
        public <T> MemberJoinAbsFactoryProcess createMemberJoin(T joinEntity) {
            return new MemberJoinAbsFactoryProcessImpl((MemberJoinEntity)joinEntity);
        }
    
        /**
         * <pre>
         *     {@link MemberJoinAbsFactory#createMemberJoinValidate(Object)}
         * </pre>
         */
        @Override
        public <T> MemberJoinValidateAbsFactoryProcess createMemberJoinValidate(T validateEntity) {
            return new MemberJoinValidateAbsFactoryProcessImpl((MemberJoinEntity) validateEntity);
        }
    }

    추상 팩토리 패턴을 사용하는 클래스

    /**
     * <pre>
     *     회원가입 트랜잭션 처리 클래스
     * </pre>
     */
    @Service
    public class MemberJoinAbsFactoryServiceImpl {
    
        /**
         * <pre>
         *     회원가입 프로세스 트랜잭션 처리를 하는 메서드
         * </pre>
         *
         * @param memberJoinAbsFactory 회원가입 추상 팩토리
         * @param joinEntity 회원가입 처리에 필요한 엔티티
         * @param <T>  회원가입에 유형에 따른 회원정보 엔티티 타입
         */
        public <T> void join(MemberJoinAbsFactory memberJoinAbsFactory, T joinEntity) {
            MemberJoinAbsFactoryProcess memberJoinAbsFactoryProcess = memberJoinAbsFactory.createMemberJoin(joinEntity);
            MemberJoinValidateAbsFactoryProcess memberJoinValidateAbsFactoryProcess = memberJoinAbsFactory.createMemberJoinValidate(joinEntity);
    
            memberJoinValidateAbsFactoryProcess.validate();
            memberJoinAbsFactoryProcess.join();
        }
    }

    테스트

    카카오톡 회원가입

    @Test
    void 카카오톡_회원가입() {
        KakaoMemberJoinEntity kakaoMemberJoinEntity = new KakaoMemberJoinEntity(13248627872L
                , LocalDateTime.now()
                , true
                , false
                , true
                , true
                , "test@kakao.com"
        );
    
        this.memberJoinAbsFactoryServiceImpl.join(new KakaoMemberJoinAbsFactory(), kakaoMemberJoinEntity);
    }

    결과

    c.d.c.r.m.w.t.KakaoMemberJoinWriteImpl   : kakaoJoin : [{"id":13248627872,"connectedAt":[2023,1,31,17,19,15,839213600],"kakaoAccountHasEmail":true,"kakaoAccountEmailNeedsAgreement":false,"kakaoAccountIsEmailValid":true,"kakaoAccountIsEmailVerified":true,"kakaoAccountEmail":"test@kakao.com"}]
    c.d.c.r.m.w.test.MemberJoinWriteImpl     : defaultJoin : [{"memberId":"test@kakao.com","password":"4c2847b227","memberJoinType":"KAKAO"}]

    기본 회원가입

    @Test
    void 일반_회원가입() {
        MemberJoinEntity memberJoinEntity = new MemberJoinEntity(UUID.randomUUID().toString().replace("-", "").substring(0, 5)
                , UUID.randomUUID().toString().replace("-", "").substring(0, 10)
                , MemberJoinEntity.MemberJoinType.DEFAULT);
    
        this.memberJoinAbsFactoryServiceImpl.join(new DefaultMemberJoinAbsFactory(), memberJoinEntity);
    }

    결과

    c.d.c.r.m.w.test.MemberJoinWriteImpl     : defaultJoin : [{"memberId":"61c46","password":"545f185990","memberJoinType":"DEFAULT"}]

    마치며

    보시는바와 같이 프로세스 구현체들을 추상 팩토리 패턴 클래스가 관리하기 때문에 응집도를 높힐 수 있고 순수히 구현체들의 객체만을 반환하기 때문에 전체 프로세스를 어떤 방식으로 구현 하더라도 상관없어 결합를 낮출 수가 있습니다.

    그러나 단점으론 상당히 많은 클래스가 생성될 여지가 있기 때문에 꼭 추상 팩토리 패턴이 좋다 추상 메서드 패턴이 좋다 할 순 없습니다. 이 경우 세상의 만능 정답인 적절한 상황에서 적절하게 사용하는게 가장 좋은 방법이라 할 수 있습니다.(?)

    해당 예제는 https://github.com/sungwookkim/spring-designpattern 의 com.designPattern.creationalPattern.pattern.abstractFactory 패키지에서 확인하실 수 있습니다.

    끝으로 마지막까지 긴 글을 읽어주셔서 감사하고 하시는 모든 코딩에 좋은 결과만이 있길 기도드리겠습니다. 감사합니다.

    반응형

    'Spring' 카테고리의 다른 글

    Spring & JTA(분산 트랜잭션)  (1) 2023.10.30
    Spring과 Builder Pattern  (0) 2023.04.05
    Spring과 Factory Pattern  (1) 2023.01.19
    Spring과 Factory Method Pattern  (0) 2023.01.18
    Spring @Sevice 클래스 유연하게 써보기  (0) 2022.07.15

    댓글

Designed by Tistory.