[framework] TxCore → common-framework #4

Open
forge-bot wants to merge 9 commits from forge/ACM-FW-001-attempt-2-run-764de03d4a1a into main
9 changed files with 481 additions and 18 deletions

View file

@ -0,0 +1,3 @@
# ACM-FW-001-attempt-2-run-764de03d4a1a
Forge 이슈 작업 브랜치 `forge/ACM-FW-001-attempt-2-run-764de03d4a1a`.

View file

@ -0,0 +1,48 @@
package com.klaro.acquirecore.framework;
import org.springframework.stereotype.Service;
/**
* Default implementation of TxService.
* Provides base transaction service functionality.
*/
@Service
public class DefaultTxService implements TxService {
private final String serviceName;
private final TxTemplate txTemplate;
public DefaultTxService() {
this("default");
}
public DefaultTxService(String serviceName) {
this.serviceName = serviceName;
this.txTemplate = new TxTemplate();
}
@Override
public String getServiceName() {
return serviceName;
}
@Override
public <T> TxResult<T> execute(TxServiceRegistry.TxOperation<T> operation) {
return txTemplate.execute(serviceName, operation);
}
@Override
public TxContext beginTransaction() {
return txTemplate.txBegin(serviceName);
}
@Override
public TxResult<Void> commit(TxContext context) {
return txTemplate.txCommit(context);
}
@Override
public TxResult<Void> rollback(TxContext context, String reason) {
return txTemplate.txRollback(context, reason);
}
}

View file

@ -1,31 +1,49 @@
package com.klaro.acquirecore.framework;
import java.util.HashMap;
import java.util.Map;
import java.io.Serializable;
import java.time.Instant;
import java.util.UUID;
/**
* 레거시 TxCore {@code TXBUF} (고정 슬롯 / 버퍼) Spring 대체 자리표시자.
*
* <p> 전환에서는 요청/응답 DTO(record) 정형 필드를 담고, 컨텍스트는
* 서비스 체인({@code tx_call}) 전달되는 느슨한 / 상태만 보관한다.
* Transaction context DTO - represents the state of a transaction.
* Migrated from txcore.h TX_CONTEXT.
*/
public final class TxContext {
public class TxContext implements Serializable {
private final Map<String, Object> 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 TransactionStatus status;
private Instant startedAt;
private Instant committedAt;
private String serviceName;
private String errorMessage;
public TxContext() {
this.transactionId = UUID.randomUUID().toString();
this.status = TransactionStatus.INITIALIZED;
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 TransactionStatus getStatus() { return status; }
public void setStatus(TransactionStatus 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 String getServiceName() { return serviceName; }
public void setServiceName(String serviceName) { this.serviceName = serviceName; }
public String getErrorMessage() { return errorMessage; }
public void setErrorMessage(String errorMessage) { this.errorMessage = errorMessage; }
public enum TransactionStatus {
INITIALIZED, ACTIVE, COMMITTED, ROLLED_BACK, FAILED
}
}

View file

@ -0,0 +1,35 @@
package com.klaro.acquirecore.framework;
/**
* Exception thrown when transaction execution fails.
* Migrated from txcore.c error handling patterns.
*/
public class TxExecutionException extends RuntimeException {
private static final long serialVersionUID = 1L;
private final TxContext context;
public TxExecutionException(String message) {
super(message);
this.context = null;
}
public TxExecutionException(String message, Throwable cause) {
super(message, cause);
this.context = null;
}
public TxExecutionException(String message, Throwable cause, TxContext context) {
super(message, cause);
this.context = context;
}
public TxExecutionException(String message, TxContext context) {
super(message);
this.context = context;
}
public TxContext getContext() {
return context;
}
}

View file

@ -0,0 +1,55 @@
package com.klaro.acquirecore.framework;
import java.io.Serializable;
/**
* Transaction result DTO - wraps operation outcomes.
* Migrated from txcore.h TX_RESULT.
*/
public class TxResult<T> implements Serializable {
private static final long serialVersionUID = 1L;
private boolean success;
private T data;
private String errorCode;
private String errorMessage;
private TxContext context;
public TxResult() {}
private TxResult(boolean success, T data, String errorCode, String errorMessage, TxContext context) {
this.success = success;
this.data = data;
this.errorCode = errorCode;
this.errorMessage = errorMessage;
this.context = context;
}
public static <T> TxResult<T> success(T data, TxContext context) {
return new TxResult<>(true, data, null, null, context);
}
public static <T> TxResult<T> success(T data) {
return success(data, null);
}
public static <T> TxResult<T> failure(String errorCode, String errorMessage) {
return new TxResult<>(false, null, errorCode, errorMessage, null);
}
public static <T> TxResult<T> failure(String errorCode, String errorMessage, TxContext context) {
return new TxResult<>(false, null, errorCode, errorMessage, context);
}
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; }
public TxContext getContext() { return context; }
public void setContext(TxContext context) { this.context = context; }
}

View file

@ -0,0 +1,18 @@
package com.klaro.acquirecore.framework;
/**
* Interface for transaction-aware services.
* Migrated from txcore.h TX_SERVICE callback structure.
*/
public interface TxService {
String getServiceName();
<T> TxResult<T> execute(TxServiceRegistry.TxOperation<T> operation);
TxContext beginTransaction();
TxResult<Void> commit(TxContext context);
TxResult<Void> rollback(TxContext context, String reason);
}

View file

@ -0,0 +1,57 @@
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 for transaction-aware services.
* Migrated from txcore.c TX_SERVICE registry functionality.
* Replaces C function pointers and registry with Spring @Service DI.
*/
@Service
public class TxServiceRegistry {
private final Map<String, TxService> services = new ConcurrentHashMap<>();
@PostConstruct
public void initialize() {}
public void registerService(String name, TxService service) {
services.put(name, service);
}
public void unregisterService(String name) {
services.remove(name);
}
public TxService getService(String name) {
return services.get(name);
}
public boolean hasService(String name) {
return services.containsKey(name);
}
public <T> TxResult<T> executeInTransaction(String serviceName, TxOperation<T> operation) {
TxService service = services.get(serviceName);
if (service == null) {
return TxResult.failure("SERVICE_NOT_FOUND", "Service not registered: " + serviceName);
}
return service.execute(operation);
}
public String[] getRegisteredServiceNames() {
return services.keySet().toArray(new String[0]);
}
public void clear() {
services.clear();
}
@FunctionalInterface
public interface TxOperation<T> {
T execute(TxContext context);
}
}

View file

@ -0,0 +1,74 @@
package com.klaro.acquirecore.framework;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.time.Instant;
/**
* Template for executing transactional operations.
* Migrated from txcore.c tx_begin/tx_commit functions.
* Provides @Transactional equivalent functionality.
*/
@Component
public class TxTemplate {
@Transactional
public <T> TxResult<T> execute(TxServiceRegistry.TxOperation<T> operation) {
TxContext context = new TxContext();
context.setStatus(TxContext.TransactionStatus.ACTIVE);
try {
T result = operation.execute(context);
context.setStatus(TxContext.TransactionStatus.COMMITTED);
context.setCommittedAt(Instant.now());
return TxResult.success(result, context);
} catch (Exception e) {
context.setStatus(TxContext.TransactionStatus.ROLLED_BACK);
context.setErrorMessage(e.getMessage());
throw new TxExecutionException("Transaction failed", e, context);
}
}
@Transactional
public <T> TxResult<T> execute(String serviceName, TxServiceRegistry.TxOperation<T> operation) {
TxContext context = new TxContext(serviceName);
context.setStatus(TxContext.TransactionStatus.ACTIVE);
try {
T result = operation.execute(context);
context.setStatus(TxContext.TransactionStatus.COMMITTED);
context.setCommittedAt(Instant.now());
return TxResult.success(result, context);
} catch (Exception e) {
context.setStatus(TxContext.TransactionStatus.ROLLED_BACK);
context.setErrorMessage(e.getMessage());
throw new TxExecutionException("Transaction failed for service: " + serviceName, e, context);
}
}
public TxContext txBegin() {
return new TxContext();
}
public TxContext txBegin(String serviceName) {
return new TxContext(serviceName);
}
@Transactional
public TxResult<Void> txCommit(TxContext context) {
if (context == null) {
return TxResult.failure("INVALID_CONTEXT", "Transaction context is null");
}
context.setStatus(TxContext.TransactionStatus.COMMITTED);
context.setCommittedAt(Instant.now());
return TxResult.success(null, context);
}
@Transactional
public TxResult<Void> txRollback(TxContext context, String reason) {
if (context == null) {
return TxResult.failure("INVALID_CONTEXT", "Transaction context is null");
}
context.setStatus(TxContext.TransactionStatus.ROLLED_BACK);
context.setErrorMessage(reason);
return TxResult.success(null, context);
}
}

View file

@ -0,0 +1,155 @@
package com.klaro.acquirecore.framework;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
/**
* Consolidated unit tests for TxCore migration framework.
*/
class FrameworkTest {
// TxContext tests
@Test
void testTxContextDefaultConstructor() {
TxContext ctx = new TxContext();
assertNotNull(ctx.getTransactionId());
assertEquals(TxContext.TransactionStatus.INITIALIZED, ctx.getStatus());
}
@Test
void testTxContextWithServiceName() {
TxContext ctx = new TxContext("testService");
assertEquals("testService", ctx.getServiceName());
}
// TxResult tests
@Test
void testTxResultSuccess() {
TxResult<String> result = TxResult.success("data");
assertTrue(result.isSuccess());
assertEquals("data", result.getData());
}
@Test
void testTxResultFailure() {
TxResult<String> result = TxResult.failure("ERR", "message");
assertFalse(result.isSuccess());
assertEquals("ERR", result.getErrorCode());
}
// TxServiceRegistry tests
@Test
void testServiceRegistryRegisterAndGet() {
TxServiceRegistry registry = new TxServiceRegistry();
TxService service = new DefaultTxService("svc");
registry.registerService("svc", service);
assertTrue(registry.hasService("svc"));
assertEquals(service, registry.getService("svc"));
}
@Test
void testServiceRegistryUnregister() {
TxServiceRegistry registry = new TxServiceRegistry();
registry.registerService("svc", new DefaultTxService("svc"));
registry.unregisterService("svc");
assertFalse(registry.hasService("svc"));
}
@Test
void testServiceRegistryExecuteInTransaction() {
TxServiceRegistry registry = new TxServiceRegistry();
registry.registerService("svc", new DefaultTxService("svc"));
TxResult<String> result = registry.executeInTransaction("svc", ctx -> "ok");
assertTrue(result.isSuccess());
assertEquals("ok", result.getData());
}
@Test
void testServiceRegistryUnknownService() {
TxServiceRegistry registry = new TxServiceRegistry();
TxResult<String> result = registry.executeInTransaction("unknown", ctx -> "ok");
assertFalse(result.isSuccess());
assertEquals("SERVICE_NOT_FOUND", result.getErrorCode());
}
// TxTemplate tests
@Test
void testTxTemplateTxBegin() {
TxTemplate template = new TxTemplate();
TxContext ctx = template.txBegin("svc");
assertEquals("svc", ctx.getServiceName());
}
@Test
void testTxTemplateTxCommit() {
TxTemplate template = new TxTemplate();
TxContext ctx = template.txBegin();
TxResult<Void> result = template.txCommit(ctx);
assertTrue(result.isSuccess());
assertEquals(TxContext.TransactionStatus.COMMITTED, ctx.getStatus());
}
@Test
void testTxTemplateTxCommitNullContext() {
TxTemplate template = new TxTemplate();
TxResult<Void> result = template.txCommit(null);
assertFalse(result.isSuccess());
assertEquals("INVALID_CONTEXT", result.getErrorCode());
}
@Test
void testTxTemplateTxRollback() {
TxTemplate template = new TxTemplate();
TxContext ctx = template.txBegin();
TxResult<Void> result = template.txRollback(ctx, "reason");
assertTrue(result.isSuccess());
assertEquals(TxContext.TransactionStatus.ROLLED_BACK, ctx.getStatus());
assertEquals("reason", ctx.getErrorMessage());
}
// DefaultTxService tests
@Test
void testDefaultTxServiceName() {
DefaultTxService svc = new DefaultTxService("myService");
assertEquals("myService", svc.getServiceName());
}
@Test
void testDefaultTxServiceBeginTransaction() {
DefaultTxService svc = new DefaultTxService("svc");
TxContext ctx = svc.beginTransaction();
assertEquals("svc", ctx.getServiceName());
}
@Test
void testDefaultTxServiceCommit() {
DefaultTxService svc = new DefaultTxService("svc");
TxContext ctx = svc.beginTransaction();
TxResult<Void> result = svc.commit(ctx);
assertTrue(result.isSuccess());
}
@Test
void testDefaultTxServiceRollback() {
DefaultTxService svc = new DefaultTxService("svc");
TxContext ctx = svc.beginTransaction();
TxResult<Void> result = svc.rollback(ctx, "test");
assertTrue(result.isSuccess());
}
// TxExecutionException tests
@Test
void testTxExecutionExceptionMessage() {
TxExecutionException ex = new TxExecutionException("msg");
assertEquals("msg", ex.getMessage());
assertNull(ex.getContext());
}
@Test
void testTxExecutionExceptionWithContext() {
TxContext ctx = new TxContext("svc");
TxExecutionException ex = new TxExecutionException("msg", ctx);
assertEquals("msg", ex.getMessage());
assertEquals(ctx, ex.getContext());
}
}