묻고 답해요
161만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
미해결[왕초보편] 앱 8개를 만들면서 배우는 안드로이드 코틀린(Android Kotlin)
안드로이드 스튜디오 버전 차이로 초기 empyt activity 선택하면 안됩니다.
안녕하세요.안드로이드 스튜디오 버전(Android Studio Otter 2 Feature Drop | 2025.2.2)에서는empty activity를 선택하면 layout 없어서 activity_main.xml 즉 UI를 설정 파일 없습니다.empty views activity를 선택해야 합니다.동영상을 수정하셔야 수강생들이 혼동이 없습니다.
-
미해결제미니의 개발실무 - 커머스 백엔드 기본편
격벽의 순환 참조(?)
안녕하세요. 지난번에도 질문 남겼었는데 또 찾아뵙게 되었습니다. user 의 경우 많은 개념들이 참조하게 될 것 같습니다.회원탈퇴라는 기능이 제공될 때, 회원이 탈퇴되면 관련된 개념들을 삭제해야된다고 할 경우 이를 어떻게 해결하는 것이 좋을까요?user 쪽에서 관련 개념들을 찾아서 삭제하기에는 개념 격벽간의 순환 참조(?)가 발생하게 될 것 같습니다.카프카나 메시지 큐 등을 이용해서 처리할 수 있을 것 같은데 현재 이를 처리할 수 있는 별도의 인프라는 없다고 가정해보고 싶습니다.그렇다면 어플리케이션 이벤트(ApplicationEventPublisher)를 사용하는 것이 방법이 생각납니다.하지만 이벤트를 사용하게 되면 어플리케이션의 로직 흐름을 보기가 조금 어려우지는 것 같다는 생각도 들어서 괜찮은 방법인지 고민이 됩니다.
-
미해결입문자를 위한 Spring Boot with Kotlin - 나만의 포트폴리오 사이트 만들기
Windows 환경에서 작업중
안녕하세요, 윈도우 환경에서 작업중입니다.다름이 아니라 Gradle을 IDEA로 변경하면 Build 시에 Error가 떠서 AI에게 물어보니 Gradle로 변경하라 하여 변경하니 되는데 이렇게 작업해도 상관 없는 부분일까요? 오류메세지:Kotlin: [Internal Error] java.lang.NoClassDefFoundError: org/jetbrains/kotlin/com/intellij/psi/PsiElement at org.jetbrains.kotlin.noarg.fir.KtErrorsNoArg.<clinit>(KtErrorsNoArg.kt:32) at org.jetbrains.kotlin.noarg.fir.FirNoArgExtensionRegistrar.configurePlugin(FirNoArgExtensionRegistrar.kt:15) at org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrar.configuredExtensionFactories_delegate$lambda$0(FirExtensionRegistrar.kt:294) at kotlin.SynchronizedLazyImpl.getValue(LazyJVM.kt:86) at org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrar.getConfiguredExtensionFactories(FirExtensionRegistrar.kt:291) at org.jetbrains.kotlin.fir.extensions.FirExtensionRegistrar.configure(FirExtensionRegistrar.kt:270) at org.jetbrains.kotlin.fir.session.FirAbstractSessionFactory.createSharedLibrarySession(FirAbstractSessionFactory.kt:107) at org.jetbrains.kotlin.fir.session.FirJvmSessionFactory.createSharedLibrarySession(FirJvmSessionFactory.kt:53) at org.jetbrains.kotlin.cli.pipeline.jvm.JvmFrontendPipelinePhase.prepareJvmSessions$lambda$0(JvmFrontendPipelinePhase.kt:326) at org.jetbrains.kotlin.cli.common.SessionConstructionUtils.prepareSessions(FirSessionConstructionUtils.kt:324) at org.jetbrains.kotlin.cli.pipeline.jvm.JvmFrontendPipelinePhase.prepareJvmSessions(JvmFrontendPipelinePhase.kt:322) at org.jetbrains.kotlin.cli.pipeline.jvm.JvmFrontendPipelinePhase.executePhase(JvmFrontendPipelinePhase.kt:137) at org.jetbrains.kotlin.cli.pipeline.jvm.JvmFrontendPipelinePhase.executePhase(JvmFrontendPipelinePhase.kt:47) at org.jetbrains.kotlin.cli.pipeline.PipelinePhase.phaseBody(PipelinePhase.kt:68) at org.jetbrains.kotlin.cli.pipeline.PipelinePhase.phaseBody(PipelinePhase.kt:58) at org.jetbrains.kotlin.config.phaser.NamedCompilerPhase.invoke(CompilerPhase.kt:102) at org.jetbrains.kotlin.backend.common.phaser.CompositePhase.invoke(PhaseBuilders.kt:22) at org.jetbrains.kotlin.config.phaser.CompilerPhaseKt.invokeToplevel(CompilerPhase.kt:53) at org.jetbrains.kotlin.cli.pipeline.AbstractCliPipeline.runPhasedPipeline(AbstractCliPipeline.kt:109) at org.jetbrains.kotlin.cli.pipeline.AbstractCliPipeline.execute(AbstractCliPipeline.kt:68) at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecutePhased(K2JVMCompiler.kt:79) at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecutePhased(K2JVMCompiler.kt:45) at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:90) at org.jetbrains.kotlin.cli.common.CLICompiler.exec(CLICompiler.kt:352) at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1617) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:569) at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:360) at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:200) at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:197) at java.base/java.security.AccessController.doPrivileged(AccessController.java:712) at java.rmi/sun.rmi.transport.Transport.serviceCall(Transport.java:196) at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:587) at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:828) at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:705) at java.base/java.security.AccessController.doPrivileged(AccessController.java:399) at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:704) at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136) at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635) at java.base/java.lang.Thread.run(Thread.java:840)Caused by: java.lang.ClassNotFoundException: org.jetbrains.kotlin.com.intellij.psi.PsiElement at java.base/java.net.URLClassLoader.findClass(URLClassLoader.java:445) at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:592) at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:525) ... 42 more
-
미해결모던 안드로이드 - Jetpack Compose 입문
onTabFavorite 콜백 관련 질문
안녕하세요,강의에 구성하신 콜백 메서드 대신 아무 파라미터도 받지 않고 부모에서 정의한 isFavorite을 바꿔주는 형태로 콜백을 구성해도 되는것인지 궁금합니다. class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() setContent { MyApplicationTheme { Scaffold { innerPadding -> Box( modifier = Modifier .padding(innerPadding) .fillMaxSize() ) { var isFavorite by rememberSaveable { mutableStateOf(true) } ImageCard( modifier = Modifier .fillMaxWidth(0.5f) .padding(16.dp), isFavorite ) { isFavorite = !isFavorite } } } } } } } @Composable fun ImageCard( modifier: Modifier = Modifier, isFavorite: Boolean, onTabFavorite: () -> Unit, ) { Card( shape = RoundedCornerShape(8.dp), elevation = CardDefaults.cardElevation(5.dp), ) { Box( modifier = Modifier.height(200.dp) ) { Image( painter = painterResource(R.drawable.moon), contentDescription = "cute", contentScale = ContentScale.Crop, ) Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.TopEnd ) { IconButton( onClick = onTabFavorite, ) { Icon( imageVector = if (isFavorite) Icons.Default.Favorite else Icons.Default.FavoriteBorder, contentDescription = "favorite", tint = Color.White, ) } } } } }감사합니다!
-
미해결제미니의 개발실무 - 커머스 백엔드 기본편
결제 관련 서킷 브레이커 전략, 데이터 정합성 및 타임아웃 설정 질문
안녕하세요, 제미니님!강의 완독 후, 결제 시스템의 안정성을 높이기 위해 개인 프로젝트에 외부 PG사 연동 구간에 장애 격리 처리를 직접 구현하며 경험하고 있습니다. 구현 과정에서 고민했던 설계 내용과 제 나름의 가설이 맞는지, 그리고 기존 강의 코드의 설정 의도에 대해 여쭤보고 싶습니다. 🙇🏻♂️ 1. 서킷 브레이커 도입 및 트랜잭션 분리 전략 @Component class BeeceptorPaymentClient( private val restClient: RestClient, private val circuitBreaker: CircuitBreaker, ) : PaymentClient { private val log = LoggerFactory.getLogger(javaClass) override fun requestPayment(command: PaymentCommand): PaymentResult { return circuitBreaker.run("beeceptor-payment") { executePayment(command) } .fallbackIfOpen { log.warn("[Circuit Open] Beeceptor 결제 서비스 차단됨. 잠시 후 재시도 필요.") throw CoreException(ErrorType.PAYMENT_EXTERNAL_API_UNAVAILABLE) } .getOrElse { e -> when (e) { is HttpClientErrorException -> { log.warn("[PAYMENT_REJECTED] 결제 거절: ${e.responseBodyAsString}") throw CoreException(ErrorType.PAYMENT_REJECTED) } is CoreException -> throw e else -> { log.error("[PAYMENT_FAILED] Beeceptor 결제 호출 실패", e) throw CoreException(ErrorType.PAYMENT_EXTERNAL_API_FAIL) } } } } }외부 PG사 장애 전파를 막기 위해 Resilience4j를 도입했고, 테스트를 위해 Beeceptor를 활용했습니다.설정 전략:COUNT_BASED (최소 10회, 실패율 50% Open), 단순 비즈니스 에러(4xx)는 ignoreExceptions로 제외하여 시스템 장애만 감지하도록 설정했습니다. 트랜잭션 분리(Facade 패턴): 트랜잭션에 대한 DB 커넥션 점유 시간을 최소화하기 위해 아래와 같이 로직을 3단계로 분리했습니다. 1. 검증: DB 조회 (ReadOnly 트랜잭션)2. 외부 요청: 외부 PG API 호출 (No 트랜잭션 + 서킷 브레이커 적용)3. 상태 업데이트: 결제 결과 저장 (Write 트랜잭션)@Component class PaymentConfirmFacade( private val paymentService: PaymentService, private val paymentClient: PaymentClient, ) { fun success(orderKey: String, externalPaymentKey: String, amount: BigDecimal): Long { // 1. [DB Transaction] 결제 검증 val command = paymentService.validatePayment(orderKey, externalPaymentKey, amount) // 2. [No Transaction] 외부 API 호출 val paymentResult = paymentClient.requestPayment(command) // 3. [DB Transaction] 결제 완료 처리 // 외부 API에서 받은 결과(transactionId 등)를 넘겨줌 return paymentService.completePayment(orderKey, paymentResult) } } [질문] Facade 패턴을 적용함에 따라 '외부 요청'(2번)은 성공했으나, '상태 업데이트 및 저장'(3번)에서 DB 장애 등으로 실패할 경우 데이터 불일치가 발생합니다. 현재 로직상 completePayment가 실패하면 트랜잭션이 롤백되어 TransactionHistory조차 남지 않고 Payment 상태는 READY로 유지됩니다.따라서 저는 배치/스케줄러를 통해 일정 시간이 지나도 READY 상태로 남아있는 결제 건들을 조회한 뒤, PG사 결제 내역 조회 API와 크로스 체크하여 누락된 결제를 보정하는 로직이 필요하다고 판단됩니다. 이러한 접근 방식이 실무에서사용하는 일반적인 보정 패턴인지 궁금합니다. 2. DB Connection/Socket Timeout 설정 의도와 Facade 적용 시의 상관관계외부 API 타임아웃(Connect 3s, Read 30s)을 학습하며 적용하던 중, 기존 강의 프로젝트의 db-core.yml 설정이 눈에 들어왔습니다.connection-timeout: 1100 (1.1초)socketTimeout: 3000 (3초)[질문 1] 보통 DB 타임아웃을 넉넉하게 잡는 경우도 있는데, 이렇게 타이트하게 설정하신 의도가 "트래픽 급증 시 DB 커넥션을 얻지 못하면 빠르게 실패 처리하여 스레드 풀 고갈을 막고 시스템 전체 장애를 방지하기 위함" 인지, 혹은 다른 운영 노하우가 담겨 있는 것인지 궁금합니다.[질문 2] 저는 Facade 패턴을 적용하여 외부 API 호출 시점에는 트랜잭션(DB Connection)을 점유하지 않도록 분리했습니다. 따라서 기술적으로는 DB 타임아웃과 서킷 브레이커(외부) 타임아웃을 서로 독립적인 관점으로 설정해도 된다고 판단했습니다.다만, 실무 운영 관점에서는 결국 '전체 사용자 대기 시간(User Latency)' 이라는 제약이 존재할 텐데, 이때 전체 응답 시간 제한 내에서 DB와 외부 API 타임아웃 비중을 어떻게 배분하시는지 강사님만의 기준이나 노하우가 궁금합니다 ! 긴 글 읽어주셔서 감사합니다.
-
미해결제미니의 개발실무 - 커머스 백엔드 기본편
비회원 개념 추가 시 개선 방향
요즘 쇼핑몰 커머스등은 비회원 주문이 있는 경우가 거의 대부분인 것 같은데 비회원 주문,결제의 개념이 추가된다면 어떻게 개선될 수 있을까요? api가 복제되는 느낌으로 가야하는지.. 주문, 장바구니, 결제 등등 유저아이디 대신 게스트아이디로 조회하는 등과 같이 작은 부분만 바뀌고 나머지 로직은 동일 반복될 것 같습니다.주문조회시에도 회원 비회원.. 서비스는하나를 쓰고 OrderFinder에서 함수로 나누는 방식도 생각납니다.어떤 방식이 더 있을까요?
-
해결됨[왕초보편] 앱 8개를 만들면서 배우는 안드로이드 코틀린(Android Kotlin)
context의 구별에 대하여
안녕하세요?코드 작성 중에 context가 상당히 많이 사용되고 있습니다. 그런데 어느 경우에는 this, 또 다른 경우에는 BaseContext, 또 다른 경우에는 context 등이 사용되고 있습니다.이것이 서로 어떻게 구별되는지요? 일정한 기준이 있는지, 있다면 어떤 방식으로 나뉠 수 있는 것인지 궁금합니다.
-
해결됨은행 서버 프로젝트 실습을 통해 배우는 코틀린 마스터 클래스
질문] 에러처리 관련 문의
현재 JWT 부분까지 들었습니다! 강의를 들으면서 갑자기 궁금한 부분이 생겨 질문드립니다.도메인 별로 현재 API (Controller) 도 나눠져있는데요, 이런 형태의 프로젝트(DDD 아키텍처)인 경우 @RestControllerAdvice를 활용한 글로벌예외처리는 어떻게 구성하시나요? 글로벌예외처리 전용 class를 만들고 특정 익셉션들을 구성 후 각각의 익센션에 대한 응답을 ResponseProvider 를 이용해서 하는지? 등...너무 추상적으로 질문을 드려서 이해하실지 모르겠지만 요약하자면 도메인별로 구별되어 있는 프로젝트에서는 글로벌익셉션처리가 어떤 형태로 구성할 수 있는지 궁금합니다 ㅋ.ㅋ;
-
해결됨제미니의 개발실무 - 커머스 백엔드 기본편
DB 레이어 잘 다루는 법
안녕하세요. DB 레이어를 다루는 것에 대해 몇가지 질문이 있습니다.프로젝트 버전 1.1 포인트 적립 트랜잭션리뷰 작성ReviewService.addReview)에서 포인트 적립PointHandler.earn )이 한 트랜잭션에서 이루어져야 될 것 같다고 생각했는데 분리되어 있습니다. 이렇게 처리해도 충분한지 여쭤보고 싶습니다.CancelService, PaymentService 에서는 같은 트랜잭션에 있음 낙관적 락 예외 처리PointBalanceEntity 에는 동시성 처리를 위해 @Version(낙관적 락)을 두었다고 해주셨습니다.현재는 낙관적 락 예외가 발생하면 500 에러가 발생할 것으로 보이는데, 저는 평소 최대한 이 낙관적 락 예외를 잡아서 적절히 다른 예외로 변환하여 던지도록 했었습니다.낙관적 락 예외에 대해서 500에러가 발생하도록 처리하는 것으로 충분할지 고민됩니다. Repository, JpaRepository 분리 현재 프로젝트는 서비스 -> 컴포넌트(Finder, Handler, ...) -> JpaRepository 형태로 계층 관계가 있는 것 같습니다.그런데 재미니님의 유튜브를 보면 서비스 -> 컴포넌트(Finder, Handler, ...) -> Repository(개념 객체를 다루는?) -> JpaRepository 형태의 계층을 이루고 있는 것으로 보입니다.첫번째 계층구조를 보면 종종 서비스에서 단순히 컴포넌트를 한번 호출하는 정도인 경우가 많은 것 같습니다. 두번째 계층구조를 사용한다면 서비스에서 단순히 컴포넌트를 한번 호출하고, 그 컴포넌트에서도 단순히 Repository를 한번 호출하는 정도인 경우도 많이 생길 것 같습니다.이러한 이유로 저는 첫번째 계층구조 정도로 충분한가? 라고도 생각했는데 두번째 계층구조를 선택하시게 되는 이유가 궁금합니다. 두번째 계층구조 관련해서 여러 영상들이 있겠지만 우선 저는 아래 영상 참고했습니다.https://www.youtube.com/watch?v=b5xWS8MYl0Q Repository 가 분리된 경우에서의 검증, 업데이트 로직데이터를 검증, 업데이트할 때 어떤 재미니님과 같은 프로젝트 구조를 가져간다면 어떤 식으로 코드가 나오게 될 지 궁금합니다.검증, 업데이트라 하면 제가 생각한 예시 상황은 리뷰가 7주일이 지나면 업데이트 할 수 없다는 요건이 있다고 가정해보겠습니다.제가 생각한 것은 아래와 같습니다.public class ReviewService { private final ReviewFinder reviewFinder; private final ReviewProcessor reviewProcessor; public void update(ReviewUpdate reviewUpdate) { var review reviewFinder.get(reviewUpdate.id()); if (review.canUpdate()) { throw new CoreException(); } var updatedReview = review.update(reviewUpdate); reviewProcessor.update(updatedReview); } }이전에 프로젝트 구조에 대해서 고민이 많이 되었을 때 재미니님의 유튜브를 보면서 프로젝트 구조를 잡는데 도움이 많이 되었습니다.그런데 제가 그 의도나 스타일을 완전히 이해하지는 못하고 있는 것 같아서 재미니님의 스타일들을 한번 배워보고 싶습니다.유튜브에서부터 정말 도움 많이 받고있습니다. 좋은 강의 감사합니다!!
-
해결됨제미니의 개발실무 - 커머스 백엔드 기본편
엔티티 연관관계 사용
안녕하세요, 강의 너무 잘보고있습니다!코드를 살펴보니 궁금한 부분이 있는데 현재 작성된 코드에서는 엔티티 간 연관관계를 맺지 않고 풀어내고계시더라고요!재미니님은 실무에서도 연관관계를 사용하지 않으시는 건지 아니면 고민 포인트(?)를 던져주신건지 궁금합니다!예를들어 Order와 OrderItem 관련해서, OrderService의 서비스 레이어인 create 메서드에서 OrderItemRepository를 사용해 saveAll을 호출하는 방식을 사용하시더라고요!저는 Order가 없으면 OrderItem은 존재하지 않아야한다고 생각해서, OrderItem의 생성을 서비스 레이어가 아닌 Order 엔티티안에서 생성하도록 강제하는 건 어떨까 생각했습니다.방법으로는OrderItem.create() 같은 생성 메서드를 protected로 막는다.OrderItemRepository를 아예 생성하지 않는다.더티 체킹을 이용하여 Order 필드에 있는 List<OrderItem> 컬렉션에 요소 추가로 OrderItem을 save한다.팀 컨벤션을 정한다.정도가 있을 것 같은데... 그렇다면 재미니님은 실무에서도 연관관계를 맺지 않는 방식을 선호하시는 건가요?사실 서비스 레이어에서 직접 호출을 통한 저장이라고 해도 문제는 없겠지만 사용하시는 방식이 궁금합니다! 그리고 추가적으로 주문 시 상품 재고를 차감시키는 메서드를 OrderHandler(OrderManager) 에 private으로 위치시키는 것과 ProductHandler(ProductManager)에 위치시키고 주입받아 사용하는 것중 어떤게 더 나은 방식일까요?private void decreaseProductStock(List<NewOrderItem> newOrderItems, Map<Long, Product> productMap) { for (NewOrderItem newOrderItem : newOrderItems) { Product product = productMap.get(newOrderItem.productId()); if (product.isStockLessThan(newOrderItem.quantity())) throw new ApiException(PRODUCT_QUANTITY_OVER); product.decreaseStock(newOrderItem.quantity()); } }고민이 되는 부분은 예를들어 위와 같은 메서드를 OrderHandler에 두자니 조금 헤비해지는 것 같고, ProductHandler에 두자니 신규 주문 시에만 사용될 것같은데.. ProductHandler에 두는게 맞는지 고민입니다..! 의견이나 힌트 주시면 좀 더 고민해보도록 하겠습니다!
-
해결됨제미니의 개발실무 - 커머스 백엔드 기본편
엔티티 상태를 조회하는 시점
안녕하십니까 좋은 강의 잘듣고 있습니다.강의를 들으면서 예제 코드도 같이 파악해보고 있는데요repository 조회시 조회한 결과에서 활성 상태를 체크할때 2가지 방법으로 구현이 되어있는 것 같습니다.repository에서 쿼리 메서드로 값 전달 findByXXAndStatus()일단 findByXX로 조회하고 filter에서 isActive 체크 2가지 경우다 결과 자체는 Active인 엔티티를 조회하겠지만, 두 경우를 언제 사용하는 것이 좋은지가 구분이 있을까요?? 일단 저라면 목록을 조회하는 경우라면 1번 방법으로 조회하면 조금이나마 성능 이점이 있을 것 같고, 단일 데이터 조회시에는 1번이나 2번이나 상관은 없을꺼 같다는 생각입니다! https://youtu.be/o5byT9Ha5Tg?si=C1zj8oP8-wSB6g7Y최근에 우테코에서도 softDelete 관련 처리를 엔티티 설정을 통해 하기도 하던데, 제미니님의 의견이 궁금하여 질문드립니다! 감사합니다! ps. 아직 제가 강의를 쿠폰 부분까지 들어서 뒤에서 이에 대한 설명이 나오는지는 확인을 못했어요!
-
해결됨제미니의 개발실무 - 커머스 백엔드 기본편
섹션3. 상품 상세. 코드느끼기 12:47 질문.
ProductFinder -> ProductUsecase or ProductUsecase<ProductSection>정의한 곳에 Product 서비스 와 ProductSection 서비스를 갖고 안에서 작업을 해보는건 어떻게 생각하실까요?ProductProductSecion 으로 명명하기에는 애매한 부분이 있어, Usecase라는 단어를 써서중간에 두 서비스만 사용하는 컴포넌트를 하나 두는 것은 어떻게 생각하실까요?혹은 Delegator 를 써서, 이 두 서비스를 의존해서 처리해야 하는 프로세스를 위임해버리는 타입을 하나 만드는겁니다.어떻게 생각하실까요?
-
해결됨제미니의 개발실무 - 커머스 백엔드 기본편
xxx서비스와 xxx핸들러 의 구분 기준
안녕하세요!궁금한점이 생겼는데요.로직을 수행하는 객체중 어떤것은 xxx서비스 어떤것은 xxx핸들러또는매니저(컴포넌트) 등의 객체로 나눠지는데요. 이에 대한 기준이 있을까요?컨트롤러가 호출하는 객체를 xxx서비스, 서비스가 호출하는 객체를 xxx핸들러또는매니저(컴포넌트)라고 보면 될까요? 그렇다면 컴포넌트는 특정 개념에서의 로직들이 응집되어있는 객체 라고 보면 될까요?
-
해결됨제미니의 개발실무 - 커머스 백엔드 기본편
상품 전체보기가 없습니다. 카테고리는 필수로 선택해야 합니다.
전체보기가 없는 상품들 목록이라면카테고리 프로덕트로 설계하는 방향은 어떻게 생각하시는지요?상품의 전체보기가 없습니다.A-Z까지 모든상품들은 카테고리로로 분류되어있고(=모든 상품들은 카테고리라는 꼬리표를 가집니다)좌측 메뉴탭에서도 카테고리별로 구분이 되어있습니다.해당 상품들을 모두 카테고리별로 선택의 상세보기에 해당상품들이 나열되도록 되어있습니다.이러한 생각은 어떤지 한번 의견 듣고싶어요.
-
해결됨제미니의 개발실무 - 커머스 백엔드 기본편
@Transactional에 관해서 질문드립니다.
안녕하세요!강의를 보던 중 ReviewService의 트랜잭션 처리에 대해 궁금한 점이 있어 질문드립니다. addReview 함수에서 리뷰를 저장하는 add()와 포인트를 적립하는 earn() 두개의 중요한 함수를 사용하고 있는데, 이는 각각 별도의 트랜잭션으로 처리되어 리뷰는 저장되고 포인트는 적립되지 않는 문제가 발생할 수 있을 것 같습니다. 현재 설계에서 addReview()에 @Transactional을 붙이지 않은 특별한 이유가 있으신지 궁금합니다. 혹은 만약 의도적으로 별도 트랜잭션으로 분리한 것이라면 그 이유가 궁금합니다. 항상 감사합니다.
-
해결됨제미니의 개발실무 - 커머스 백엔드 기본편
'개념과 격벽' 을 실제 업무에 어떻게 사용하면 좋을까요?
안녕하세요, 제미니님.유투브 및 인프런 강의 잘 시청하고 있습니다 :) 인프콘 2024에서 '지속 성장 가능한 설계를 만들어가는 방법' 이라는 주제로 발표해주신 내용이 무척 공감이 되었고, 제미니님 유투브를 찾아봤다가 강의까지 수강하게 되었습니다 이번 강의 내용은 아니지만 인프콘 영상에서 말씀해주신 개념인 '개념' 과 '격벽'을 구체적으로 실무에서 어떻게 사용하면 좋을지를 여쭤보고자 인프런 질문을 통해서 글을 올리게 되었습니다. 편의상 신규 프로젝트를 진행한다고 가정했을때,요구사항을 분석하고 도메인을 구성하는 여러 '개념'들을 나열한다.나열된 '개념'들의 급(1급, 2급, 3급 ..) 을 나눠보고 그룹화 하면서 '격벽'으로 분리한다.'격벽'으로 분리된 그룹이 어떤 개념을 통해서 연결될지 방화벽으로 동작할 개념을 생각해본다. 이는 개념간 무분별한 참조를 막기 위함이다.'개념'들과 '격벽'들을 기반으로 일단 코드로 구현부터 해본다. ('설계를 하지 말고 구현을 먼저' 하는 포인트는 이것)구현하면서 또는 운영하면서 더 나누거나 신규로 추가할 개념이 있다면 반영한다. 결과적으로 설계를 하지 않고 구현을 먼저 하고, 구현 하는 과정이나 운영 하는 과정에서 최적화 시킨다. 이것이 곧 최적의 설계로 나아가는 방향이 된다. (질문) 제가 제미니님이 말씀하신 '개념'과 '격벽'을 잘 이해한 것이 맞을까요? 실무에서 위 흐름대로 적용하면 제미니님이 강조하신 내용에 기반한 작업이 될 수 있을까요? 실무에서 실제로 말씀해주신 내용을 적용해보고 싶은데 구체적으로 어떤식으로 적용하면 될 지 몰라서 제가 이해한 내용을 바탕으로 작성을 해보았습니다. 틀린 부분이 있다면 피드백을 부탁드리고 싶습니다 :) 긴 글 읽어주셔서 감사합니다~
-
해결됨제미니의 개발실무 - 커머스 백엔드 기본편
타임베이스 정산 배치 실패 시 처리에 대해
안녕하세요 시간날 때 틈틈히 보다가 어느새 막바지에 다다랐네요! 타임베이스 정산 배치가 실패했을 때 궁금한점이 있습니다. 우선 현재는 시간 대별로 오전 1시, 4시, 9시로 배치가 나눠져있는데이 상황에서 강의에서도 설명하셨듯이 오전 1시 배치가 오전 4시 30분에 끝나서 오전 4시의 배치가 에러가 뱉는 상황에 대해 문제를 해결하는 방법들이 궁금합니다! 우선 제가 생각한 방법들은 다음과 같습니다.1. 이벤트 기반 이벤트 기반으로 각 단계가 완료되면 바로 다름 배치 단계로 넘어가는 방식입니다.하지만 이 방법은 이미 기존 인프라와 코드를 변경해야하고 정산 시간이 고정 된다는 요구사항이 있다고도 보여 현재 요구사항에는 적절하진 않은 것 같습니다. 2. 각 배치마다 전 단계 완료 여부 상태를 확인오전 4시, 오전 9시 배치가 이전 단계의 배치 중 실패한 배치 이력이 있는지 확인하는 방법입니다.현재 스케줄링 방식으로는 이를 위해 별도의 배치 기록 테이블을 만들어서 배치 이력 관리를 추가합니다.오전 4시, 오전 9시에 해당 정산 날짜에 대해 실패한 이전 배치 단계 이력이 있다면 성공할 때 까지 N번 재시도 합니다.정산의 즉시성이 필요하다면 N번 재시도 후 실패했을 때 해당 배치 단계를 에러 상태로 처리하고,에러 로깅과 알림을 전송합니다.다음 배치 단계는 정산 날짜에 해당하는 이전 배치 이력이 에러 상태로 남아 있다면 실행하지 않습니다. 현재 요구사항에서는 수동으로 처리할 부분은 최대한 제거하는 2번 방식을 사용할 수 있을 것 같습니다.제가 배치 관리에 대해 잘 이해하고 있는지 모르겠어서 생각한 방법에 대해서 재민님은 어떻게 생각하시는지 궁금합니다.! 항상 잘 보고 있습니다 감사합니다!
-
해결됨[왕초보편] 앱 8개를 만들면서 배우는 안드로이드 코틀린(Android Kotlin)
"프롤로그에서 ..." 오류 관련해 직전 질문에 대한 추가 질문입니다.
선생님, "프롤로그에서 ..." 오류 관련해 직전 질문에 대한 추가 질문입니다.지금의 상황에서 수평 관련 제약 조건이 추가되지 않는다면, 왜 문제의 오류가 발생하는 것인지요?지금의 경우 이처럼 constraintLayout을 써서 복잡하게 제약 조건이 필요하다면, 이때 차라리 그 전체 layout으로서 Linearlayout을 사용하는 것이 더 간단하지 않을까요?
-
해결됨제미니의 개발실무 - 커머스 백엔드 기본편
강의 PDF는 어디에서 다운로드 할 수 있을까요?
"4. 강의 PDF 자료 및 프로젝트"에서 프로젝트 소스는 있는데 강의PDF는 안 보이네요..어디에서 강의 PDF를 찾을 수 있을까요?
-
해결됨제미니의 개발실무 - 커머스 백엔드 기본편
취소-코드느끼기 / Cancel을 별도의 스키마로 관리하는 방식의 장점
안녕하세요, 강사님. 취소 - 코드느끼기 수업의 5:00~ 부분부터'결제', '취소'의 스키마를 분리하여 레코드를 immutable하게 다루는 상황의 예시로 결제 취소 상황을 설명하셨는데 어떤 부분에서 유리한건지 모르겠습니다. 7일 전 주문을 30일 후에 취소함'취소'를 '결제'의 상태로 반영할 때'결제'의 상태를 '취소'상태로 변경 - 레코드 수정 시각 갱신취소된 '결제' 조회 시 레코드 수정 시각을 이용해야 함'취소'를 스키마로 관리할 때새로운 '취소' 레코드 추가제가 이해한 상황은 '취소된 결제를 결제 id 없이 취소 시각으로 조회해야한다'는 것입니다.취소된 결제를 결제 id 대신 취소 시각으로 찾는 경우는 어떤 상황인지, 조회를 위해 어느정도 취소시각의 범위를 특정할 수 있는 데이터가 존재할텐데 데이터가 결제 id와 분리되어 존재하는 이유가 무엇인가요?'취소'스키마가 따로 존재해서 결제 취소가 레코드로 쌓일 때, 취소 시각으로 찾는다면 무엇이 다른건지 모르겠습니다.'규모적으로 선택하라. 테이블이 적고 테이블 로우가 적고 접근 범위 자체를 줄일 수 있고, 이런 장점으로 보면 페이먼트 테이블을 만들어도 되는데요' 라고 말씀하신 부분도 모르겠습니다!수업 진행하시면서 1) 각 개념의 레코드가 자신의 영역 안의 맥락으로만 수정된다, 2) '결제'의 레코드는 많고, '취소'의 레코드는 적으므로 취소 일자로 조회하는 속도 차이가 난다고 하시는 것은 이해했습니다만 앞서서 예시로 들어주신 부분은 잘 모르겠네요. 감사합니다.