Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
6a03748
feat: DIP์œ„๋ฐ˜ ํ”ผ๋“œ๋ฐฑ ๋ฐ˜์˜
adminhelper Dec 4, 2025
c41ffe6
chore: BaseEntity์ƒ์†์œผ๋กœ ์ธํ•œ createdAt์ œ๊ฑฐ
adminhelper Dec 4, 2025
afcae1c
chore: ํ”ผ๋“œ๋ฐฑ ๋ฐ˜์˜
adminhelper Dec 4, 2025
df0fb03
docs : 6round.md
adminhelper Dec 4, 2025
7b1111b
docs: mdํŒŒ์ผ ์ˆ˜์ •
adminhelper Dec 5, 2025
5e36c3f
feat : pg-simulator ๋ชจ๋“ˆ ์ถ”๊ฐ€
adminhelper Dec 5, 2025
8fb6e51
feat : pg-simulator ๋ชจ๋“ˆ ์ถ”๊ฐ€
adminhelper Dec 5, 2025
23b9170
feat : ์ฃผ๋ฌธ API ์ ์šฉ
adminhelper Dec 5, 2025
a1ffe5e
Refactor: DIP์œ„๋ฐ˜ ์ œ๊ฑฐ
adminhelper Dec 5, 2025
f1205ab
fix: Point ํ™˜๋ถˆ ๋ฉ”์„œ๋“œ ์ถ”๊ฐ€
adminhelper Dec 5, 2025
c5f6eaf
feat: resilience4j ์˜์กด์„ฑ ์ถ”๊ฐ€
adminhelper Dec 5, 2025
8c6d050
docs: 6round.md
adminhelper Dec 6, 2025
0e3419f
feat: resilience4j, feign ์˜์กด์„ฑ ์ถ”๊ฐ€
adminhelper Dec 6, 2025
20d6942
feat: pg, feign, resilience4j ์„ค์ •
adminhelper Dec 6, 2025
7d12217
fix: user ์ƒ์„ฑ์‹œ point ์ƒ์„ฑ ๋กœ์ง ์ˆ˜์ •
adminhelper Dec 6, 2025
38838b4
feat: PG ๊ฒฐ์ œ ์š”์ฒญ API ๋กœ์ง ์ถ”๊ฐ€
adminhelper Dec 6, 2025
ff83042
fix: 6round ํ”ผ๋“œ๋ฐฑ
adminhelper Dec 8, 2025
482bed2
docs: 7round.md
adminhelper Dec 8, 2025
859eefc
docs: 7round.md
adminhelper Dec 12, 2025
743262e
feat: ์ด๋ฒคํŠธ๊ธฐ๋ฐ˜์œผ๋กœ ์ข‹์•„์š” ์ฒ˜๋ฆฌ
adminhelper Dec 12, 2025
11bcdfb
feat: ์ฃผ๋ฌธ๊ฒฐ์ œ ๋ถ„๋ฆฌ์ œ ๊ฑฐ ๋ฐ ์ด๋ฒคํŠธ๊ธฐ๋ฐ˜์œผ๋กœ ์ฒ˜๋ฆฌ
adminhelper Dec 12, 2025
dd8cb1a
feat: ๋ฐ์ดํ„ฐ ํ”Œ๋žซํผ ์ „์†Œํ•˜๋Š” ํ›„์†์ฒ˜๋ฆฌ
adminhelper Dec 12, 2025
cf6c0c5
docs: 8round.md ์ถ”๊ฐ€
adminhelper Dec 19, 2025
ef5fd9d
docs: 8round.md ์ˆ˜์ •
adminhelper Dec 25, 2025
94b5739
feat: kafka ์„ค์ •๋ณ€๊ฒฝ
adminhelper Dec 25, 2025
4ab9eba
feat: outbox ํŒจํ„ด ์ถ”๊ฐ€
adminhelper Dec 25, 2025
96bcccd
feat: ๋„๋ฉ”์ธ ์ด๋ฒคํŠธ ๋ฐœ์ƒ topic ์ถ”๊ฐ€
adminhelper Dec 25, 2025
5fa1a90
chore: ํŠธ๋žœ์žญ์…˜ ์ „ํŒŒ์˜ต์…˜ ์ œ๊ฑฐ
adminhelper Dec 25, 2025
548647b
feat: event_handled ํ…Œ์ด๋ธ”ํ†ตํ•œ ๋ฉฑ๋“ฑ์ฒ˜๋ฆฌ
adminhelper Dec 25, 2025
e0d5737
feat: Metrics ์ง‘๊ณ„์ฒ˜๋ฆฌ
adminhelper Dec 25, 2025
987d68a
feat: ์žฌ๊ณ ์†Œ์ง„์‹œ ์ƒํ’ˆ์บ์‹œ ๊ฐฑ์‹ 
adminhelper Dec 25, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions apps/commerce-api/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ dependencies {
// add-ons
implementation(project(":modules:jpa"))
implementation(project(":modules:redis"))
implementation(project(":modules:kafka"))
implementation(project(":supports:jackson"))
implementation(project(":supports:logging"))
implementation(project(":supports:monitoring"))
Expand All @@ -11,6 +12,12 @@ dependencies {
implementation("org.springframework.boot:spring-boot-starter-actuator")
implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:${project.properties["springDocOpenApiVersion"]}")

// resilience4j
implementation("io.github.resilience4j:resilience4j-spring-boot3")

// feign
implementation("org.springframework.cloud:spring-cloud-starter-openfeign")
Comment on lines +15 to +19
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

โš ๏ธ Potential issue | ๐ŸŸก Minor

๐Ÿงฉ Analysis chain

๐Ÿ Script executed:

#!/bin/bash

# Search for Spring Cloud BOM in all gradle files
echo "=== Searching for Spring Cloud BOM ==="
rg "spring-cloud-dependencies" --type=gradle

# Search for resilience4j BOM
echo -e "\n=== Searching for resilience4j BOM ==="
rg "resilience4j-bom" --type=gradle

# Check for platform/BOM imports
echo -e "\n=== Searching for platform declarations ==="
rg "platform|dependencyManagement" --type=gradle -A 2

# Check the build.gradle.kts structure
echo -e "\n=== Checking build.gradle.kts structure ==="
find . -name "build.gradle.kts" | head -5

Repository: Loopers-dev-lab/loopers-spring-java-template

Length of output: 886


๐Ÿ Script executed:

#!/bin/bash

# Read the entire build.gradle.kts file to understand version management
echo "=== Reading apps/commerce-api/build.gradle.kts ==="
wc -l apps/commerce-api/build.gradle.kts

Repository: Loopers-dev-lab/loopers-spring-java-template

Length of output: 176


๐Ÿ Script executed:

#!/bin/bash

# Read the apps/commerce-api/build.gradle.kts file
echo "=== apps/commerce-api/build.gradle.kts ==="
cat -n apps/commerce-api/build.gradle.kts

# Read the root build.gradle.kts to check for parent BOM declarations
echo -e "\n=== root build.gradle.kts ==="
cat -n build.gradle.kts

Repository: Loopers-dev-lab/loopers-spring-java-template

Length of output: 5972


resilience4j-spring-boot3์˜ ๋ฒ„์ „ ๊ด€๋ฆฌ ์ „๋žต ํ™•์ธ ํ•„์š”

Spring Cloud BOM์€ root build.gradle.kts์˜ dependencyManagement ๋ธ”๋ก(lines 42-46)์—์„œ ์ด๋ฏธ ์„ ์–ธ๋˜์–ด ์žˆ์œผ๋ฏ€๋กœ spring-cloud-starter-openfeign์˜ ๋ฒ„์ „์€ ์ ์ ˆํžˆ ๊ด€๋ฆฌ๋ฉ๋‹ˆ๋‹ค.

๋‹ค๋งŒ resilience4j-spring-boot3์€ resilience4j-bom์ด ํ”„๋กœ์ ํŠธ์— ์„ ์–ธ๋˜์–ด ์žˆ์ง€ ์•Š์•„ ๋ช…์‹œ์ ์ธ ๋ฒ„์ „ ๊ด€๋ฆฌ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ํ˜„์žฌ ๋ฒ„์ „์ด ๋ช…์‹œ๋˜์ง€ ์•Š์€ ์ƒํƒœ์ด๋ฏ€๋กœ, ๋‹ค์Œ ์ค‘ ํ•˜๋‚˜๋ฅผ ๊ฒ€ํ† ํ•˜์„ธ์š”:

  1. resilience4j-bom์„ dependencyManagement์— ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜
  2. resilience4j-spring-boot3์— ๋ช…์‹œ์ ์œผ๋กœ ๋ฒ„์ „์„ ์ง€์ •ํ•˜๊ฑฐ๋‚˜
  3. ํ”„๋กœ์ ํŠธ ์†์„ฑ(project.properties)์„ ํ†ตํ•ด ๋ฒ„์ „์„ ๊ด€๋ฆฌ

์ถ”๊ฐ€๋กœ Feign ๋ฐ resilience4j ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•œ ์ „์šฉ ํ…Œ์ŠคํŠธ ์˜์กด์„ฑ ์ถ”๊ฐ€๋„ ๊ฒ€ํ† ํ•˜์„ธ์š”.


// querydsl
annotationProcessor("com.querydsl:querydsl-apt::jakarta")
annotationProcessor("jakarta.persistence:jakarta.persistence-api")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,16 @@
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.annotation.EnableScheduling;

import java.util.TimeZone;

@ConfigurationPropertiesScan
@EnableFeignClients
@EnableScheduling
@EnableAsync
@SpringBootApplication
public class CommerceApiApplication {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,11 @@ public class LikeFacade {

private final LikeService likeService;

public void createLike(String userId, Long productId) {
public void like(String userId, Long productId) {
likeService.like(userId, productId);
}

public void deleteLike(String userId, Long productId) {
public void unlike(String userId, Long productId) {
likeService.unlike(userId, productId);
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@
*/
public record CreateOrderCommand(
String userId,
List<OrderItemCommand> items
List<OrderItemCommand> items,
OrderPaymentCommand payment
) {}
Original file line number Diff line number Diff line change
Expand Up @@ -3,38 +3,33 @@
import com.loopers.domain.order.Order;
import com.loopers.domain.order.OrderItem;
import com.loopers.domain.order.OrderService;
import com.loopers.domain.order.OrderStatus;
import com.loopers.domain.order.event.OrderEvent;
import com.loopers.domain.order.event.OrderEventPublisher;
import com.loopers.domain.payment.Payment;
import com.loopers.domain.payment.PaymentService;
import com.loopers.domain.point.Point;
import com.loopers.domain.point.PointService;
import com.loopers.domain.product.Product;
import com.loopers.domain.product.ProductService;
import com.loopers.support.error.CoreException;
import com.loopers.support.error.ErrorType;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

/**
* packageName : com.loopers.application.order
* fileName : OrderFacade
* author : byeonsungmun
* date : 2025. 11. 13.
* description :
* ===========================================
* DATE AUTHOR NOTE
* -------------------------------------------
* 2025. 11. 13. byeonsungmun ์ตœ์ดˆ ์ƒ์„ฑ
*/

@Slf4j
@Component
@RequiredArgsConstructor
public class OrderFacade {

private final OrderService orderService;
private final ProductService productService;
private final PointService pointService;
private final PaymentService paymentService;
private final OrderEventPublisher orderEventPublisher;
@Value("${app.callback.base-url}")
private String callbackBaseUrl;


@Transactional
public OrderInfo createOrder(CreateOrderCommand command) {
Expand All @@ -47,13 +42,10 @@ public OrderInfo createOrder(CreateOrderCommand command) {

for (OrderItemCommand itemCommand : command.items()) {

//์ƒํ’ˆ๊ฐ€์ ธ์˜ค๊ณ 
Product product = productService.getProduct(itemCommand.productId());

// ์žฌ๊ณ ๊ฐ์†Œ
product.decreaseStock(itemCommand.quantity());

// OrderItem์ƒ์„ฑ
OrderItem orderItem = OrderItem.create(
product.getId(),
product.getName(),
Expand All @@ -64,7 +56,6 @@ public OrderInfo createOrder(CreateOrderCommand command) {
orderItem.setOrder(order);
}

//์ด ๊ฐ€๊ฒฉ๊ตฌํ•˜๊ณ 
long totalAmount = order.getOrderItems().stream()
.mapToLong(OrderItem::getAmount)
.sum();
Expand All @@ -74,10 +65,39 @@ public OrderInfo createOrder(CreateOrderCommand command) {
Point point = pointService.findPointByUserId(command.userId());
point.use(totalAmount);

//์ €์žฅ
Order saved = orderService.createOrder(order);
saved.updateStatus(OrderStatus.COMPLETE);

OrderPaymentCommand paymentCommand = command.payment();
String cardType = paymentCommand.cardType();
String orderReference = createOrderReference(saved.getId());
String callbackUrl = callbackBaseUrl + "/api/v1/orders/" + orderReference + "/callback";

Payment payment = Payment.pending(
saved.getId(),
command.userId(),
orderReference,
cardType,
paymentCommand.cardNo(),
saved.getTotalAmount()
);

paymentService.save(payment);
orderEventPublisher.publish(
OrderEvent.PaymentRequested.of(
saved.getId(),
command.userId(),
orderReference,
cardType,
paymentCommand.cardNo(),
saved.getTotalAmount(),
callbackUrl
)
);

return OrderInfo.from(saved);
}

private String createOrderReference(Long orderId) {
return "order-" + orderId;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,13 @@
import com.loopers.domain.order.Order;
import com.loopers.domain.order.OrderStatus;

import java.time.LocalDateTime;
import java.util.List;

/**
* packageName : com.loopers.application.order
* fileName : OrderInfo
* author : byeonsungmun
* date : 2025. 11. 14.
* description :
* ===========================================
* DATE AUTHOR NOTE
* -------------------------------------------
* 2025. 11. 14. byeonsungmun ์ตœ์ดˆ ์ƒ์„ฑ
*/
public record OrderInfo(
Long orderId,
String userId,
Long totalAmount,
OrderStatus status,
LocalDateTime createdAt,
List<OrderItemInfo> items
) {
public static OrderInfo from(Order order) {
Expand All @@ -35,7 +22,6 @@ public static OrderInfo from(Order order) {
order.getUserId(),
order.getTotalAmount(),
order.getStatus(),
order.getCreatedAt(),
itemInfos
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.loopers.application.order;

public record OrderPaymentCommand(
String cardType,
String cardNo
) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package com.loopers.application.order;

import com.loopers.domain.order.Order;
import com.loopers.domain.order.OrderItem;
import com.loopers.domain.order.OrderService;
import com.loopers.domain.order.OrderStatus;
import com.loopers.domain.payment.Payment;
import com.loopers.domain.payment.PaymentService;
import com.loopers.domain.point.Point;
import com.loopers.domain.point.PointService;
import com.loopers.domain.product.Product;
import com.loopers.domain.product.ProductService;
import com.loopers.infrastructure.dataplatform.OrderDataPlatformClient;
import com.loopers.infrastructure.payment.PgPaymentClient;
import com.loopers.infrastructure.payment.dto.PgPaymentV1Dto;
import com.loopers.interfaces.api.ApiResponse;
import com.loopers.support.error.CoreException;
import com.loopers.support.error.ErrorType;
import lombok.RequiredArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

@Component
@RequiredArgsConstructor
public class OrderPaymentProcessor {

private static final Logger log = LoggerFactory.getLogger(OrderPaymentProcessor.class);

private final PaymentService paymentService;
private final OrderService orderService;
private final ProductService productService;
private final PointService pointService;
private final PgPaymentClient pgPaymentClient;
private final OrderDataPlatformClient orderDataPlatformClient;

@Transactional
public void handlePaymentCallback(String orderReference, PgPaymentV1Dto.TransactionStatus status, String transactionKey, String reason) {
Payment payment = paymentService.findByOrderReference(orderReference)
.orElseThrow(() -> new CoreException(ErrorType.NOT_FOUND, "๊ฒฐ์ œ ์ •๋ณด๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."));

if (payment.getTransactionKey() != null && payment.getTransactionKey().equals(transactionKey)) {
return;
}

Order order = orderService.findById(payment.getOrderId());
applyPaymentResult(order, payment, status, transactionKey, reason);
}

@Transactional
public void syncPayment(Long orderId) {
Payment payment = paymentService.findByOrderId(orderId)
.orElseThrow(() -> new CoreException(ErrorType.NOT_FOUND, "๊ฒฐ์ œ ์ •๋ณด๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."));
Order order = orderService.findById(orderId);
ApiResponse<PgPaymentV1Dto.OrderResponse> response = pgPaymentClient.getPayments(payment.getUserId(), payment.getOrderReference());
PgPaymentV1Dto.OrderResponse data = response != null ? response.data() : null;

if (data == null || data.transactions() == null || data.transactions().isEmpty()) {
log.warn("๊ฒฐ์ œ ๋‚ด์—ญ์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. orderId={}, orderReference={}", orderId, payment.getOrderReference());
return;
}

PgPaymentV1Dto.TransactionRecord record = data.transactions().get(data.transactions().size() - 1);
applyPaymentResult(order, payment, record.status(), record.transactionKey(), record.reason());
}

@Transactional
public void handlePaymentResult(Long orderId, PgPaymentV1Dto.TransactionStatus status, String transactionKey, String reason) {
Payment payment = paymentService.findByOrderId(orderId)
.orElseThrow(() -> new CoreException(ErrorType.NOT_FOUND, "๊ฒฐ์ œ ์ •๋ณด๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."));
Order order = orderService.findById(orderId);
applyPaymentResult(order, payment, status, transactionKey, reason);
}

private void applyPaymentResult(
Order order,
Payment payment,
PgPaymentV1Dto.TransactionStatus status,
String transactionKey,
String reason
) {
OrderStatus newStatus = OrderPaymentSupport.mapOrderStatus(status);
payment.updateStatus(OrderPaymentSupport.mapPaymentStatus(status), transactionKey, reason);
paymentService.save(payment);

if (newStatus == OrderStatus.COMPLETE) {
order.updateStatus(OrderStatus.COMPLETE);
} else if (newStatus == OrderStatus.FAIL) {
revertOrder(order, payment.getUserId());
} else {
order.updateStatus(OrderStatus.PENDING);
}

orderDataPlatformClient.send(order, payment);
}

private void revertOrder(Order order, String userId) {
for (OrderItem item : order.getOrderItems()) {
Product product = productService.getProduct(item.getProductId());
product.increaseStock(item.getQuantity());
}
Point point = pointService.findPointByUserId(userId);
if (point != null) {
point.refund(order.getTotalAmount());
}
order.updateStatus(OrderStatus.FAIL);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.loopers.application.order;

import com.loopers.domain.order.OrderStatus;
import com.loopers.domain.payment.PaymentStatus;
import com.loopers.infrastructure.payment.dto.PgPaymentV1Dto.Response;
import com.loopers.infrastructure.payment.dto.PgPaymentV1Dto.TransactionStatus;
import com.loopers.interfaces.api.ApiResponse;
import com.loopers.support.error.CoreException;
import com.loopers.support.error.ErrorType;

import static com.loopers.infrastructure.payment.dto.PgPaymentV1Dto.TransactionStatus.SUCCESS;

public final class OrderPaymentSupport {

private OrderPaymentSupport() {
}

public static OrderStatus mapOrderStatus(TransactionStatus status) {
if (status == SUCCESS) {
return OrderStatus.COMPLETE;
}
if (status == TransactionStatus.FAILED) {
return OrderStatus.FAIL;
}
return OrderStatus.PENDING;
}

public static PaymentStatus mapPaymentStatus(TransactionStatus status) {
if (status == SUCCESS) {
return PaymentStatus.SUCCESS;
}
if (status == TransactionStatus.FAILED) {
return PaymentStatus.FAIL;
}
return PaymentStatus.PENDING;
}

public static Response requirePgResponse(ApiResponse<Response> response) {
Response data = response != null ? response.data() : null;
if (data == null) {
throw new CoreException(ErrorType.INTERNAL_ERROR, "PG ์‘๋‹ต์„ ํ™•์ธํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.");
}
return data;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.loopers.application.user;

import com.loopers.domain.point.PointService;
import com.loopers.domain.user.User;
import com.loopers.domain.user.UserService;
import com.loopers.support.error.CoreException;
Expand All @@ -11,9 +12,11 @@
@Component
public class UserFacade {
private final UserService userService;
private final PointService pointService;

public UserInfo register(String userId, String email, String birth, String gender) {
User user = userService.register(userId, email, birth, gender);
pointService.initPoint(user.getUserId());
return UserInfo.from(user);
}
Comment on lines 17 to 21
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

โš ๏ธ Potential issue | ๐Ÿ”ด Critical

๐Ÿงฉ Analysis chain

๐Ÿ Script executed:

cd /tmp && find . -name "UserFacade.java" 2>/dev/null | head -5

Repository: Loopers-dev-lab/loopers-spring-java-template

Length of output: 70


๐Ÿ Script executed:

rg -n --type java -A 3 -B 10 'public UserInfo register' apps/commerce-api/src/main/java/com/loopers/application/user/UserFacade.java

Repository: Loopers-dev-lab/loopers-spring-java-template

Length of output: 657


๐Ÿ Script executed:

rg -n --type java '@Transactional' apps/commerce-api/src/main/java/com/loopers/application/user/UserFacade.java

Repository: Loopers-dev-lab/loopers-spring-java-template

Length of output: 70


ํŠธ๋žœ์žญ์…˜ ๊ฒฝ๊ณ„๋ฅผ ํ™•์ธํ•˜๊ณ  ํ•ด๊ฒฐํ•˜์„ธ์š”.

register() ๋ฉ”์„œ๋“œ์— @Transactional ์–ด๋…ธํ…Œ์ด์…˜์ด ์—†์Šต๋‹ˆ๋‹ค. ํ˜„์žฌ ์ฝ”๋“œ์—์„œ userService.register()๋กœ ์‚ฌ์šฉ์ž๊ฐ€ ์ƒ์„ฑ๋œ ํ›„ pointService.initPoint()์—์„œ ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด, ์‚ฌ์šฉ์ž๋Š” ์ด๋ฏธ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅ๋œ ์ƒํƒœ๋กœ ๋‚จ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์ด๋Š” ๋ฐ์ดํ„ฐ ์ผ๊ด€์„ฑ์„ ๊นจ๋œจ๋ฆฌ๊ณ  ํฌ์ธํŠธ ์ดˆ๊ธฐํ™” ์—†๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ์‹œ์Šคํ…œ์— ๋‚จ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋ฉ”์„œ๋“œ์— @Transactional ์–ด๋…ธํ…Œ์ด์…˜์„ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜ ๋‹ค๋ฅธ ํŠธ๋žœ์žญ์…˜ ๊ด€๋ฆฌ ๋ฉ”์ปค๋‹ˆ์ฆ˜์œผ๋กœ ์‚ฌ์šฉ์ž ๋“ฑ๋ก๊ณผ ํฌ์ธํŠธ ์ดˆ๊ธฐํ™”๋ฅผ ์›์ž์„ฑ ์žˆ๊ฒŒ ์ฒ˜๋ฆฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿค– Prompt for AI Agents
In apps/commerce-api/src/main/java/com/loopers/application/user/UserFacade.java
around lines 17-21, the register method lacks a transactional boundary so if
pointService.initPoint(...) throws after userService.register(...) the user
remains persisted without points; annotate the method (or the containing class)
with @Transactional so both user creation and point initialization run in the
same transaction (or move the combined logic into a single @Transactional
service method), ensure the transaction manager is the same for both services
and use default Propagation.REQUIRED (or explicitly set it) and configure
rollbackFor as needed so exceptions during initPoint cause the whole transaction
to roll back.


Expand Down
Loading