From a6ffc43f925fe90bbe3ad610879197681953e3e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=BC=ED=98=81?= Date: Mon, 24 Aug 2026 01:23:20 +0900 Subject: [PATCH] =?UTF-8?q?refactor(core):=20core:di=20=EB=AA=A8=EB=93=88?= =?UTF-8?q?=EC=9D=84=20=EC=82=AD=EC=A0=9C=ED=95=98=EA=B3=A0=20DI=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=EC=9D=84=20=EA=B0=81=20=EB=AA=A8=EB=93=88?= =?UTF-8?q?=EB=A1=9C=20=EB=B6=84=EC=82=B0=ED=95=9C=EB=8B=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 중간 계층이었던 `core:di` 모듈을 제거하고, 의존성 주입 설정을 해당 코드가 속한 모듈(`core:data`, `core:common`)로 이동시킨다. 이를 통해 불필요한 모듈 파편화를 줄이고, 구현체 가시성을 제어하여 캡슐화를 강화한다. - **모듈 정리**: `core:di` 모듈을 삭제하고 `app` 모듈의 의존성을 `core:data`로 변경한다. `settings.gradle.kts`와 빌드 스크립트에서 더 이상 사용하지 않는 `:core:di` 참조를 제거한다. - **가시성 제어 및 캡슐화**: `core:data` 내부의 저장소 구현체(`RepositoryImpl`)들을 `internal`로 전환한다. DI 모듈이 구현체와 같은 모듈에 위치하게 됨에 따라, 외부 모듈에서 인터페이스가 아닌 상세 구현에 직접 접근하는 것을 방지한다. - **DI 모듈 재배치**: - `DispatcherModule`을 공통 유틸리티 성격에 맞춰 `core:common`으로 이동한다. - 기존 `CoreRepositoryModule`을 `core:data` 내의 기능별 모듈(`Account`, `Upload`, `User`)로 세분화하여 분리한다. - **테스트 지원**: `app` 모듈의 `androidTest`에서 페이크 객체 교체가 필요한 `CoreUserRepositoryModule`은 예외적으로 `public` 가시성을 유지한다. 다만, 내부 바인딩 메서드는 `internal`로 닫기 위해 `interface` 대신 `abstract class` 구조를 채택한다. --- app/build.gradle.kts | 2 +- .../afternote_fe/test/TestHiltModules.kt | 25 ++-------- .../core/common}/di/DispatcherModule.kt | 3 +- .../data/di/CoreAccountRepositoryModule.kt | 18 +++++++ .../data/di/CoreUploadRepositoryModule.kt | 24 ++++++++++ .../core/data/di/CoreUserRepositoryModule.kt | 40 ++++++++++++++++ .../repoimpl/PhotoUploadRepositoryImpl.kt | 2 +- .../repoimpl/UserProfileRepositoryImpl.kt | 2 +- .../core/data/repoimpl/UserRepositoryImpl.kt | 2 + .../repoimpl/VideoUploadRepositoryImpl.kt | 2 +- .../repoimpl/account/AccountRepositoryImpl.kt | 2 +- .../data/repoimpl/auth/AuthRepositoryImpl.kt | 2 +- core/di/.gitignore | 1 - core/di/build.gradle.kts | 16 ------- core/di/consumer-rules.pro | 0 core/di/proguard-rules.pro | 21 --------- core/di/src/main/AndroidManifest.xml | 4 -- .../afternote/core/di/CoreRepositoryModule.kt | 47 ------------------- settings.gradle.kts | 1 - 19 files changed, 94 insertions(+), 120 deletions(-) rename core/{di/src/main/java/com/afternote/core => common/src/main/kotlin/com/afternote/core/common}/di/DispatcherModule.kt (82%) create mode 100644 core/data/src/main/java/com/afternote/core/data/di/CoreAccountRepositoryModule.kt create mode 100644 core/data/src/main/java/com/afternote/core/data/di/CoreUploadRepositoryModule.kt create mode 100644 core/data/src/main/java/com/afternote/core/data/di/CoreUserRepositoryModule.kt delete mode 100644 core/di/.gitignore delete mode 100644 core/di/build.gradle.kts delete mode 100644 core/di/consumer-rules.pro delete mode 100644 core/di/proguard-rules.pro delete mode 100644 core/di/src/main/AndroidManifest.xml delete mode 100644 core/di/src/main/java/com/afternote/core/di/CoreRepositoryModule.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 1056cd77be..5746d258af 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -212,7 +212,7 @@ dependencies { implementation(projects.core.ui) implementation(projects.core.model) implementation(projects.core.startup) - implementation(projects.core.di) + implementation(projects.core.data) implementation(projects.core.domain) // Feature — presentation diff --git a/app/src/androidTest/java/com/afternote/afternote_fe/test/TestHiltModules.kt b/app/src/androidTest/java/com/afternote/afternote_fe/test/TestHiltModules.kt index e53c4d7c54..f4ac30b43b 100644 --- a/app/src/androidTest/java/com/afternote/afternote_fe/test/TestHiltModules.kt +++ b/app/src/androidTest/java/com/afternote/afternote_fe/test/TestHiltModules.kt @@ -2,16 +2,9 @@ package com.afternote.afternote_fe.test import com.afternote.afternote_fe.reporting.ErrorReportingModule import com.afternote.core.common.reporting.ErrorReporter -import com.afternote.core.data.repoimpl.PhotoUploadRepositoryImpl -import com.afternote.core.data.repoimpl.UserProfileRepositoryImpl -import com.afternote.core.data.repoimpl.VideoUploadRepositoryImpl -import com.afternote.core.data.repoimpl.account.AccountRepositoryImpl -import com.afternote.core.di.CoreRepositoryModule -import com.afternote.core.domain.repository.PhotoUploadRepository +import com.afternote.core.data.di.CoreUserRepositoryModule import com.afternote.core.domain.repository.UserProfileRepository import com.afternote.core.domain.repository.UserRepository -import com.afternote.core.domain.repository.VideoUploadRepository -import com.afternote.core.domain.repository.account.AccountRepository import com.afternote.core.domain.repository.auth.AuthRepository import com.afternote.feature.mindrecord.data.di.MindRecordRepositoryModule import com.afternote.feature.mindrecord.data.repositoryimpl.MindRecordReceiverRepositoryImpl @@ -29,9 +22,9 @@ import javax.inject.Singleton @Module @TestInstallIn( components = [SingletonComponent::class], - replaces = [CoreRepositoryModule::class], + replaces = [CoreUserRepositoryModule::class], ) -object TestCoreRepositoryModule { +object TestCoreUserRepositoryModule { @Provides @Singleton fun provideAuthRepository(): AuthRepository = FakeAuthRepository(loggedIn = false) @@ -43,18 +36,6 @@ object TestCoreRepositoryModule { @Provides @Singleton fun provideUserProfileRepository(): UserProfileRepository = FakeUserProfileRepository() - - @Provides - @Singleton - fun provideAccountRepository(impl: AccountRepositoryImpl): AccountRepository = impl - - @Provides - @Singleton - fun providePhotoUploadRepository(impl: PhotoUploadRepositoryImpl): PhotoUploadRepository = impl - - @Provides - @Singleton - fun provideVideoUploadRepository(impl: VideoUploadRepositoryImpl): VideoUploadRepository = impl } @Module diff --git a/core/di/src/main/java/com/afternote/core/di/DispatcherModule.kt b/core/common/src/main/kotlin/com/afternote/core/common/di/DispatcherModule.kt similarity index 82% rename from core/di/src/main/java/com/afternote/core/di/DispatcherModule.kt rename to core/common/src/main/kotlin/com/afternote/core/common/di/DispatcherModule.kt index eb3dd661d2..8a543d85c9 100644 --- a/core/di/src/main/java/com/afternote/core/di/DispatcherModule.kt +++ b/core/common/src/main/kotlin/com/afternote/core/common/di/DispatcherModule.kt @@ -1,6 +1,5 @@ -package com.afternote.core.di +package com.afternote.core.common.di -import com.afternote.core.common.di.IoDispatcher import dagger.Module import dagger.Provides import dagger.hilt.InstallIn diff --git a/core/data/src/main/java/com/afternote/core/data/di/CoreAccountRepositoryModule.kt b/core/data/src/main/java/com/afternote/core/data/di/CoreAccountRepositoryModule.kt new file mode 100644 index 0000000000..0a54f8388f --- /dev/null +++ b/core/data/src/main/java/com/afternote/core/data/di/CoreAccountRepositoryModule.kt @@ -0,0 +1,18 @@ +package com.afternote.core.data.di + +import com.afternote.core.data.repoimpl.account.AccountRepositoryImpl +import com.afternote.core.domain.repository.account.AccountRepository +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +/** 구현체와 같은 모듈에 있으므로 `internal` 로 닫는다 — 바깥에서 impl 을 만질 수 없다. */ +@InstallIn(SingletonComponent::class) +@Module +internal interface CoreAccountRepositoryModule { + @Binds + @Singleton + fun bindAccountRepository(impl: AccountRepositoryImpl): AccountRepository +} diff --git a/core/data/src/main/java/com/afternote/core/data/di/CoreUploadRepositoryModule.kt b/core/data/src/main/java/com/afternote/core/data/di/CoreUploadRepositoryModule.kt new file mode 100644 index 0000000000..8eb3dd20a2 --- /dev/null +++ b/core/data/src/main/java/com/afternote/core/data/di/CoreUploadRepositoryModule.kt @@ -0,0 +1,24 @@ +package com.afternote.core.data.di + +import com.afternote.core.data.repoimpl.PhotoUploadRepositoryImpl +import com.afternote.core.data.repoimpl.VideoUploadRepositoryImpl +import com.afternote.core.domain.repository.PhotoUploadRepository +import com.afternote.core.domain.repository.VideoUploadRepository +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +/** 구현체와 같은 모듈에 있으므로 `internal` 로 닫는다 — 바깥에서 impl 을 만질 수 없다. */ +@InstallIn(SingletonComponent::class) +@Module +internal interface CoreUploadRepositoryModule { + @Binds + @Singleton + fun bindPhotoUploadRepository(impl: PhotoUploadRepositoryImpl): PhotoUploadRepository + + @Binds + @Singleton + fun bindVideoUploadRepository(impl: VideoUploadRepositoryImpl): VideoUploadRepository +} diff --git a/core/data/src/main/java/com/afternote/core/data/di/CoreUserRepositoryModule.kt b/core/data/src/main/java/com/afternote/core/data/di/CoreUserRepositoryModule.kt new file mode 100644 index 0000000000..4c9880b589 --- /dev/null +++ b/core/data/src/main/java/com/afternote/core/data/di/CoreUserRepositoryModule.kt @@ -0,0 +1,40 @@ +package com.afternote.core.data.di + +import com.afternote.core.data.repoimpl.UserProfileRepositoryImpl +import com.afternote.core.data.repoimpl.UserRepositoryImpl +import com.afternote.core.data.repoimpl.auth.AuthRepositoryImpl +import com.afternote.core.domain.repository.UserProfileRepository +import com.afternote.core.domain.repository.UserRepository +import com.afternote.core.domain.repository.auth.AuthRepository +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +/** + * 세션·사용자 저장소 바인딩. + * + * 이 모듈만 `public` 인 이유 — `app` 의 androidTest 가 + * `@TestInstallIn(replaces = [CoreUserRepositoryModule::class])` 로 이 셋을 페이크로 갈아끼운다. + * `replaces` 는 모듈 클래스를 참조하므로 `internal` 로 닫을 수 없다. + * + * 그래서 형제 모듈들과 달리 `interface` 가 아니라 `abstract class` 다. 바인딩 메서드가 `public` 이면 + * `'public' function exposes its 'internal' parameter type` 으로 컴파일되지 않는데, interface 멤버에는 + * 가시성을 줄 수 없다. abstract class 라야 클래스는 열어 두고 메서드만 `internal` 로 닫는다. + */ +@InstallIn(SingletonComponent::class) +@Module +abstract class CoreUserRepositoryModule { + @Binds + @Singleton + internal abstract fun bindAuthRepository(impl: AuthRepositoryImpl): AuthRepository + + @Binds + @Singleton + internal abstract fun bindUserRepository(impl: UserRepositoryImpl): UserRepository + + @Binds + @Singleton + internal abstract fun bindUserProfileRepository(impl: UserProfileRepositoryImpl): UserProfileRepository +} diff --git a/core/data/src/main/java/com/afternote/core/data/repoimpl/PhotoUploadRepositoryImpl.kt b/core/data/src/main/java/com/afternote/core/data/repoimpl/PhotoUploadRepositoryImpl.kt index 2e9750f15d..9e405aaa2b 100644 --- a/core/data/src/main/java/com/afternote/core/data/repoimpl/PhotoUploadRepositoryImpl.kt +++ b/core/data/src/main/java/com/afternote/core/data/repoimpl/PhotoUploadRepositoryImpl.kt @@ -62,7 +62,7 @@ private fun resolveExtension(mime: String?): String { return resolved?.takeIf { it in ALLOWED_EXTENSIONS } ?: DEFAULT_EXTENSION } -class PhotoUploadRepositoryImpl +internal class PhotoUploadRepositoryImpl @Inject constructor( @param:ApplicationContext private val context: Context, diff --git a/core/data/src/main/java/com/afternote/core/data/repoimpl/UserProfileRepositoryImpl.kt b/core/data/src/main/java/com/afternote/core/data/repoimpl/UserProfileRepositoryImpl.kt index c57855c483..dac34d4907 100644 --- a/core/data/src/main/java/com/afternote/core/data/repoimpl/UserProfileRepositoryImpl.kt +++ b/core/data/src/main/java/com/afternote/core/data/repoimpl/UserProfileRepositoryImpl.kt @@ -5,7 +5,7 @@ import com.afternote.core.domain.repository.UserProfileRepository import kotlinx.coroutines.flow.Flow import javax.inject.Inject -class UserProfileRepositoryImpl +internal class UserProfileRepositoryImpl @Inject constructor( private val dataSource: UserProfileDataSource, diff --git a/core/data/src/main/java/com/afternote/core/data/repoimpl/UserRepositoryImpl.kt b/core/data/src/main/java/com/afternote/core/data/repoimpl/UserRepositoryImpl.kt index 2f56b0d9eb..8c797f04d4 100644 --- a/core/data/src/main/java/com/afternote/core/data/repoimpl/UserRepositoryImpl.kt +++ b/core/data/src/main/java/com/afternote/core/data/repoimpl/UserRepositoryImpl.kt @@ -30,6 +30,8 @@ import kotlinx.coroutines.flow.update import javax.inject.Inject import com.afternote.core.data.mapper.delivery.toDomain as toDeliveryConditionsDomain +// `app` 의 androidTest(SettingCompletionAndroidTest)가 Hilt 를 우회해 이 구현을 직접 조립하므로 +// 형제 구현체들과 달리 `internal` 로 닫지 못한다. 닫으려면 그 테스트의 페이크 기반 개조가 선행돼야 한다 (#930). class UserRepositoryImpl @Inject constructor( diff --git a/core/data/src/main/java/com/afternote/core/data/repoimpl/VideoUploadRepositoryImpl.kt b/core/data/src/main/java/com/afternote/core/data/repoimpl/VideoUploadRepositoryImpl.kt index 29693b7c3f..0e892ffb64 100644 --- a/core/data/src/main/java/com/afternote/core/data/repoimpl/VideoUploadRepositoryImpl.kt +++ b/core/data/src/main/java/com/afternote/core/data/repoimpl/VideoUploadRepositoryImpl.kt @@ -23,7 +23,7 @@ import javax.inject.Named /** MIME 타입에서 확장자를 못 뽑았을 때 폴백. 대부분의 안드로이드 영상이 mp4 라 합리적 디폴트. */ private const val DEFAULT_VIDEO_EXTENSION = "mp4" -class VideoUploadRepositoryImpl +internal class VideoUploadRepositoryImpl @Inject constructor( @param:ApplicationContext private val context: Context, diff --git a/core/data/src/main/java/com/afternote/core/data/repoimpl/account/AccountRepositoryImpl.kt b/core/data/src/main/java/com/afternote/core/data/repoimpl/account/AccountRepositoryImpl.kt index 2ec06529fc..c7a4fe4c27 100644 --- a/core/data/src/main/java/com/afternote/core/data/repoimpl/account/AccountRepositoryImpl.kt +++ b/core/data/src/main/java/com/afternote/core/data/repoimpl/account/AccountRepositoryImpl.kt @@ -16,7 +16,7 @@ import com.afternote.core.network.model.requireStatus import com.afternote.core.network.service.AccountApiService import javax.inject.Inject -class AccountRepositoryImpl +internal class AccountRepositoryImpl @Inject constructor( private val accountApiService: AccountApiService, diff --git a/core/data/src/main/java/com/afternote/core/data/repoimpl/auth/AuthRepositoryImpl.kt b/core/data/src/main/java/com/afternote/core/data/repoimpl/auth/AuthRepositoryImpl.kt index a91b32cf10..5d02e79f95 100644 --- a/core/data/src/main/java/com/afternote/core/data/repoimpl/auth/AuthRepositoryImpl.kt +++ b/core/data/src/main/java/com/afternote/core/data/repoimpl/auth/AuthRepositoryImpl.kt @@ -22,7 +22,7 @@ import kotlinx.coroutines.flow.Flow import java.io.IOException import javax.inject.Inject -class AuthRepositoryImpl +internal class AuthRepositoryImpl @Inject constructor( private val tokenDataSource: TokenDataSource, diff --git a/core/di/.gitignore b/core/di/.gitignore deleted file mode 100644 index 42afabfd2a..0000000000 --- a/core/di/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build \ No newline at end of file diff --git a/core/di/build.gradle.kts b/core/di/build.gradle.kts deleted file mode 100644 index b1b575e6d9..0000000000 --- a/core/di/build.gradle.kts +++ /dev/null @@ -1,16 +0,0 @@ -plugins { - id("afternote.android.library") - id("afternote.android.hilt") -} - -android { - namespace = "com.afternote.core.di" -} - -dependencies { - implementation(libs.androidx.work.runtime.ktx) - implementation(projects.core.common) - implementation(projects.core.data) - implementation(projects.core.domain) - implementation(libs.kakao.sdk.auth) -} diff --git a/core/di/consumer-rules.pro b/core/di/consumer-rules.pro deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/core/di/proguard-rules.pro b/core/di/proguard-rules.pro deleted file mode 100644 index 481bb43481..0000000000 --- a/core/di/proguard-rules.pro +++ /dev/null @@ -1,21 +0,0 @@ -# Add project specific ProGuard rules here. -# You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. -# -# For more details, see -# http://developer.android.com/guide/developing/tools/proguard.html - -# If your project uses WebView with JS, uncomment the following -# and specify the fully qualified class name to the JavaScript interface -# class: -#-keepclassmembers class fqcn.of.javascript.interface.for.webview { -# public *; -#} - -# Uncomment this to preserve the line number information for -# debugging stack traces. -#-keepattributes SourceFile,LineNumberTable - -# If you keep the line number information, uncomment this to -# hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/core/di/src/main/AndroidManifest.xml b/core/di/src/main/AndroidManifest.xml deleted file mode 100644 index 44008a4332..0000000000 --- a/core/di/src/main/AndroidManifest.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/core/di/src/main/java/com/afternote/core/di/CoreRepositoryModule.kt b/core/di/src/main/java/com/afternote/core/di/CoreRepositoryModule.kt deleted file mode 100644 index 997b34dfb1..0000000000 --- a/core/di/src/main/java/com/afternote/core/di/CoreRepositoryModule.kt +++ /dev/null @@ -1,47 +0,0 @@ -package com.afternote.core.di - -import com.afternote.core.data.repoimpl.PhotoUploadRepositoryImpl -import com.afternote.core.data.repoimpl.UserProfileRepositoryImpl -import com.afternote.core.data.repoimpl.UserRepositoryImpl -import com.afternote.core.data.repoimpl.VideoUploadRepositoryImpl -import com.afternote.core.data.repoimpl.account.AccountRepositoryImpl -import com.afternote.core.data.repoimpl.auth.AuthRepositoryImpl -import com.afternote.core.domain.repository.PhotoUploadRepository -import com.afternote.core.domain.repository.UserProfileRepository -import com.afternote.core.domain.repository.UserRepository -import com.afternote.core.domain.repository.VideoUploadRepository -import com.afternote.core.domain.repository.account.AccountRepository -import com.afternote.core.domain.repository.auth.AuthRepository -import dagger.Binds -import dagger.Module -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton - -@InstallIn(SingletonComponent::class) -@Module -interface CoreRepositoryModule { - @Binds - @Singleton - fun bindAccountRepository(impl: AccountRepositoryImpl): AccountRepository - - @Binds - @Singleton - fun bindPhotoUploadRepository(impl: PhotoUploadRepositoryImpl): PhotoUploadRepository - - @Binds - @Singleton - fun bindVideoUploadRepository(impl: VideoUploadRepositoryImpl): VideoUploadRepository - - @Binds - @Singleton - fun bindAuthRepository(impl: AuthRepositoryImpl): AuthRepository - - @Binds - @Singleton - fun bindUserRepository(impl: UserRepositoryImpl): UserRepository - - @Binds - @Singleton - fun bindUserProfileRepository(impl: UserProfileRepositoryImpl): UserProfileRepository -} diff --git a/settings.gradle.kts b/settings.gradle.kts index c6df5163b8..e8bcc9c313 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -33,7 +33,6 @@ include(":app") include(":core:common") include(":core:data") include(":core:datastore") -include(":core:di") include(":core:domain") include(":core:model") include(":core:network")