[Java] MVC를 적용할 수 없는 legacy 구조에서 Adapter 패턴을 적용하여 확장성을 개선할 수 있는 방안

Hyo Kyun Lee·4일 전
0

Java

목록 보기
100/100

1. 개요

Controller layer만 존재하고 Service layer는 존재하지 않아 JPA Repository의 확장성 확보가 쉽지 않은 상태에서, 구조적인 변경을 최소화하고 유연한 대응을 가능하도록 하는 방안을 생각해보았다.

일단 MVC Pattern이나 Clean Architecture를 직접적으로 도입하는 것은 쉽지 않았기에, 최소한 JPA Repository를 사용하고 있는 util의 영속성 계층에 대한 직접 의존도를 제거하고 Adapter를 바라보게 하여 확장성을 개선할 수 있는 방안을 생각하여 실제 적용해보았다.

Service layer가 아니기도 하고, 프로젝트 구조가 MVC Pattern이 아니기에 JPA Repository의 직접 의존은 확장성이 매우 떨어질 것으로 판단, Adapter layer를 활용하여 직접 의존성를 제거하여 결합도를 낮추고 변경에 유연하게 대응할 수 있는 방안을 생각해보았다.

2. ASIS : Service layer가 아닌 특정 도메인에서 Repository layer를 직접 참조한다.

기존 구조에서는 Repository를 생성자 주입을 통해 의존성을 주입받고, 마치 Service layer에서 Repository layer를 참조하는 것처럼 주입받은 의존성을 직접 참조하는 방식으로 로직을 구현하였다.

public class WriterProvider {

    //구조상 autowired 불가하므로 생성자 주입을 통해 의존성 주입
    private static EngDataReaderRepository engDataReaderRepository;

    public WriterProvider(EngDataReaderRepository engDataReaderRepository){
        this.engDataReaderRepository = engDataReaderRepository;
    }
    
    //시도 : 시도영문명과 함께 데이터 삽입
    if(engHeader.equals(SIDO)) {
          String sidoEngName = String.valueOf(engDataReaderRepository.findById(value).map(TbReGreenRegionMeta::getRegionNameEn).orElseThrow(() -> new RuntimeException("시도 ["+value+"]에 해당하는 시도영문명이 존재하지 않습니다. 메타데이터를 추가하십시오.")));
          row.createCell(colIndex + 1).setCellValue(sidoEngName);
    }

    //시군구 : 시군구영문명과 함께 데이터 삽입
    if(engHeader.equals(SIGUNGU)) {
          String sigunguEngName = String.valueOf(engDataReaderRepository.findById(value).map(TbReGreenRegionMeta::getRegionNameEn).orElseThrow(() -> new RuntimeException("시군구 ["+value+"]에 해당하는 시군구영문명이 존재하지 않습니다. 메타데이터를 추가하십시오.")));
          row.createCell(colIndex + 1).setCellValue(sigunguEngName);
    }
}

이는 기존 legacy 구조의 변경점은 최소화하는 것들 의도하였으나 이로 인해 의존성을 Controller layer에서 부터 다소 부자연스럽게 주입을 받게 되었고(생성자 주입), 해당 Repository를 직접 활용하기에 매핑 로직이 바뀌면 변경할 곳이 많아지고 구조 파악이 힘들어진다.

따라서 상기 Repository를 직접 참조하는 부분을 Adapter를 활용하여 결합도를 낮추도록 하고, 변경사항이 생길경우 Repository와 이를 사용하는 도메인이 아닌 Adapter만 변경하면 되도록 바꾸어 확장성을 개선하였다.

3. TOBE : Adapter 별도 구성하고 이를 바라보도록 해서 확장성에 유리하도록 구조 보완

기존 구조는 위와 같이 JPA Repository를 직접 참조하고 있기에, 변동사항이 생기면 해당 JPA Logic을 찾아 변경해주어야 하였고 이로 인해 확장성에 매우 불리하였다.

Adapter 패턴을 도입하게 되면, JPA Repository를 직접 참조하지 않도록 구조를 보완할 수 있고, 로직을 변경할 경우 Adapter만 변경하고 다른 변경점은 건드리지 않아도 되도록 확장성을 개선할 수 있을 것이라 판단하였다(변경점 최소화).

3-1. Adapter 패턴

가장 먼저 Adapter 패턴을 만들었다.

/*
* JPA Repository를 직접 참됨하지 않도록 하는 어댑터 패턴
* 변경점이 생길 경우 Adapter만 변경하면 됨
* */
public class EngDataReaderAdapter {
    private EngDataReaderRepository engDataReaderRepository;

    public EngDataReaderAdapter(EngDataReaderRepository engDataReaderRepository) {
        this.engDataReaderRepository = engDataReaderRepository;
    }

    /*
    * 시군구 JPA Adapter
    * */
    public String findSIGUNGUById(String id){
        return engDataReaderRepository.findById(id).map(TbReGreenRegionMeta::getRegionNameEn).orElseThrow(() -> new RuntimeException("시군구 ["+id+"]에 해당하는 시군구영문명이 존재하지 않습니다. 메타데이터를 추가하십시오."));
    }

    /*
    * 시도 JPA Adapter
    * */
    public String findSIDOById(String id){
        return engDataReaderRepository.findById(id).map(TbReGreenRegionMeta::getRegionNameEn).orElseThrow(() -> new RuntimeException("시도 ["+id+"]에 해당하는 시도영문명이 존재하지 않습니다. 메타데이터를 추가하십시오."));
    }
}

Adapter는 모든 인터페이스에 대해 적용할 수 있는 패턴이기에 기본적으로는 필요한 인터페이스를 멤버변수로 지녀야 하겠지만, 지금은 굳이 해당 인터페이스를 위한 도메인을 나눌 필요도 없고 직관적인 구조만 유지하는 것에 신경을 쓰도록 하였기에 Adapter 패턴에서 직접 Jpa Repository를 참고하도록 하였다.

로직이 변경되면 Adapter만 바라보면서 바꿀 수 있도록 하여 변경점을 최소화하는 것이 이번 구조 보완의 핵심이라 할 수 있겠다.

3-2. Repository가 아닌 Repoaitory를 주입받은 Adapter를 전달

기존에는 생성자 주입을 통해 Controller에서 Repository를 특정 도메인에 넘겨주었는데, 이게 부자연스럽고 확장성에 많은 불리함이 있을 것으로 판단하여 Repository 대신에 위에서 만든 Adapter를 넘겨주도록 구성해주었다.

/*
    * Provider에서 의존성을 생성자로 주입받기 위해 Controller에서 의존성 주입용 멤버변수를 생성
    * ASIS : Repository 의존성 주입을 기존 생성자 주입에서 TOBE : Adapter 패턴을 통해 주입받도록 변경
    * */
    @Autowired
    private EngDataReaderRepository engDataReaderRepository;

이처럼 Repository를 주입받은 후에

//JPA의존성 주입 후 데이터 쓰기(Provider 측에서 Autowired가 불가하여 생성자 주입으로 의존성을 주입해주기 위함)
//WriterProvider writerProvider = new WriterProvider(engDataReaderRepository);
WriterProvider writerProvider = new WriterProvider(new EngDataReaderAdapter(engDataReaderRepository));

기존에는 주입받은 Repository를 바로 넘겨주었다면, 이제는 Adapter를 넘겨주도록 하였다.
Adapter 객체가 Repository를 주입받은 상태에서 넘겨주므로, Adapter를 넘겨받은 쪽에서는 Adapter에서 제공하는 메소드를 사용해주면 된다.

3-3. 전달받은 Adapter 활용

최종적으로 전달받은 Adapter를 활용할 수 있도록 하여 구조 개선을 진행하였다.

//시도 : 시도영문명과 함께 데이터 삽입
                if(engHeader.equals(SIDO)) {
                    //String sidoEngName = String.valueOf(engDataReaderRepository.findById(value).map(TbReGreenRegionMeta::getRegionNameEn).orElseThrow(() -> new RuntimeException("시도 ["+value+"]에 해당하는 시도영문명이 존재하지 않습니다. 메타데이터를 추가하십시오.")));
                    String sidoEngName = engDataReaderAdapter.findSIDOById(value);
                    row.createCell(colIndex + 1).setCellValue(sidoEngName);
                }

                //시군구 : 시군구영문명과 함께 데이터 삽입
                if(engHeader.equals(SIGUNGU)) {
                    //String sigunguEngName = String.valueOf(engDataReaderRepository.findById(value).map(TbReGreenRegionMeta::getRegionNameEn).orElseThrow(() -> new RuntimeException("시군구 ["+value+"]에 해당하는 시군구영문명이 존재하지 않습니다. 메타데이터를 추가하십시오.")));
                    String sigunguEngName = engDataReaderAdapter.findSIGUNGUById(value);
                    row.createCell(colIndex + 1).setCellValue(sigunguEngName);
                }

WriterProvider 측은 Repository를 직접 참조하여 활용하지 않고, Adapter를 통해 Repository layer 로직을 활용할 수 있게 되었다.

4. 결론

기존 Repository를 직접 의존성 주입받는 방식에서 Adapter를 주입받는 구조로 개선하였다.
이로 인해 로직 변경이 일어나더라도 Adapter의 변경만 해주면 되기에 유지관리성이 더 편해졌다.

또한 구조 자체를 직관적으로 파악하고 의미를 이해할 수 있도록 적절한 수준으로 도메인 분리 및 확장성 개선 작업을 진행하였기에, 이후 유지관리에 대한 소모도 상대적으로 절감할 수 있는 효과를 기대할 수 있게 되었다.

0개의 댓글