

# DynamoDB Transactions 예
<a name="transaction-example"></a>

Amazon DynamoDB Transactions가 유용할 수 있는 상황의 예로 온라인 마켓플레이스용 이 샘플 Java 애플리케이션을 고려하세요.

이 애플리케이션의 백엔드에는 다음 세 개의 DynamoDB 테이블이 있습니다.
+ `Customers` - 이 테이블은 마켓플레이스 고객에 대한 세부 정보를 저장합니다. 기본 키는 `CustomerId` 고유 식별자입니다.
+ `ProductCatalog` - 이 테이블은 마켓플레이스에서 판매할 제품에 대한 가격, 가용성 등의 세부 정보를 저장합니다. 기본 키는 `ProductId` 고유 식별자입니다.
+ `Orders` - 이 테이블은 마켓플레이스에서 수행된 주문에 대한 세부 정보를 저장합니다. 기본 키는 `OrderId` 고유 식별자입니다.

## 주문하기
<a name="transaction-example-write-order"></a>

다음 코드 조각은 DynamoDB 트랜잭션을 사용하여 주문을 생성 및 처리하는 데 필요한 여러 단계를 조정하는 방법을 보여 줍니다. 하나의 양자택일 방식 작업을 사용하면 트랜잭션의 일부라도 실패할 경우 트랜잭션에서 아무 작업도 실행되지 않고 아무 내용도 변경되지 않습니다.

이 예제에서는 `customerId`이 `09e8e9c8-ec48`인 고객으로부터 주문을 설정합니다. 그리고 다음과 같이 간단한 주문 처리 워크플로를 사용하여 단일 트랜잭션으로 이를 실행합니다.

1. 고객 ID가 유효한지 확인합니다.

1. 제품이 `IN_STOCK` 상태인지 확인하고 제품 상태를 `SOLD`로 업데이트합니다.

1. 주문이 아직 없는지 확인하고 주문을 생성합니다.

### 고객 검증
<a name="transaction-example-order-part-a"></a>

먼저 `customerId`가 `09e8e9c8-ec48`인 고객이 고객 테이블에 있는지 확인하는 작업을 정의합니다.

```
final String CUSTOMER_TABLE_NAME = "Customers";
final String CUSTOMER_PARTITION_KEY = "CustomerId";
final String customerId = "09e8e9c8-ec48";
final HashMap<String, AttributeValue> customerItemKey = new HashMap<>();
customerItemKey.put(CUSTOMER_PARTITION_KEY, new AttributeValue(customerId));

ConditionCheck checkCustomerValid = new ConditionCheck()
    .withTableName(CUSTOMER_TABLE_NAME)
    .withKey(customerItemKey)
    .withConditionExpression("attribute_exists(" + CUSTOMER_PARTITION_KEY + ")");
```

### 제품 상태 업데이트
<a name="transaction-example-order-part-b"></a>

그런 다음 제품 상태가 현재 `IN_STOCK`으로 설정되어 있는지 확인하는 조건이 `true`인 경우 제품 상태를 `SOLD`로 업데이트하도록 하는 작업을 정의합니다. `ReturnValuesOnConditionCheckFailure` 파라미터를 설정하면 항목의 제품 상태 속성이 `IN_STOCK`이 아닌 경우 해당 항목을 반환합니다.

```
final String PRODUCT_TABLE_NAME = "ProductCatalog";
final String PRODUCT_PARTITION_KEY = "ProductId";
HashMap<String, AttributeValue> productItemKey = new HashMap<>();
productItemKey.put(PRODUCT_PARTITION_KEY, new AttributeValue(productKey));

Map<String, AttributeValue> expressionAttributeValues = new HashMap<>();
expressionAttributeValues.put(":new_status", new AttributeValue("SOLD"));
expressionAttributeValues.put(":expected_status", new AttributeValue("IN_STOCK"));

Update markItemSold = new Update()
    .withTableName(PRODUCT_TABLE_NAME)
    .withKey(productItemKey)
    .withUpdateExpression("SET ProductStatus = :new_status")
    .withExpressionAttributeValues(expressionAttributeValues)
    .withConditionExpression("ProductStatus = :expected_status")
    .withReturnValuesOnConditionCheckFailure(ReturnValuesOnConditionCheckFailure.ALL_OLD);
```

### 주문 생성
<a name="transaction-example-order-part-c"></a>

마지막으로 `OrderId`를 가진 주문이 아직 없는 경우 주문을 생성합니다.

```
final String ORDER_PARTITION_KEY = "OrderId";
final String ORDER_TABLE_NAME = "Orders";

HashMap<String, AttributeValue> orderItem = new HashMap<>();
orderItem.put(ORDER_PARTITION_KEY, new AttributeValue(orderId));
orderItem.put(PRODUCT_PARTITION_KEY, new AttributeValue(productKey));
orderItem.put(CUSTOMER_PARTITION_KEY, new AttributeValue(customerId));
orderItem.put("OrderStatus", new AttributeValue("CONFIRMED"));
orderItem.put("OrderTotal", new AttributeValue("100"));

Put createOrder = new Put()
    .withTableName(ORDER_TABLE_NAME)
    .withItem(orderItem)
    .withReturnValuesOnConditionCheckFailure(ReturnValuesOnConditionCheckFailure.ALL_OLD)
    .withConditionExpression("attribute_not_exists(" + ORDER_PARTITION_KEY + ")");
```

### 트랜잭션 실행
<a name="transaction-example-order-part-d"></a>

다음 예제는 앞에서 정의한 작업을 하나의 양자택일 방식 작업으로 실행하는 방법을 보여 줍니다.

```
    Collection<TransactWriteItem> actions = Arrays.asList(
        new TransactWriteItem().withConditionCheck(checkCustomerValid),
        new TransactWriteItem().withUpdate(markItemSold),
        new TransactWriteItem().withPut(createOrder));

    TransactWriteItemsRequest placeOrderTransaction = new TransactWriteItemsRequest()
        .withTransactItems(actions)
        .withReturnConsumedCapacity(ReturnConsumedCapacity.TOTAL);

    // Run the transaction and process the result.
    try {
        client.transactWriteItems(placeOrderTransaction);
        System.out.println("Transaction Successful");

    } catch (ResourceNotFoundException rnf) {
        System.err.println("One of the table involved in the transaction is not found" + rnf.getMessage());
    } catch (InternalServerErrorException ise) {
        System.err.println("Internal Server Error" + ise.getMessage());
    } catch (TransactionCanceledException tce) {
        System.out.println("Transaction Canceled " + tce.getMessage());
    }
```

## 주문 세부 정보 읽기
<a name="transaction-example-read-order"></a>

다음 예제는 `Orders` 및 `ProductCatalog` 테이블에서 완료된 주문 트랜잭션을 읽는 방법을 보여줍니다.

```
HashMap<String, AttributeValue> productItemKey = new HashMap<>();
productItemKey.put(PRODUCT_PARTITION_KEY, new AttributeValue(productKey));

HashMap<String, AttributeValue> orderKey = new HashMap<>();
orderKey.put(ORDER_PARTITION_KEY, new AttributeValue(orderId));

Get readProductSold = new Get()
    .withTableName(PRODUCT_TABLE_NAME)
    .withKey(productItemKey);
Get readCreatedOrder = new Get()
    .withTableName(ORDER_TABLE_NAME)
    .withKey(orderKey);

Collection<TransactGetItem> getActions = Arrays.asList(
    new TransactGetItem().withGet(readProductSold),
    new TransactGetItem().withGet(readCreatedOrder));

TransactGetItemsRequest readCompletedOrder = new TransactGetItemsRequest()
    .withTransactItems(getActions)
    .withReturnConsumedCapacity(ReturnConsumedCapacity.TOTAL);

// Run the transaction and process the result.
try {
    TransactGetItemsResult result = client.transactGetItems(readCompletedOrder);
    System.out.println(result.getResponses());
} catch (ResourceNotFoundException rnf) {
    System.err.println("One of the table involved in the transaction is not found" + rnf.getMessage());
} catch (InternalServerErrorException ise) {
    System.err.println("Internal Server Error" + ise.getMessage());
} catch (TransactionCanceledException tce) {
    System.err.println("Transaction Canceled" + tce.getMessage());
}
```