Stay Hungry. Stay Foolish. Don't Be Satisfied.

Grunt 플러그인: grunt-connect-proxy

이전 글에서 grunt-contrib-connect를 소개했는데 실제 프로젝트를 수행하다 보면 정적 웹서버만으로는 충분하지 않고 웹서버를 백엔드 서버로 프락시를 해야 하는 경우가 있다. 퍼블리싱 작업을 한다면 정적 웹서버만으로 충분하겠지만, 보통은 ajax 호출을 하는 백엔드 서버가 있으므로 이 서버와 connect 서버를 연결하고 싶을 수 있다. 이 부분은 JavaScript에서 서버에 대한 호스트를 변수로 두고 환경에 따라 바꿔치기하면서 개발할 수도 있지만 CORS를 지원하는 백엔드 서버가 아니라면 브라우저의 동일출처정책때문에 호출도 제대로 되지 않는다.

grunt-connect-proxy

이럴 때 사용할 수 있는 것이 grunt-connect-proxy이다. grunt-connect-proxy는 grunt-contrib-connect에 연결에서 일부 URL을 다른 서버를 리버스 프록시로 연결한다.

connect: {
  dev: {
    options: {},
  }
}

위와 같은 connect 설정이 있다고 해보자. grunt-contrib-connect에도 다양한 설정이 있지만 여기서는 별로 중요치 않다. npm install grunt-connect-proxy --save-dev으로 grunt-connect-proxy를 설치한 뒤 Gruntfile에서 태스크를 불러오고 다음과 같은 설정을 추가한다.

connect: {
  dev: {
    options: {
      middleware: function (connect, options) {
         var proxy = require('grunt-connect-proxy/lib/utils').proxyRequest;
         return [
            proxy,
            connect.static(options.base),
            connect.directory(options.base)
         ];
      }
    },
    proxies: [
      {
        context: ['/api'],
        host: '127.0.0.1',
        port: 3000
      }
    ]
  }
}

grunt-connect-proxy의 문서를 보면 설정이 꽤 헷갈리게 쓰여 있는데 proxies설정과 middleware 설정을 모두 추가해야 한다. proxies에서는 리버스프락시를 할 대상 서버를 지정한다. 여기서 connect서버는 8000포트로 실행되는데 그중에서 /api로 시작하는 모든 요청은 127.0.0.1:3000으로 보낸다. 그래서 모든 요청은 127.0.0.1:8000으로 보내면서도 일부 요청은 다른 서버에서 응답을 받아서 보낼 수 있다. URL 패턴이 다양하다면 context 배열에 추가하면 된다. 그리고 proxies 설정만으로는 충분하지 않고 grunt-contrib-connect 옵션에 미들웨어를 등록해서 프락시가 동작하도록 해야 한다. grunt-connect-proxy에서 제공하는 프락시를 가져와서 미들웨어에서 요청을 프락시가 먼저 처리하고 그다음에 connect가 처리하도록 바꿔준 것이다.

$ grunt configureProxies:dev connect:dev:keepalive
Running "configureProxies:dev" (configureProxies) task
Proxy created for: /api to 127.0.0.1:3000

Running "connect:dev:keepalive" (connect) task
Waiting forever...
Started connect web server on http://0.0.0.0:8000

connect태스크를 실행하기 전에 configureProxies 태스크를 먼저 실행해야 한다.(여기서 이름은 connect 태스크의 이름과 같다.) 그러면 위처럼 실행하면 로그에서 프락시가 실행된 것을 볼 수 있다. http://0.0.0.0:8000/api/로 요청을 보내면 정적서버가 아닌 프락시 한 서버의 응답이 오는 것을 볼 수 있다.

옵션

  • context: 프락시 할 URL을 지정하고 /로 시작하고 /로 끝나면 안 된다.
  • host: 프락시 할 서버의 호스트(http/https는 적지 않는다.)
  • port: 프락시 할 서버의 포트. 기본값: 80
  • https: https로 프락시 할 것인지 여부. 기본값: false
  • changeOrigin: 프락시 서버에 요청을 보낼 때 출처를 변경할 것인지 여부. 기본값: false
  • xforward: "x-forwarded-for": "127.0.0.1", "x-forwarded-port": 50892, "x-forwarded-proto": "http"같은 x-forward 헤더를 프락시 요청에 추가할 것인지 여부. 기본값: false
  • appendProxies: false로 설정하면 grunt-contrib-connect로 여러 서버를 설정할 때 프락시가 각각 동작하도록 설정을 격리한다. 기본값: true
  • rewrite: 객체로 전달하고 프락시를 할 때 URL을 재작성한다. {'^/changingcontext': '/anothercontext'}처럼 킷값에 정규표현식을 사용해서 값에 해당하는 URL로 교체해서 재작성한다.
  • timeout: 연결에 대한 타임아웃. 기본값 2분(120,000ms)
  • headers: 프락시하는 요청에 추가할 헤더
  • ws: true로 설정하면 웹소켓을 프락시한다. 기본값 false

추가로 grunt-connect-proxy에서도 livereload도 지원한다.

2014/04/23 00:03 2014/04/23 00:03

관련 글

[Spring 레퍼런스] 24장 JCA CCI

이 문서는 개인적인 목적이나 배포하기 위해서 복사할 수 있다. 출력물이든 디지털 문서든 각 복사본에 어떤 비용도 청구할 수 없고 모든 복사본에는 이 카피라이트 문구가 있어야 한다.



24. JCA CCI


24.1 소개

Java EE는 엔터프라이즈 인포메이션 시스템(EIS, Enterprise Information Systems)에 대한 표준화된 접근의 명세 JCA(J2EE Connector Architecture)를 제공한다. 이 명세는 여러 부분으로 나누어져 있다.

  • 커넥터 프로바이더인 SPI (Service provider interfaces)는 반드시 구현해야 한다. 이러한 인터페이스는 Java EE 애플리케이션 서버에 배포할 수 있는 리소스 어댑터다. 이런 시나리오에서 서버는 커넥션 풀, 트랜잭션, 보안(관리 모드)를 관리한다. 애플리케이션 서버는 클라이언트 애플리케이션 외부에 있는 구성도 관리한다. 애플리케이션 서버 없이도 커넥터를 사용할 수 있는데 이 경우에는 애플리케이션이 커넥터를 직접 구성해야 한다.(비관리 모드)
  • 애플리케이션이 사용할 수 있는 CCI (Common Client Interface) 는 커넥터와 상호작용하므로 EIS로 통신한다. 로컬 트랜잭션 경계에 대한 API도 제공된다.

스프링 CCI 지원은 스프링 프레임워크의 일반적인 리소스 관리와 트랜잭션 관리 기능을 사용하면서 일반적인 스프링 방식으로 CCI 커넥터에 접근하는 클래스를 제공한다.

Note
커넥터의 클라이언트 측이 항상 CCI를 사용하는 것은 아니다. 어떤 커넥터는 자신만의 API를 가지고 Java EE 컨테이너의 시스템 규약(커넥션 풀링, 전역 트랜잭션, 보안)을 사용하는 JCA 리소스 어댑터만 제공하기도 한다. 이렇게 커넥터에 특화된 API를 스프링이 따로 지원하진 않는다.


24.2 CCI 구성

24.2.1 커넥터 구성

JCA CCI를 사용하는 기반은 ConnectionFactory 인터페이스다. 사용하는 커넥터는 반드시 이 인터페이스의 구현체를 제공해야 한다.

커넥터를 사용하려면 애플리케이션 서버에 커넥터를 배포하고 서버의 JNDI 환경에서 ConnectionFactory를 가져올 수 있어야 한다.(관리 모드) 커넥터를 RAR 파일(resource adapter archive)로 패키징해야 하고 배포와 관련된 정보를 담고 있는 ra.xml이 포함되어 있어야 한다. 리소스의 실제 이름은 배포할 때 지정한다. 스프링 내에서 커넥터에 접근하려면 JNDI 이름으로 팩토리를 가져오는 스프링의 JndiObjectFactoryBean / 를 사용하면 된다.

어플리케이션서버를 사용해서 배포하고 구성하는 대신 애플리케이션에 커넥터를 내장시키는 것도 커넥터를 사용하는 또 하나의 방법이다.(비관리 모드) 스프링에서는 제공된 FactoryBean으로 커넥터를 빈으로 구성할 수 있다. 이 방법을 사용할 때는 클래스패스에 커넥터 라이브러리만 넣으면 된다.(RAR 파일이나 ra.xml 디스크립터가 필요 없다.) 필요하다만 커넥터의 RAR 파일에서 라이브러리만 추출해야 한다.

일단 ConnectionFactory 인스턴스에 접근하고 나면 컴포넌트에 주입할 수 있다. 이러한 컴포넌트는 평범한 CCI API를 기반으로 작성하거나 스프링의 CCI 접근 지원 클래스(CciTemplate 같은)를 기반으로 작성할 수 있다.

Note
비관리 모드에서 커넥터를 사용할 때는 현재 스레드의 현재 전역 트랜잭션에 리소스가 절대 추가되거나 빠지지 않으므로 전역 트랜잭션을 사용할 수 없다. 리소스는 어떤 전역 Java EE 트랜잭션도 인지하지 못한다.


24.2.2 스프링에서 ConnectionFactory의 구성

EIS에 연결하려고 할 때 관리 모드인 경우에는 애플리케이션 서버에서 ConnectionFactory를 얻어야 하고 비관리 모드인 경우에는 스프링에서 직접 ConnectionFactory를 가져와야 한다.

관리 모드에서는 JNDI에서 ConnectionFactory에 접근한다. ConnectionFactory의 프로퍼티는 애플리케이션 서버에서 설정할 것이다.

<jee:jndi-lookup id="eciConnectionFactory" jndi-name="eis/cicseci"/>

비관리 모드에서는 스프링 설정에서 사용할 ConnectionFactory를 JavaBean으로 설정해야 한다. LocalConnectionFactoryBean 클래스가 커넥터의 ManagedConnectionFactory 구현체를 전달하고 애플리케이션 수준의 CCI ConnectionFactory를 노출시켜서 이러한 설정 방법을 제공한다.

<bean id="eciManagedConnectionFactory" class="com.ibm.connector2.cics.ECIManagedConnectionFactory">
  <property name="serverName" value="TXSERIES"/>
  <property name="connectionURL" value="tcp://localhost/"/>
  <property name="portNumber" value="2006"/>
</bean>

<bean id="eciConnectionFactory" class="org.springframework.jca.support.LocalConnectionFactoryBean">
  <property name="managedConnectionFactory" ref="eciManagedConnectionFactory"/>
</bean>
Note
특정 ConnectionFactory를 직접 인스턴스화 할 수는 없다. 커넥터에 대한 ManagedConnectionFactory 인터페이스의 대응되는 구현체를 통해야 한다. 이 인터페이스는 JCA SPI 명세에 포함된 내용이다.


24.2.3 CCI 연결 구성

개발자는 JCA CCI를 사용해서 커넥터의 ConnectionSpec 구현체를 사용해서 EIS에 대한 연결을 구성할 수 있다. ConnectionSpec의 프로퍼티를 설정하려면 전용 어댑터 ConnectionSpecConnectionFactoryAdapter로 대상 커넥션 팩토리를 감싸야 한다. 그래서 전용 ConnectionSpec를 connectionSpec 프로퍼티(내부 빈으로)로 설정할 수 있다.

CCI ConnectionFactory 인터페이스는 CCI 연결을 얻는 두 가지 방법을 정의하고 있으므로 이 프로퍼티는 의무사항은 아니다. ConnectionSpec 프로퍼티 중 일부는 애플리케이션 서버에서 설정하거나(관리모드에서) 대응되는 로컬 ManagedConnectionFactory 구현체에서 설정할 수 있다.

public interface ConnectionFactory implements Serializable, Referenceable {
  ...
  Connection getConnection() throws ResourceException;
  Connection getConnection(ConnectionSpec connectionSpec) throws ResourceException;
  ...
}

스프링은 해당 팩토리의 모든 동작을 사용할 수 있도록 ConnectionSpec 인스턴스를 지정할 수 있는 ConnectionSpecConnectionFactoryAdapter를 제공한다. 이 어댑터의 connectionSpec 프로퍼티를 지정했다면 어댑터는 인자 없이 getConnection 계열을 사용하고 지정하지 않았다면 ConnectionSpec 인자와 함께 사용한다.

<bean id="managedConnectionFactory"
    class="com.sun.connector.cciblackbox.CciLocalTxManagedConnectionFactory">
  <property name="connectionURL" value="jdbc:hsqldb:hsql://localhost:9001"/>
  <property name="driverName" value="org.hsqldb.jdbcDriver"/>
</bean>

<bean id="targetConnectionFactory"
    class="org.springframework.jca.support.LocalConnectionFactoryBean">
  <property name="managedConnectionFactory" ref="managedConnectionFactory"/>
</bean>

<bean id="connectionFactory"
    class="org.springframework.jca.cci.connection.ConnectionSpecConnectionFactoryAdapter">
  <property name="targetConnectionFactory" ref="targetConnectionFactory"/>
  <property name="connectionSpec">
    <bean class="com.sun.connector.cciblackbox.CciConnectionSpec">
      <property name="user" value="sa"/>
      <property name="password" value=""/>
    </bean>
  </property>
</bean>


24.2.4 단일 CCI 연결의 사용

단일 CCI 연결을 사용하고 싶은 경우를 위해서 스프링은 ConnectionFactory 어댑터로 이를 관리할 수 있게 한다. SingleConnectionFactory 어댑터 클래스가 단일 연결을 지연해서 열고 애플리케이션 종료 시 해당 빈이 파괴될 때 연결을 닫는다. 이 클래스는 물리적 연결에 기반을 둬서 같은 연결을 공유하면서 상황에 따라 적절하게 동작하는 전용 Connection 프록시를 노출할 것이다.

<bean id="eciManagedConnectionFactory"
    class="com.ibm.connector2.cics.ECIManagedConnectionFactory">
  <property name="serverName" value="TEST"/>
  <property name="connectionURL" value="tcp://localhost/"/>
  <property name="portNumber" value="2006"/>
</bean>

<bean id="targetEciConnectionFactory"
    class="org.springframework.jca.support.LocalConnectionFactoryBean">
  <property name="managedConnectionFactory" ref="eciManagedConnectionFactory"/>
</bean>

<bean id="eciConnectionFactory"
    class="org.springframework.jca.cci.connection.SingleConnectionFactory">
  <property name="targetConnectionFactory" ref="targetEciConnectionFactory"/>
</bean>
Note
이 ConnectionFactory 어댑터는 ConnectionSpec로 직접 설정할 수 없다. 특정 ConnectionSpec에 대한 단일 커넥션이 필요한 경우에는 SingleConnectionFactory와 통신하는 ConnectionSpecConnectionFactoryAdapter로 중개해서 사용해라.


24.3 스프링의 CCI 접근에 대한 지원 사용하기

24.3.1 레코드(Record) 변환

JCA CCI 지원 중 하나는 CCI 레코드를 편리하게 조작할 수 있도록 하는 것이다. 개발자는 스프링의 CciTemplate를 사용해서 레코드를 생성하고 레코드에서 데이터를 추출하는 전략을 지정할 수 있다. 다음 인터페이스들은 애플리케이션에서 레코드를 직접 다루길 원하지 않는 경우 입력과 출력 레코드를 사용하는 전략을 설정할 것이다.

입력 Record를 생성하기 위해 개발자가 RecordCreator 인터페이스의 전용 구현체를 사용할 수 있다.

public interface RecordCreator {
  Record createRecord(RecordFactory recordFactory) throws ResourceException, DataAccessException;
}

여기서 보듯이 createRecord(..) 메서드는 파라미터로 RecordFactory를 받는다. 이 파라미터는 사용한 ConnectionFactory의 RecordFactory를 가리키는데 IndexedRecord나 MappedRecord를 생성하는 데 사용한다. 다음 예제는 RecordCreator 인터페이스와 색인 되거나(indexed) 매핑된(mapped) 레코드를 사용하는 방법을 보여준다.

public class MyRecordCreator implements RecordCreator {
  public Record createRecord(RecordFactory recordFactory) throws ResourceException {
    IndexedRecord input = recordFactory.createIndexedRecord("input");
    input.add(new Integer(id));
    return input;
  }
}

EIS에서 다시 데이터를 받을 때 출력 Record를 사용할 수도 있다. 그러므로 출력 Record의 데이터를 추출하기 위해 RecordExtractor 인터페이스의 특정 구현체를 스프링 CciTemplate에 전달할 수 있다.

public interface RecordExtractor {
  Object extractData(Record record) throws ResourceException, SQLException, DataAccessException;
}

다음 예제는 RecordExtractor 인터페이스를 사용하는 방법을 보여준다.

public class MyRecordExtractor implements RecordExtractor {
  public Object extractData(Record record) throws ResourceException {
    CommAreaRecord commAreaRecord = (CommAreaRecord) record;
    String str = new String(commAreaRecord.toByteArray());
    String field1 = string.substring(0,6);
    String field2 = string.substring(6,1);
    return new OutputObject(Long.parseLong(field1), field2);
  }
}


24.3.2 CciTemplate

CciTemplate은 핵심 CCI 지원 패키지 (org.springframework.jca.cci.core)의 중심이 되는 클래스다. CciTemplate는 리소스의 생성과 해제를 관리해서 CCI를 쉽게 사용할 수 있도록 한다. 그리고 연결을 닫는 것을 잊어버리는 등의 일반적인 오류를 피할 수 있게 해준다. CciTemplate는 연결과 상호작용(interaction) 객체의 생명주기를 관리해서 애플리케이션 코드는 애플리케이션에서 입력 레코드를 생성하고 출력 레코드에서 애플리케이션 데이터를 추출하는 데 집중하도록 한다.

JCA CCI 명세는 EIS에서 작업을 호출하는 별개의 두 메서드를 정의하고 있다. CCI Interaction 인터페이스는 두 가지 execute 메서드 시그니처를 제공한다.

public interface javax.resource.cci.Interaction {
  ...
  boolean execute(InteractionSpec spec, Record input, Record output) throws ResourceException;

  Record execute(InteractionSpec spec, Record input) throws ResourceException;
  ...
}

호출된 템플릿 메서드에 따라 CciTemplate이 상호작용에 어떤 execute 메서드를 호출할지 알 수 있다. 어떤 경우에든 제대로 초기화된 InteractionSpec 인스턴스가 필요하다.

CciTemplate.execute(..)는 두 가지로 사용할 수 있다.

  • 직접 Record 인자로 사용하는 방법. 이 경우 CCI 입력 레코드를 전달해야 하고 반환된 객체는 CCI 출력 레코드에 대응된다.
  • 레코드 매핑을 사용해서 애플리케이션 객체를 사용하는 방법. 이 경우에는 대응되는 RecordCreator와 RecordExtractor 인스턴스를 제공해야 한다.

첫번째 방법을 사용하면 템플릿의 다음 메서드를 사용할 것이다. 이 메서드들은 Interaction 인터페이스의 메서드들과 직접 대응된다.

public class CciTemplate implements CciOperations {
  public Record execute(InteractionSpec spec, Record inputRecord)
      throws DataAccessException { ... }

  public void execute(InteractionSpec spec, Record inputRecord, Record outputRecord)
      throws DataAccessException { ... }
}

두번째 방법을 사용하면 인자로 레코드 생성 전략과 레코드 추출 전략을 지정해야 한다. 레코드 변환에 사용하는 인터페이스는 이전 장에서 설명했다. 대응되는 CciTemplate 메서드는 다음과 같다.

public class CciTemplate implements CciOperations {
  public Record execute(InteractionSpec spec, RecordCreator inputCreator)
      throws DataAccessException { ... }

  public Object execute(InteractionSpec spec, Record inputRecord, RecordExtractor outputExtractor)
      throws DataAccessException { ... }

  public Object execute(InteractionSpec spec, RecordCreator creator, RecordExtractor extractor)
      throws DataAccessException { ... }
}

템플릿에 outputRecordCreator 프로퍼티를 설정하지 않으면(다음 장 참고) 모든 메서드가 적절한 CCI Interaction의 execute 메서드를 두 개의 파라미터 InteractionSpec와 입력 Record로 호출할 것이고 이 메서드는 출력 Record를 반환한다.

CciTemplate은 RecordCreator 구현체 외부에서 IndexRecord와 MappedRecord를 생성하는 createIndexRecord(..)와 createMappedRecord(..) 메서드도 제공한다. 해당 CciTemplate.execute(..) 메서드에 전달할 Record 인스턴스를 생성하기 위해서 DAO 구현체 내에서 이 메서드를 사용할 수 있다.

public class CciTemplate implements CciOperations {
  public IndexedRecord createIndexedRecord(String name) throws DataAccessException { ... }

  public MappedRecord createMappedRecord(String name) throws DataAccessException { ... }
}


24.3.3 DAO 지원

스프링의 CCI 지원은 DAO에 대한 추상 클래스를 제공하고 ConnectionFactory나 CciTemplate 인스턴스의 주입을 지원한다. 클래스 이름은 CciDaoSupport이고 setConnectionFactory와 setCciTemplate 메서드를 제공한다. 내부적으로 이 클래스는 하위클래스에서 데이터 접근 구현체를 구현할 수 있게 CciTemplate 인스턴스를 노출하는 해당 ConnectionFactory를 위해서 CciTemplate 인스턴스를 생성할 것이다.

public abstract class CciDaoSupport {
  public void setConnectionFactory(ConnectionFactory connectionFactory) { ... }
  public ConnectionFactory getConnectionFactory() { ... }

  public void setCciTemplate(CciTemplate cciTemplate) { ... }
  public CciTemplate getCciTemplate() { ... }
}


24.3.4 출력 레코드 자동 생성

사용한 커넥터가 입력 레코드와 출력 레코드를 파라미터로 받는 Interaction.execute(..) 메서드(즉, 이 메서드는 적절한 출력 레코드를 반환하지 않고 원하는 출력 레코드를 전달해야 한다.)만 지원한다면 응답을 받을 때 JCA 커넥터가 작성하는 출력 레코드를 자동으로 생성할 수 있게 CciTemplate의 outputRecordCreator 프로퍼티를 설정할 수 있다. 그러면 템플릿의 호출자에게 이 레코드를 반환할 것이다.

위 목적에 맞게 이 프로퍼티에는 RecordCreator 인터페이스의 구현체가 담겨 있다. RecordCreator 인터페이스는 Section 24.3.1, “레코드(Record) 변환”에서 이미 설명했다. outputRecordCreator 프로퍼티는 CciTemplate에서 직접 지정해야 한다. 애플리케이션 코드에서는 다음과 같이 작성한다.

cciTemplate.setOutputRecordCreator(new EciOutputRecordCreator());

아니면 스프링 설정에서 전용 빈 인스턴스로 CciTemplate을 설정한다.(추천하는 방법)

<bean id="eciOutputRecordCreator" class="eci.EciOutputRecordCreator"/>

<bean id="cciTemplate" class="org.springframework.jca.cci.core.CciTemplate">
  <property name="connectionFactory" ref="eciConnectionFactory"/>
  <property name="outputRecordCreator" ref="eciOutputRecordCreator"/>
</bean>
Note
CciTemplate 클래스가 스레드 세이프 하므로 공유하는 인스턴스로 설정하는 게 일반적이다.


24.3.5 요약

다음 표는 CciTemplate 클래스의 메커니즘과 CCI Interaction 인터페이스에서 호출되는 해당 메서드를 요약해서 보여주고 있다.

Table 24.1. Interaction execute 메서드의 사용방법

CciTemplate 메서드 시그니처 CciTemplate outputRecordCreator 프로퍼티 CCI Interaction에서 호출되는 execute 메서드
Record execute(InteractionSpec, Record) 설정안함 Record execute(InteractionSpec, Record)
Record execute(InteractionSpec, Record) 설정함 boolean execute(InteractionSpec, Record, Record)
void execute(InteractionSpec, Record, Record) 설정안함 void execute(InteractionSpec, Record, Record)
void execute(InteractionSpec, Record, Record) 설정함 void execute(InteractionSpec, Record, Record)
Record execute(InteractionSpec, RecordCreator) 설정안함 Record execute(InteractionSpec, Record)
Record execute(InteractionSpec, RecordCreator) 설정함 void execute(InteractionSpec, Record, Record)
Record execute(InteractionSpec, Record, RecordExtractor) 설정안함 Record execute(InteractionSpec, Record)
Record execute(InteractionSpec, Record, RecordExtractor) 설정함 void execute(InteractionSpec, Record, Record)
Record execute(InteractionSpec, RecordCreator, RecordExtractor) 설정안함 Record execute(InteractionSpec, Record)
Record execute(InteractionSpec, RecordCreator, RecordExtractor) 설정함 void execute(InteractionSpec, Record, Record)






24.3.6 CCI Connection와 Interaction을 직접 사용하기

CciTemplate도 JdbcTemplate나 JmsTemplate와 같은 방식으로 직접 CCI 연결과 상호작용을 할 수 있게 지원한다. 이는 CCI 연결이나 상호작용에서 여러 작업을 수행하고자 할 때 유용하다.

ConnectionCallback 인터페이스는 CCI Connection에서 임의의 작업을 할 수 있도록 인자로 CCI Connection을 제공한다. 아니면 Connection를 생성한 CCI ConnectionFactory을 제공할 수도 있다. 후자의 경우는 관련 RecordFactory 인스턴스를 가져와서 색인 된/매핑된 레코드를 생성할 때 유용하게 사용할 수 있다.

public interface ConnectionCallback {
  Object doInConnection(Connection connection, ConnectionFactory connectionFactory)
      throws ResourceException, SQLException, DataAccessException;
}

InteractionCallback 인터페이스는 CCI Interaction에서 임의의 작업을 할 수 있도록 CCI Interaction를 제공하고 대응되는 CCI ConnectionFactory를 제공할 수도 있다.

public interface InteractionCallback {
  Object doInInteraction(Interaction interaction, ConnectionFactory connectionFactory)
      throws ResourceException, SQLException, DataAccessException;
}
Note
InteractionSpec 객체는 여러 템플릿 호출 간에 공유할 수도 있고 콜백 메서드마다 내부에서 새로 생성할 수도 있다. 어떻게 되느냐는 오로지 DAO 구현체에 달려있다.


24.3.7 CciTemplate 사용 예제

이번 장의 CciTemplate 사용방법은 IBM CICS ECI 커넥터를 사용해서 ECI 모드로 CICS에 접근하는 방법을 보여준다.

우선 접근할 CICS 프로그램을 지정하고 CICS 프로그램과 상호작용하는 방법을 지정할 수 있도록 CCI InteractionSpec의 초기화가 이뤄져야 한다.

ECIInteractionSpec interactionSpec = new ECIInteractionSpec();
interactionSpec.setFunctionName("MYPROG");
interactionSpec.setInteractionVerb(ECIInteractionSpec.SYNC_SEND_RECEIVE);

이 프로그램은 스프링의 템플릿을 사용해서 CCI를 사용할 수 있고 커스텀 객체와 CCI Records간의 매핑을 지정할 수 있다.

public class MyDaoImpl extends CciDaoSupport implements MyDao {
  public OutputObject getData(InputObject input) {
    ECIInteractionSpec interactionSpec = ...;
    OutputObject output = (ObjectOutput) getCciTemplate().execute(interactionSpec,
        new RecordCreator() {
          public Record createRecord(RecordFactory recordFactory) throws ResourceException {
            return new CommAreaRecord(input.toString().getBytes());
          }
        },
        new RecordExtractor() {
          public Object extractData(Record record) throws ResourceException {
            CommAreaRecord commAreaRecord = (CommAreaRecord)record;
            String str = new String(commAreaRecord.toByteArray());
            String field1 = string.substring(0,6);
            String field2 = string.substring(6,1);
            return new OutputObject(Long.parseLong(field1), field2);
          }
        });

    return output;
  }
}

앞에서 얘기했듯이 CCI 연결과 상호작용에서 직접 동작하도록 콜백을 사용할 수 있다.

public class MyDaoImpl extends CciDaoSupport implements MyDao {
  public OutputObject getData(InputObject input) {
    ObjectOutput output = (ObjectOutput) getCciTemplate().execute(
        new ConnectionCallback() {
          public Object doInConnection(Connection connection, ConnectionFactory factory)
              throws ResourceException {

            // 여기서 작업을 수행한다...
          }
        });
    }
    return output;
  }
}

Note
ConnectionCallback에서 사용한 Connection를 관리할 것이고 CciTemplate가 Connection를 닫을 것이지만 연결에서 생성한 모든 상호작용을 콜백 구현체에서 관리해야 한다.

더 상세한 콜백이 필요하다면 InteractionCallback를 구현할 수 있다. 이 경우 전달한 Interaction를 관리할 것이고 CciTemplate가 Interaction를 닫을 것이다.

public class MyDaoImpl extends CciDaoSupport implements MyDao {
  public String getData(String input) {
    ECIInteractionSpec interactionSpec = ...;
    String output = (String) getCciTemplate().execute(interactionSpec,
        new InteractionCallback() {
          public Object doInInteraction(Interaction interaction, ConnectionFactory factory)
              throws ResourceException {
            Record input = new CommAreaRecord(inputString.getBytes());
            Record output = new CommAreaRecord();
            interaction.execute(holder.getInteractionSpec(), input, output);
            return new String(output.toByteArray());
          }
        });

    return output;
  }
}

위 예제에 포함된 스프링 빈의 해당 설정은 비관리 모드에서는 다음과 같을 것이다.

<bean id="managedConnectionFactory" class="com.ibm.connector2.cics.ECIManagedConnectionFactory">
  <property name="serverName" value="TXSERIES"/>
  <property name="connectionURL" value="local:"/>
  <property name="userName" value="CICSUSER"/>
  <property name="password" value="CICS"/>
</bean>

<bean id="connectionFactory" class="org.springframework.jca.support.LocalConnectionFactoryBean">
  <property name="managedConnectionFactory" ref="managedConnectionFactory"/>
</bean>

<bean id="component" class="mypackage.MyDaoImpl">
  <property name="connectionFactory" ref="connectionFactory"/>
</bean>

관리모드(즉, Java EE 환경)에서는 설정이 다음과 같을 것이다.

<jee:jndi-lookup id="connectionFactory" jndi-name="eis/cicseci"/>

<bean id="component" class="MyDaoImpl">
  <property name="connectionFactory" ref="connectionFactory"/>
</bean>


24.4 operation 객체로 CCI 접근 모델링하기

org.springframework.jca.cci.object 패키지에서는 다른 방법으로 EIS에 접근할 수 있는 지원 클래스가 있다. 이는 스프링의 JDBC operation 객체(JDBC 장을 봐라)와 유사한 재사용 가능한 operation 객체를 통해서 이뤄지고 보통 CCI API를 은닉화할 것이다. 이는 애플리케이션 수준의 입력 객체를 operation 객체에 전달할 것이므로 입력 레코드를 구성하고 받은 레코드 데이터를 애플리케이션 수준의 출력 객체로 변환해서 반환할 수 있다.

Note: 이 접근방법은 내부적으로 CciTemplate 클래스와 RecordCreator / RecordExtractor 인터페이스에 기반을 두고 있고 스프링의 핵심 CCI 지원 기능을 재사용한다.

24.4.1 MappingRecordOperation

MappingRecordOperation는 본래 CciTemplate와 같은 작업을 하지만 미리 구성한 특정 작업을 객체로 나타낸다. MappingRecordOperation는 입력 객체를 입력 레코드로 변환하는(레코드 매핑) 방법을 지정하는 두 가지 템플릿 메서드를 제공한다.

  • 입력 객체를 입력 Record로 변환하는 방법을 지정하는 createInputRecord(..)
  • 출력 Record에서 출력 객체를 추출하는 방법을 지정하는 extractOutputData(..)

이 메서드들의 시그니처는 다음에 나와 있다.

public abstract class MappingRecordOperation extends EisOperation {
  ...
  protected abstract Record createInputRecord(RecordFactory recordFactory, Object inputObject)
      throws ResourceException, DataAccessException { ... }

  protected abstract Object extractOutputData(Record outputRecord)
      throws ResourceException, SQLException, DataAccessException { ... }
  ...
}

EIS 작업을 실행하려면 단일 execute 메서드를 사용해야 하고 애플리케이션 수준의 입력 객체를 전달해서 그 결과로 애플리케이션 수준의 출력 객체를 받는다.

public abstract class MappingRecordOperation extends EisOperation {
  ...
  public Object execute(Object inputObject) throws DataAccessException {
  ...
}

여기서 보듯이 CciTemplate 클래스와는 반대로 execute(..) 메서드는 인자로 InteractionSpec를 갖지 않는다. 대신에 InteractionSpec는 작업(operation)에 대해서 전역적이다. 작업(operation) 객체를 특정 InteractionSpec로 인스턴스화 하는데 다음의 생성자를 반드시 사용해야 한다.

InteractionSpec spec = ...;
MyMappingRecordOperation eisOperation = new MyMappingRecordOperation(getConnectionFactory(), spec);
...


24.4.2 MappingCommAreaOperation

일부 커넥터는 EIS에 전송할 파라미터와 EIS에서 반환받은 데이터를 담고 있는 바이트 배열을 나타내는 COMMAREA에 기반을 둬서 레코드를 사용한다. 레코드 대신 COMMAREA와 직접 동작하는 특수한 오퍼레이션 클래스를 스프링이 제공하고 있다. MappingCommAreaOperation 클래스는 COMMAREA 등을 지원하기 위해 MappingRecordOperation 클래스를 상속받는다. MappingCommAreaOperation는 암묵적으로 입력과 출력 레코드로 CommAreaRecord 클래스를 사용하고 입력 객체를 입력 COMMAREA로 변환하고 출력 COMMAREA를 출력 객체로 변환하는 새로운 두 메서드를 제공한다.

public abstract class MappingCommAreaOperation extends MappingRecordOperation {
  ...
  protected abstract byte[] objectToBytes(Object inObject)
      throws IOException, DataAccessException;

  protected abstract Object bytesToObject(byte[] bytes)
      throws IOException, DataAccessException;
  ...
}


24.4.3 출력 레코드 자동 생성

모든 MappingRecordOperation 하위클래스가 내부적으로 CciTemplate에 기반을 두고 있으므로 CciTemplate와 같을 방법으로 출력 레코드를 자동 생성할 수 있다. 각 작업 객체는 이에 대응되는 setOutputRecordCreator(..) 메서드를 제공한다. 더 자세한 내용은 Section 24.3.4, “출력 레코드 자동 생성”를 참고해라.

24.4.4 요약

작업(operation) 객체 접근방법은 CciTemplate 클래스와 같은 방법으로 레코드를 사용한다.

Table 24.2. Interaction execute 메서드의 사용방법

MappingRecordOperation 메서드 시그니처 MappingRecordOperation outputRecordCreator 프로퍼티 CCI Interaction에서 호출된 execute 메서드
Object execute(Object) 설정 안함 Record execute(InteractionSpec, Record)
Object execute(Object) 설정함 boolean execute(InteractionSpec, Record, Record)






24.4.5 MappingRecordOperation 사용방법의 예시

이번 장에서는 블랙박스 CCI 커넥터로 데이터베이스에 접근하는 MappingRecordOperation의 사용방법을 볼 것이다.

Note
이 커넥터의 원래 버전은 (Sun의) Java EE SDK (버전 1.3)에서 제공한 것이다.


우선, 어떤 SQL 요청을 처리할 것인지 정하기 위해 CCI InteractionSpec의 일부 초기화 작업이 완료되어야 한다. 이 예제에서는 요청의 파라미터들을 CCI 레코드로 변환하는 방법과 CCI 결과 레코드를 Person 클래스의 인스턴스로 변환하는 방법을 직접 정의한다.

public class PersonMappingOperation extends MappingRecordOperation {

  public PersonMappingOperation(ConnectionFactory connectionFactory) {
    setConnectionFactory(connectionFactory);
    CciInteractionSpec interactionSpec = new CciConnectionSpec();
    interactionSpec.setSql("select * from person where person_id=?");
    setInteractionSpec(interactionSpec);
  }

  protected Record createInputRecord(RecordFactory recordFactory, Object inputObject)
      throws ResourceException {
    Integer id = (Integer) inputObject;
    IndexedRecord input = recordFactory.createIndexedRecord("input");
    input.add(new Integer(id));
    return input;
  }

  protected Object extractOutputData(Record outputRecord)
      throws ResourceException, SQLException {
    ResultSet rs = (ResultSet) outputRecord;
    Person person = null;
    if (rs.next()) {
      Person person = new Person();
      person.setId(rs.getInt("person_id"));
      person.setLastName(rs.getString("person_last_name"));
      person.setFirstName(rs.getString("person_first_name"));
    }
    return person;
  }
}

그러면 애플리케이션이 인자로 person 식별자를 사용해서 작업 객체를 실행할 수 있다. 이 작업객체는 스레드 세이프 하므로 공유 인스턴스로 설정할 수도 있다.

public class MyDaoImpl extends CciDaoSupport implements MyDao {
  public Person getPerson(int id) {
    PersonMappingOperation query = new PersonMappingOperation(getConnectionFactory());
    Person person = (Person) query.execute(new Integer(id));
    return person;
  }
}

비관리 모드에서 이와 같은 스프링 빈 설정은 다음과 같을 것이다.

<bean id="managedConnectionFactory"
    class="com.sun.connector.cciblackbox.CciLocalTxManagedConnectionFactory">
  <property name="connectionURL" value="jdbc:hsqldb:hsql://localhost:9001"/>
  <property name="driverName" value="org.hsqldb.jdbcDriver"/>
</bean>

<bean id="targetConnectionFactory"
    class="org.springframework.jca.support.LocalConnectionFactoryBean">
  <property name="managedConnectionFactory" ref="managedConnectionFactory"/>
</bean>

<bean id="connectionFactory"
    class="org.springframework.jca.cci.connection.ConnectionSpecConnectionFactoryAdapter">
  <property name="targetConnectionFactory" ref="targetConnectionFactory"/>
  <property name="connectionSpec">
    <bean class="com.sun.connector.cciblackbox.CciConnectionSpec">
      <property name="user" value="sa"/>
      <property name="password" value=""/>
    </bean>
  </property>
</bean>

<bean id="component" class="MyDaoImpl">
  <property name="connectionFactory" ref="connectionFactory"/>
</bean>

관리 모드에서는(즉, Java EE 환경) 설정이 다음과 같을 것이다.

<jee:jndi-lookup id="targetConnectionFactory" jndi-name="eis/blackbox"/>

<bean id="connectionFactory"
    class="org.springframework.jca.cci.connection.ConnectionSpecConnectionFactoryAdapter">
  <property name="targetConnectionFactory" ref="targetConnectionFactory"/>
  <property name="connectionSpec">
    <bean class="com.sun.connector.cciblackbox.CciConnectionSpec">
      <property name="user" value="sa"/>
      <property name="password" value=""/>
    </bean>
  </property>
</bean>

<bean id="component" class="MyDaoImpl">
  <property name="connectionFactory" ref="connectionFactory"/>
</bean>


24.4.6 MappingCommAreaOperation 사용방법의 예시

이번 장에서는 MappingCommAreaOperation을 사용해서 IBM CICS ECI 커넥터로 ECI 모드에서 CICS에 접근하는 방법을 볼 것이다.

먼저 어떤 CICS 프로그램에 접근하고 어떻게 상호작용할 것인지 지정하기 위해 CCI InteractionSpec을 초기화 해야 한다.

public abstract class EciMappingOperation extends MappingCommAreaOperation {
  public EciMappingOperation(ConnectionFactory connectionFactory, String programName) {
    setConnectionFactory(connectionFactory);
    ECIInteractionSpec interactionSpec = new ECIInteractionSpec(),
    interactionSpec.setFunctionName(programName);
    interactionSpec.setInteractionVerb(ECIInteractionSpec.SYNC_SEND_RECEIVE);
    interactionSpec.setCommareaLength(30);
    setInteractionSpec(interactionSpec);
    setOutputRecordCreator(new EciOutputRecordCreator());
  }

  private static class EciOutputRecordCreator implements RecordCreator {
    public Record createRecord(RecordFactory recordFactory) throws ResourceException {
      return new CommAreaRecord();
    }
  }
}

초기화하고 나면 커스텀 객체와 Records의 매핑을 위해 추상 EciMappingOperation 클래스가 하위 클래스가 될 수 있다.

public class MyDaoImpl extends CciDaoSupport implements MyDao {
  public OutputObject getData(Integer id) {
    EciMappingOperation query = new EciMappingOperation(getConnectionFactory(), "MYPROG") {
      protected abstract byte[] objectToBytes(Object inObject) throws IOException {
        Integer id = (Integer) inObject;
        return String.valueOf(id);
      }
      protected abstract Object bytesToObject(byte[] bytes) throws IOException;
        String str = new String(bytes);
        String field1 = str.substring(0,6);
        String field2 = str.substring(6,1);
        String field3 = str.substring(7,1);
        return new OutputObject(field1, field2, field3);
      }
    });

    return (OutputObject) query.execute(new Integer(id));
  }
}

비관리 모드에서 스프링 빈을 사용한 설정은 다음과 같을 것이다.

<bean id="managedConnectionFactory" class="com.ibm.connector2.cics.ECIManagedConnectionFactory">
  <property name="serverName" value="TXSERIES"/>
  <property name="connectionURL" value="local:"/>
  <property name="userName" value="CICSUSER"/>
  <property name="password" value="CICS"/>
</bean>

<bean id="connectionFactory" class="org.springframework.jca.support.LocalConnectionFactoryBean">
  <property name="managedConnectionFactory" ref="managedConnectionFactory"/>
</bean>

<bean id="component" class="MyDaoImpl">
  <property name="connectionFactory" ref="connectionFactory"/>
</bean>

관리모드에서는(즉, Java EE 환경) 설정이 다음과 같을 것이다.

<jee:jndi-lookup id="connectionFactory" jndi-name="eis/cicseci"/>

<bean id="component" class="MyDaoImpl">
  <property name="connectionFactory" ref="connectionFactory"/>
</bean>


24.5 트랜잭션

JCA는 리소스 어댑터의 트랜잭션을 여러 단계로 지원한다. 리소스 어댑터가 지원하는 트랜잭션 종류는 리소스 어댑터의 ra.xml 파일에서 지정한다. 사실상 세 가지 옵션이 있는데 none(예시: CICS EPI 커넥터), 지역(local) 트랜잭션(예시: CICS ECI 커넥터), 전역(global) 트랜잭션(예시: IMS 커넥터)이다.

<connector>
  <resourceadapter>
    <!-- <transaction-support>NoTransaction</transaction-support> -->
    <!-- <transaction-support>LocalTransaction</transaction-support> -->
    <transaction-support>XATransaction</transaction-support>
  <resourceadapter>
<connector>

전역 트랜잭션에서는 트랜잭션의 경계를 정하기 위해 JtaTransactionManager를 백엔드로 사용해서(Java EE 서버의 분산 트랜잭션 코디네이터에 위임해서) 스프링의 제너릭 트랜잭션을 사용할 수 있다.

단일 CCI ConnectionFactory상의 지역 트랜잭션에서는 JDBC의 DataSourceTransactionManager와 유사하게 스프링이 CCI에 대한 구체적인 트랜잭션 관리 전략을 제공한다. CCI API는 지역 트랜잭션 객체와 해당 지역 트랜잭션 경계 메서드를 정의한다. 스프링의 CciLocalTransactionManager는 이러한 지역 CCI 트랜잭션을 실행하고 이는 스프링의 제너릭 PlatformTransactionManager 추상화와 완전히 호환된다.

<jee:jndi-lookup id="eciConnectionFactory" jndi-name="eis/cicseci"/>
<bean id="eciTransactionManager"
    class="org.springframework.jca.cci.connection.CciLocalTransactionManager">
  <property name="connectionFactory" ref="eciConnectionFactory"/>
</bean>

두 트랜잭션 전략 모두 스프링의 어떤 트랜잭션 경계 기능(선언적이거나 프로그래밍적인)과도 사용할 수 있다. 이는 실제 실행 전략과 트랜잭션 경계를 디커플링한 스프링의 제너릭 PlatformTransactionManager 추상화 덕이다. 그래서 트랜잭션 경계는 그대로 둔 채 JtaTransactionManager와 CciLocalTransactionManager를 필요한대로 바꿔 쓸 수 있다.

스프링의 트랜잭션 기능에 대한 자세한 내용은 Chapter 11, 트랜잭션 관리장을 참고해라.

2014/04/18 02:37 2014/04/18 02:37

관련 글