본문 바로가기
스프링과 JPA 기반 웹 어플리케이션 개발/6부 패키지와 테스트 정리

69. 패키지 구조 정리

by Backchus 2020. 5. 6.

패키지 구조

 

ArchUnit

 

Unit test your Java architecture

Start enforcing your architecture within 30 minutes using the test setup you already have.

www.archunit.org

<!-- https://mvnrepository.com/artifact/com.tngtech.archunit/archunit -->
        <dependency>
            <groupId>com.tngtech.archunit</groupId>
            <artifactId>archunit-junit5</artifactId>
            <version>0.13.1</version>
            <scope>test</scope>
        </dependency>

패키지 정리

  • 인프라 패키지에서 모듈 패키지 사용하지 않기.
  • Study 패키지에 있는 클래스는 Event와 Study에 들어있는 클래스에서만 사용한다.
  • Event 패키지에 있는 클래스는 Study와 Account 그리고 Event 패키지에 들어있는 클래스를 사용한다.
  • ...
  • 모듈 간에 순환 참조가 없는지 확인한다.

 

수정한 패키지

 

infra에 있는 패키지에서 modules패키지를 사용하지 않게 리팩토링 한다.

package me.weekbelt.studyolle.infra.config;

@RequiredArgsConstructor
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private final UserDetailsService userDetailsService;   // 수정
    private final DataSource dataSource;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        
        // ..........
        
        http.rememberMe()
                .userDetailsService(userDetailsService)   // 수정
                .tokenRepository(tokenRepository())
        ;
    }

    // .........
}

 

패키지에서 위에 구조처럼 참조를 하는지 테스트하기 위한 테스트 코드를 작성한다.

Study 도메인은 Study와 Event에서만 참조가 가능해야한다. 그런지 검증하는 테스트 코드를 작성한다.

package me.weekbelt.studyolle;

// .......

@AnalyzeClasses(packagesOf = App.class)
public class PackageDependencyTests {

    private static final String STUDY = "..modules.study..";
    private static final String EVENT = "..modules.event..";
    private static final String ACCOUNT = "..modules.account..";
    private static final String TAG = "..modules.tag..";
    private static final String ZONE = "..modules.zone..";


    @ArchTest
    ArchRule studyPackageRule = classes().that().resideInAPackage("..modules.study..")
            .should().onlyBeAccessed().byClassesThat()
            .resideInAnyPackage(STUDY, EVENT);


}

 

테스트를 돌려보니 실패가 나온다.

Acount엔티티에 isManagerOf메소드가 Study를 참조하고 있다고 에러메시지가 떴다.

package me.weekbelt.studyolle.modules.account;

@Builder @AllArgsConstructor @NoArgsConstructor
@Getter @Setter @EqualsAndHashCode(of = "id")
@Entity
public class Account {

    // ......
    
    // 삭제
    public boolean isManagerOf(Study study) {
        return study.getManagers().contains(this);
    }
}

 

isManagerOf 메소드를 사용하는 곳을 찾는다.

package me.weekbelt.studyolle.modules.study;


@RequiredArgsConstructor
@Transactional
@Service
public class StudyService {

    // ..........

    private void checkIfManager(Account account, Study study) {
        if (!study.isManagedBy(account)) {  // account.isManager(study)를 study.isManageredBy(account)로 수정한다.
            throw new AccessDeniedException("해당 기능을 사용할 수 없습니다.");
        }
    }

    // .........
}

 

Study 엔티티에 isManageredBy메소드를 생성한다.

package me.weekbelt.studyolle.modules.study;

// ......
public class Study {

    // .........

    public boolean isManagedBy(Account account) {
        return this.getManagers().contains(account);
    }

    // ..........
}

 

나머지 패키지를 검증하는 테스트코드도 작성한다.

package me.weekbelt.studyolle;

@AnalyzeClasses(packagesOf = App.class)
public class PackageDependencyTests {

    // ........

    @ArchTest
    ArchRule eventPackageRule = classes().that().resideInAPackage(EVENT)
            .should().accessClassesThat()
            .resideInAnyPackage(STUDY, ACCOUNT, EVENT);

    @ArchTest
    ArchRule accountPackageRule = classes().that().resideInAPackage(ACCOUNT)
            .should().accessClassesThat().resideInAnyPackage(TAG, ZONE, ACCOUNT);
}

 

그 다음 순환 참조가 없는지 검증하는 테스트를 작성한다.

package me.weekbelt.studyolle;

@AnalyzeClasses(packagesOf = App.class)
public class PackageDependencyTests {

    // .....
    
    @ArchTest
    ArchRule cycleCheck = slices().matching("me.weekbelt.studyolle.modules.(*)..")
            .should().beFreeOfCycles();
}

 

순환 참조가 발생했단는 에러가 떴다.

 

settings패키지에 있는 요소들을 account 패키지로 이동시킨다.

패키지 이동
테스트 패키지 이동

 

마지막으로 modules패키지는 moduels패키지내에서만 사용할 수 있는지  확인하는 테스트 추가

package me.weekbelt.studyolle;

@AnalyzeClasses(packagesOf = App.class)
public class PackageDependencyTests {

    // ........
    
    @ArchTest
    ArchRule modulesPackageRule = classes().that().resideInAPackage("me.weekbelt.studyolle.modules..")
            .should().onlyBeAccessed().byClassesThat()
            .resideInAnyPackage("me.weekbelt.studyolle.modules..");


    // ..........
}

 

패키지 이동시킨다.

테스트 패키지 이동

 

참고: https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-JPA-%EC%9B%B9%EC%95%B1#

 

스프링과 JPA 기반 웹 애플리케이션 개발 - 인프런

이 강좌에서 여러분은 실제로 운영 중인 서비스를 스프링, JPA 그리고 타임리프를 비롯한 여러 자바 기반의 여러 오픈 소스 기술을 사용하여 웹 애플리케이션을 개발하는 과정을 학습할 수 있습니다. 이 강좌를 충분히 학습한다면 여러분 만의 웹 서비스를 만들거나 취직에 도움이 될만한 포트폴리오를 만들 수 있을 겁니다. 중급이상 웹 개발 프레임워크 및 라이브러리 Java Spring Spring Boot Spring Data JPA Thymeleaf 온라인 강의

www.inflearn.com