Compare commits

...
This repository has been archived on 2026-07-19. You can view files and clone it, but you cannot make any changes to it's state, such as pushing and creating new issues, pull requests or comments.

9 commits

9 changed files with 452 additions and 17 deletions

View file

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

View file

@ -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 대체 자리표시자.
*
* <p> 전환에서는 요청/응답 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<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 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 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<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;
}
}

View file

@ -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());
}
}

View file

@ -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<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;
}
}

View file

@ -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);
}

View file

@ -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<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();
}
}

View file

@ -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<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());
}
}

View file

@ -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
}

View file

@ -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 <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."
);
}
}