안드로이드 모듈 분리 배포

박규훤·2022년 7월 10일
0

최근 클린 아키텍처를 공부하면서 모듈을 나누어 배포하는 것에 대해 알게 되었다.

이와 동시에 회사 동료가 버즈빌에서 모듈을 나눠 배포한 영상을 보내줘서 나도 현재 진행중인 사이드 프로젝트에 적용해 보았다.

(정확히는 미루고 미루다 쓰는 글이다...)

모듈 분리 배포

모듈 분리 배포는 각각의 모듈을 하나의 프로덕트라고 생각하고 버저닝 및 배포하는 것을 말한다.

기존에는 프로젝트의 모든 모듈(App, Presentation, Domain, Data, ...)들을 하나로 묶어서 배포를 했었다면, 모듈 분리 배포는 각각의 모듈을 jar/aar로 배포하거나 maven 같은 저장소에 배포해서 라이브러리처럼 쓰는 것이다.

분리 배포를 하면, 특정 모듈의 특정 버전에 문제가 발생했을 때 이를 의존하는 다른 모듈에서 이전 버전을 사용하도록 설정만 바꿔주면 프로젝트 전체에는 문제가 전파되지 않는 장점을 얻을 수 있다.

보통은 팀 단위로 모듈이 분리되어 있을 때 이렇게 나누어 배포를 진행하는데, 안드로이드 개발자가 2명인 지금 프로젝트에는 그냥 한 번 적용해보고 싶어서 진행을 해보았다.

진행 방법

jar / aar

처음에는 gradle task를 추가해서 자동으로 jar / aar 파일을 생성하고, 다른 모듈에서는 해당 파일들을 가져다가 사용하는 방법을 쓰려고 했다.

android library는 모듈의 build.gradle에 아래 내용을 추가하고,

version = '0.1.0'

task exportLib(type: Copy) {
    from('build/outputs/aar/')
    into('release/')
    include("${project.name}-release.aar")
    rename("${project.name}-release.aar", "${project.name}-${version}.aar")
}

exportLib.dependsOn("assembleRelease")

Kotlin library는 모듈의 build.gradle에 아래 내용을 추가했다.

version = '0.1.0'

task exportLib(type: Copy) {
    from('build/libs/')
    into('release/')
    include("${project.name}-${version}.jar")
    rename("${project.name}-${version}.jar", "${project.name}-${version}.jar")
}

exportLib.dependsOn(build)

이렇게 하면 gradle exportLib를 실행했을 때 자동으로 빌드가 이루어지면서 {모듈이름}/release 디렉토리 안에 배포 파일이 생성된다.

의존성을 추가할 때는 implementation(file("../domain/release/domain-{버전}.{jar/aar}"))으로 사용하고자 했다.

빌드와 배포 파일 생성까지는 정상적으로 돌아갔는데, 사용하는 과정에서 app 모듈이 하위 모듈들에 필요한 library들을 받아오지 못해서 runtime에서 앱이 터지는 문제가 발생했다.

그래서 다음으로는 저장소를 사용하는 방법을 고려해보았다.

Jitpack

인터넷에 검색해보니까 Bintray와 Jitpack에 배포하는 방법이 가장 많이 나왔는데, Bintray는 Jcenter가 사라지면서 사용하기 어려워졌고, 그래서 나는 Jitpack 배포를 도전해보기로 했다.

나는 android library module과 kotlin library module을 같이 쓰고 있는데, 배포 코드가 조금 차이점이 있다.

우선 프로젝트의 root 디렉토리에 jitpack.yml 파일을 만들어서 아래 내용을 담는다.

jdk:
	- openjdk11

Kotlin library 모듈에는 아래 내용을 추가한다.

plugins {
	id 'maven-publish'
}

afterEvaluate {
    publishing {
        publications {
            release(MavenPublication) {
                from components.java
                groupId = 'com.upf464.koonsdiary' 	// 라이브러리의 그룹 이름
                artifactId = project.name			// 라이브러리 이름
                version = '0.1.0'					// 버전
            }
        }
    }
}

Android library 모듈에는 아래 내용을 추가한다.

plugins {
	id 'maven-publish'
}

afterEvaluate {
    publishing {
        publications {
            release(MavenPublication) {
                from components.release
                groupId = 'com.upf464.koonsdiary'	// 라이브러리 그룹 이름
                artifactId = project.name			// 라이브러리 이름
                version = '0.1.0'					// 라이브러리 버전
            }
        }
    }
}

Github에 프로젝트를 push하고 새로운 release를 추가하면, 자동으로 jitpack에서 빌드가 이루어지고 배포까지 되는 방법이었지만, 또 다시 실패했다.

이번에는 비공개 파일이 문제였다. 우리 프로젝트에서는 Firebase와 Kakao 로그인 같은 외부 서비스를 사용하고 있었는데, API Key를 .gitignore에 추가하여 Github에 올리지 않고 CI/CD에서는 Github Secrets를 사용하여 비공개 처리를 해놓았는데, Jitpack에서는 해당 파일들이 존재하지 않아 빌드가 안되는 것이었다.

그래서 이 방법도 실패로 끝이 났다.

Github Packages

마지막으로 시도한 방법은 Github Packages를 사용한 방법이다.

Github에서 제공하는 maven package 서비스를 이용하면 Github Repository를 Maven 저장소로 사용할 수 있다.

Android library 버전.

apply plugin: 'maven-publish'

def githubProperties = new Properties()
githubProperties.load(new FileInputStream(rootProject.file("local.properties")))

task sourceJar(type: Jar) {
    from android.sourceSets.main.java.srcDirs
    classifier "sources"
}

publishing {
    repositories {
        maven {
            name = 'GithubPackages'
            url = uri("https://maven.pkg.github.com/UPF2022SS-464/KoonsDiary-Android")
            credentials {
                username = githubProperties['github_username'] ?: System.getenv("github_username")
                password = githubProperties['github_access_token'] ?: System.getenv("github_access_token")
            }
        }
        maven {
            name = 'CustomMavenRepo'
            url = "file://${buildDir}/repo"
        }
    }
    publications {
        deploy(MavenPublication) {
            groupId 'com.upf464.koonsdiary'
            artifactId project.name
            artifact("$buildDir/outputs/aar/${project.name}-release.aar")
            artifact(sourceJar)

            pom.withXml {
                def dependenciesNode = asNode().appendNode('dependencies')

                configurations.api.allDependencies.each {
                    def dependencyNode = dependenciesNode.appendNode('dependency')
                    dependencyNode.appendNode('groupId', it.group)
                    dependencyNode.appendNode('artifactId', it.name)
                    dependencyNode.appendNode('version', it.version)
                }
            }
        }
    }
}

task deploy
publishDeployPublicationToCustomMavenRepoRepository.dependsOn(build)
publishDeployPublicationToGithubPackagesRepository.dependsOn(publishDeployPublicationToCustomMavenRepoRepository)
deploy.dependsOn(publishDeployPublicationToGithubPackagesRepository)

Kotlin library 버전

apply plugin: 'maven-publish'

def githubProperties = new Properties()
githubProperties.load(new FileInputStream(rootProject.file("local.properties")))

publishing {
    repositories {
        maven {
            name = 'GithubPackages'
            url = uri("https://maven.pkg.github.com/UPF2022SS-464/KoonsDiary-Android")
            credentials {
                username = githubProperties['github_username'] ?: System.getenv("github_username")
                password = githubProperties['github_access_token'] ?: System.getenv("github_access_token")
            }
        }
        maven {
            name = 'CustomMavenRepo'
            url = "file://${buildDir}/repo"
        }
    }
    publications {
        deploy(MavenPublication) {
            groupId 'com.upf464.koonsdiary'
            artifactId project.name
            artifact("$buildDir/libs/${project.name}-${version}.jar")

            pom.withXml {
                def dependenciesNode = asNode().appendNode('dependencies')

                //Iterate over the compile dependencies (we don't want the test ones), adding a <dependency> node for each
                configurations.api.allDependencies.each {
                    def dependencyNode = dependenciesNode.appendNode('dependency')
                    dependencyNode.appendNode('groupId', it.group)
                    dependencyNode.appendNode('artifactId', it.name)
                    dependencyNode.appendNode('version', it.version)
                }
            }
        }
    }
}

task deploy
publishDeployPublicationToCustomMavenRepoRepository.dependsOn(build)
publishDeployPublicationToGithubPackagesRepository.dependsOn(publishDeployPublicationToCustomMavenRepoRepository)
deploy.dependsOn(publishDeployPublicationToGithubPackagesRepository)

그 이후...

거기에 추가로 git으로 변경된 파일을 추적해서 수정된 모듈만 빌드하도록 CI/CD 구성하고, Gradle 설정도 Groovy에서 Kotlin DSL로 옮겼는데, 이건 나중에 생각나면 별도로 포스팅하겠다.

정말 궁금한 사람은 이 곳에서 확인할 수 있다.

결론

지금까지 이렇게 진행해 본 결과, 작은 프로젝트나 규모가 작은 팀에서는 분리 배포를 하지 않는 것이 좋을 것 같다... Domain, Presentation, Data 등등 모든 모듈을 한 명 또는 두 명이 수정하는데, 레이어를 오갈 때마다 배포해야 다른 모듈에서 사용할 수 있어서 소규모 팀에서는 개발할 때 매우 불편하다.

최소한 따로 팀이 나뉘어진 상태에서 배포를 분리하는 것이 정신 건강에 좋을 것 같다.

profile
개발팀의 소방수

0개의 댓글