애플리케이션 아키텍처

클라이언트와 백엔트 시스템의 종류와 사용 기술, 연동 방법을 결정했다면 시스템 레벨의 아키텍처는 대략 구성된 셈이다. 다음으로 결정할 사항은 스프링 웹 애플리케이션의 아키텍처다.

아키텍처는 여러 가지 방식으로 정의되고 이해될 수 있는 용어다. 가장 단순한 정의로는 어떤 경계 안에 있는 내부 구성요소들이 어떤 책임을 갖고 있고, 어떤 방식으로 서로 관계를 맺고 동작하는지를 규정하는 것이라고 할 수 있다. 아키텍처는 단순히 정적인 구조를 나타내는 것으로만 생각하기 쉽지만 실제로는 그 구조에서 일어나는 동적인 행위와 관계가 있다.

계층형 아키텍처

아키텍처와 SoC

주로 오브젝트 레벨에서 관심, 책임, 성격, 변하는 이유와 방식에 따라 분리의 문제에 대해 생각해봤다. 유연한 결합을 갖도록 인터페이스를 두고, 그 관계를 맺어주는 제 3의 존재인 DI 컨테이너를 둬서 오브젝트는 직접적인 관계를 알지 못하도록 만드는 것이 DI 기반의 유연한 설계와 전략이었다.

서비스 오브젝트들은 특정 기술과 환경에 종속되지 않으면서 도메인의 업무에는 밀접하게 관련을 갖고 있는 POJO로 만들어진다. 주로 DAO를 이용해 정보의 저장과 검색을 하고, DB의 엔티티 모델과 유사한 도메인 오브젝트를 이용해 데이터를 저장하고 가공하고 주고받는다. 서블릿 컨테이너로부터 받은 사용자의 요청정보를 해석해서 그것을 POJO 기반의 서비스 오브젝트에 전달해주고, 결과를 받아서 다시 웹 사용자 인터페이스에 표시 가능한 형태로 변환해주는 일을 한다.

책임과 성격이 다른 것을 크게 그룹으로 분리해두는 것을 아키텍처 차원에서는 계층형 아키텍처(layered architecture)라고 부른다. 또는 계층의 의미를 가진 Tier라는 단어를 이용해 멀티 티어 아키텍처라고도 한다. 웹 기반 엔터프라이즈 애플리케이션은 일반적으로 세 개의 계층을 갖는다고 해서 3계층(3-tier or 3-layer)애플리케이션이라고 한다.

3계층 아케텍처와 수직 계층

3계층 아키텍처는 백엔드의 DB나 레거시 시스템과 연동하는 인터페이스 역할을 하는 데이터 액세스 계층, 비즈니스 로직을 담고 있는 서비스 계층, 주로 웹 기반의 UI를 만들어내고 그 흐름을 관리하는 프레젠테이션 계층으로 구분한다.

  • 프레젠테이션 계층 : 웹 계층, MVC 계층, UI 계층
  • 서비스 계층 : 매니저 계층, 비즈니스 로직 계층
  • 데이터 액세스 계층 : DAO 계층, EIS 계층

데이터 액세스 계층

DAO 패턴을 보편적으로 사용하기 때문에 DAO 계층이라고도 불린다. ERP, 레거시 시스템, 메인 프레임 등에 접근하는 역할을 하기 때문에 EIS(Enterprise Information System) 계층이라고도 한다.

대게는 장기적인 데이터 저장을 목적으로 하는 DB 이용이 주된 책임이다. 외부 시스템을 호출해서 서비스를 이용하는 것은 기반(infrastructure)계층으로 따로 분류하기도 한다.

데이터 액세스 계층은 사용 기술에 따라서 다시 세분화된 계층으로 구분될 수 있다. 담당 역할에 따라 분류하는 애플리케이션 계층과 달리 데이터 액세스 계층은 추상화 수준에 따라 분류한다. 그래서 수직적인 계층이라고 부르기도 한다.

그림으로 나타낼 때 담당하는 역할에 따른 구분은 가로로 그린다. 같은 책임은 가졌지만 추상화 레벨에 따라 구분하는 경우는 세로로 배열해서 표현한다. 계층이라는 말은 각각 다른 의미와 상황에서 쓰일 수 있으니 문맥에 주의하여 이해해야 한다.

새로운 추상 계층을 도입해서 하위 계층의 종류가 다른 서비스를 일관된 방식으로 접근하게 할 수 있다. 새로운 계층을 추가하면 개발자의 애플리케이션 코드에 지대한 영향을 주기 때문에 신중하게 결정해야 한다. 새로운 계층과 API를 만들면 이를 최대한 유지할 수 있도록 하위 계층의 변화에 대응해야 하는 책임도 갖게 된다.

코드의 사용 패턴을 잘 분석하고 정리하면 새로운 추상 계층의 도입을 고려해볼 수 있다. 추상 계층을 새로 추가하는 것은 부담스럽고 하위 계층의 API를 활용할 필요가 있다면, 공통적인 기능을 분리해서 유틸리티나 헬퍼 메소드 또는 오브젝트로 제공해주는 것도 좋은 방법이다.

서비스 계층

서비스 계층은 잘 만들어진 스프링 애플리케이션이라면 POJO로 작성된 가장 단순한 계층이다. 객체지향적인 설계를 통해 비즈니스 로직의 핵심을 잘 담아내고 쉽게 테스트하고 유연하게 확장할 수 있다.

서비스 계층은 특별한 경우가 아니라면 추상화 수직 계층 구조를 가질 필요가 없다. 단순히 POJO 레벨에서 비즈니스 로직의 상속구조를 만들 수 있더라도 기술 API를 직접 다루는 코드가 아니기 때문에 기술에 일관된 방식으로 접근하게 하거나 편하게 해주는 추상화는 필요 없다.

기반 서비스 계층이 서비스 계층의 오브젝트를 호출하는 경우도 있다. 스케줄링 같은 경우에는 정해진 시간에 특정 서비스 계층의 로직이 동작하게 만든다.

원칙적으로는 서비스 계층 코드가 기반 서비스 계층의 구현에 종속되면 안 된다. 서비스 계층의 코드는 추상화된 기반 서비스 인터페이스를 통해서만 접근하도록 만들어서 특정 구현과 기술에 대한 종속성을 제거해야 한다. 또는 AOP를 통해서 서비스 계층의 코드를 침범하지 않고 부가기능을 추가하는 방법을 활용해야 한다.

이상적인 서비스 계층은 데이터 액세스 계층 또는 프레젠테이션 계층이 변경되어도 그대로 유지될 수 있어야 한다. 엔터프라이즈 애플리케이션에서 가장 중요한 자산은 도메인 핵심 비즈니스 로직이 들어 있는 서비스 계층이어야 한다.

프레젠테이션 계층

프레젠테이션 계층은 가장 복잡한 계층이다. 왜냐하면 매우 다양한 기술과 프레임워크의 조합을 가질 수 있기 때문이다. 웹과 프레젠테이션 기술은 끊임없이 발전하며 새로운 모델이 나오기 때문이다. 엔터프라이즈 애플리케이션의 프레젠테이션 계층은 클라이언트와 종류에 상관없이 HTTP 프로토콜을 사용하는 서블릿이 바탕이 된다.

프레젠테이션 계층은 다른 계층과 달리 클라이언트까지 그 범위를 확장될 수도 있다. 모든 프레젠테이션 로직은 서버의 프레젠테이션 계층의 컴포넌트에서 처리된다. 화면의 흐름, 입력 값의 검증, 서비스 계층의 호출, 전달 값의 포맷 전환, 뷰의 표현 로직 등 모두 서버의 프레젠테이션 계층에서 처리됐었다. 최그 점점 많은 프레젠테이션 로직이 클라이언트로 이동하고 있다. RIA(Rich Internet Application) 또는 SOFEA(Service Oriented Front End Architecture)가 대표적인 예다.

스프링은 웹 기반의 프레젠테이션 계층을 개발할 수 있는 전용 웹 프레임워크 및 서드파티 웹 기술을 제공한다.

계층형 아키텍처 설계의 원칙

아키텍처 레벨의 계층에서도 객체지향 설계의 원칙은 동일하게 적용될 수 있다. 각 계층은 응집도(cohesion)가 높은 동시에 낮은 결합도(coupling)을 유지해야 한다.

각 계층은 각자 맡은 책임(역할)에만 충실해야 하고 자신과 관련된 기술이 아닌 다른 기술 API의 사용을 삼가해야 한다.

낮은 결합도를 유지하기 위해 계층 레벨에 정의한 인터페이스를 통해서 각 계층 간의 요청이 전달되게 해야 한다. 계층 간에 사용되는 인터페이스 메소드에는 특정 계층의 기술이 최대한 드러나지 않게 만들어야 한다. 그렇지 않으면 계층 사이에 결합도가 높아지고 기술이나 역할이 서로 침범하는 일이 일어난다.

강한 결합은 유연성이 떨어지기 때문에 각 계층의 내부 구현이 변화되면 다른 계층의 코드도 함께 수정해줘야 한다. 또한 코드의 중복이 일어날 가능성이 높고 전체 코드를 이해하기는 힘들어진다.

실수로 특정 계층의 오브젝트를 그대로 서비스 계층을 전달하면 안된다. 서비스 계층 인터페이스 메소드의 파라미터 타입으로 특정 계층의 타입으로 사용하면 안된다. 계층의 경계를 넘어갈 때는 반드시 특정 계층에 종속되지 않는 오브젝트 형태로 변환해줘야 한다.

웹 방식의 클라이언트가 아닌 다른 시스템에서 요청을 받아서 처리해야 하는 경우에는 웹 기술에 종속된 코드는 재사용이 불가능해진다. 결국 같은 로직을 갖지만 클라이언트에 따라 다른 서비스 인터페이스를 써야할 수 있다. 서비스 계층에서 웹과 관련된 예외가 발생할 수도 있다.

어떤 경우에도 낮은 결합도를 유지하도록 설계해야 한다. 인터페이스를 하나 더 만드는 것이 번거롭다고 그냥 클래스를 이용해서는 안 된다. 인터페이스를 사용한다는 건 각 계층의 경계를 넘어서 들어오는 요청을 명확히 정의하겠다는 의미다. 단순히 자바 interface를 사용하는 것이 아니다. 인터페이스에 아무 생각 없이 클래스의 모든 public 메소드를 추가하라는 의미가 아니다. 다른 계층에서 꼭 필요한 메소드만 노출해야 한다. 한 번 정의되서 다른 계층에서 사용되기 시작한 인터페이스 메소드의 변경은 비용이 많이 든다. 따라서 매우 신중하게 결정해야 하며 계층 내부의 예상되는 변화에도 쉽게 바뀌지 않도록 주의해서 만들어야 한다.

Spring DI는 오브젝트 사이의 관계를 다룬다. 따라서 계층 사이의 경계나 그 관계에 직접적으로 관여하지 않는다. DI는 계층을 구분해주지 않기 때문에 빈 사이의 의존관계를 만들 때 주의해야 한다. 한 계층의 내부에서만 사용되도록 만든 빈 오브젝트가 있는데, 이를 DI를 통해 다른 계층에서 함부로 가져다 쓰는 일은 피해야 한다. 중간 계층을 뛰어넘어 관계를 갖지 않는 계층의 빈을 직접 DI하지 않아야 한다.

애플리케이션 정보 아키텍처

애플리케이션을 사이에 두고 흐르는 정보의 처리를 결정하는 일은 아키텍처를 결정할 때 매우 중요한 기준이 된다. 엔터프라이즈 애플리케이션에 존재하는 정보를 단순히 데이터로 다루는 경우와 오브젝트로 다루는 경우, 두 가지 기준으로 구분할 수 있다.

데이터 중심 아키텍처는 애플리케이션의 정보를 단순히 값이나 값을 담기 위한 목적의 오브젝트 형태로 취급하는 구조다. DB나 백엔드 시스템에서 가져온 정보를 값으로 다루고 그 값을 취급하는 코드로 만들어 로직을 구현하고 값을 그대로 프레젠테이션 계층의 뷰로 연결해 준다. 이런 방식은 객체지향 기술이나 언어를 사용하던 시절의 엔터프라이즈 애플리케이션과 다를 바 없다. 데이터 중심 설계의 특징은 비즈니스 로직이 DB 내부의 프로시저나 SQL에 담겨 있는 경우가 많다. 보통 DB에서 돌려준 내용을 그대로 맵이나 단순 결과 저장용 오브젝트에 넣어서 전달한다.

데이터 중심 아키텍처는 핵심 비즈니스 로직의 존재 위치에 따라 DB 중심 구조와 서비스 중심 구조로 구분할 수 있다.

DB/SQL 중심의 로직 구현 방식

데이터 중심 구조의 특징은 하나의 업무 트랜잭션에 모든 계층의 코드가 종속되는 경향이 있다. 검색을 구현한다고 치자. 검색 조건은 SQL로 만들어지며 SQL을 통해 화면에 출력 될 정보를 알 수 있다. SQL의 결과는 컬럼 이름을 키로 값는 맵에 저장되거나 조회 페이지에 필요한 정보를 담을 수 있는 단순한 오브젝트에 저장돼서 전달된다. 서비스 계층은 별로 할 일이 없다. 뷰는 데이터 액세스 계층이 어떤 필드 값을 반환하고, 어떤 포맷으로 전달할지 알고 있어야 한다. 그 값을 토대로 화면에 출력하기 때문이다.

모든 계층의 코드는 검색 업무에 종속된다. 업무의 내용이 바뀌면 모든 계층의 코드가 함께 변경된다. 종속적이며 동시에 배타적이라서 다른 단위 업무에 사용되기 어렵다. 화면에 나타날 정보가 변경되면 SQL도 달라져야 하기 때문에 새로 만들어야 한다.

대용량 데이터를 다루면서 빠른 처리가 필요한 경우에는 일부 로직을 DB 내에 존재하도록 하는 PL/SQL과 같은 저장 프로시저 형태로 만들기도 한다. 조회 로직이 조건이 많고 복잡하다면 그만큼 복잡한 SQL이 만들어질 것이다. 자바 코드는 사용자의 요청에 따라서 어떤 SQL을 가진 DAO를 실행할지를 결정하는 정도만 수행 할 것이다.

DB 중심의 업무 단위로 코드를 만들면 애플리케이션 내의 흐르는 정보는 항상 단순한 포맷의 데이터가 된다. 데이터를 분석하거나 조작하는 간단한 비즈니스 로직은 항상 SQL 결과에 종속되기 때문에 SQL과 함께 변화한다.

로직을 DB와 SQL에 많이 담으면 담을수록 점점 확장성이 떨어진다. DB는 확장에 한계가 있을 뿐 아니라 확장에 큰 비용이 든다. 잘 작성된 SQL 하나가 수백 라인의 자바 코드가 필요한 비즈니스 로직을 한번에 처리할 수도 있다. 하지만 복잡한 SQL문은 누구나 쉽게 이해하고 필요에 따라 유연하게 변경할 수 없다. 복잡한 SQL을 처리하기 위해 제한된 자원인 DB에 큰 부담을 줄 수 있다.

상대적으로 애플리케이션 서버와 그 안에 담긴 오브젝트는 비용이 적게 든다. 서버를 늘려 쉽게 확장할 수도 있다. 여러 대의 서버를 클러스터로 묶어서 하나의 서버처럼 동작하게 만들 수 있다. 오브젝트를 만들고 코드를 동작시키는 비용은 DB에서 비슷한 작업을 할 때보다 저렴하다. 따라서 로직을 DB보다는 애플리케이션으로 가져오는 편이 유리한 점이 많다. 비용과 안정성, 코드의 검증에도 편리하다.

SQL이나 저장 프로시저에 담긴 로직은 테스트하기 어렵다. 반면 오브젝트에 담긴 로직은 쉽게 테스트할 수 있다.

거대한 서비스 계층 방식

DB에는 부하가 걸리지 않도록 저장 프로시저의 사용을 자제하고 복잡한 SQL을 피하면서, 주요 로직은 서비스 계층의 코드에서 처리하도록 만드는 것이다.

여전히 SQL의 결과를 그대로 담고 있는 단순 오브젝트 또는 맵을 이용해 데이터를 주고받는다. 대신 비즈니스 로직은 DB의 저장 프로시저나 SQL에서 서비스 계층으로 옮겨왔기 때문에 애플리케이션 코드의 비중이 커진다. 그만큼 구조는 단순해지고 객체지향 개발의 장점을 살릴 기회는 많아진다.

거대 서비스 계층 방식에서는 DAO에서 좀 더 단순한 결과를 돌려준다. DAO가 돌려준 정보를 분석, 가공하면서 비즈니스 로직을 적용하는 것은 서비스 계층의 책임이 된다. DAO와 SQL은 상대적으로 단순해지고, 그중 일부는 여러 서비스 계층 코드에서 재사용이 가능해진다.

비즈니스 로직이 복잡해지면 서비스 계층의 코드도 매우 복잡해지고 커진다. 업무 트랜잭션 단위로 서비스 계층의 메소드가 만들어질 가능성이 높은데, 그러다 보면 하나의 메소드가 매우 거대해지기도 한다. 이를 여러 메소드로 분리하더라도 전체 클래스 코드의 양은 변하지 않는다. 상대적으로 단순한 DAO 로직을 사용하고, 비즈니스 로직을 서비스에 집중하여 결국 거대한 서비스 계층을 만들게 된다. 서비스 계층의 코드가 업무 트랜잭션 단위로 만들어지기 때문에 DAO를 공유하는 것을 제외하면 코드의 중복도 많이 발생한다.

  • 장점

    초기 개발 속도가 빠르다

    개발자 사이에 간섭 없이 독립적인 개발이 가능하다

    핵심 로직이 자바 코드 안에 담겨 있으므로 테스트하기 상대적으로 수월하다

  • 단점

    객체 지향적인 설계를 하기 어렵다

    비슷한 로직이어도 전혀 다른 스타일의 코드가 나올 수 있다.

    자바 코드로 구현한 비즈니스 로직이 복잡한 SQL 보다 이해하기 어려울 수 있다.

    계층 별로 독립된 설계와 개발이 어렵다.

    구현할 비즈니스 로직의 설계의 변경과 유지보수가 어렵다.

데이터 중심 아키텍처는 계층 사이의 결합도가 높고 응집도가 낮다는 것이다. 화면을 중심으로 하는 업무 트랜잭션 단위로 코드가 모이기 때문에 처음엔 개발하기 편하지만 중복이 많아지기 쉽고 장기적으로 코드를 관리하고 발전시키기 힘들다는 단점이 있다.

오브젝트 중심 아키텍처

오브젝트 중심 아키텍처는 도메인 모델을 반영하는 오브젝트 구조를 만들어두고 그것을 각 계층 사이에서 정보를 전송하는 데 사용한다는 것이다. 그래서 오브젝트 중심 아키텍처는 객체지향 분석과 모델링의 결과로 도메인 모델을 오브젝트 모델로 활용한다. 그래서 오브젝트 중심 아키텍처는 객체지향 분석과 모델링의 결과로 나오는 도메인 모델을 오브젝트 모델로 활용한다.

도메인 모델은 DB의 엔티티 설계에도 반영되기 때문에 관계형 DB의 엔티티 구조와도 유사한 형태일 가능성이 높다. 도메인 모델은 애플리케이션 전 계층에서 동일한 의미를 갖는다. 따라서 도메인 모델이 반영된 도메인 오브젝트도 전 계층에서 일관된 구조를 유지한 채 사용할 수 있다. SQL이나 웹 페이지의 출력 포멧, 입력 폼 등에 종속되지 않는 일관된 형식의 애플리케이션의 정보를 다룰 수 있게 된다.

자바에는 관계하고 있는 다른 오브젝트와 직접 연결하는 방법이 있다. 레퍼런스 변수를 통해 다른 오브젝트를 참조할 수 있다. 하나 이상의 오브젝트와 관계를 갖는 컬렉션을 활용할 수도 있다.

오브젝트 중심 방식에서는 테이블의 정보와 그 관계를 유지한 채 정확한 개수의 오브젝트와 그에 대응되는 오브젝트를 만들어 사용한다. 따라서 도메인 모델을 따르는 오브젝트 구조를 만들면 DB에서 가

DAO는 자신이 DB에서 가져와서 도메인 모델 오브젝트에 담아주는 정보가 어떤 업무 트랜잭션에 어떻게 사용될지는 신경쓰지 않아도 된다.

서비스 계층 또한 DAO에서 어떤 SQL을 사용했는지 몰라도 된다. 서비스 계층에서는 필요한 정보를 조건에 맞게 조회해서 도메인 모델 오브젝트 형태로 돌려주는 DAO를 이용하기만 하면 된다. 이어서 가져온 도메인 오브젝트의 정보를 비즈니스 로직에서 처리하면 된다.

프레젠테이션 계층에 전달할 때도 마찬가지다. 어떤 DAO가 사용됐고, 어떤 비즈니스 로직을 거쳤는지에 관해서 프레젠테이션 계층은 알 필요가 없다. 자신에게 전달된 도메인 오브젝트를 활용해서 필요한 정보를 화면에 출력하기만 하면 된다.

도메인 오브젝트 사용의 문제점

  • 장점

    코드가 이해하기 쉽고 로직을 작성하기도 수월하다.

    프레젠테이션 영역에서도 이미 정의된 도메인 오브젝트 구조만 알고 있다면 아직 DAO가 작성되지 않았어도 뷰를 미리 만들 수 있다.

    코드의 재사용성은 높아지고 DAO는 더 작고 효율적으로 만들어질 수 있다.

  • 단점

    최적화된 SQL을 매번 만들어 사용하는 경우에 비해 성능 면에서 조금은 손해를 감수해야 한다.

    DAO는 비즈니스 로직의 사용 방식을 알지 못하므로, 도메인 오브젝트의 모든 필드 값을 다 채워서 전달해야 한다. 비즈니스 로직에 따라서 필요한 정보가 달라 발생한다.

    최적화를 고려한 DAO를 작성하려면 DAO는 비즈니스 로직에서 각 오브젝트를 어디까지 사용하는지 알아야 한다. DAO와 비즈니스 코드의 결합도가 높아질 수 있다.

이런 문제를 해결하는 방법

  • 지연된 로딩(lazy loading)기법을 이용하면 최소한의 오브젝트 정보만 읽어두고 관계하는 오브젝트만 Dynamic하게 DB에서 읽어온다.
  • 필드가 너무 많은 테이블이 있다면 그중에서 자주 사용되는 것을 골라내서 별도의 오브젝트로 정해두고 필요에 따라 구분해서 사용하게 할 수 있다. 그에 따라 DAO 메소드가 추가돼야 하고, 어느 DAO를 사용할 지 알아야 하기 때문에, 약한 계층간 결합이 발생한다.
  • JPA, JDO, 하이버네이트 등 RDB 맵핑기술 ORM을 사용한다. 이들 기술은 기본적으로 지연된 로딩 기법 등을 제공하기 때문에 번거로운 코드를 만들지 않고도 도메인 오브젝트의 생성을 최적화할 수 있다. 또한 SQL 결과를 가지고 도메인 오브젝트를 채우는 등의 복잡한 DAO 코드를 만들지 않아도 된다. 내부적으로 최적화된 SQL을 사용하도록 세밀하게 튜닝할 수 있다. 자주 변경되지 않으면서 많은 로직에서 참조하는 레퍼런스 테이블이 있다면 ORM이 제공하는 캐시에 담아두고 사용할 수 있다.

빈약한 도메인 오브젝트 방식

  • 빈약한(anemic) 도메인 오브젝트 : 도메인 오브젝트에 정보만 담겨있고, 정보를 활용하는 기능은 없는 오브젝트

도메인 오브젝트의 그 기능은 비즈니스 로직이라고 볼 수 있다. 빈약한 도메인 오브젝트 방식에서는 서비스 계층에 비즈니스 로직이 존재한다. 다루는 정보의 구조만 다를 뿐이지 빈약한 도메인 오브젝트 방식은 거대 서비스 계층구조와 비슷하다.

  • 한계

    서비스 계층의 메소드에 대부분의 비즈니스 로직이 들어있기 때문에 로직의 재사용성이 떨어지고 중복의 문제가 발생한다. 하지만 비즈니스 로직이 복잡하지 않다면 가장 만들기 쉽고 3계층 구조의 특징을 가장 잘 살릴 수 있는 아키텍처다.

풍성한 도메인 오브젝트 방식

  • 풍성 또는 스마트(rich or smart domain object) : 도메인 오브젝트의 객체지향적인 특징을 잘 사용할 수 있도록 개선한 방식, 어떤 비즈니스 로직은 특정 도메인 오브젝트나 그 관련 오브젝트가 가진 정보와 깊은 관계가 있다. 이런 로직을 서비스 계층의 코드가 아닌 도메인 오브젝트에 넣어주고, 서비스 계층의 비즈니스 로직에서 재사용하게 만드는 것이다.

도메인 오브젝트 안에 로직을 담으면 서비스 계층에 비즈니스 로직을 담을 때보다 응집도가 높아진다.

빈약한 도메인 오브젝트 방식에서 사용하던 도메인 관련 서비스를 도메인에 담음으로써 간결하고 좀 더 객체지향적인 설계가 가능하게 만들어 준다. ex) 다른 서비스에서 도메인 관련 서비스를 DI 받을 필요가 없게 된다.

도메인 오브젝트 안에 메소드로 들어가는 로직들은 대부분 해당 오브젝트나, 긴밀한 연관관계를 맺고 있는 관련 오브젝트의 정보와 기능을 활용한다. 여러 종류의 도메인 오브젝트의 기능을 조합해서 복잡한 비즈니스 로직을 만들었다면 특정 도메인 오브젝트에 넣기는 힘들다. 이런 비즈니스 로직은 서비스 계층의 오브젝트에 두는 것이 적당하다.

도메인 오브젝트는 직접 데이터 액세스 계층이나 기반 계층 또는 다른 서비스 계층의 오브젝트에 접근할 수 없기 때문에 서비스 계층이 필요하기도 하다.

도메인 오브젝트는 DAO 오브젝트를 DI 받을 수 없다. 왜냐하면 도메인 오브젝트는 스프링이 관리하는, 즉 빈이 아니기 때문이다. DI를 받기 위해서 자신 역시 스프링 컨테이너가 관리하는 빈이여야 한다. 그런데 도메인 오브젝트는 스프링 빈이 아니다. 애플리케이션 코드, 기타 프레임워크나 라이브러리, JDBC 템플릿 등에 의해 필요할 떄마다 새롭게 만들어 진다.

수식 계산이나 조건에 따른 데이터의 변경 또는 자신이 가진 정보에 대한 분석과 같은 도메인 자신에 국한된 로직은 도메인 오브젝트 안에 추가할 수 있지만, 그 결과를 DB에 저장하거나 외부 시스템에 전송하거나 DB에서 검색해서 원하는 정보를 가져와 활용하는 작업은 도메인 오브젝트에서 불가능하다. 그래서 DAO와 기반계층 오브젝트를 DI 받아 사용할 수 있는 서비스 계층의 코드가 필요하다.

스프링의 빈으로 관리되는 3계층의 오브젝트들은 도메인 오브젝트를 자유롭게 이용할 수 있지만 그 반대는 안된다는 사실을 주의해야 한다.

  • 도메인 오브젝트

    3계층의 오브젝트를 직접 이용할 수 없다.

    오브젝트가 가진 데이터를 저장하는 기능과 함계 데이터를 활용하는 로직을 갖고 있다.

  • 서비스 계층(핵심로직)

    도메인 오브젝트가 제공하는 도메인 로직을 활용해서 비즈니스 로직을 구현한다. 따라서 훨씬 간결하고 명확하며 중복이 없는 코드가 만들어 진다.

  • 데이터 액세스 계층(SQL/ORM)

    빈약한 도메인 오브젝트와 별 차이는 없다. 하지만 도메인 오브젝트 내에서 간단한 계산 및 포매팅, 변환, 조건에 따라 달라지는 값을 줄 수 있으므로 SQL 펑션의 사용이 줄어든다.

잘 사용하기 위해서는 충실한 도메인 모델링과 도메인 오브젝트 개발이 선행되고 그 내용이 개발자에게 사전에 충분히 공유되어야 한다.

도메인 계층 방식

  • 풍성한 도메인 오브젝트 방식의 한계

    스스롤 필요한 정보를 DAO를 통해 가져올 수 없다.

    생성이나 변경이 일어나도 DAO에게 변경사항의 반영을 요청할 수 없다

    다양한 기반계층의 서비스를 이용할 수 없다.

도메인 오브젝트가 기존 3계층과 같은 레벨로 격상되어 하나의 계층을 이루게 하는 도메인 계층 방식이다. 도메인 오브젝트이 하나의 독립적인 계층을 이뤄서 서비스 계층과 데이터 액세스 계층의 사이에 존재하게 하는 것이다.

특징

  1. 도메인에 종속적인 비즈니스 로직의 처리는 서비스 계층이 아니라 도메인 계층의 오브젝트 안에서 진행된다. 해당 도메인 오브젝트를 중심으로 만들어진 로직이라면 그 이후의 작업은 도메인 오브젝트와 그 관련 오브젝트 사이에서 진행된다. 일단 도메인 계층으로 들어가면 서비스 계층의 도움 없이도 비즈니스 로직의 대부분의 작업을 수행할 수 있다는 뜻

  2. 도메인 오브젝트가 기존 데이터 액세스 계층이나 기반 계층의 기능을 직접 활용할 수 있다. 스프링이 관리하는 오브젝트가 아니더라도 간단한 설정을 추가하여 도메인 오브젝트도 DI 받을 수 있다.

스프링이 관리하지 않는 오브젝트가 DI를 적용하기 위해서는 AOP가 필요하다. 스프링 AOP는 부가기능을 추가할 수 있는 위치가 메소드의 호출 과정으로 한정되고 AOP의 적용 대상도 스프링의 빈 오브젝트뿐이다. 대신 AspectJ AOP를 사용하면 클래스의 생성자가 호출되면서 오브젝트가 만들어지는 시점을 조인 포인트로 사용할 수 있고 스프링 빈이 아닌 일반 오브젝트에도 AOP 부가기능을 적용할 수 있다.

도메인 오브젝트가 생성되는 시점에 특별한 부가기능을 추가할 수 있다. 이 부가기능은 오브젝트의 수정자 메소드나 DI 애노테이션을 참고해서 DI 가능한 대상을 스프링 컨테이너에서 찾아 DI 해주는 기능이다. 스프링이 관리하지 않는 오브젝트에 대한 DI 서비스가 일종의 AOP 부가기능으로 도메인 오브젝트에 적용될 수 있다.

서비스 계층

도메인 계층 방식이 많은 비즈니스 로직이 도메인 오브젝트가 가져가더라도 서비스 계층의 역할이 사라지는 것은 아니다. 여러 도메인 오브젝트의 기능을 조합해서 복잡한 작업을 해야하는 경우, 특정 도메인 오브젝트에담길 수 없는 이런 작업은 서비스 계층에서 도메인 계층과 협력하여 처리해야 한다. 굳이 도메인 계층을 거치지 않고 바로 데이터 액세스 계층으로부터 정보를 가져와 클라이언트에 제공해야 하는 경우, 서비스 게층이 인터페이스 역할을 담당한다. 트랜잭션의 경계를 설정하거나 특정 도메인 로직에 포함되지는 않지만 애플리케이션에서 사용하는 기반 서비스를 이용해야 하는 경우

도메인 오브젝트가 도메인 계층을 벗어나 사용되기 할지 말지 결정해야 한다. 계층화된 도메인 오브젝트는 이전과 달리 정보 전달 도구의 역할을 하지 않는다.

선택할 방법은 두 가지가 있다.

  1. 여전히 모든 계층에서 도메인 오브젝트를 사용한다. 서비스, 프레젠테이션, 뷰 등에서 직접 도메인 오브젝트를 받아 사용할 수 있게 한다. 가장 손쉽고 편한 방법이다. 하지만 주의하지 않으면 심각한 혼란을 초래한다. 프레젠테이션 및 뷰 등에서 사용하게 해주면 이를 함부로 사용하는 위험이 뒤따른다. 중요한 비즈니스 로직이 담긴 메소드가 함부로 호출될 수 있다. 이를 막기 위해 강력한 가이드라인을 만들거나 AspectJ의 정책/표준 강제화 기능을 사용하면 된다. (AspectJ In Action)
  2. 도메인 오브젝트는 도메인 계층을 벗어나지 못하게 하는 것이다. 도메인 계층 밖으로 전달되 때는 별도로 준비된 데이터 전달용 오브젝트에 도메인 오브젝트의 내용을 복사해서 넘겨줘야 한다. 데이터 전달용 오브젝트를 DTO(Data Transfer Object)라고 불린다. DTO는 상태 변화를 허가하지 않고 읽기 전용으로 사용된다. 사용자의 등록값이나 외부 시스템으로부터 전달 받은 정보를 도메인 계층으로 전달하는 경우에도 DTO를 이용할 수 있다. DTO는 기능이 없어 사용하기 안전하다. 도메인 오브젝트를 외부 계층의 코드로부터 보호해준다. 반면에 도메인 오브젝트와 비슷한 구조의 오브젝트를 만들어야 하고 이를 매번 변화해줘야 한다. 따라서 이와 같은 방법을 이용해 자동으로 변환해주도록 만들 필요가 있다.
  • 도메인 계층

    도메인 모델을 그대로 반영하는 오브젝트 구조를 이용해 핵심적인 비즈니스 로직의 처리를 담당한다.

    서비스 계층, 데이터 액세스 계층에 양방향 접근이 가능하다.

  • DTO : 기능이 제한된 도메인 오브젝트

    도메인 계층의 밖에서 정보를 제공한다. 제한된 정보를 제공하거나 정보 전달을 위한 DTO로 변환되서 사용된다.

  • 서비스 계층 : 보조로직

    도메인 계층에 핵심적인 비즈니스 로직 처리를 위임하기 때문에 서비스 계층은 상당히 가벼워진다. 필요에 따라서 데이터 액세스 계층을 바로 이용하기도 한다.

  • 데이터 액세스 계층 : SQL

    도메인 계층과 서비스 계층이라는 두 개의 클라이언트를 갖게 된다. 결과는 가능한 도메인 오브젝트를 사용해 리턴한다. 특히 도메인 계층이 직접 사용하는 메소드는 반드시 그래야 한다.

도메인 계층은 기존 3계층과 달리 싱글톤으로 존재하지 않고 매우 짧은 시간 동안만 존재했다가 사리지는 것을 반복한다. 각 사용자의 요청별로 독립적으로 도메인 계층을 이루는 오브젝트들이 생성됐다가 해당 요청을 처리하고 나면 버려진다. 상태정보를 담고 있기 때문에 여러 스레드가 공유하는 싱글톤이 될 수 없다. DAO, 컨트롤러, 스프링 외의 라이브러리를 통해 오브젝트가 만들어지는 경우가 많기 때문에 스프링이 관리하는 빈으로 등록조차 불가능하다. 그렇기 때문에 특별한 방법(AOP)으로 DI를 해줘야지만 다른 3계층의 빈들과 협력해서 일을 처리할 수 있다.

여러가지 제약과 불편을 감수하면서라도 이 방식을 택해야 하는 경우는 매우 복잡하고 변경이 잦은 도메인을 가졌을 때다. 복잡한 도메인의 구조와 로직을 최대한 도메인 계층의 오브젝트에 반영하고, 도메인 모델과 설계에 변경이 발생했을 때 도메인 계층의 오브젝트도 빠르게 대응해서 변경해주기 위해서다.

DTO와 리포트 쿼리

도메인 계층 방식의 경우 계층을 벗어난 정보를 DTO라 불리는 특정 계층에 종속되지 않는 정보 전달 목적을 가진 단순 오브젝트에 담아서 사용하기도 한다.

리포트 쿼리(report query)라고 불리는 DB 쿼리의 실행 결과를 담는 경우 DTO가 반드시 필요하다. 리포트 쿼리는 리포트를 출력하기 위해 생성하는 쿼리라는 의미인데 단지 리포트 보다는 종합 분석 리포트처럼 여러 테이블에 걸쳐 존재하는 자료를 분석하고 그에 따른 분석/통계 결과를 생성하는 쿼리라는 의미다. 이런 쿼리의 결과는 DB 테이블에 담긴 필드의 내용보다는 그 합계, 평균과 같은 계산 값이거나 아니면 여러 테이블의 필드를 다양한 방식으로 조합해서 만들어진다. 따라서 DB 쿼리의 실행 결과를 담을 만한 적절한 도메인 오브젝트를 찾을 수 없다. 그래서 이런 리포트 쿼리의 결과는 DTO라고 불리는 단순한 자바빈 아니면 키와 쌍 값을 갖는 맵에 담아서 전달해야 한다.

DB쿼리 하나로 최종 결과를 만들어내기 힘들기 때문에 코드를 통해 데이터를 분석하고 가공하는 작업이 필요하다. 이런 경우에도 최종 결과는 DTO나 맵, 컬렉션에 담겨서 전달돼야 한다.

웹 서비스 등의 시스템과 자료를 주고받을 때 전송 규약에 맞춰서 도메인 오브젝트에 담긴 정보를 가공해야 할 때가 있다. 이런 경우도 DTO나 맵을 이용해 해당 형식에 맞도록 변경하는 작업이 필요하다.

출처

토비의 스프링(책)

카테고리:

업데이트:

댓글남기기