본문 바로가기

Dev.../소프트웨어 아키텍처

[SA강좌] Part 4-6 Transaction Script 패턴

트랜잭션 스크립트(Transaction Script) 패턴

트랜잭션 스크립트 패턴의 정의

개별 프로시저가 프리젠테이션 레이어에서 단일 요청을 관리할 때 비즈니스 로직을 프로시저로 사용하여 조직화하는 패턴이다.

그림 -9. 트랜잭션 스크립트 패턴의 구조

트랜잭션 스크립트 패턴의 설명

대부분의 어플리케이션은 트랜잭션의 연속으로 생각된다. 트랜잭션은 특별한 방법에 의해 조직된 정보를 보여주고, 다른 것으로 변화시킨다. 클라이언트와 서버 시스템 사이에 상호 작용은 비즈니스 로직의 많은 부분을 포함하고 있다. 몇몇 경우는 데이터베이스에 있는 정보를 화면에 보여주는 것 같이 단순 할 수 있다. 다른 경우는 다수의 검증과 계산의 단계들을 포함할 수 있다.

트랜잭션 스크립트는 단일 프로시저, 데이터베이스 직접 호출 또는 씬 데이터베이스 레퍼와 비슷하게 모든 비즈니스 로직을 조직화한다. 각 트랜잭션은 자신의 트랜잭션 스크립트를 갖는다. 뿐만 아니라 공통 서브 작업들은 서브프로시저로 나누어 진다.

트랜잭션 스크립트 패턴은 언제 사용하는가?

트랜잭션 스크립트의 장점은 단순성에 있다. 이러한 방법으로 로직을 구조화하는 것은 단지 로직의 작은 단위를 가지는 어플리케이션을 위해 자연스러운 것 이다. 그리고, 이 것은 수행과 이해에서 매우 적은 과부하를 포함한다.

비즈니스 로직은 점점 더 복잡해지고 있다. 그러나, 비즈니스 로직을 잘 설계된 상태로 유지하는 것은 어렵다. 하나 특별한 문제점은 트랜잭션들 사이의 중첩성 이다.

주의 깊은 분해는 많은 이러한 문제점들을 완화해준다. 그러나, 더욱 복잡한 비즈니스 도메인은 도메인 모델 패턴의 작성을 필요로 한다. 도메인 모델 패턴은 코드를 구조화하데 더 많은 선택을 주고, 가독성을 증가하고, 중복성을 감속한다.

트랜잭션 스크립트 패턴의 적용 예제 : 세금 계산

이 예제는 두 가지 트랜잭션 스크립트를 사용한다. 하나는 계약을 위해 세금을 계산하는 것이고, 다른 하나는 계약에서 특정 일자에 의해 계산된 세금 금액을 알려주는 것이다. 데이터베이스는 세 가지의 테이블을 가지고 있다. 제품을 위한 테이블, 계약을 위한 테이블, 그리고 세금 인식을 위한 테이블이다.

 

CREATE TABLE products(ID int primary key, name varchar, type varchar)

CREATE TABLE contracts(ID int primary key, product int, revenue decimal, dateString date)

CREATE TABLE revenueRecognitions(contract int, amount decimal, recognizedOn Date,

PRIMARY KEY (contract, recognizedOn))

 

첫 번째 트랜잭션 스크립트는 특정 일자의 세금의 합계를 계산하는 것이다. 계산을 위해 두 단계를 거치게 된다. 세금 인식 테이블에서 적절한 행을 선택한다. 그리고, 합계를 구한다.

많은 트랜잭션 스크립트 설계들은 데이터베이스에 직접적으로 조작을 위한 스크립트를 가지고 있다. 예를 들어서 프로시저에 SQL 코드를 넣는 것이다. 테이블 데이터 게이트웨이 패턴은 SQL 질의어를 포함하고 있는 간단한 예이다. 이 예제는 매우 간단하다. 각 테이블을 위해 하나 이상의 단일 게이트웨이를 사용한다. 아래의 예제는 게이트웨이에서 검색 메서드를 정의하고 있다.

 

class GateWay ...

 

private static final String findRecognitionsStatement =

"SELECT amount " +

" FROM revenueRecognitions " +

" WHERE contract = ? AND recognizedOn <= ? ";

private Connection db;

 

public ResultSet findRecognitionsFor(long contractID, MfDate asof) throws SQLException {

PreparedStatement stmt = db.preparedStatement(findRecognitionsStatement);

stmt = db.preparedStatment(findRecognitionsStatement);

stmt.setLong(1, contractID);

stmt.setDate(2, asof.toSqlDate());

ResultSet result = stmt.executeQuery();

return result;

}

 

게이트웨이에서 결과 집합으로 전달된 것을 근간으로 합계를 구하는 스크립트는 다음과 같다.

 

class RegonitionService ...

 

public Money recognizedRevenue(long contractNumber, mfDate asOf) {

Money result = Money.dollars(0);

try {

Result rs = db.findRecognitionsFor(contractNumber, asOf);

while(rs.next()) {

result = result.add(Money.dollars(rs.getBigDecimal("amount")));

}

return result;

} catch(SQLException e) {throw new ApplicationException(e);

}

}

 

이와 같이 간단히 계산을 하는 경우 메모리 상의 스크립트는 금액을 합하는 집합 함수를 사용하는 SQL문 호출로 대치될 수 있다. 존재하는 계약 테이블에서 세금 인식을 계산하기 위해서, 아래와 같이 코드를 나누었다. 서비스의 스크립트는 비즈니스 로직을 수행한다.

 

class RegonitionService ...

 

public void calculateRevenueRecognitions(long contractNumber) {

try {

ResultSet contracts = db.findContract(contractName);

contracts.next();

Money totalRevenue = Money.dollars(contracts.getBigDecimal("revenue");

MfDate recognitionDate = new MfDate(contracts.getDate("dateSigned"));

String type = contracts.getString("type");

if(type.equls("S")) {

Money[] allocation = totalRevenue.allocate(3);

db.insertRecognition(contractNumber, allocation[0], recognitionDate);

    db.insertRecognition(contractNumber, allocation[1], recognitionDate.addDate(60));

    db.insertRecognition(contractNumber, allocation[2], recognitionDate.addDate(90));

} else if (type.equals("W")) {

db.insertRecognition(contractNumber, totalRevenue, recognitionDate);

} else if (type.equals("D")) {

Money[] allocation = totalRevenue.allocate(3);

db.insertRecognition(contractNumber, allocation[0], recognitionDate);

    db.insertRecognition(contractNumber, allocation[1], recognitionDate.addDate(30));

    db.insertRecognition(contractNumber, allocation[2], recognitionDate.addDate(60));

}

} catch(SQLException e) { throw new ApplicationException(e));

}

}

 

Money 패턴을 사용하여 세금 수집을 수행헀다는 것에 주목하자. 테이블 데이터 게이트웨이 패턴은 SQL 지원을 제공한다. 계약 테이블 검색을 위한 코드는 아래와 같다.

 

class GateWay ...

 

private static final String findContractStatement =

" SELECT " +

" FROM contract c, product p " +

0]

]

ttutri " WHERE ID = ? AND c.product = p.ID ";

 

public ResultSet findContract(long contractID) throws SQLException {

PreparedStatement stmt = db.preparedStatement(findContractStatement);

stmt.setLong(1, contractID);

ResultSet result = stmt.executeQuery();

return result;

}

 

두 번째로 입력을 위해서 코드를 추가하는 것은 아래와 같다.

 

class GateWay ...

 

public void insertRecognition(long contractID, Money amount, MfDate asof) throws SQLException {

PreparedStatement stmt = db.preparedStatement(insertRecognitionStatment);

stmt.setLong(1, contractID);

stmt.setBigDate(2, amount);

stmt.setDate(3, asof.toSqlDate());

stmt.executeUpdate();

}

private static final String insertRecognitionStatement =

"- INSERT INTO revenueRecognitions VALUE(?, ?, ?)";

 

자바 시스템에서 인식 서비스는 일반 클래스 또는 세션 빈이 될 수 있다. 도메인 모델 패턴의 예제와 비교를 하면, 위의 예제가 매우 간단하다는 것을 할 수 있다.