diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index 1aae290..35b8bf7 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -38,7 +38,11 @@ object Dependencies { const val GRPC_KOTLIN_STUB = "io.grpc:grpc-kotlin-stub:${DependencyVersion.GRPC_KOTLIN}" const val PROTOBUF_KOTLIN = "com.google.protobuf:protobuf-kotlin:${DependencyVersion.PROTOBUF}" const val GRPC_TESTING = "io.grpc:grpc-testing:${DependencyVersion.GRPC}" + const val GRPC_CLIENT = "net.devh:grpc-client-spring-boot-starter:${DependencyVersion.GRPC_CLIENT}" + const val GOOGLE_PROTOBUF = "com.google.protobuf:protobuf-java:${DependencyVersion.GOOGLE_PROTOBUF}" + // coroutines + const val COROUTINES = "org.jetbrains.kotlinx:kotlinx-coroutines-core:${DependencyVersion.COROUTINES}" // swagger const val SWAGGER = "org.springdoc:springdoc-openapi-starter-webmvc-ui:${DependencyVersion.SWAGGER_VERSION}" diff --git a/buildSrc/src/main/kotlin/DependencyVersion.kt b/buildSrc/src/main/kotlin/DependencyVersion.kt index 17d8889..295cf53 100644 --- a/buildSrc/src/main/kotlin/DependencyVersion.kt +++ b/buildSrc/src/main/kotlin/DependencyVersion.kt @@ -4,6 +4,7 @@ object DependencyVersion { const val SPRING_DEPENDENCY_MANAGEMENT = "1.1.7" const val DETEKT = "1.23.6" const val KTLINT = "12.1.1" + const val COROUTINES = "1.8.0" const val JWT = "0.11.5" const val ORG_JSON = "20230227" @@ -12,6 +13,8 @@ object DependencyVersion { const val GRPC = "1.61.1" const val GRPC_KOTLIN = "1.4.1" const val PROTOBUF = "3.25.3" + const val GRPC_CLIENT = "2.15.0.RELEASE" + const val GOOGLE_PROTOBUF = "3.25.3" const val SWAGGER_VERSION = "2.5.0" const val AWS = "1.12.281" diff --git a/buildSrc/src/main/kotlin/PluginVersion.kt b/buildSrc/src/main/kotlin/PluginVersion.kt index 9b16506..ec67b6d 100644 --- a/buildSrc/src/main/kotlin/PluginVersion.kt +++ b/buildSrc/src/main/kotlin/PluginVersion.kt @@ -1,8 +1,8 @@ object PluginVersion { - const val KOTLIN_VERSION = "1.9.25" + const val KOTLIN_VERSION = "1.9.23" const val SPRING_BOOT_VERSION = "3.4.4" const val SPRING_DEPENDENCY_MANAGEMENT_VERSION = "1.1.7" const val DETEKT_VERSION = "1.23.6" const val KTLINT_VERSION = "12.1.1" const val PROTOBUF_VERSION = "0.9.4" -} \ No newline at end of file +} diff --git a/casper-feed/build.gradle.kts b/casper-feed/build.gradle.kts new file mode 100644 index 0000000..a47fade --- /dev/null +++ b/casper-feed/build.gradle.kts @@ -0,0 +1,128 @@ +plugins { + id(Plugin.KOTLIN_JVM) version PluginVersion.KOTLIN_VERSION + id(Plugin.KOTLIN_SPRING) version PluginVersion.KOTLIN_VERSION + id(Plugin.KOTLIN_KAPT) + id(Plugin.SPRING_BOOT) version PluginVersion.SPRING_BOOT_VERSION + id(Plugin.SPRING_DEPENDENCY_MANAGEMENT) version PluginVersion.SPRING_DEPENDENCY_MANAGEMENT_VERSION + id(Plugin.CASPER_DOCUMENTATION) + id(Plugin.PROTOBUF) version PluginVersion.PROTOBUF_VERSION +} + +group = "hs.kr.entrydsm" +version = "0.0.1-SNAPSHOT" + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(17) + } +} + +repositories { + mavenCentral() +} + +dependencies { + // 스프링 부트 기본 기능 + implementation(Dependencies.SPRING_BOOT_STARTER) + + // 코틀린 리플렉션 + implementation(Dependencies.KOTLIN_REFLECT) + + // 스프링 부트 테스트 도구 + testImplementation(Dependencies.SPRING_BOOT_STARTER_TEST) + + // 코틀린 + JUnit5 테스트 + testImplementation(Dependencies.KOTLIN_TEST_JUNIT5) + + // JUnit5 실행 런처 + testRuntimeOnly(Dependencies.JUNIT_PLATFORM_LAUNCHER) + + // 웹 관련 + implementation(Dependencies.SPRING_BOOT_STARTER_WEB) + + // 데이터베이스 + implementation(Dependencies.SPRING_BOOT_STARTER_DATA_JPA) + implementation(Dependencies.SPRING_BOOT_STARTER_DATA_REDIS) + runtimeOnly(Dependencies.MYSQL_CONNECTOR) + + // 보안 + implementation(Dependencies.SPRING_BOOT_STARTER_SECURITY) + + // 검증 + implementation(Dependencies.SPRING_BOOT_STARTER_VALIDATION) + + // JSON 처리 + implementation(Dependencies.JACKSON_MODULE_KOTLIN) + implementation(Dependencies.ORG_JSON) + + // JWT + implementation(Dependencies.JWT_API) + implementation(Dependencies.JWT_IMPL) + runtimeOnly(Dependencies.JWT_JACKSON) + + implementation(Dependencies.MAPSTRUCT) + kapt(Dependencies.MAPSTRUCT_PROCESSOR) + + // grpc + implementation(Dependencies.GRPC_NETTY_SHADED) + implementation(Dependencies.GRPC_PROTOBUF) + implementation(Dependencies.GRPC_STUB) + implementation(Dependencies.GRPC_KOTLIN_STUB) + implementation(Dependencies.PROTOBUF_KOTLIN) + testImplementation(Dependencies.GRPC_TESTING) + implementation(Dependencies.GRPC_CLIENT) + implementation(Dependencies.GOOGLE_PROTOBUF) + + // 코루틴 + implementation(Dependencies.COROUTINES) + + // swagger + implementation(Dependencies.SWAGGER) + + // aws + implementation(Dependencies.AWS) + + // feign + implementation(Dependencies.OPEN_FEIGN) + + // Kafka + implementation(Dependencies.KAFKA) + implementation("org.springframework.kafka:spring-kafka:3.1.2") + + // Cloud Config + // implementation(Dependencies.CLOUD_CONFIG) +} + +protobuf { + protoc { + artifact = "com.google.protobuf:protoc:${DependencyVersion.PROTOBUF}" + } + plugins { + create("grpc") { + artifact = "io.grpc:protoc-gen-grpc-java:${DependencyVersion.GRPC}" + } + create("grpckt") { + artifact = "io.grpc:protoc-gen-grpc-kotlin:${DependencyVersion.GRPC_KOTLIN}:jdk8@jar" + } + } + generateProtoTasks { + all().forEach { + it.plugins { + create("grpc") + create("grpckt") + } + } + + } + +} + +kotlin { + compilerOptions { + freeCompilerArgs.addAll("-Xjsr305=strict") + } +} + +tasks.withType { + useJUnitPlatform() +} diff --git a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/grpc/client/AdminGrpcClient.kt b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/grpc/client/AdminGrpcClient.kt new file mode 100644 index 0000000..eb60c30 --- /dev/null +++ b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/grpc/client/AdminGrpcClient.kt @@ -0,0 +1,56 @@ +package hs.kr.entrydsm.feed.infrastructure.grpc.client + +import hs.kr.entrydsm.casper.admin.proto.AdminServiceGrpc +import hs.kr.entrydsm.casper.admin.proto.AdminServiceProto +import hs.kr.entrydsm.feed.infrastructure.grpc.client.dto.response.InternalAdminResponse +import io.grpc.Channel +import io.grpc.stub.StreamObserver +import kotlinx.coroutines.suspendCancellableCoroutine +import net.devh.boot.grpc.client.inject.GrpcClient +import org.springframework.stereotype.Component +import java.util.UUID +import kotlin.coroutines.resume +import kotlin.coroutines.resumeWithException + +/** + * 관리자 서비스와의 gRPC 통신을 담당하는 클라이언트 클래스입니다. + * + * @property channel gRPC 통신을 위한 채널 (user-service로 자동 주입됨) + */ +@Component +class AdminGrpcClient { + + @GrpcClient("user-service") + lateinit var channel: Channel + + /** + * 관리자 ID를 기반으로 관리자 정보를 비동기적으로 조회합니다. + * gRPC 비동기 스트리밍을 사용하여 관리자 서비스로부터 정보를 가져옵니다. + * + * @param adminId 조회할 관리자의 고유 식별자(UUID) + * @return 조회된 관리자 정보를 담은 [InternalAdminResponse] 객체 + * @throws io.grpc.StatusRuntimeException gRPC 서버에서 오류가 발생한 경우 + * @throws java.util.concurrent.CancellationException 코루틴이 취소된 경우 + */ + suspend fun getAdminInfoByAdminId(adminId: UUID): InternalAdminResponse { + val adminStub = AdminServiceGrpc.newStub(channel) + + val request = AdminServiceProto.GetAdminIdRequest.newBuilder() + .setAdminId(adminId.toString()) + .build() + + val response = suspendCancellableCoroutine { continuation -> + adminStub.getAdminByUUID(request, object : StreamObserver { + override fun onNext(value: AdminServiceProto.GetAdminIdResponse) { + continuation.resume(value) + } + override fun onError(t: Throwable) { + continuation.resumeWithException(t) + } + override fun onCompleted() {} + }) + } + + return InternalAdminResponse(id = UUID.fromString(response.adminId)) + } +} diff --git a/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/grpc/client/dto/response/InternalAdminResponse.kt b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/grpc/client/dto/response/InternalAdminResponse.kt new file mode 100644 index 0000000..e4e33cc --- /dev/null +++ b/casper-feed/src/main/kotlin/hs/kr/entrydsm/feed/infrastructure/grpc/client/dto/response/InternalAdminResponse.kt @@ -0,0 +1,12 @@ +package hs.kr.entrydsm.feed.infrastructure.grpc.client.dto.response + +import java.util.UUID + +/** + * 관리자 정보를 담는 데이터 클래스입니다. + * + * @property id 관리자의 고유 식별자(UUID) + */ +data class InternalAdminResponse( + val id: UUID +) diff --git a/casper-feed/src/main/proto/admin.proto b/casper-feed/src/main/proto/admin.proto new file mode 100644 index 0000000..ab6fa28 --- /dev/null +++ b/casper-feed/src/main/proto/admin.proto @@ -0,0 +1,18 @@ +syntax = "proto3"; + +package casper.feed; + +option java_package = "hs.kr.entrydsm.casper.admin.proto"; +option java_outer_classname = "AdminServiceProto"; + +service AdminService { + rpc GetAdminByUUID(GetAdminIdRequest) returns (GetAdminIdResponse); +} + +message GetAdminIdRequest { + string admin_id =1; +} + +message GetAdminIdResponse{ + string admin_id = 1; +} diff --git a/casper-feed/src/test/kotlin/hs/kr/entrydsm/feed/CasperFeedApplicationTests.kt b/casper-feed/src/test/kotlin/hs/kr/entrydsm/feed/CasperFeedApplicationTests.kt new file mode 100644 index 0000000..43b364e --- /dev/null +++ b/casper-feed/src/test/kotlin/hs/kr/entrydsm/feed/CasperFeedApplicationTests.kt @@ -0,0 +1,26 @@ +package hs.kr.entrydsm.feed + +import org.junit.jupiter.api.Test +import org.springframework.boot.test.context.SpringBootTest + +/** + * Casper Feed 애플리케이션의 통합 테스트 클래스입니다. + * + * 이 클래스는 Spring 애플리케이션 컨텍스트가 모든 필요한 빈과 설정과 함께 + * 성공적으로 로드되는지 검증합니다. + */ +@SpringBootTest(classes = [CasperFeedApplication::class]) +class CasperFeedApplicationTests { + + /** + * 애플리케이션 컨텍스트가 성공적으로 로드되는지 테스트합니다. + * + * 이 테스트는 Spring 애플리케이션 컨텍스트가 모든 필요한 설정과 빈이 제대로 + * 초기화된 상태로 시작될 수 있는지 확인합니다. + * 컨텍스트 로딩에 실패할 경우 이 테스트는 실패합니다. + */ + @Test + fun contextLoads() { + // 애플리케이션 컨텍스트가 성공적으로 로드되면 테스트가 통과합니다. + } +} diff --git a/detekt.yml b/detekt.yml index c7d1683..16badba 100644 --- a/detekt.yml +++ b/detekt.yml @@ -1,7 +1,7 @@ complexity: active: true LongParameterList: - active: true + active: false functionThreshold: 6 constructorThreshold: 7 @@ -11,7 +11,7 @@ style: active: true maxLineLength: 120 MagicNumber: - active: true + active: false ignoreNumbers: - '-1' - '0'