From 3e14ccb65c310f7480b7caf4e06086b3ebc0261f Mon Sep 17 00:00:00 2001 From: forge-bot Date: Sat, 18 Jul 2026 11:10:56 +0000 Subject: [PATCH 1/9] forge: open work branch for ACM-FW-001-attempt-3-run-cf9312821cb6 --- .forge/ACM-FW-001-attempt-3-run-cf9312821cb6.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .forge/ACM-FW-001-attempt-3-run-cf9312821cb6.md diff --git a/.forge/ACM-FW-001-attempt-3-run-cf9312821cb6.md b/.forge/ACM-FW-001-attempt-3-run-cf9312821cb6.md new file mode 100644 index 0000000..4a5f0c4 --- /dev/null +++ b/.forge/ACM-FW-001-attempt-3-run-cf9312821cb6.md @@ -0,0 +1,3 @@ +# ACM-FW-001-attempt-3-run-cf9312821cb6 + +Forge 이슈 작업 브랜치 `forge/ACM-FW-001-attempt-3-run-cf9312821cb6`. From 7b4c48295db80c18d64cc6e87e29b723cb7cb921 Mon Sep 17 00:00:00 2001 From: forge-bot Date: Sat, 18 Jul 2026 11:11:51 +0000 Subject: [PATCH 2/9] =?UTF-8?q?[framework]=20TxCore=20=E2=86=92=20common-f?= =?UTF-8?q?ramework=20(ACM-FW-001)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../acquirecore/framework/TxContext.java | 93 +++++++++++++++---- 1 file changed, 76 insertions(+), 17 deletions(-) diff --git a/boot/common-framework/src/main/java/com/klaro/acquirecore/framework/TxContext.java b/boot/common-framework/src/main/java/com/klaro/acquirecore/framework/TxContext.java index 1a56634..35e5443 100644 --- a/boot/common-framework/src/main/java/com/klaro/acquirecore/framework/TxContext.java +++ b/boot/common-framework/src/main/java/com/klaro/acquirecore/framework/TxContext.java @@ -1,31 +1,90 @@ package com.klaro.acquirecore.framework; -import java.util.HashMap; +import java.io.Serializable; +import java.time.Instant; import java.util.Map; +import java.util.UUID; /** - * 레거시 TxCore {@code TXBUF} (고정 슬롯 키/값 버퍼) 의 Spring 대체 자리표시자. - * - *

실 전환에서는 요청/응답 DTO(record) 가 정형 필드를 담고, 이 컨텍스트는 - * 서비스 체인({@code tx_call}) 간 전달되는 느슨한 키/값 상태만 보관한다. + * Transaction context DTO carrying runtime state across service calls. + * Mirrors the C tx_context struct fields. */ -public final class TxContext { +public class TxContext implements Serializable { - private final Map slots = new HashMap<>(); + private static final long serialVersionUID = 1L; - /** TXBUF 필드 설정 (레거시 {@code Fchg} 상당). */ - public TxContext put(String key, Object value) { - slots.put(key, value); - return this; + private String transactionId; + private String serviceName; + private TxStatus status; + private Instant startedAt; + private Instant committedAt; + private Map metadata; + + public TxContext() { + this.transactionId = UUID.randomUUID().toString(); + this.status = TxStatus.INITIAL; + this.startedAt = Instant.now(); } - /** TXBUF 필드 조회 (레거시 {@code Fget} 상당). */ - public Object get(String key) { - return slots.get(key); + public TxContext(String serviceName) { + this(); + this.serviceName = serviceName; } - /** 적재된 슬롯 개수. */ - public int size() { - return slots.size(); + public String getTransactionId() { + return transactionId; + } + + public void setTransactionId(String transactionId) { + this.transactionId = transactionId; + } + + public String getServiceName() { + return serviceName; + } + + public void setServiceName(String serviceName) { + this.serviceName = serviceName; + } + + public TxStatus getStatus() { + return status; + } + + public void setStatus(TxStatus status) { + this.status = status; + } + + public Instant getStartedAt() { + return startedAt; + } + + public void setStartedAt(Instant startedAt) { + this.startedAt = startedAt; + } + + public Instant getCommittedAt() { + return committedAt; + } + + public void setCommittedAt(Instant committedAt) { + this.committedAt = committedAt; + } + + public Map getMetadata() { + return metadata; + } + + public void setMetadata(Map metadata) { + this.metadata = metadata; + } + + public void markCommitted() { + this.status = TxStatus.COMMITTED; + this.committedAt = Instant.now(); + } + + public void markRolledBack() { + this.status = TxStatus.ROLLED_BACK; } } From 78d01f24298ab60756766946c5eef0a935cf28c4 Mon Sep 17 00:00:00 2001 From: forge-bot Date: Sat, 18 Jul 2026 11:11:52 +0000 Subject: [PATCH 3/9] =?UTF-8?q?[framework]=20TxCore=20=E2=86=92=20common-f?= =?UTF-8?q?ramework=20(ACM-FW-001)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/klaro/acquirecore/framework/TxStatus.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 boot/common-framework/src/main/java/com/klaro/acquirecore/framework/TxStatus.java diff --git a/boot/common-framework/src/main/java/com/klaro/acquirecore/framework/TxStatus.java b/boot/common-framework/src/main/java/com/klaro/acquirecore/framework/TxStatus.java new file mode 100644 index 0000000..91cf88a --- /dev/null +++ b/boot/common-framework/src/main/java/com/klaro/acquirecore/framework/TxStatus.java @@ -0,0 +1,12 @@ +package com.klaro.acquirecore.framework; + +/** + * Transaction lifecycle states mirroring C tx_status enum. + */ +public enum TxStatus { + INITIAL, + ACTIVE, + COMMITTED, + ROLLED_BACK, + FAILED +} From 242d9d13de41e293929911e276a9435e40ffbc42 Mon Sep 17 00:00:00 2001 From: forge-bot Date: Sat, 18 Jul 2026 11:11:54 +0000 Subject: [PATCH 4/9] =?UTF-8?q?[framework]=20TxCore=20=E2=86=92=20common-f?= =?UTF-8?q?ramework=20(ACM-FW-001)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../klaro/acquirecore/framework/TxResult.java | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 boot/common-framework/src/main/java/com/klaro/acquirecore/framework/TxResult.java diff --git a/boot/common-framework/src/main/java/com/klaro/acquirecore/framework/TxResult.java b/boot/common-framework/src/main/java/com/klaro/acquirecore/framework/TxResult.java new file mode 100644 index 0000000..b8a0099 --- /dev/null +++ b/boot/common-framework/src/main/java/com/klaro/acquirecore/framework/TxResult.java @@ -0,0 +1,67 @@ +package com.klaro.acquirecore.framework; + +import java.io.Serializable; + +/** + * Generic result wrapper for transactional operations. + * Mirrors C tx_result struct semantics. + */ +public class TxResult implements Serializable { + + private static final long serialVersionUID = 1L; + + private boolean success; + private T data; + private String errorCode; + private String errorMessage; + + public TxResult() { + } + + public static TxResult ok(T data) { + TxResult result = new TxResult<>(); + result.success = true; + result.data = data; + return result; + } + + public static TxResult error(String errorCode, String errorMessage) { + TxResult result = new TxResult<>(); + result.success = false; + result.errorCode = errorCode; + result.errorMessage = errorMessage; + return result; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public T getData() { + return data; + } + + public void setData(T data) { + this.data = data; + } + + public String getErrorCode() { + return errorCode; + } + + public void setErrorCode(String errorCode) { + this.errorCode = errorCode; + } + + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } +} From dedbfdc4a62e50ac9d36b4dd2fea351d1c7f8f85 Mon Sep 17 00:00:00 2001 From: forge-bot Date: Sat, 18 Jul 2026 11:11:55 +0000 Subject: [PATCH 5/9] =?UTF-8?q?[framework]=20TxCore=20=E2=86=92=20common-f?= =?UTF-8?q?ramework=20(ACM-FW-001)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../klaro/acquirecore/framework/TxService.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 boot/common-framework/src/main/java/com/klaro/acquirecore/framework/TxService.java diff --git a/boot/common-framework/src/main/java/com/klaro/acquirecore/framework/TxService.java b/boot/common-framework/src/main/java/com/klaro/acquirecore/framework/TxService.java new file mode 100644 index 0000000..27cfd16 --- /dev/null +++ b/boot/common-framework/src/main/java/com/klaro/acquirecore/framework/TxService.java @@ -0,0 +1,18 @@ +package com.klaro.acquirecore.framework; + +/** + * Functional interface for transactional service operations. + * Represents a single unit of work within a transaction context. + */ +@FunctionalInterface +public interface TxService { + + /** + * Execute the service operation within the given transaction context. + * + * @param ctx the transaction context + * @param input the input data for the operation + * @return the result of the operation + */ + Object execute(TxContext ctx, Object input); +} From 78505a81ea4e7103f5c8c33717fbd824874ceb17 Mon Sep 17 00:00:00 2001 From: forge-bot Date: Sat, 18 Jul 2026 11:11:56 +0000 Subject: [PATCH 6/9] =?UTF-8?q?[framework]=20TxCore=20=E2=86=92=20common-f?= =?UTF-8?q?ramework=20(ACM-FW-001)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../framework/TxServiceRegistry.java | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 boot/common-framework/src/main/java/com/klaro/acquirecore/framework/TxServiceRegistry.java diff --git a/boot/common-framework/src/main/java/com/klaro/acquirecore/framework/TxServiceRegistry.java b/boot/common-framework/src/main/java/com/klaro/acquirecore/framework/TxServiceRegistry.java new file mode 100644 index 0000000..7c43a3f --- /dev/null +++ b/boot/common-framework/src/main/java/com/klaro/acquirecore/framework/TxServiceRegistry.java @@ -0,0 +1,77 @@ +package com.klaro.acquirecore.framework; + +import org.springframework.stereotype.Service; + +import jakarta.annotation.PostConstruct; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Service registry replacing C TX_SERVICE/registry pattern. + * Acts as a central dispatcher for transactional services. + * Annotated with @Service for Spring component scanning. + */ +@Service +public class TxServiceRegistry { + + private final Map services = new ConcurrentHashMap<>(); + + @PostConstruct + public void init() { + // Registry initialized and ready for service lookups + } + + /** + * Register a transactional service by name. + * Mirrors C tx_registry_add(). + */ + public void registerService(String name, TxService service) { + services.put(name, service); + } + + /** + * Unregister a service by name. + */ + public void unregisterService(String name) { + services.remove(name); + } + + /** + * Lookup a registered service by name. + * Mirrors C tx_registry_lookup(). + */ + public TxService lookupService(String name) { + return services.get(name); + } + + /** + * Execute a transactional operation on a named service. + * Mirrors C tx_service_dispatch(). + */ + public TxResult dispatch(String serviceName, TxContext ctx, Object input) { + TxService service = lookupService(serviceName); + if (service == null) { + return TxResult.error("SERVICE_NOT_FOUND", "Service not registered: " + serviceName); + } + try { + Object result = service.execute(ctx, input); + return TxResult.ok(result); + } catch (Exception e) { + return TxResult.error("EXECUTION_FAILED", e.getMessage()); + } + } + + /** + * Check if a service is registered. + */ + public boolean isRegistered(String name) { + return services.containsKey(name); + } + + /** + * Get count of registered services. + */ + public int getServiceCount() { + return services.size(); + } +} From 3491780f0a21b851e1df5d401e690955912ff3cd Mon Sep 17 00:00:00 2001 From: forge-bot Date: Sat, 18 Jul 2026 11:11:57 +0000 Subject: [PATCH 7/9] =?UTF-8?q?[framework]=20TxCore=20=E2=86=92=20common-f?= =?UTF-8?q?ramework=20(ACM-FW-001)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../acquirecore/framework/TxTemplate.java | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 boot/common-framework/src/main/java/com/klaro/acquirecore/framework/TxTemplate.java diff --git a/boot/common-framework/src/main/java/com/klaro/acquirecore/framework/TxTemplate.java b/boot/common-framework/src/main/java/com/klaro/acquirecore/framework/TxTemplate.java new file mode 100644 index 0000000..a831ed2 --- /dev/null +++ b/boot/common-framework/src/main/java/com/klaro/acquirecore/framework/TxTemplate.java @@ -0,0 +1,74 @@ +package com.klaro.acquirecore.framework; + +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +/** + * Transaction template replacing C tx_begin/tx_commit pattern. + * Provides @Transactional-backed execution with context management. + * Mirrors C tx_begin(), tx_commit(), tx_rollback() lifecycle. + */ +@Component +public class TxTemplate { + + /** + * Execute a transactional operation with automatic commit on success, + * rollback on exception. Mirrors C tx_begin + tx_commit pattern. + * + * @param serviceName the name of the registered service + * @param input the input data + * @param the result type + * @return TxResult containing the outcome + */ + @Transactional(rollbackFor = Exception.class) + public TxResult execute(String serviceName, Object input) { + return executeWithContext(serviceName, new TxContext(serviceName), input); + } + + /** + * Execute with an existing transaction context. + * + * @param serviceName the name of the registered service + * @param ctx the existing transaction context + * @param input the input data + * @param the result type + * @return TxResult containing the outcome + */ + @Transactional(rollbackFor = Exception.class) + public TxResult executeWithContext(String serviceName, TxContext ctx, Object input) { + ctx.setStatus(TxStatus.ACTIVE); + try { + Object result = doExecute(serviceName, ctx, input); + ctx.markCommitted(); + return TxResult.ok(result); + } catch (Exception e) { + ctx.markRolledBack(); + return TxResult.error("TX_FAILED", e.getMessage()); + } + } + + /** + * Execute without transaction boundary (read-only operations). + * + * @param serviceName the name of the registered service + * @param input the input data + * @param the result type + * @return TxResult containing the outcome + */ + @Transactional(readOnly = true) + public TxResult query(String serviceName, Object input) { + try { + TxContext ctx = new TxContext(serviceName); + Object result = doExecute(serviceName, ctx, input); + return TxResult.ok(result); + } catch (Exception e) { + return TxResult.error("QUERY_FAILED", e.getMessage()); + } + } + + private Object doExecute(String serviceName, TxContext ctx, Object input) { + throw new UnsupportedOperationException( + "Direct execution not supported. Use TxServiceRegistry.dispatch() or inject TxService directly." + ); + } +} From bcc27280dcba7852d7002f87e3416371d2207680 Mon Sep 17 00:00:00 2001 From: forge-bot Date: Sat, 18 Jul 2026 11:11:58 +0000 Subject: [PATCH 8/9] =?UTF-8?q?[framework]=20TxCore=20=E2=86=92=20common-f?= =?UTF-8?q?ramework=20(ACM-FW-001)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../framework/TxServiceRegistryTest.java | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 boot/common-framework/src/main/java/com/klaro/acquirecore/framework/TxServiceRegistryTest.java diff --git a/boot/common-framework/src/main/java/com/klaro/acquirecore/framework/TxServiceRegistryTest.java b/boot/common-framework/src/main/java/com/klaro/acquirecore/framework/TxServiceRegistryTest.java new file mode 100644 index 0000000..28b362a --- /dev/null +++ b/boot/common-framework/src/main/java/com/klaro/acquirecore/framework/TxServiceRegistryTest.java @@ -0,0 +1,75 @@ +package com.klaro.acquirecore.framework; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class TxServiceRegistryTest { + + private TxServiceRegistry registry; + + @BeforeEach + void setUp() { + registry = new TxServiceRegistry(); + registry.init(); + } + + @Test + void registerAndLookupService() { + TxService service = (ctx, input) -> "result"; + registry.registerService("testService", service); + assertTrue(registry.isRegistered("testService")); + assertEquals(service, registry.lookupService("testService")); + } + + @Test + void lookupUnregisteredServiceReturnsNull() { + assertNull(registry.lookupService("nonExistent")); + assertFalse(registry.isRegistered("nonExistent")); + } + + @Test + void unregisterService() { + TxService service = (ctx, input) -> "result"; + registry.registerService("toRemove", service); + registry.unregisterService("toRemove"); + assertFalse(registry.isRegistered("toRemove")); + } + + @Test + void dispatchExecutesRegisteredService() { + TxService service = (ctx, input) -> "processed: " + input; + registry.registerService("echo", service); + TxContext ctx = new TxContext("echo"); + TxResult result = registry.dispatch("echo", ctx, "hello"); + assertTrue(result.isSuccess()); + assertEquals("processed: hello", result.getData()); + } + + @Test + void dispatchFailsForUnregisteredService() { + TxContext ctx = new TxContext("unknown"); + TxResult result = registry.dispatch("unknown", ctx, null); + assertFalse(result.isSuccess()); + assertEquals("SERVICE_NOT_FOUND", result.getErrorCode()); + } + + @Test + void dispatchHandlesServiceException() { + TxService failingService = (ctx, input) -> { throw new RuntimeException("Service error"); }; + registry.registerService("failing", failingService); + TxContext ctx = new TxContext("failing"); + TxResult result = registry.dispatch("failing", ctx, null); + assertFalse(result.isSuccess()); + assertEquals("EXECUTION_FAILED", result.getErrorCode()); + } + + @Test + void getServiceCount() { + assertEquals(0, registry.getServiceCount()); + registry.registerService("s1", (ctx, input) -> null); + registry.registerService("s2", (ctx, input) -> null); + assertEquals(2, registry.getServiceCount()); + } +} From a40de37bcd86a785c2af0a8ac26981f7cf0d2070 Mon Sep 17 00:00:00 2001 From: forge-bot Date: Sat, 18 Jul 2026 11:12:00 +0000 Subject: [PATCH 9/9] =?UTF-8?q?[framework]=20TxCore=20=E2=86=92=20common-f?= =?UTF-8?q?ramework=20(ACM-FW-001)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../acquirecore/framework/TxContextTest.java | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 boot/common-framework/src/main/java/com/klaro/acquirecore/framework/TxContextTest.java diff --git a/boot/common-framework/src/main/java/com/klaro/acquirecore/framework/TxContextTest.java b/boot/common-framework/src/main/java/com/klaro/acquirecore/framework/TxContextTest.java new file mode 100644 index 0000000..429b994 --- /dev/null +++ b/boot/common-framework/src/main/java/com/klaro/acquirecore/framework/TxContextTest.java @@ -0,0 +1,50 @@ +package com.klaro.acquirecore.framework; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class TxContextTest { + + @Test + void defaultConstructorSetsInitialState() { + TxContext ctx = new TxContext(); + assertNotNull(ctx.getTransactionId()); + assertEquals(TxStatus.INITIAL, ctx.getStatus()); + assertNotNull(ctx.getStartedAt()); + } + + @Test + void constructorWithServiceName() { + TxContext ctx = new TxContext("myService"); + assertEquals("myService", ctx.getServiceName()); + assertEquals(TxStatus.INITIAL, ctx.getStatus()); + } + + @Test + void markCommittedUpdatesStatusAndTimestamp() { + TxContext ctx = new TxContext(); + assertNull(ctx.getCommittedAt()); + ctx.markCommitted(); + assertEquals(TxStatus.COMMITTED, ctx.getStatus()); + assertNotNull(ctx.getCommittedAt()); + } + + @Test + void markRolledBackUpdatesStatus() { + TxContext ctx = new TxContext(); + ctx.markRolledBack(); + assertEquals(TxStatus.ROLLED_BACK, ctx.getStatus()); + } + + @Test + void settersAndGetters() { + TxContext ctx = new TxContext(); + ctx.setTransactionId("tx-123"); + ctx.setServiceName("testService"); + ctx.setStatus(TxStatus.ACTIVE); + assertEquals("tx-123", ctx.getTransactionId()); + assertEquals("testService", ctx.getServiceName()); + assertEquals(TxStatus.ACTIVE, ctx.getStatus()); + } +}