Compare commits
No commits in common. "forge/ACM-FW-001-attempt-3-run-cf9312821cb6" and "main" have entirely different histories.
forge/ACM-
...
main
9 changed files with 17 additions and 452 deletions
|
|
@ -1,3 +0,0 @@
|
|||
# ACM-FW-001-attempt-3-run-cf9312821cb6
|
||||
|
||||
Forge 이슈 작업 브랜치 `forge/ACM-FW-001-attempt-3-run-cf9312821cb6`.
|
||||
|
|
@ -1,90 +1,31 @@
|
|||
package com.klaro.acquirecore.framework;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.time.Instant;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Transaction context DTO carrying runtime state across service calls.
|
||||
* Mirrors the C tx_context struct fields.
|
||||
* 레거시 TxCore {@code TXBUF} (고정 슬롯 키/값 버퍼) 의 Spring 대체 자리표시자.
|
||||
*
|
||||
* <p>실 전환에서는 요청/응답 DTO(record) 가 정형 필드를 담고, 이 컨텍스트는
|
||||
* 서비스 체인({@code tx_call}) 간 전달되는 느슨한 키/값 상태만 보관한다.
|
||||
*/
|
||||
public class TxContext implements Serializable {
|
||||
public final class TxContext {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
private final Map<String, Object> slots = new HashMap<>();
|
||||
|
||||
private String transactionId;
|
||||
private String serviceName;
|
||||
private TxStatus status;
|
||||
private Instant startedAt;
|
||||
private Instant committedAt;
|
||||
private Map<String, Object> metadata;
|
||||
|
||||
public TxContext() {
|
||||
this.transactionId = UUID.randomUUID().toString();
|
||||
this.status = TxStatus.INITIAL;
|
||||
this.startedAt = Instant.now();
|
||||
/** TXBUF 필드 설정 (레거시 {@code Fchg} 상당). */
|
||||
public TxContext put(String key, Object value) {
|
||||
slots.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public TxContext(String serviceName) {
|
||||
this();
|
||||
this.serviceName = serviceName;
|
||||
/** TXBUF 필드 조회 (레거시 {@code Fget} 상당). */
|
||||
public Object get(String key) {
|
||||
return slots.get(key);
|
||||
}
|
||||
|
||||
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<String, Object> getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
public void setMetadata(Map<String, Object> metadata) {
|
||||
this.metadata = metadata;
|
||||
}
|
||||
|
||||
public void markCommitted() {
|
||||
this.status = TxStatus.COMMITTED;
|
||||
this.committedAt = Instant.now();
|
||||
}
|
||||
|
||||
public void markRolledBack() {
|
||||
this.status = TxStatus.ROLLED_BACK;
|
||||
/** 적재된 슬롯 개수. */
|
||||
public int size() {
|
||||
return slots.size();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,50 +0,0 @@
|
|||
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());
|
||||
}
|
||||
}
|
||||
|
|
@ -1,67 +0,0 @@
|
|||
package com.klaro.acquirecore.framework;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* Generic result wrapper for transactional operations.
|
||||
* Mirrors C tx_result struct semantics.
|
||||
*/
|
||||
public class TxResult<T> implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private boolean success;
|
||||
private T data;
|
||||
private String errorCode;
|
||||
private String errorMessage;
|
||||
|
||||
public TxResult() {
|
||||
}
|
||||
|
||||
public static <T> TxResult<T> ok(T data) {
|
||||
TxResult<T> result = new TxResult<>();
|
||||
result.success = true;
|
||||
result.data = data;
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <T> TxResult<T> error(String errorCode, String errorMessage) {
|
||||
TxResult<T> 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
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);
|
||||
}
|
||||
|
|
@ -1,77 +0,0 @@
|
|||
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<String, TxService> 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 <T> TxResult<T> 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,75 +0,0 @@
|
|||
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<String> 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());
|
||||
}
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
package com.klaro.acquirecore.framework;
|
||||
|
||||
/**
|
||||
* Transaction lifecycle states mirroring C tx_status enum.
|
||||
*/
|
||||
public enum TxStatus {
|
||||
INITIAL,
|
||||
ACTIVE,
|
||||
COMMITTED,
|
||||
ROLLED_BACK,
|
||||
FAILED
|
||||
}
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
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 <T> the result type
|
||||
* @return TxResult containing the outcome
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public <T> TxResult<T> 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 <T> the result type
|
||||
* @return TxResult containing the outcome
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public <T> TxResult<T> 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 <T> the result type
|
||||
* @return TxResult containing the outcome
|
||||
*/
|
||||
@Transactional(readOnly = true)
|
||||
public <T> TxResult<T> 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."
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in a new issue