asm

1 개의 포스트

netflix5분 읽기큐레이션 요약

Nebula ArchRules로 ArchUnit 확장하기

Netflix는 수만 개의 Java 저장소에서 공통 아키텍처 규칙과 기술 부채를 일관되게 점검하기 위해 ArchUnit을 여러 저장소에 배포·적용하는 Nebula ArchRules를 구축했다. ArchUnit은 AST가 아닌 JVM 바이트코드를 분석하므로 Java뿐 아니라 Kotlin·Scala에도 적용할 수 있고, 타입 안전한 Java API로 규칙을 작성·테스트할 수 있다. Nebula 플러그인은 이를 공유 가능한 Gradle 규칙 라이브러리로 확장해 조직 전체의 라이브러리 사용 규칙과 API 생명주기를 자동 검증한다. ## Netflix의 문제: 라이브러리 생명주기와 기술 부채 - Netflix는 수만 개의 Java polyrepo를 운영하므로 공통 빌드 로직과 품질 규칙을 여러 저장소에 배포해야 한다. - 하위 호환성을 깨는 라이브러리 변경 사고를 계기로, deprecated API를 언제 안전하게 제거할 수 있는지 파악할 필요가 생겼다. - API에는 다음과 같은 생명주기 애너테이션을 사용한다. - `@Deprecated`: 더 이상 사용하지 않도록 지정된 API - `@Public`: 하위 프로젝트가 사용하도록 공개된 API - `@Experimental`: 아직 안정성이 보장되지 않는 신규 API - 그 외 API: 기본적으로 내부 구현으로 간주 - 문제는 라이브러리 작성자가 어떤 downstream 프로젝트가 내부 API나 deprecated API를 잘못 사용하고 있는지 알기 어렵다는 점이다. - Spring Boot 주요 버전 업그레이드 같은 fleet-wide migration에서도 deprecated API 사용 현황을 파악하는 것이 중요하다. ## ArchUnit을 선택한 이유 - ArchUnit은 JUnit 테스트 안에서 아키텍처 규칙을 검증하는 오픈소스 라이브러리다. - ASM 기반으로 JVM 바이트코드를 분석하므로 특정 소스 언어의 문법에 덜 의존한다. - Java, Kotlin, Scala처럼 서로 다른 JVM 언어로 작성된 코드에도 같은 규칙을 적용할 수 있다. - 소스 코드의 syntactic sugar에 가려진 실제 실행 바이트코드까지 검사할 수 있다. - 규칙 작성 방식은 두 가지다. - 대부분의 규칙은 fluent builder API로 간결하게 작성한다. - 복잡한 분석에는 더 낮은 수준의 API와 클래스 관계 정보에 접근할 수 있다. - 분석 대상 전체 classpath를 읽고 클래스 간 의존성, 호출 관계, 소유자 등을 그래프로 유지한다. - 단순한 파일 단위 검사를 넘어 호출 사이트와 클래스 관계를 기준으로 규칙을 작성할 수 있다. ## AST 기반 도구와의 차이 - PMD 같은 AST 기반 도구는 소스 문법 구조를 직접 검사한다. - 언어별 문법 차이 때문에 Kotlin이나 Scala 지원 시 규칙을 별도로 작성해야 할 수 있다. - 예상하지 못한 문법적 표현으로 규칙을 우회할 가능성도 있다. - ArchUnit 규칙은 컴파일 결과인 바이트코드를 대상으로 하므로 코드가 어떤 JVM 언어로 작성됐는지보다 실제 동작 구조가 중요해진다. - PMD의 XPath 문자열 규칙과 달리 ArchUnit은 타입 안전한 Java 코드와 fluent API를 사용한다. - IDE 자동완성의 도움을 받을 수 있다. - 별도의 분석 프로세스를 구성하지 않고 규칙 객체와 클래스 목록을 직접 전달해 단위 테스트할 수 있다. ## 규칙 작성의 편의성 - ArchUnit에서는 다음과 같은 형태로 규칙을 표현할 수 있다. - 특정 클래스가 특정 타입에 의존하지 않아야 함 - 특정 생성자를 호출할 때 필수 인자를 전달해야 함 - 특정 패키지 간 의존성을 금지함 - 예를 들어 `DateTime` 객체를 시간대 정보 없이 생성하지 못하게 하는 규칙을 fluent API로 작성할 수 있다. - 규칙은 일반적인 Java 코드이므로 다음 작업이 쉽다. - 리팩터링 - IDE 기반 탐색 - 컴파일 시 오류 검출 - 단위 테스트 - 클래스 관계 그래프를 활용하면 단순한 이름 매칭보다 풍부한 맥락을 가진 규칙을 만들 수 있다. ## 공유 가능한 ArchRules 라이브러리 - 기본 ArchUnit은 하나의 저장소에서 JUnit 테스트로 사용하는 구조다. - Nebula ArchRules는 규칙을 별도 라이브러리로 패키징해 여러 Gradle 저장소에서 재사용할 수 있도록 한다. - `ArchRules Library Plugin`은 Gradle 프로젝트에 `archRules`라는 별도 source set을 추가한다. - 이 source set에는 `ArchRulesService`를 구현하는 클래스를 작성한다. - `ArchRulesService`는 `Map<String, ArchRule>`을 반환하는 단일 추상 메서드를 가진다. - map의 key는 규칙 이름이고 value는 실제 ArchUnit 규칙이다. - 규칙 코드는 본 애플리케이션 코드와 분리되어 별도의 JAR로 패키징된다. - 산출물에는 `arch-rules` classifier가 붙는다. - Gradle Module Metadata를 통해 `arch-rules` usage variant로 배포된다. - 따라서 downstream 프로젝트가 규칙을 사용하려면 Gradle Module Metadata 기반 의존성 해결이 필요하다. ## 독립형 규칙 라이브러리 - Standalone rule library는 본체 애플리케이션 코드 없이 `archRules`만 포함한다. - 다음과 같은 외부 코드 검사에 적합하다. - Java 표준 API 사용 규칙 - Guava 같은 오픈소스 라이브러리 사용 규칙 - 조직 공통 규칙 - `@Deprecated` API 사용 금지 - Netflix는 누구나 사용할 수 있는 OSS standalone 규칙 라이브러리 모음을 유지하며, 직접 규칙을 작성할 때 참고할 수 있는 예제로도 활용한다. ## 번들형 규칙 라이브러리 - Bundled rule library는 일반 라이브러리 코드와 해당 라이브러리 사용 규칙을 함께 배포한다. - `main` source set에는 라이브러리의 실제 기능을 담고, `archRules` source set에는 해당 라이브러리를 사용할 때 지켜야 할 규칙을 담는다. - 예를 들어 특정 라이브러리가 제공하는 공개 API의 올바른 사용법이나, 내부 API·deprecated API에 대한 접근 제한을 규칙으로 포함할 수 있다. - 라이브러리와 사용 규칙을 함께 배포하면 라이브러리 작성자가 권장 사용 방식을 downstream 빌드 과정에서 직접 검증할 수 있다. ## 실용적인 결론 ArchUnit은 단순한 단일 저장소용 아키텍처 테스트를 넘어, 바이트코드와 클래스 관계를 활용하는 범용 JVM 정적 분석 도구로 활용할 수 있다. 조직 차원에서는 Nebula ArchRules처럼 규칙을 별도 Gradle 라이브러리로 배포해 deprecated API 사용, 내부 API 접근, 라이브러리별 권장 패턴을 모든 저장소의 빌드 과정에서 자동 검증하는 방식이 효과적이다.

원문 읽기(새 탭에서 열림)