728x90

👏 퍼사드 패턴 적용기

퍼사드 패턴에 대한 설명은 깃허브 에 있다.
리팩토링 진행도중에 좀 좋은 구조를 구성해서 적어본다.

🔥 개요

우선 Service 레이어에서 Repository를 너무 많이 의존하는것이 상당히 컸다.

여러군데에서 가져오는 것 사실 이것은 개발하면서 어쩔 수 없는 노릇일 수 있다.

그러나 의존을 너무 많이 가져간다는 것은 유지보수 측면에서도 힘들것이고,
응집도도 낮은 그런 클래스가 되어버린다.
그래서 일단 생각해 낸것은 우리는 객체지향을 사용하고 있다.
어떤 한 클래스는 조합하는것만을 목표로 하는 클래스가 있다면 어떨까🤔 하고 생각했다.
이 클래스를 생각한것은 기존 프로젝트에 테스트 코드를 추가하려고 하다보니까 고민하게 되었다. 🤔
기존 클래스에서 테스트를 하려면 너무 필요없는 의존성까지 추가해줘야해서 자칫하면 혼동을 불러올 수가 있었다.
그래서 생각한 것이 퍼사드 패턴...

이 퍼사드 클래스가 여러개의 Service 레이어를 두면 그것들을 한데 모아서

처리를 해주면 의존은 낮아지면서 응집도는 올릴 수 있을거라고 생각했다.

왜?

각각 역할에 대한 Repository 와 필요한 의존성만 가지고 하나의 Service로 나눈것을 퍼사드쪽에서

조립해서 써주면 지금과 같은 로직이 되면서 동시에 테스트도 잘 가져갈 수 있을거라 생각했다.

🔥 클래스 다이어그램

스크린샷 2021-12-31 오전 9 43 48

예를 들어 이런 클래스가 있다고 가정하자.

Repository 객체를 두개를 사용하고 있다.

이게 2개일 수 있고 그 이상이 될 수도 있다.
이 구조를 개선하려고 했던 다음 클래스 다이어그램을 보도록 하자.

스크린샷 2021-12-31 오전 9 47 21

이런식으로 하위 객체를 의존성을 1개씩만 갖게하고 퍼사드에서는 조합만 해주면 되는게 된다.
이렇게 되면 각 기능에 대한 단위 테스트를 깔끔하게 진행할 수 있고,
문제가 발생하는 로직에 한해서만 유지보수를 가능할 수 있게 되었다.

결국엔 Spring MVC 패턴이 그냥 이 퍼사드 패턴이 적용되어 있다 라고 생각하면 되지만,

아래의 의존성을 덕지덕지 붙여가면서 하나의 서비스를 뚱뚱하게 유지하기는 싫었다.
예시의 코드로 보자면
멤버와 아이템두개를 합쳐서 보여주는게 있다고 하자.

Member.java

public class Member {
    private Long id;
    private String name;
    private String email;
}

Item.java

public class Item {
    private Long id;
    private String name;
    private int price;
    //getter, setter 생략
}

Repository

public class MemberRepository extends JpaRepository<Member, Long> {
}

public class ItemRepository extends JpaRepository<Item, Long> {
}

레거시

@Service
public class FooService {
    @Autowired
    private MemberRepository memberRepository;
    @Autowired
    private ItemRepository itemRepository;

    public void getMemberAndItem(int id) {
        Member member = memberRepository.findById(id).orElseThrow();
        Item item = itemRepository.findById(id).orElseThrow();
        //두 값의 비즈니스 로직을 수행

        return //두개를 합쳐 반환한다.
    }

}

간결하게 두줄만 작성했지만 벌써 두개 조회를 동시에 하고 안에서 로직을 구현하고 있으니 스파게티 코드가 생성될 것이 예상된다.
이 구조를 아래와 같이 변경한다.

개선안

기본적으로 생성자 주입으로 변경을 해준다.

public class MemberService {
    private final MemberRepository memberRepository;

    public MemberService(fianl MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }

    public Member findById(final int id) {
        Member member = memberRepository.findById(id).orElseThrow();
        // member에 대한 로직 수행...
        return member;
    }
}

public class ItemService {
    private final ItemRepository itemRepository;

    public ItemService(final ItemRepository itemRepository) {
        this.itemRepository = itemRepository;
    }

    public Item findById(final int id) {
        Item item = itemRepository.findById(id).orElseThrow();
        // item에 대한 로직 수행...
        return item;
    }
}

일단 서비스를 위와같이 두개로 분리한다. 그러면 각 Repository에 대해 해야할 일만 명확하게 구분이 되어진다.

이것을 사용할 퍼사드로 넣어주면 되는 것이다 👍

public class TogetherFacade {
    private final MemberService memberService;
    private final ItemService itemService;

    public TogetherFacade(final MemberService memberService, final ItemService itemService) {
        this.memberService = memberService;
        this.itemService = itemService;
    }

    public void 전부를_만들어_돌려준다(final int id) {
        //이미 비즈니스 로직은 각 서비스에서 실행되었다.
        Member member = memberService.findById(id);
        Item item = itemService.findById(id);

        // 받은 그대로 조합식만 구성해주면 되는 것
        //return....
    }
}

이렇게 되었을 때, 각 서비스 레이어부터 단위테스트를 잘 구성해서 퍼사드쪽으로 넘어올 수가 있다.

테스트만을 위한 코드작성? 이라고는 볼 수 없을 것 같다.

결국 모든 프로그래밍의 구조를 따진다면 조합해서 놓고보면 쭉 이어진 코드는 맞다.
하지만 객체지향의 의미는 해당 객체에게 메세지를 보내 기능을 수행하도록 구성하는게 바람직하다.
그렇기에 이렇게 분리해주면 오류가 어디서 나는지 명확하게 짚을 수 있다.
그러면서 단일 책임 원칙을 준수하게 된 아래의 레이어들이다. 🤩

여기서 더 어떻게 고쳐야하나? 라는건 나의 남은 숙제다 ㅋㅋㅋㅋㅋ

🔥 후기

일단 로직이 더러운것은 놔두고 각자의 역할만 따로따로 분리해서 만들고 보니
로직이 더러운게 아니었다. 그냥 모든 코드가 합쳐져서 한 메소드가
많은 일을 수행하고 있던거지 로직 자체가 이상하게 짜여져있는 것은 아니었다. (물론 이 클래스만 그랬을거다.)

Mocking을 하면서 Mockito로 각 서비스 레이어를 테스트하였다.

@Autowired로 되어있던 것을 생성자 주입으로 바꾸면서 이 테스트도 진행할 수 있게 되었던건데,

이것은 다음 포스트에서 자세하게 다뤄보도록 하겠다.
아무튼 구조를 개선하여 조금 더 깔끔하게 코드를 관리할 수 있게 되었다. 🤣

728x90

+ Recent posts