반응형
테이블의 각각의 레코드에 대해 객체가 하나씩 존재하는 로우 데이터 게이트웨이 패턴에 대해서 살펴본다.
로우 데이터 게이트웨이
'로우 데이터 게이트웨이(Row Data Gateway)'는 데이터베이스 테이블의 한 레코드를 정확하게 묘사하는 객체의 역할을 한다. 앞서 살펴봤었던 '데이터 소스 아키텍처 패턴 1 - 테이블 데이터 게이트웨이'는 테이블의 모든 행에 대해서 CRUD 작업을 수행하는 반면에, 로우 데이터 게이트웨이는 오직 관련된 하나의 행에 대해서만 작업을 수행한다. (이 패턴의 구현에 대해서는 자바캔에 필자가 기재한 'JSP, 자바빈 컴포넌트 그리고 데이터베이스'에도 명시되어 있으니 참고하기 바란다.)
구현 방법
로우 데이터 게이트웨이를 구현하기 위해서는 보통 다음과 같은 두 가지 클래스를 필요로 한다.
먼저 로우 데이터 게이트웨이 클래스의 구현에 대해서 살펴보자. 로우 데이터 게이트웨이 클래스는 테이블의 필드를 나타내는 프로퍼티를 포함하고 있으며, 데이터의 삽입(insert), 변경(update), 삭제(delete) 쿼리를 수행해주는 메소드를 제공한다. 예를 들어, 회원 테이블인 MEMBER 테이블에 대한 로우 데이터 게이트웨이 클래스는 다음과 같이 작성할 수 있다.
로우 데이터 게이트웨이 클래스에서는 SELECT 쿼리를 사용해서 객체를 읽어오는 메소드가 존재하지 않는데, 행에 해당하는 객체를 생성하는 부분은 찾기 클래스에서 수행한다. 찾기 클래스는 다음과 같이 구현한다.
MemberFind 클래스는 두 가지 종류의 find() 메소드를 제공하는 데, 하나는 Primary Key를 사용하여 정확하게 하나의 행에 해당하는 MemberGateway 객체를 리턴하는 것이고, 다른 하나는 주어진 조건에 따라서 여러 행을 읽어와 MemberGateway의 리스트를 리턴하는 것이다. (두번째 find() 메소드의 Filter에 대한 내용은 이 시리즈의 이전 기사인 "데이터 소스 아키텍처 패턴 1 - 테이블 데이터 게이트웨이"에서 설명한 바 있으니 참고하기 바란다.)
테이블 로우 게이트웨이 클래스나 찾기 클래스의 CRUD 관련 메소드가 사용할 데이터베이스 Connection을 어떻게 전달할 것인가의 문제는 앞선 기사인 "데이터 소스 아키텍처 패턴 1 - 테이블 데이터 게이트웨이"에서 설명한 바 있다.
사용 방법
로우 데이터 게이트웨이는 트랜잭션 스크립트 뿐만 아니라 도메인 모델과도 잘 어울리게 사용할 수 있다. 먼저 트랜잭션 스크립트의 경우는 데이터베이스 처리를 로우 데이터 게이트웨이가 처리해주기 때문에 비즈니스 로직에만 신경쓸 수 있게 된다. 예를 들어, 회원 가입과 관련된 코드를 JSP에서 처리할 때, 로우 데이터 게이트웨이를 사용한다면 다음과 같이 작성할 수 있을 것이다.
각 테이블마다 그에 해당하는 찾기 클래스와 로우 데이터 게이트웨이를 작성해주어야 한다는 점이 불편할 수도 있는데 이 문제는 코드 자동화 툴을 사용해서 해결할 수 있다. 예를 들어, 테이블의 스키마로부터 자동으로 그에 해당하는 로우 데이터 게이트웨이 클래스를 생성해주는 도구가 있다고 가정해보자. 이 경우 개발자는 테이블과 관련된 코드는 거의 작성하지 않아도 되므로 좀더 많은 시간을 비즈니스 로직을 처리하는 코드에 투자할 수 있게 된다.
도메인 모델을 사용하는 경우에도 로우 데이터 게이트웨이를 사용할 수 있다. 예를 들어, 도메인 영역의 회원을 나타내는 클래스를 Member라고 할 경우, Member 클래스는 다음과 같이 작성할 수 있을 것이다.
도메인 영역의 객체는 내부적으로 게이트웨이를 통해서 데이터에 접근하는 방식을 취한다. 하지만, 도메인 영역의 객체와 데이터베이스 테이블의 필드가 1-1 매핑이 되지 않는 경우에는 위와 같은 방식은 잘 들어맞지 않기 때문에, 테이블과 도메인 객체 사이에 1-1매핑이 되지 않을 때에는 테이블과 도메인 객체 사이의 매핑을 처리해주는 객체인 매퍼(Mapper)를 사용하는 것이 좋다. 매퍼 패턴에 대해서는 이 시리즈의 다음 글에서 살펴볼 것이다.
필자의 경우는 앞서 살펴봤던 테이블 데이터 게이트웨이와 로두 데이터 게이트웨이를 모두 사용해봤었는데, 두 패턴 모두 거의 비슷한 개발 효율을 보여주었다. 물론, 로우 데이터 게이트웨이의 경우 코드 자동화 처리가 더 쉽기 때문에 데이터 베이스와 관련된 코드를 작성하는 시간을 줄일 수 있다는 장점을 갖고 있다. 하지만 이 장점을 제외하면 비즈니스 로직 코드를 작성하는 데 있어서 테이블 데이터 게이트웨이를 사용하든 로우 데이터 게이트웨이를 사용하든 비슷한 업무 효율을 나타낸다.
로우 데이터 게이트웨이와 액티브 레코드(Active Record)의 차이
마틴 파울러의 '엔터프라이즈 어플리케이션 아키텍처 패턴' 책에는 로우 데이터 게이트웨이와 거의 동일한 액티브 레코드 패턴을 소개하고 있다. 마틴 파울러의 정의에 의하면 액티브 레코드는 로우 데이터 게이트웨이와 CRUD 기능을 제공하는 것은 동일하나 추가적으로 도메인 로직까지 제공한다는 점이 다르다. 로우 데이터 게이트웨이의 경우는 도메인 로직을 전혀 포함하지 않고 오직 CRUD와 관련된 기능을 제공한다고 정의하고 있다.
필자의 경우는 액티브 레코드의 경우는 선호하지 않는 편이다. CRUD를 처리하는 기능과 도메인 로직을 처리하는 기능이 함께 섞여 있을 경우 코드가 복잡해질 뿐만 아니라 유지보수할 때에도 용이하지 않기 때문이다. 가급적이면 액티브 레코드를 사용하지 말 것을 권한다.
관련링크:
로우 데이터 게이트웨이
본 글에서 소개하는 패턴은 '엔터프라이즈 어플리케이션 아키텍처 패턴' (마틴 파울러 저)에 소개된 패턴으로서, 필자는 파울러가 소개한 패턴을 보다 쉽게 이해할 수 있도록 재구성한 것이다. |
'로우 데이터 게이트웨이(Row Data Gateway)'는 데이터베이스 테이블의 한 레코드를 정확하게 묘사하는 객체의 역할을 한다. 앞서 살펴봤었던 '데이터 소스 아키텍처 패턴 1 - 테이블 데이터 게이트웨이'는 테이블의 모든 행에 대해서 CRUD 작업을 수행하는 반면에, 로우 데이터 게이트웨이는 오직 관련된 하나의 행에 대해서만 작업을 수행한다. (이 패턴의 구현에 대해서는 자바캔에 필자가 기재한 'JSP, 자바빈 컴포넌트 그리고 데이터베이스'에도 명시되어 있으니 참고하기 바란다.)
구현 방법
로우 데이터 게이트웨이를 구현하기 위해서는 보통 다음과 같은 두 가지 클래스를 필요로 한다.
- 객체 찾기 클래스 - 테이블의 특정 행에 대한 게이트웨이 객체를 생성해주는 클래스. 보통 이름이 ~Finder 로 끝난다.
- 게이트웨이 클래스 - 테이블의 특정 행의 값을 표현하는 클래스. 테이블의 각 필드에 해당하는 프로퍼티를 갖는다.
먼저 로우 데이터 게이트웨이 클래스의 구현에 대해서 살펴보자. 로우 데이터 게이트웨이 클래스는 테이블의 필드를 나타내는 프로퍼티를 포함하고 있으며, 데이터의 삽입(insert), 변경(update), 삭제(delete) 쿼리를 수행해주는 메소드를 제공한다. 예를 들어, 회원 테이블인 MEMBER 테이블에 대한 로우 데이터 게이트웨이 클래스는 다음과 같이 작성할 수 있다.
public class MemberGateway {
// 프로퍼티는 테이블의 필드와 매칭
private String id;
private String name;
private String email;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
// 행과 관련된 Insert, Update, Delete 처리 메소드
public void insert() throws SQLException {
Connection conn = null;
PreparedStatement pstmt = null;
try {
conn = ...
pstmt = conn.prepareStatement(
"insert into MEMBER (ID, NAME, EMAIL) values (?, ?, ?)");
pstmt.setString(1, id);
pstmt.setString(2, name);
pstmt.setString(3, email);
pstmt.executeUpdate();
} finally {
...
}
}
public void update() throws SQLException {
..
try {
conn = ...
pstmt = conn.prepareStatement(
"update MEMBER set NAME = ?, EMAIL = ? where ID = ?");
pstmt.setString(1, name);
pstmt.setString(2, email);
pstmt.setString(3, id);
pstmt.executeUpdate();
} finally {
...
}
}
public void delete() throws SQLException {
...
}
}
// 프로퍼티는 테이블의 필드와 매칭
private String id;
private String name;
private String email;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
// 행과 관련된 Insert, Update, Delete 처리 메소드
public void insert() throws SQLException {
Connection conn = null;
PreparedStatement pstmt = null;
try {
conn = ...
pstmt = conn.prepareStatement(
"insert into MEMBER (ID, NAME, EMAIL) values (?, ?, ?)");
pstmt.setString(1, id);
pstmt.setString(2, name);
pstmt.setString(3, email);
pstmt.executeUpdate();
} finally {
...
}
}
public void update() throws SQLException {
..
try {
conn = ...
pstmt = conn.prepareStatement(
"update MEMBER set NAME = ?, EMAIL = ? where ID = ?");
pstmt.setString(1, name);
pstmt.setString(2, email);
pstmt.setString(3, id);
pstmt.executeUpdate();
} finally {
...
}
}
public void delete() throws SQLException {
...
}
}
로우 데이터 게이트웨이 클래스에서는 SELECT 쿼리를 사용해서 객체를 읽어오는 메소드가 존재하지 않는데, 행에 해당하는 객체를 생성하는 부분은 찾기 클래스에서 수행한다. 찾기 클래스는 다음과 같이 구현한다.
public class MemberFinder {
public MemberGateway find(String id) throws SQLException, MemberNotFoundException {
..
try {
conn = ...
pstmt = conn.prepareStatement(
"select ID, NAME, EMAIL from MEMBER where ID = ?");
pstmt.setString(1, id);
rs = pstmt.executeQuery();
if (rs.next()) {
MemberGateway member = new MemberGateway();
... // rs의 내용을 member에 복사
return member;
} else {
throw new MemberNotFoundException(..);
}
} finally {
...
}
}
public List find(Filter filter) throws SQLException {
...
}
}
public MemberGateway find(String id) throws SQLException, MemberNotFoundException {
..
try {
conn = ...
pstmt = conn.prepareStatement(
"select ID, NAME, EMAIL from MEMBER where ID = ?");
pstmt.setString(1, id);
rs = pstmt.executeQuery();
if (rs.next()) {
MemberGateway member = new MemberGateway();
... // rs의 내용을 member에 복사
return member;
} else {
throw new MemberNotFoundException(..);
}
} finally {
...
}
}
public List find(Filter filter) throws SQLException {
...
}
}
MemberFind 클래스는 두 가지 종류의 find() 메소드를 제공하는 데, 하나는 Primary Key를 사용하여 정확하게 하나의 행에 해당하는 MemberGateway 객체를 리턴하는 것이고, 다른 하나는 주어진 조건에 따라서 여러 행을 읽어와 MemberGateway의 리스트를 리턴하는 것이다. (두번째 find() 메소드의 Filter에 대한 내용은 이 시리즈의 이전 기사인 "데이터 소스 아키텍처 패턴 1 - 테이블 데이터 게이트웨이"에서 설명한 바 있으니 참고하기 바란다.)
테이블 로우 게이트웨이 클래스나 찾기 클래스의 CRUD 관련 메소드가 사용할 데이터베이스 Connection을 어떻게 전달할 것인가의 문제는 앞선 기사인 "데이터 소스 아키텍처 패턴 1 - 테이블 데이터 게이트웨이"에서 설명한 바 있다.
사용 방법
로우 데이터 게이트웨이는 트랜잭션 스크립트 뿐만 아니라 도메인 모델과도 잘 어울리게 사용할 수 있다. 먼저 트랜잭션 스크립트의 경우는 데이터베이스 처리를 로우 데이터 게이트웨이가 처리해주기 때문에 비즈니스 로직에만 신경쓸 수 있게 된다. 예를 들어, 회원 가입과 관련된 코드를 JSP에서 처리할 때, 로우 데이터 게이트웨이를 사용한다면 다음과 같이 작성할 수 있을 것이다.
<jsp:useBean id="member" class="MemberGateway">
<jsp:setProperty name="member" property="*" />
</jsp:useBean>
<%
// 값 검증 코드
...
member.insert();
%>
<jsp:setProperty name="member" property="*" />
</jsp:useBean>
<%
// 값 검증 코드
...
member.insert();
%>
각 테이블마다 그에 해당하는 찾기 클래스와 로우 데이터 게이트웨이를 작성해주어야 한다는 점이 불편할 수도 있는데 이 문제는 코드 자동화 툴을 사용해서 해결할 수 있다. 예를 들어, 테이블의 스키마로부터 자동으로 그에 해당하는 로우 데이터 게이트웨이 클래스를 생성해주는 도구가 있다고 가정해보자. 이 경우 개발자는 테이블과 관련된 코드는 거의 작성하지 않아도 되므로 좀더 많은 시간을 비즈니스 로직을 처리하는 코드에 투자할 수 있게 된다.
도메인 모델을 사용하는 경우에도 로우 데이터 게이트웨이를 사용할 수 있다. 예를 들어, 도메인 영역의 회원을 나타내는 클래스를 Member라고 할 경우, Member 클래스는 다음과 같이 작성할 수 있을 것이다.
public class MemberImpl implements Member {
private MemberGateway data;
public Member(MemberGateway data) {
this.data = data;
}
public String getId() {
// 데이터 읽기/쓰기 기능을 게이트웨이로 위임
return data.getId();
}
...
}
private MemberGateway data;
public Member(MemberGateway data) {
this.data = data;
}
public String getId() {
// 데이터 읽기/쓰기 기능을 게이트웨이로 위임
return data.getId();
}
...
}
도메인 영역의 객체는 내부적으로 게이트웨이를 통해서 데이터에 접근하는 방식을 취한다. 하지만, 도메인 영역의 객체와 데이터베이스 테이블의 필드가 1-1 매핑이 되지 않는 경우에는 위와 같은 방식은 잘 들어맞지 않기 때문에, 테이블과 도메인 객체 사이에 1-1매핑이 되지 않을 때에는 테이블과 도메인 객체 사이의 매핑을 처리해주는 객체인 매퍼(Mapper)를 사용하는 것이 좋다. 매퍼 패턴에 대해서는 이 시리즈의 다음 글에서 살펴볼 것이다.
필자의 경우는 앞서 살펴봤던 테이블 데이터 게이트웨이와 로두 데이터 게이트웨이를 모두 사용해봤었는데, 두 패턴 모두 거의 비슷한 개발 효율을 보여주었다. 물론, 로우 데이터 게이트웨이의 경우 코드 자동화 처리가 더 쉽기 때문에 데이터 베이스와 관련된 코드를 작성하는 시간을 줄일 수 있다는 장점을 갖고 있다. 하지만 이 장점을 제외하면 비즈니스 로직 코드를 작성하는 데 있어서 테이블 데이터 게이트웨이를 사용하든 로우 데이터 게이트웨이를 사용하든 비슷한 업무 효율을 나타낸다.
로우 데이터 게이트웨이와 액티브 레코드(Active Record)의 차이
마틴 파울러의 '엔터프라이즈 어플리케이션 아키텍처 패턴' 책에는 로우 데이터 게이트웨이와 거의 동일한 액티브 레코드 패턴을 소개하고 있다. 마틴 파울러의 정의에 의하면 액티브 레코드는 로우 데이터 게이트웨이와 CRUD 기능을 제공하는 것은 동일하나 추가적으로 도메인 로직까지 제공한다는 점이 다르다. 로우 데이터 게이트웨이의 경우는 도메인 로직을 전혀 포함하지 않고 오직 CRUD와 관련된 기능을 제공한다고 정의하고 있다.
필자의 경우는 액티브 레코드의 경우는 선호하지 않는 편이다. CRUD를 처리하는 기능과 도메인 로직을 처리하는 기능이 함께 섞여 있을 경우 코드가 복잡해질 뿐만 아니라 유지보수할 때에도 용이하지 않기 때문이다. 가급적이면 액티브 레코드를 사용하지 말 것을 권한다.
관련링크: