아키텍처

도메인 로직(1)

아키텍쳐의 3가지 대표적인 레이어(프레젠테이션, 도메인, 데이터 리소스) 중 도메인 레이어에 대해 배워봅시다!

 

도메인 레이어의 패턴은 총 세 가지로 나뉩니다.

  • 트랜잭션 스크립트
  • 도메인 모델
  • 테이블 모듈

어느 것이 가장 좋다 보다는, 그 상황에서 가장 좋은 패턴을 채택하기 때문에 세 가지의 경우를 알아봅시다.

(참고로 테이블 모듈의 경우 .NET에서 사용하기 때문에, 자바진영 개발자는 넘어가셔도 무방합니다.)

 

 

 

1. 트랜잭션 스크립트

비즈니스 로직를 프로시저 별로 구성해 각 프로시저가 프레젠테이션의 단일 요청 처리.
스크립트 : 프레젠테이션 입력 -> 도메인 로직 동작 -> 데이터 원본에 저장 -> 다른 시스템에서 잔여 작업 호출
              -> 응답 생성 -> 응답 서식을 결정하는 계산 수행 -> 프레젠테이션 응답.

 

트랜잭션 스크립트는 모든 로직를 단일 프로시저로 구성하는 절차 지향적 성향을 띄는 패턴입니다.

이 패턴은 데이터베이스를 직접 또는 씬 데이터베이스 래퍼를 통해 호출합니다.

 

작동 원리

모든 로직를 단일 프로시저로 구성한다는 말이 무엇일까요? 하나의 작업에 모든 로직들이 다 들어가 있다는 것입니다. ATM기를 예시로 들어봅시다.

 

동네 작은 햄버거 가게

동네에 아는 사람만 아는 햄버거 가게가 있습니다. 왠만한 규모의 매장이 아니라면, 햄버거 조리사는 한명이 맡아서 진행을 합니다. 햄버거 하나를 주문해 봅시다.

햄버거 제작
1. 재고_확인()
2. 빵_굽기()
3. 패티_올리기()
4. 소스_뿌리기()
5. 양상추_올리기()
6. 치즈_올리기()
7. 피클_올리기()
8. 양파_올리기()
9. 빵 덮기()

조리사는 지정된 레시피대로 햄버거를 제작하고 있습니다! 이렇게 햄버거와 관련된 모든 로직들을 제작이라는 하나의 과정으로 묶어 절차대로 진행하는 것이 바로 절차지향의 특성을 가지는 트랜잭션 스크립트입니다!

중앙 집중식 제어 스타일

 

 

번외로 만약 조리사가 실수로 다른 버거를 조리했다고 해보죠. 그렇다면 빵을 굽는 것 부터 새로 시작하게 될 것입니다!

이렇게 햄버거를 성공적으로 만들어야 손님에게 제공(커밋)하고, 그렇지 않으면 새로 만들기 시작(롤백)할 것입니다.

이러한 점이 트랜잭션 스크립트의 특징 중 하나입니다. (All or Nothing) 정확히 말하면 

try {
    주문 받기(트랜잭션 생성)
    만들기 시작(트랜잭션 시작)
    
    재고_확인()
	빵_굽기()
	패티_올리기()	// 어이쿠 실수! -> 새로 만들어!
	소스_뿌리기()
	양상추_올리기()
	치즈_올리기()
	피클_올리기()
	양파_올리기()
	빵 덮기()
    
    햄버거 제공(트랜잭션 커밋)         
} catch(..) {
    새로 만들어!(롤백)
} finally {
    뒷정리(트랜잭션 종료)
}

 

 

장/단점

트랜잭션 스크립트의 가장 큰 장점은 단순함입니다! 하나의 작업에 모든 로직들이 들어가 있으니 오버헤드도 적고 전체적인 과정을 훑어볼 수 있습니다.

하지만, 로직이 복잡해질수록 좋은 상태를 유지하기가 어렵다는 것이 단점입니다. (코드가 난잡해진다.)

그나마 중복되는 코드가 있는 경우 공통 모듈로 분리해 사용할 수 있으나, 개발자들이 바라는 객체지향까지는 미치지 못합니다. 그래서 다음에 설명하게 될 도메인 모델을 차용하기 시작합니다.

 

또한 해당 스크립트가 트랜잭션 시작과 함께 시작해 닫기와 함께 종료되어 트랜잭션 경계 설정이 쉽습니다.

추가적으로 행 데이터 게이트웨이, 테이블 데이터 게이트웨이를 적용해 데이터 원본 계층과 함께 사용하면 좋습니다.

 

그래도 무조건적으로 이 패턴을 무시하는 것은 피하는 것이 좋습니다!

닭 잡는데 소 잡는 칼라는 말이 있듯이 우리가 구현해야 할 문제들 중 간단한 작업들도 많고 이들을 구현하는 데는 간단한 방식이면 충분하기 때문입니다!(적절한 선택으로 비용 감소)

추가적으로 트랜잭션 스크립트는 얼마나 모듈화를 잘하느냐에 따라 효율이 천차만별이기 때문에 꼭 트랜잭션 스크립트 방식을 고려해보고 다음 선택지로 넘어가는 것이 좋습니다!

 

 

 

 

2. 도메인 모델

트랜잭션 스크립트의 문제를 객체 지향(프로세스와 데이터를 하나로 합쳐 생각)을 사용해 해결하는 방법이 도메인 모델 패턴입니다! 객체 간의 복잡한 연관 관계를 구성할 수 있고, 상속 등을 통해 객체의 기능과 역할의 확장성이 더욱 넓어집니다!

 

도메인 모델은 총 세 단계의 과정을 거칩니다.

  • 명사와 동사를 통해 비즈니스 영역에서 사용되는 객체 판별 
  • 객체가 제공해야 할 목록을 추출 (정보 추출)
  • 객체 간의 관계 정립

 

도메인 모델 종류

주로 두 가지 형식의 도메인 모델이 사용됩니다.

 

1) 단순 도메인 모델

DB 테이블과 도메인 객체가 1대 1 대응(같다고 보시면 됩니다.)을 갖는 단순한 형태입니다. 이 모델에서는 하나의 테이블에 해당하는 하나의 자바 빈 객체가 존재하는 게 보통이며, 자바 빈 객체에 프로세스가 함께 포함되기도 하고, 프로세스를 처리하는 객체를 생성하기도 합니다. 

 

2) 리치 도메인 모델

상속, 전략, 등 다양한 패턴을 사용하고 각각의 객체가 연결된 복잡한 그물망을 갖는 구조입니다. 이 모델은 복잡한 로직를 나타내는 데 적합하나, 객체를 판별하고, 연관관계를 유추하는 등의 작업을 통해 객체를 판별하기 때문에 DB와 매핑하기는 더 어렵습니다.

단순 도메인 모델의 경우 활성 레코드를 사용할 수 있으나, 리치 도메인 모델에서는 데이터 매퍼(ORM) 패턴이나 보조 유틸리티가 필요합니다.

 

도메인 모델 구현

도메인 모델 구현의 최대 관건은 두 가지입니다.

  • 매핑을 어떻게 처리할 것인가?
  • 이 계층을 손쉽게 수정, 구축, 테스트할 수 있게 만들었나?

전자의 경우 웹 애플리케이션의 대부분 비즈니스 로직은 CRUD와 관련되어 있기 때문에, 비즈니스 로직을 자바 객체로 표현한 도메인 모델에 알맞은 데이터베이스 매핑 패턴을 사용하는 것이 좋습니다. (데이터 매퍼 패턴이 가장 최적의 방법)

 

후자의 경우 애플리케이션(시스템) 간의 결합도나 의존성을 최소화해야 합니다. 이에 대해서는 객체 지향 개념에 기반한 설계를 익혀야 합니다.

 

도메인 모델을 구현하면서 걱정하는 것이 도메인 [객체가 과하게 비대해지지는 않았는가?]인데, 이를 미리 방지하기 위해서 이벤트 클래스를 따로 생성하는 것으로 생긴 중복 오류가 오히려 객체의 비대함보다 더 치명적인 문제가 될 수 있습니다. 때문에 특정한 동작을 분리하려 하지 말고 가장 적당한 객체에 모두 넣은 후 비대해졌다면,  그 후 고민해도 늦지 않습니다!

 

장/단점

도메인 모델은 객체 지향에 기반했기 때문에 확장성과 재사용성이 좋습니다. 처음 구축하는 것이 난이도가 있으나, 일단 도메인 모델을 구축하고 나면 언제든지 필요에 따라 확장과 재사용이 가능합니다. 

유효성 검사, 계산 등 복잡하고 동적인 비즈니스 로직을 구현해야 한다면 도메인 모델을 채택하는 것이 좋습니다.

 

도메인 모델의 단점은 장점에서 얘기했다시피 구축하는 데 많은 비용(시간, 노력, 등)이 들어간다는 것입니다. 객체를

판별하고, 객체 간 관계를 정립해야 하며, 마지막으로 가장 중요한 객체와 DB 사이의 매핑에 대해서 고민해야 하기

때문입니다. 이 과정은 정말 이론과 경험이 빠삭하지 않으면 쉽게 풀 수 없는 문제이기 때문에, 도메인 모델에 능숙한

팀원이 없을 경우 도메인 모델을 구축하는 것 자체가 힘들어질 수도 있습니다.

따라서 애플리케이션의 수명(사용기간), 데드라인이 짧거나, 정말 단순한 비즈니스 로직만 필요한 경우 도메인 모델을

섣불리 채택하는 것은 오히려 개발 일정에 부담을 줄 수 있으므로 여러 요인들을 고려해서 도메인 모델의 사용 여부를

결정하는 것이 좋습니다.

 

 

3. 테이블 모듈

DB 테이블이나 뷰의 모든 행에 대한 비즈니스 로직을 처리하는 단일 인스턴스

MS의 닷넷(.NET)에서 주로 사용하는 설계패턴입니다. 자바진영에서는 사용하지 않기 때문에 자바 개발자라면 그냥 이런게 있구나만 알고 넘어가시면 됩니다.

 

테이블 모듈은 데이터베이스의 테이블당 하나의 클래스로 도메인 로직를 구성하고, 클래스의 한 인스턴스가 해당 데이터에 대해 수행하는 여러 프로시저를 포함하도록 만들었습니다. 이러한 구조로 트랜잭션과 1대 1 대응을 하던 도메인 모델과 다르게 테이블 모듈은 모든 트랜잭션을 객체 하나가 처리하게 됩니다.

 

일반적으로 도메인 모델을 사용하는 경우 기본 테이블의 각 행마다 인스턴스를 만들지만(행 단위), 테이블 모듈은 식별자의 개념이 없어 단 하나의 인스턴스(테이블 단위)만으로도 해당 테이블의 트랜잭션을 처리할 수 있게 되었습니다.

이를 통해 캡슐화를 가능케 합니다.

 

객체와 DB 테이블이 상대적으로 비슷하다면 도메인 모델 + 활성 레코드의 조합과 테이블 모듈 중 하나를 고르는 것이 좋습니다. 물론 자바는 비슷하지 않기 때문에 테이블 모듈의 사용률이 저조합니다.

 

구현

테이블 모듈은 테이블 기반의 기본 자료구조와 함께 사용하는 것이 가장 일반적입니다.

이 자료구조들을 SQL 테이블로 흉내내어 저장해 사용하게 되는 것이죠. 이러한 자료구조들을 레코드 집합이라고 합니다. 테이블 모듈은 이 데이터를 대상으로 작업하는 메서드 기반 인터페이스를 제공합니다.

동작을 테이블과 함께 그룹화함으로써 동작과 그 동작의 대상이 되는 데이터를 하나로 묶는 캡슐화의 장점도 제공할 수 있게 됩니다.

 

장/단점

1) 레코드 집합을 사용해 테이블 형식의 데이터에 접근할 때 가장 적합한 패턴입니다.

행 단위로 인스턴스를 생성하는게 아닌 테이블 단위로 생성되기 때문에, DB의 여러 행에 적용되는 로직를 쉽게 작성할 수 있습니다. (= 테이블을 기준으로 짯기 때문에 DB 관리가 쉽다.)

 

지정된 사용자에 대한 프로필을 생성하는 예를 들어봅시다.

해당 속성을 모두 지정해 일련의 INSERT 문으로 변환하는 코드를 작성할 수 있습니다.

$table->setUserProfile( $userid, array('firstname'=>'Kevin', 'lastname'=>'Loney') );

 

마찬가지로 주어진 프로필을 쿼리하면 테이블 모듈을 사용해 쿼리 결과 집합의 여러 행을 객체에 매핑하게 됩니다.

$hashArray = $table->getUserProfile( $userid );

이렇게 쿼리 조작에도 유리합니다.

이 특성을 통해 데이터와 동작을 하나로 묶으면서도, 동시에 관계형 DB의 장점을 그대로 활용할 수 있습니다.

 

단점으로는 복잡한 로직를 구성하는 객체의 특성을 완전히 활용하지는 못한다는 점입니다.

직접적인 인스턴스-인스턴스 관계를 만들 수 없고, 다형성을 활용하지 못하게 됩니다.

따라서 복잡한 도메인 로직를 처리해야 한다면 도메인 모델을 선택하는 것이 좋습니다.

 

 

참고자료

도서 : 엔터프라이즈 애플리케이션 아키텐처 패턴 [마틴 파울러]

'아키텍처' 카테고리의 다른 글

도메인 로직(2) : 서비스 계층  (0) 2021.10.28