9.3) 애플리케이션 아키텍처
아키텍처
- 클라이언트와 백엔드 시스템의 종류와 사용 기술, 연동 방법을 결정했다면 시스템 레벨의 아키텍처는 대략 구성된 셈이다.
다음으로 결정할 사항은 스프링 웹 애플리케이션의 아키텍처다.- 아키텍처의 가장 단순한 정의는 어떤 경계 안에 있는 내부 구성요소들이 어떤 책임을 갖고 있고,
어떤 방식으로 서로 관계를 맺고 동작하는지를 규정하는 것이라고 할 수 있다. - 아키텍처는 단순히 정적인 구조를 나타내는 것으로만 생각하기 쉽지만
실제로는 그 구조에서 일어나는 동적인 행위와 깊은 관계가 있다.
- 아키텍처의 가장 단순한 정의는 어떤 경계 안에 있는 내부 구성요소들이 어떤 책임을 갖고 있고,
계층형 아키텍처
- 지금까지는 주로 오브젝트 레벨에서 분리의 문제에 대해 생각해보며 DI를 기반으로한 유연한 설계와 구현 전략을 해왔다.
- 관심, 책임, 성격, 변하는 이유와 방식이 서로 다른 것들을 분리함으로써
분리된 각 요소의 응집도는 높여주고 서로의 결합도를 낮추었다. - 이러한 원리는 아키텍처 레벨에서 좀 더 큰 단위에 대해서도 동일하게 적용할 수 있다.
또한 오브젝트보다 작은 단위인 메소드 레벨에서도 같은 원리를 적용할 수 있다. - 성격이 다른 것을 아키텍처 레벨에서 분리해줄 경우
독자적으로 개발과 테스트가 가능해서 개발과 변경 작업이 모두 빨라질 수 있다.
또 구현 방법이나 세부 로직은 서로 영향을 주지 않고 변경될 수 있을 만큼 유연하다. 전체를 이해하기도 상대적으로 쉽다. - 이렇게 책임과 성격이 다른 것을 크게 그룹으로 만들어 분리해두는 것을 아키텍처 차원에서는 계층형 아키텍처라고 부른다.
또는 계층이라는 의미를 가진 영어 단어인 티어를 써서 멀티 티어 아키텍처라고 한다. - 보통 웹 기반의 엔터프라이즈 애플리케이션은 세 개의 계층을 갖는다고 해서 3계층 애플리케이션이라고도 한다.
물론 반드시 모든 엔터프라이즈 애플리케이션을 3계층으로 만들어야만 하는 것은 아니다.
경우에 따라서 전통적인 3계층 방식이 아닌 다른 구분 방법을 선택하기도 한다.
또 각 계층을 좀 더 세분화해서 더 작은 단위의 계층으로 나눌 수도 있다.
- 관심, 책임, 성격, 변하는 이유와 방식이 서로 다른 것들을 분리함으로써
- 3계층 아키텍처는 데이터 액세스 계층, 서비스 계층, 프레젠테이션 계층으로 구분한다.
- 그런데 이 3계층 아키텍처의 각 계층을 부르는 이름은 워낙 다양하지만
각각 의미가 있는 이름이기 때문에 모두 기억해두고 사용하는 것이 좋다.
- 그런데 이 3계층 아키텍처의 각 계층을 부르는 이름은 워낙 다양하지만

- 데이터 액세스 계층은 백엔드의 DB나 레거시 시스템과 연동하는 인터페이스 역할을 한다.
- 데이터 액세스 계층은 DAO 패턴을 보편적으로 사용하기 때문에 DAO 계층이라고도 불린다.
- 또한 DB 외에도 ERP, 레거시 시스템, 메인프레임 등에 접근하는 역할을 하기 때문에 EIS 계층이라고도 한다.
- 하지만 대개는 장기적인 데이터 저장을 목적으로 하는 DB 이용이 주된 책임이다.
- 데이터 액세스 계층은 사용 기술에 따라서 다시 세분화된 계층으로 구분될 수 있다.
데이터 액세스 계층 안에서 다시 세분화하는 경우는 추상화 수준에 따른 구분이므로 수직적인 계층이라고 부르기도 한다. - 기본 3계층은 기술 계층보다는 역할에 따라 구분한 것이므로 보통 그림으로 나타낼 때 가로로 배열한다.
반면에 이처럼 같은 책임을 가졌지만 추상화 레벨에 따라 구분하는 경우는 세로로 배열해서 표현한다. - 예를 들어 스프링의 JdbcTemplate을 사용하는 DAO 계층은 JdbcTemplate이 추상화를 위한 계층으로 사용돼서
항상 JdbcTemplate을 통해 로우레벨의 기반 계층에 존재하는 JDBC와 드라이버,
스프링의 트랜잭션 추상화 서비스의 동기화 기능을 간접적으로 이용하게 만든다. - 또한 또 하나의 추상 계층을 추가해 하위 계층의 종류가 다른 서비스를 일관된 방식으로 접근할 수 있게도 할 수 있다.
이렇게 새로운 계층을 추가하면 개발자의 애플리케이션 코드에 지대한 영향을 주기 때문에 매우 신중하게 결정해야 한다.
한번 새로운 계층과 API를 만들어 적용하면 이를 최대한 유지하도록 하위 계층의 변화에 대응해야 하는 책임도 갖게 된다. - 그러므로 만약 추상 계층을 새로 추가하는 것은 부담스럽고
경우에 따라서 유연하게 하위 계층의 API를 활용할 필요가 있다면
공통적인 기능을 분리해서 유틸리티나 헬퍼 메소드 또는 오브젝트로 제공해주는 것도 좋은 방법이다.
- 데이터 액세스 계층은 DAO 패턴을 보편적으로 사용하기 때문에 DAO 계층이라고도 불린다.


- 서비스 계층은 비즈니스 로직을 담는다.
- 엔터프라이즈 애플리케이션에서 가장 중요한 자산은 도메인의 핵심 비즈니스 로직이 들어 있는 서비스 계층이어야 한다.
- 잘 만들어진 스프링 애플리케이션의 서비스 계층 클래스는 이상적인 POJO로 작성된다.
POJO로 만든다면 객체지향적인 설계 기법이 적용된 코드를 통해서 비즈니스 로직의 핵심을 잘 담아내고,
이를 쉽게 테스트하고 유연하게 확장할 수 있다. - 서비스 계층은 DAO 계층을 호출하고 이를 활용해서 만들어진다.
- 때론 데이터 액세스를 위한 기능 외에도 서버나 시스템 레벨에서 제공하는 기반 서비스를 활용하기도 한다.
일반적으로는 서비스 계층이 필요에 따라 기반 서비스 계층의 API를 호출해서 이용한다.
예) 원격 호출을 통해 정보 가져오기, 메일 또는 메시징 서비스 이용
하지만 반대로 서비스 계층의 코드를 기반 서비스 계층에서 실행히시키는 경우도 있다.
예) 스케줄링 - 이런 기반 서비스는 3계층 어디에서나 접근이 가능하도록 만들 수도 있고,
아키텍처를 설계하기에 따라서 반드시 서비스 계층을 통해 사용되도록 제한할 수도 있다. - 서비스 계층은 특별한 경우가 아니라면 추상화 수직 계층구조를 가질 필요가 없다.
단순히 POJO 레벨에서 비즈니스 로직을 모델링하다가 상속구조를 만들 수 있을진 몰라도
기술 API를 직접 다루는 코드가 아니기 때문에 기술에 일관된 방식으로 접근하게 하거나
편하게 사용하게 해주는 추상화는 필요 없기 때문이다. - 기반 서비스 계층을 사용하는 경우에도 데이터 액세스 계층을 사용하는 경우와 마찬가지로
독립된 계층의 서비스를 이용하는 것으로 봐야 한다.
비즈니스 로직을 담은 서비스 계층과 엔터프라이즈 서비스를 제공하는 기반 서비스 계층은 완전히 다르다. - 이때 원칙적으로 서비스 계층 코드가 기반 서비스 계층의 구현에 종속되면 안 되므로
서비스 계층의 코드는 추상화된 기반 서비스 인터페이스를 통해서만 접근하도록 만들어서
특정 구현과 기술에 대한 종속성을 제거해야 한다.
또는 AOP를 통해서 서비스 계층의 코드를 침범하지 않고 부가기능을 추가하는 방법을 활용해야 한다.

- 프레젠테이션 계층은 주로 웹 기반의 UI를 만들어내고 그 흐름을 관리한다.
- 프레젠테이션 계층은 매우 다양한 기술과 프레임워크의 조합을 가질 수 있다.
- 대부분의 엔터프라이즈 애플리케이션을 사용하는 클라이언트들은 HTTP 프로토콜을 선호하므로
이런 클라이언트와 연결돼서 동작하는 엔터프라이즈 애플리케이션의 프레젠테이션 계층은
클라이언트의 종류와 상관없이 HTTP 프로토콜을 처리하는 가장 기본 엔진이 서블릿 기술을 바탕으로 한다. - 프레젠테이션 계층은 다른 계층과 달리 클라이언트까지 그 범위를 확장될 수도 있다.
초기 클라이언트 모델은 단순히 HTML로 만들어진 결과를 사람이 볼 수 있게 그려주고, 폼을 통해 입력받은 값을 전달했다.
모든 프레젠테이션 로직은 서버의 프레젠테이션 계층의 컴포넌트에서 처리되어 화면 흐름을 결정하는 것이나
사용자 입력 값에 대한 검증, 서비스 계층의 호출과 전달되는 값의 포맷의 변화, 화면을 어떻게 그릴지에 대한 로직 등이
모두 서버에서 처리되고 클라이언트는 단순히 서버 프레젠테이션 계층의 기능에 대한 사용자 인터페이스에 불과했다.
하지만 최근에는 점점 많은 프레젠테이션 로직이 클라이언트로 이동하고 있다. - 스프링은 웹 기반의 프레젠테이션 계층을 개발할 수 있는 전용 웹 프레임워크를 제공한다.
동시에 다양한 서드파티 웹 기술을 지원하기도 하며
아예 프레젠테이션 계층을 통째로 스프링이 아닌 다른 웹 기술을 가져다 사용할 수도 있다.
스프링 애플리케이션에 적용할 수 없는 웹 기술은 없다고 봐도 좋다.
- 오브젝트와 그 관계에 적용했던 대부분의 객체지향 설계의 원칙은 아키텍처 레벨의 계층과 그 관계에도 동일하게 적용할 수 있다.
- 각 계층은 응집도가 높으면서 다른 계층과는 낮은 결합도를 유지할 수 있어야 한다.
- 각 계층은 자신의 계층의 책임에만 충실해야 한다.
이를 위해 자신과 관련된 기술이 아닌 다른 기술 API의 사용을 삼가해야 한다. - 자신의 역할과 기술에만 충실한 계층을 만들면 각 계층 사이의 결합도는 자연스럽게 낮아지며
각 계층이 자신의 책임에만 충실하게 작성되어 있다면 필요한 그 밖의 작업은 다른 계층에 요청하게 될 것이다.
이때는 계층 레벨에 정의한 인터페이스를 통해서 요청을 하게 되고,
계층 간에 사용되는 인터페이스 메소드에는 특정 계층의 기술이 최대한 드러나지 않게 만들어야 한다.
그렇지 않으면 계층 사이에 결합도가 높아질 뿐만 아니라, 계층 간의 기술이나 역할이 서로 침범하는 일이 일어난다. - 계층 간 강한 결합이 생기면 유연성이 떨어지므로 각 계층의 내부 구현이 변화되면 다른 계층의 코드도 함께 수정해야 한다.
또한 코드의 중복이 일어날 가능성이 높고 전체 코드를 이해하기는 힘들어진다.
오브젝트 간의 강한 결합이 있을 때 발생하는 문제와 성격이 유사하고 그 파장은 훨씬 심각하다.
- 스프링의 DI는 기본적으로 오브젝트 사이의 관계를 다룬다.
- 따라서 계층 사이의 경계나 그 관계에 직접적으로 관여하지 않는다.
- 하지만 모든 경계에는 오브젝트가 존재하고 그 사이의 관계도 오브젝트 대 오브젝트로 정의되기 마련이다.
그런 면에서 스프링의 DI가 계층 사이의 관계에도 적용된다고 볼 수 있다. - 하지만 DI는 계층을 구분해주지 않기 때문에 빈 사이의 의존관계를 만들 때 다른 계층과의 관계에 주의해야 한다.
애플리케이션 정보 아키텍처
- 애플리케이션을 사이에 두고 흘러다니는 정보를 어떤 식으로 다룰지를 결정하는 일도 아키텍처를 결정할 때 매우 중요하다.
- 엔터프라이즈 애플리케이션에 존재하는 정보를 단순히 데이터로 다루는 경우(데이터 중심 아키텍처)와
오브젝트로 다루는 경우(오브젝트 중심 아키텍처), 두 가지 기준으로 구분해볼 수 있다.
- 엔터프라이즈 애플리케이션에 존재하는 정보를 단순히 데이터로 다루는 경우(데이터 중심 아키텍처)와
데이터 중심 아키텍처
- 데이터 중심 아키텍처는 엔터프라이즈 애플리케이션에 존재하는 정보를 단순히 데이터로 다룬다.
- 데이터 중심 아키텍처는 애플리케이션에 흘러다니는 정보를
단순히 값이나 값을 담기 위한 목적의 오브젝트 형태로 취급하는 구조로,
DB나 백엔드 시스템에서 가져온 정보를 값으로 다루고 그 값을 취급하는 코드를 만들어 로직을 구현하고
값을 그대로 프레젠테이션 계층의 뷰, 즉 사용자가 보는 화면과 연결해주는 것이다. - 그러므로 비즈니스 로직이 DB 내부의 저장 프로시저나 SQL에 담겨 있는 경우가 많으며
보통 DB에서 돌려주는 내용을 그대로 맵이나 단순 결과 저장용 오브젝트에 넣어서 전달한다. - 데이터 중심 아키텍처는 핵심 비즈니스 로직을 어디에 많이 두는지에 따라
DB에 무게를 두는 구조와 서비스 계층에 무게를 두는 구조로 구분할 수 있다.
- 데이터 중심 아키텍처는 애플리케이션에 흘러다니는 정보를
- 데이터 중심 구조의 특징은 하나의 업무 트랜잭션에 모든 계층의 코드가 종속되는 경향이 있다는 점이다.
- 예를 들어 사용자의 이름으로 사용자 정보를 검색해서 일치하는 사용자의 아이디, 비밀번호, 이름, 가입일자만을
보여주는 작업이 있다면 이것이 하나의 업무 단위가 되고 모든 계층의 코드가 이 기준에 맞춰서 만들어진다.
즉, 사용자 조회라는 단위 업무를 위해서만 존재하는 각 계층의 코드가 만들어진다는 뜻이다. - 이때 검색조건은 SQL로 만들어지며 사용자 정보를 웹 페이지에 나타낼 때 가입일자 중에서 연도만 보여줘야 한다면
SQL의 날짜처리 함수를 이용해야 한다. 하지만 이럴 경우 SQL은 이미 화면에 어떤 식으로 출력이 될지 알고 있는 셈이다. - 또한 SQL의 결과는 컬럼 이름을 키로 갖는 맵에 저장되거나 조회 페이지에 필요한 네 가지 정보를 담을 수 있는
단순한 오브젝트를 저쟁돼서 전달되므로 서비스 계층은 별로 할일이 없게 된다. - 이때 만약 새로운 필드가 추가되거나 DB 테이블의 컬럼 이름이 변경됐다면, 그에 따라서 맵이나 오브젝트에 저장될
엔트리 또는 프로퍼티 이름이 바뀌거나 추가될 것이고 그에 맞게 뷰의 내용도 변경된다. - 뿐만 아니라 모든 계층의 코드는 업무에 종속되므로 업무의 내용이 바뀌면 모든 계층의 코드가 함께 변경되게 되므로
종속적일 뿐 아니라 배타적이어서 다른 단위 업무에 재사용되기 힘들다. - 자바 코드의 로직은 기껏해야 사용자 요청에 따라서 어떤 SQL을 가진 DAO를 실행할지를 결정하는 정도일 것이다.
- 이로 인해 대부분의 코드는 대응되는 작업 단위에 1:1로 매핑되어
업무 트랜잭션 단위로 코드를 묶어서 만들어 기능을 세분화해서 분리하고 재사용하기 쉽지 않아
여러 작업에서 반복되는 기능이 있다면 코드는 중복되기 쉽다.
- 예를 들어 사용자의 이름으로 사용자 정보를 검색해서 일치하는 사용자의 아이디, 비밀번호, 이름, 가입일자만을
- 데이터 중심 아키텍처는 하나의 업무 트랜잭션을 모두 담은 서비스 계층 코드와
해당 업무에 특화된 SQL을 담은 하나 또는 여러 개의 DAO 메소드로 구성된다.- 서비스 계층이 프레젠테이션 계층에 전달하는 결과의 포맷은 보통 DAO의 SQL 결과와 같고,
웹 페이지의 출력 내용과도 1:1로 대응되게 된다. - 이런 식의 개발 방법과 아키텍처는 사실 자바 기술이 발전하기 이전의 엔터프라이즈 시스템에서 흔히 발견할 수 있다.
굳이 자바라는 객체지향 언어와 프로그래밍 기술을 사용하지 않더라도 3계층 구조로 만드는 전통적인 개발 방법이다.
또는 서비스 계층이 별 의미가 없으므로 주요 로직을 클라이언트의 독립 프로그램에 담아두고
DB 처리 로직만 DB에 분리해둔 2계층 구조에서도 비슷하게 발견할 수 있다. - 이런 방식은 개발하기 쉽다는 장점이 있으며 각 업무의 핵심을 담은 SQL을 중심으로 DAO부터 사용자가 보는 화면까지
한 벌씩 만들면 되기 때문에 툴이나 코드 생성기를 이용해서 자동화하는데도 유리하다. - 하지만 이런 방식은 자바 코드를 단지 DB와 웹 화면을 연결해주는 단순한 인터페이스 도구로 전략시키는 것이다.
자바의 오브젝트는 단지 HTTP 서비스 채널을 만들어주고
JDBC를 이용해 DB 기능을 사용하게 해주는 스크립트 정도로 역할이 축소된다.
굳이 자바를 쓰지 않고 전통적인 언어나 단순한 스크립트 언어 또는 DB 등에서 제공하는 툴로 대치하더라도 차이가 없다. - 이렇게 DB 중심의 업무 단위로 코드를 만들면 애플리케이션 내에서 흘러다니는 정보는 항상 단순한 포맷의 데이터다.
그러므로 이런 코드는 항상 SQL과 그 결과에 종속되기 때문에 SQL의 변화가 일어나면 같이 변경돼야 한다. - 이는 겉으로 보기에는 각 계층이 독립적으로 보이지만, 그 사이를 이동하는 데이터가 강한 결합을 만들게 된다.
- 서비스 계층이 프레젠테이션 계층에 전달하는 결과의 포맷은 보통 DAO의 SQL 결과와 같고,

- 스프링을 사용하면 이런 데이터 중심의 코드를 만들 수 있을 뿐만 아니라, 실제로 매우 흔하게 발견된다.
- 데이터와 업무 트랜잭션 중심의 개발에 익숙한 사람들이 많고 이런 아키텍처를 의도적으로 선호하는 개발자도 많다.
- 이로 인해 최소한의 공통 모듈정도만 제공되는 것을 사용하고, 그 외의 기능은 단위 업무 또는 웹 화면 단위로 만들어진다.
- 하지만 이런 개발 방식은 객체지향의 장점이 별로 활용되지 못하는데다
각 계층의 코드가 긴밀하게 연결되어 있어 변화에 매우 취약하다. - 이로 인해 필드 하나가 달라도 거의 비슷한 DAO 메소드를 새로 만들게 되어 로직을 DB와 SQL에 많이 담게 되면
점점 확장성이 떨어져 한계가 있으며 매우 큰 비용이 든다.
또한 복잡한 SQL을 누구나 쉽게 이해하고 필요에 따라 유연하게 변경하기 힘들어진다. - 상대적으로 애플리케이션 서버와 그 안에 담긴 오브젝트는 비용이 적게 든다.
서버는 늘려 쉽게 확장이 가능하고 서버의 배용은 매우 빠르게 하락하고 있으므로
오브젝트를 만들고 코드를 동작시키는 비용은 DB에서 비슷한 작업을 할 때에 비해 저렴하다.
따라서 로직을 DB보다는 애플리케이션으로 가져오는 편이 유리한 점이 많다. - 또한 SQL이나 저장 프로시저에 담긴 로직은 테스트하기 힘든 반면에
오브젝트에 담긴 로직은 간단히 검증할 수 있으므로 안정성도 높아진다. - 따라서 DB에는 부하를 가능한 한 주지 않는 간단한 작업만 하고
복잡한 로직은 오브젝트에 담아서 애플리케이션 내에서 처리하도록 만드는 편이 낫다.
- DB에서 가져온 데이터가 애플리케이션에 흘러다니는 정보의 중심이 되는 아키텍처긴 하지만
DB에 많은 로직을 두는 개발 방법의 단점을 피하면서 애플리케이션 코드의 비중을 높이는 방법이 있다.- 즉, DB에는 부하가 걸리지 않도록 저장 프로시저의 사용을 자제하고 복잡한 SQL을 피하면서,
주요 로직은 서비스 계층의 코드에서 처리하도록 만드는 것이다. - 여전히 SQL의 결과를 그대로 담고 있는 단순한 오브젝트 또는 맵을 이용해 데이터를 주고받지만
대신 많은 비즈니스 로직을 DB의 저장 프로시저나 SQL에서 서비스 계층의 오브젝트로 옮겨왔기 때문에
애플리케이션 코드의 비중이 커져 그만큼 구조는 단순해지고 객체지향 개발의 장점을 살릴 기회가 많아진다. - 비즈니스 로직을 DB나 SQL에 담는 경우에는 항상 최종 결과만 DAO에서 서비스 계층으로 전달되지만
반면 이러한 방식에서는 DAO에서 좀 더 단순한 결과를 돌려준다.
이러한 DAO가 돌려준 정보를 분석, 가공하면서 비즈니스 로직을 적용하는 것이 서비스 계층 코드의 책임이 된다.
이로 인해 DAO와 SQL은 상대적으로 단순해지고, 그중 일부는 여러 서비스 계층 코드에서 재사용이 가능해진다. - 하지만 비즈니스 로직이 복잡해지면 서비스 계층의 코드도 매우 복잡해지고 커진다.
업무 트랜잭션 단위로 서비스 계층의 메소드가 만들어진 가능성이 높은데, 그러면 하나의 메소드가 매우 거대해지기도 한다.
이처럼 상대적으로 단순한 DAO 로직을 사용하고,
비즈니스 로직의 대부분을 서비스 계층에 집중하는 이런 접근 방법은 결국 거대한 서비스 계층을 만들게 된다. - 서비스 계층의 코드는 여전히 업무 트랜잭션 단위로 집중돼서 만들어지기 때문에
DAO를 공유할 수 있는 것을 제외하면 코드의 중복도 적지 않게 발생한다. - 거대 서비스 계층 방식은 애플리케이션 코드에 비즈니스 로직이 담겨 있기 때문에
자바 언어의 장점을 활용해 로직을 구현할 수 있고 테스트하기도 수월하다.
또한 각 단위 업무별로 독립적인 개발이 가능하므로 초기 개발 속도가 빠르고,
개발자 사이에 간섭 없이 독립적인 개발이 가능하다. - 하지만 데이터 액세스 계층의 SQL은 서비스 계층의 비즈니스 로직의 필요에 따라 만들어지므로 계층 간의 결합도가 크다.
또한 서비스 계층의 메소드는 DAO가 제공해주는 값 포맷에 따라 취급하는 방법이 달라져 공통 기능을 일반화하기 힘들다.
- 즉, DB에는 부하가 걸리지 않도록 저장 프로시저의 사용을 자제하고 복잡한 SQL을 피하면서,

- 이러한 데이터 중심 아키텍처의 특징은 계층 사이의 결합도가 높은 편이고 응집도는 떨어진다는 점이다.
화면을 중심으로 하는 업무 트랜잭션 단위로 코드가 모이기 때문에 처음엔 개발하기 편하지만
중복이 많아지기 쉽고 장기적으로 코드를 관리하고 발전시키기 힘들다는 단점이 있다.
오브젝트 중심 아키텍처
- 오브젝트 중심 아키텍처는 엔터프라이즈 애플리케이션에 존재하는 정보를 오브젝트로 다룬다.
- 오브젝트 중심 아키텍처는 데이터 중심 아키텍처와 달리
도메인 모델을 반영하는 오브젝트 구조를 만들어서 그것을 각 계층 사이에서 정보를 전송하는데 사용한다. - 그래서 오브젝트 중심 아키텍처는 객체지향 분석과 모델링의 결과로 나오는 도메인 모델을 오브젝트 모델로 활용한다.
대개 도메인 모델은 DB의 엔티티 설계에도 반영되기 때문에 관계형 DB의 엔티티 구조와도 유사한 형태일 가능성이 높다. - 이렇게 오브젝트를 만들고 오브젝트 구조 안에 정보를 담아 계층 사이에 전달하게 만드는 것이 오브젝트 중심 아키텍처다.
- 도메인 모델은 애플리케이션 전 계층에서 동일한 의미를 가지므로
도메인 모델이 반영된 도메인 오브젝트도 전 계층에서 일관된 구조를 유지한 채로 사용될 수 있다.
그러므로 SQL이나 웹 페이지의 출력 포맷, 입력 폼 등에 종속되지 않는 일관된 형식의 애플리케이션 정보를 다룰 수 있다. - 이를 위해 먼저 도메인 모델의 구조를 따라서 의미 있는 타입과 정보를 가진 클래스를 정의한다.
이 구조는 단순히 특정 SQL에 대응되는 맵과 배열, 매번 달리지는 SQL 결과를 담기 위해 급조해서 만든 오브젝트와 달리,
애플리케이션 어디에서도 사용될 수 있는 일관된 형식의 도메인 정보를 담고 있다.
이로 인해 DB에서 SQL 결과로 가져온 값을 그대로 사용하는 경우와 다르게,
도메인 모델을 반영하는 오브젝트를 사용하면 자바 언어의 특성을 최대한 활용할 수 있도록 정보를 가공할 수 있다. - 대표적으로 오브젝트 사이의 관계를 나타내기 위해 키의 조합을 통해
그때그때 테이블을 조인해서 의미 있는 관계를 만들어내던 것과 달리,
레퍼런스 변수를 이용해서 다른 오브젝트를 참조할 수 있으며,
하나 이상의 오브젝트와 관계를 가지려면 컬렉션을 이용할 수도 있다.
그러므로 데이터 중심 방식에서 SQL을 이용해 조인한 다음 하나의 맵에 뭉뚱그려서 가져온 것과 달리,
오브젝트 중심 방식에서는 테이블의 정보와 그 관계를 유지한 채로 정확한 오브젝트를 만들어 사용한다. - 이렇게 도메인 모델을 따르는 오브젝트 구조를 만들려면
DB에서 가져온 데이터를 도메인 오브젝트 구조에 맞게 변환해줄 필요가 있다.
한 번 변환되면 그 이후의 작업은 수월해지므로 DAO는 자신이 DB에서 가져와서
도메인 모델 오브젝트에 담아주는 정보가 어떤 업무 트랜잭션에서 어떻게 사용될지는 신경 쓰지 않아도 된다.
서비스 계층 또한 DAO에서 어떤 SQL을 사용했는지는 몰라도 되므로
단순히 필요한 정보를 조건에 맞게 조회해서 도메인 모델 오브젝트 형태로 돌려주는 DAO를 이용하기만 하면 된다.
프레젠테이션 계층에 전달할 때도 어떤 DAO가 사용됐고, 어떤 비즈니스 로직을 거쳤는지에 관해서
프레젠테이션 계층은 알 필요가 없어 단순히 전달된 도메인 오브젝트를 활용해 필요한 정보를 화면에 출력하기만 하면 된다.
- 오브젝트 중심 아키텍처는 데이터 중심 아키텍처와 달리
Category | ||
필드명 | 타입 | 설정 |
CategoryId | int | Primary Key |
Description | varchar(100) |
Product | ||
필드명 | 타입 | 설정 |
ProductId | int | Primary Key |
Name | varchar(100) | |
Price | int | |
CategoryId | int | Foreign Key (Category) |
/* 조건에 맞는 모든 카테고리와 상품 정보를 가져오는 데이터 중심 아키텍처 */
/* 1. JOIN을 이용해 두 개의 정보를 조합해서 2차원 구조의 정보를 만들어야 함 */
select c.categoryid, c.description, p.productid, p.name, p.price
from product p join category c
on p.categoryid = c.categoryid
/* 2. 이때 SQL 실행 결과는 맵이나 배열에 담을 수 있지만 배열에 담으려면 인덱스별로 필드 이름을 일일이 기억해야 하므로
맵에 필드 이름과 값을 함께 담고 맵의 리스트를 만들어 돌려주도록 함
하지만 이 경우 서비스 계층에 전달되는 것이 List<Map<String, Object>> 타입이므로 안에 담긴 내용이 어떤 것인지 알 수 없음
그러므로 어떠한 테이블을 조인해서 어떤 필드의 값을 가져오고, 필드 이름을 키로 갖는 맵에 값을 저장했음을 알아야 사용할 수 있음
만약 DAO에서 SQL을 필드 개수나 순서, 이름을 바꾼다면 서비스 계층, 프레젠테이션 계층의 코드도 같이 변경돼야 함 */
while(rs.next()) {
Map<String, Object> resMap = new HashMap<String, Object>();
resMap.put("categoryid", rs.getString(1));
resMap.put("description", rs.getString(2));
...
list.add(resMap);
}
/* 조건에 맞는 모든 카테고리와 상품 정보를 가져오는 오브젝트 중심 아키텍처 */
/* 1. 도메인 모델의 구조를 따라서 의미 있는 타입과 정보를 가진 클래스를 정의 */
public class Category {
int categoryid;
String description;
Set<Product> products; // 0 ~ N개의 Product를 참조하고 있는 컬렉션
// 접근자, 수정자
...
}
public class Product {
int productid;
String name;
int price;
Category category; // 1개의 Category를 가리키는 레퍼런스를 직접 가짐
// 접근자, 수정자
...
}
/* 2. 레퍼런스 변수를 통한 상호 참조가 가능하기 때문에
원한다면 Category 오브젝트에서 Category에 속한 Product를 간단히 가져올 수도 있음 */
Set<Product> products = myCategory.getProducts();
- 오브젝트 중심 방식에서 비즈니스 로직의 구현이 얼마나 간단하고 명확한지 살펴보자.
- 어떤 카테고리에 포함된 상품의 모든 가격을 계산해야 하는 로직이 필요하다면
서비스 계층의 오브젝트 안에서 메소드를 만들어서 사용하면 된다. - 이때 어떤 DAO를 이용해서 Category를 가져왔는지는 중요하지 않으며
어떻게든 Category 오브젝트를 가지고 있다면 메소드를 호출해서 카테고리에 담긴 모든 상품 가격의 합을 계산할 수 있다. - 도메인 모델을 알고 있다면 메소드가 무슨 작업을 하는지 이해하기 어렵지 않으며
테스트를 만들어 검증하기도 간단하고, 로직이 변경될 때 코드를 수정하기도 수월하다.
또한 Category 자체가 독립된 오브젝트이므로 서비스 계층 어디에서든지 이 메소드를 사용할 수 있다. - 이처럼 오브젝트 구조로 정보를 갖고 있으면 어떤 식으로든 활용하기 편리하다.
자바에서는 '.'을 이용해 레퍼런스 변수를 따라가면 관련된 정보를 손쉽게 이용할 수 있다. - 반면에 데이터 중심 방식에서라면 이런 식의 재사용 가능한 메소드를 만들어 사용하기가 쉽지 않으며 테스트하는 것도 복잡하다.
- 어떤 카테고리에 포함된 상품의 모든 가격을 계산해야 하는 로직이 필요하다면
// 서비스 계층
public int calcTotalOfProductPrice(Category cate) {
int sum = 0;
// Category는 도메인 모델을 따라 만들어진 오브젝트이므로 Category에 포함된 모든 Product를 간단히 가져올 수 있음
for (Product prd : cate.getProducts()) {
sum += prd.getPrice();
}
return sum;
}
- 반면 최적화된 SQL을 매번 만들어 사용하는 경우에 비해 성능 면에서 조금은 손해를 감수해야 할 수도 있다.
- DAO는 비즈니스 로직의 사용 방식을 알지 못하므로, 도메인 오브젝트의 모든 필드 값을 다 채워서 전달하는 경우가 대부분이다.
그런데 하나의 오브젝트에 담긴 필드의 개수가 많아지다 보면 그중에는 드물게 사용되는 필드도 있을 수 있다.
이처럼 비즈니스 로직에 따라서 필요한 정보가 달라질 수 있기 때문에 모든 필드 정보를 채워서 전달하는 문제가 발생하게 된다. - 또한 오브젝트 관계에서도 단순히 Product 정보만 필요한 비즈니스 로직이 있을 때
DAO는 Product 오브젝트에는 관계를 갖고 있는 Category 오브젝트도 함께 담겨 있을 것이므로
이를 다 조회해서 오브젝트로 만들어서 가져오는 것은 상당한 낭비다. - 이를 위해 Product 정보를 가져올 때 Category가 필요한 경우와 그렇지 않은 경우를 구분해서 DAO를 만들 수도 있으나
이처럼 최적화를 고려해서 DAO를 작성하려면 DAO는 비즈니스 로직에서 각 오브젝트를 어디까지 사용해야 하는지
어느 정보 알고 있어야 하므로 DAO와 비즈니스 로직 코드의 결합도가 높아지는 문제가 발생할 수 있다. - 이런 문제를 해결하는 접근 방법으로는 지연된 로딩 기법, 별도의 오브젝트 정의, 오브젝트/RDB 매핑 기술이 있다.
- 지연된 로딩 기법을 이용하면 일단 최소한의 오브젝트 정보만 읽어두고
관계하고 있는 오브젝트가 필요한 경우에만 다이내믹하게 DB에서 다시 읽어올 수 있다.
도메인 오브젝트를 사용하는 코드는 이런 사실을 전혀 의식하지 않고
처음부터 모든 오브젝트의 정보가 다 제공된다고 생각하고 작성하면 된다. - 필드가 너무 많은 테이블이 있다면 그중에서 가장 사용되는 것을 골라내서 별도의 오브젝트로 정의해두고
필요에 따라 구분해서 사용하게 할 수 있다.
물론 그에 따라 DAO 메소드가 추가돼야 하고, 어느 DAO를 사용할지를 서비스 계층에서 알고 있어야 하기 때문에,
약하긴 하지만 계층 사이의 결합이 발생한다. - 가장 이상적인 방법은 JPA, JDO, 하이버네이트, TopLinK와 같은 오브젝트/RDB 매핑 기술(ORM)을 사용하는 것이다.
이런 데이터 액세스 기술은 기본적으로 지연된 로딩 기법 등을 제공해주기 때문에
번거로운 코드를 만들지 않고도 도메인 오브젝트의 생성을 최적화할 수 있다.
또한 SQL 결과를 가지고 도메인 오브젝트를 만들고 값을 채우는 등의 복잡한 DAO 코드를 만들지 않아도 되며,
내부적으로 최적화된 SQL을 사용하도록 세밀히 튜닝할 수도 있다.
자주 변경되지 않으면서 많은 로직을 참조하는 레퍼런스 테이블이 있다면 오브젝트 캐시에 담아두고 사용할 수도 있다. - 그러므로 도메인 오브젝트를 사용하는 오브젝트 중심 아키텍처에서는 가능하다면
ORM과 같은 오브젝트 중심 데이터 액세스 기술을 사용하는 것을 권장한다.
ORM을 사용하지 않고 JDBC를 이용하는 경우라면 지연된 로딩 기법을 제공하는 코드를 추가해주거나,
사용되는 필드의 종류와 사용되는 관련 오브젝트의 범위에 따라서 여러 개의 DAO 메소드를 만들어 사용해야 할 수도 있다. - 그런데 도메인 오브젝트는 자바오브젝트다.
오브젝트는 원래 데이터를 저장하기 위해서만 사용하는 것이 아니라 내부의 정보를 이용하는 기능도 함께 갖고 있어야 한다.
클래스는 속성과 행위의 조합이므로 필드와 그에 대한 접근자, 수정자만 갖고 있는 오브젝트는 반쪽짜리다.
그러므로 가능하다면 이를 더 적극적으로 활용하게 만들어야 한다.
- DAO는 비즈니스 로직의 사용 방식을 알지 못하므로, 도메인 오브젝트의 모든 필드 값을 다 채워서 전달하는 경우가 대부분이다.
- 오브젝트 중심 아키텍처는 오브젝트 활용 방법을 기준으로 빈약한 도메인 오브젝트 방식, 풍성한 도메인 오브젝트 방식으로 구분된다.
- 도메인 오브젝트에 정보만 담겨 있고, 정보를 활용하는 아무런 기능도 갖고 있지 않는 오브젝트를 빈약한 오브젝트라고 부른다.
물론 도메인 모델을 반영한 오브젝트에 정보를 담아 활용하는 편이 도메인 오브젝트를 전혀 사용하지 않는 것보다는 훨씬 낫다.
이는 계층 사이의 독립성을 확보하기 위해 특정 계층에 종속되지 않으면서 애플리케이션 전반에서 사용될 수 있는 정보를 담는다. - 그렇다면 도메인 오브젝트에 넣을 수 있는 기능은 어떤 것일까?
도메인 모델을 반영해서 만들어진 도메인 오브젝트이니 그 기능이라고 하면 도메인의 비즈니스 로직이라고 볼 수 있다.
이러한 비즈니스 로직은 서비스 계층에 존재하게 되므로
사실 다루는 정보의 구조가 다를 뿐이지 빈약한 도메인 오브젝트 방식도 거대한 서비스 계층 방식의 하나라고 보면 된다.
도메인 오브젝트는 3개의 계층에는 독립적으로 존재하면서 일관된 구조의 정보를 담아서 계층 간에 전달하는데 사용된다.
비록 도메인 오브젝트라는 훨씬 유연하고 간결하지만,
여전히 서비스 계층의 메소드에 대부분의 비즈니스 로직이 들어 있어 로직의 재사용성이 떨어지고 중복의 문제가 발생하기 쉽다.
하지만 비즈니스 로직이 복잡하지 않다면 가장 만들기 쉽고 3계층 구조의 특징을 잘 살려서 개발할 수 있는 유용한 아키텍처다. - 풍성한 도메인 오브젝트 또는 영리한 도메인 오브젝트 방식은 빈약한 도메인 오브젝트의 단점을 극복하고
도메인 오브젝트의 객체지향적인 특징을 잘 사용할 수 있도록 개선한 것이다.
어떤 비즈니스 로직이 특정 도메인 오브젝트나 그 관련 오브젝트가 가진 정보와 깊은 관계가 있을 때
서비스 계층의 코드가 아니라 도메인 오브젝트에 넣어주고, 서비스 계층의 비즈니스 로직에서 재사용하게 만드는 것이다. - 앞선 calcaTotalOfProductPrice()는 Category라는 오브젝트와 그 관련 Product의 정보만을 사용한 간단한 로직이므로
굳이 서비스 계층의 메소드에 별도로 만들지 않고 Category 클래스의 메소드에 넣을 수도 있다.
이렇게 도메인 오브젝트 안에 로직을 담아두면 데이터와 그것을 사용하는 기능이 한 곳에 모여 있으므로
이 로직을 서비스 계층의 메소드에 따로 만드는 경우보다 응집도가 높다.
또한 이를 서비스 계층에 작성한 후 이 서비스 오브젝트 외의 서비스 계층 오브젝트에서 필요할 경우
그때마다 Category 오브젝트를 파라미터로 해서 서비스 오브젝트의 메소드를 호출하기 위해 DI도 해줘야 한다.
다른 모듈의 비즈니스 로직을 작성하고 있는 개발자는 이 기능을 몰라서 같은 기능을 가진 코드를 스스로 만들어 쓸지도 모른다.
하지만 이 로직을 Category 오브젝트 안에 넣어뒀다면 Category 오브젝트에게 직접 필요한 계산 작업을 요청하면 되므로
DI 등의 번거로운 작업이 필요 없고 중복도 나타나지 않는다.
그러므로 특정 도메인 오브젝트에 종속되는 비즈니스 로직은 서비스 계층의 오브젝트가 아닌 도메인 오브젝트 안에 넣으면 된다. - 하지만 도메인 오브젝트는 직접 데이터 액세스 계층이나 기반 계층
또는 다른 서비스 계층의 오브젝트에 접근할 수 없기 때문에 서비스 계층이 필요하기도 하다.
대개는 비즈니스 로직을 처리하는 중에 DB에서 정보를 가져오거나 결과를 다시 DB나 외부 시스템에 전송하는 작업이 필요하다.
그러러면 서비스 계층의 오브젝트와 같이 DAO 오브젝트를 DI 받아서 사용할 수 있어야 하지만 도메인 오브젝트는 그럴 수 없다.
3계층의 오브젝트는 모두 스프링의 빈으로 등록되기 때문에 필요에 따라 서로 DI 할 수 있다.
하지만 도메인 오브젝트는 스프링의 빈이 아니라 필요할 때마다 새롭게 만들어지므로
스프링이 생성하거나 관리하는 오브젝트가 아니므로 DI 받을 수 없다.
결국 이런 도메인 오브젝트는 DAO와 기반계층 오브젝트를 DI 받아 사용할 수 있는 서비스 계층의 코드가 필요하다.
서비스 계층은 도메인 오브젝트를 DB나 외부 리소스에서 가져오고 변경된 정보나 새로 등록된 정보를 DB에 반영하는 등의
작업과 함께 도메인 오브젝트가 갖고 있는 기능이 있다면 이를 활용해서 비즈니스 로직을 처리해야 한다.
그러므로 스프링의 빈으로 관리되는 3계층의 오브젝트들은 도메인 오브젝트를 자유롭게 이용할 수 있지만 반대는 안 된다.
- 도메인 오브젝트에 정보만 담겨 있고, 정보를 활용하는 아무런 기능도 갖고 있지 않는 오브젝트를 빈약한 오브젝트라고 부른다.


// 도메인 오브젝트
public class Category {
...
List<Product> products;
public int calcTotalOfProductPrice() { // Category를 따로 파라미터로 받을 필요 없이 자신이 가진 정보를 직접 사용
int sum = 0;
for (Product prd : this.products()) { // 오브젝트가 가진 내부 정보를 활용해서 로직을 수행
sum += prd.getPrice();
}
return sum;
}
}
// 서비스 계층에서 사용
public class InventoryService {
public void complexInventoryAnalysis() {
...
int total = category.calcTotalOfProdictPrice();
}
}
- 도메인 오브젝트가 스스로 필요한 정보를 DAO를 통해 가져올 수 있고,
생성이나 변경이 일어났을 때 직접 DAO에게 변경사항을 반영해달라고 요청할 수는 없을까?
DAO 외에도 다양한 기반계층의 서비스를 이용하도록 할 방법은 없을까?- 도메인 계층의 역할과 비중을 극대화하려다 보면 기존의 풍성한 도메인 오브젝트 방식으로는 만족할 수 없으므로
도메인 오브젝트가 기존 3계층과 같은 레벨로 격상되어 하나의 계층을 이루게 하는 도메인 계층 방식이 등장한다. - 도메인 오브젝트가 독립된 계층을 이뤘기 때문에 기존 방식과는 달리
도메인에 종속적인 비즈니스 로직의 처리는 서비스 계층이 아니라 도메인 계층의 오브젝트 안에서 진행되게 된다.
기존에는 서비스 계층에서 사용자가 입력한 정보를 바탕으로 새로운 도메인 오브젝트를 만들거나
데이터 액세스 계층을 통해 도메인 오브젝트를 가져와 도메인 오브젝트에게 비즈니스 로직의 처리를 요청했지만,
도메인 계층으로 들어가면 서비스 계층의 도움 없이도 비즈니스 로직의 대부분의 작업을 수행할 수 있다. - 또한 도메인 오브젝트가 기존 데이터 액세스 계층이나 기반 계층의 기능을 직접 활용할 수 있게 된다.
이때 도메인 오브젝트는 스프링에 등록돼서 싱글톤으로 관리되는 빈이 아니기 때문에 다른 빈을 DI 받을 수 없다고 했지만,
이러한 스프링이 관리하지 않는 오브젝트에도 간단한 설정인 AOP을 추가해 DI를 적용할 수 있다.
스프링 AOP는 부가기능을 추가할 수 있는 위치가 메소드 호출 과정에 한정되고 AOP의 적용 대상도 스프링의 빈 오브젝트뿐이다.
하지만 AspectJ AOP를 사용하면 클래스의 생성자가 호출되면서 오브젝트가 만들어지는 시점을 조인 포인트로 사용할 수 있고
스프링 빈이 아닌 일반 오브젝트에도 AOP 부가기능을 적용할 수 있다.
이를 이용해서 도메인 오브젝트가 생성되는 시점에 오브젝트의 수정자 메소드나 DI용 애노테이션을 참고해서
스프링의 빈 오브젝트를 스프링 컨테이너에서 찾아 DI 하도록 하면 된다.|
이 덕분에 도메인 오브젝트 기능의 제약이 사라져 이전의 어떤 방식보다 도메인 오브젝트에 많은 비즈니스 로직을 담아낼 수 있다. - 그럼에도 서비스 계층의 역할이 완전히 사라지는 건 아니다.
때로는 여러 도메인 오브젝트의 기능을 조합해서 복잡한 작업을 진행해야 하는 경우가 있다.
특정 도메인 오브젝트에 담길 수 없는 이런 작업은 서비스 계층에서 도메인 계층과 협력을 통해 진행하는 것이 바람직하다.
또는 도메인 계층을 거치지 않고 바로 데이터 액세스 계층으로부터 정보를 가져와 클라이언트에게 제공해야 하는 경우도 있다.
이럴 때도 서비스 계층이 인터페이스 역할을 담당하다.
대신 서비스 계층의 비중과 규모는 단순히 도메인 오브젝트를 사용하는 방식에 비해 훨씬 작다.
- 도메인 계층의 역할과 비중을 극대화하려다 보면 기존의 풍성한 도메인 오브젝트 방식으로는 만족할 수 없으므로
- 이러한 도메인 오브젝트를 독립적인 계층으로 만들려고 할 때 고려해야 할 중요한 사항이 있다.
- 도메인 오브젝트가 도메인 계층을 벗어나서도 사용되게 할지 말지 결정해야 한다.
도메인 오브젝트가 계층을 이루기 전에는 모든 계층에 걸쳐 사용되는 일종의 정보 전달 도구 같은 역할을 했다.
하지만 독자적인 계층을 이뤘을 때는 상황이 달라질 수 있으므로 선택할 수 있는 방법은 두 가지가 있다. - 첫 번째 방법은 여전히 모든 계층에서 도메인 오브젝트를 사용하는 것이다. 가장 손쉽고 편한 방법이다.
도메인 오브젝트를 이용해 도메인 로직을 적용하면 도메인 계층에서 진행되지만,
그 결과를 DB에 반영할 때나 화면에 출력하거나 페이지 이동을 위한 정보로 활용하기 위해
프레젠테이션 계층에서 참조할 때도 도메인 오브젝트를 활용할 수 있어
도메인 모델을 따르는 오브젝트 구조를 활용하는 면에서 오브젝트 중심 아키텍처의 장점을 그대로 누릴 수 있다. - 하지만 도메인 오브젝트의 메소드는 이제 단순한 값의 조작이나 분석, 변환 정도가 아니라
중요한 도메인/비즈니스 로직을 담당하게 되므로 심각한 혼란을 초래할 수 있다.
이런데 이런 막강한 기능을 가진 도메인 오브젝트를 프레젠테이션 계층이나 뷰 등에서 사용하게 해주면
이를 함부로 사용하는 위험이 뒤따를 수 있다.
이를 피하기 위해서는 철저한 개발 가이드라인을 만들어두고 이를 강력하게 적용해야 한다.
그럼에도 불구하고 이런 규정을 어기는 개발자가 있으므로 코딩 정책의 적용을 분석할 수 있는 툴을 이용해 검증하거나
AspectJ의 정책/표준 강제화 기능을 사용해 간단한 포인트컷 표현식만으로
특정 계층의 오브젝트가 사용할 수 있는 메소드의 범위를 제한하는 등을 할 수 있다. - 두 번째 방법은 도메인 오브젝트는 도메인 계층을 벗어나지 못하게 하는 것이다.
도메인 계층 밖으로 전달될 때는 별도로 준비된 정보 전달용 오브젝트에 도메인 오브젝트의 내용을 복사해 넘겨주는 것이다.
이는 데이터 전달을 위해 사용된다고 해서 DTO라고 불리며 상태의 변화를 허용하지 않고 읽기 전용으로 만들어지기도 한다.
반대로 사용자가 등록한 값이나 외부 시스템으로부터 전달받은 정보를 도메인 계층으로 전달하는 경우도 DTO를 이용할 수 있다.
DTO는 기능을 갖지 않으므로 사용하기 안전하며 외부 계층의 코드로부터 보호해준다.
하지만 도메인 오브젝트와 비슷한 구조를 가진 오브젝트를 따로 만들어야 하고 이를 매번 변환해줘야 하는 번거로움이 있다.
따라서 AOP와 같은 방법을 이용해 변환을 자동으로 해주도록 만들 필요가 있다.
- 도메인 오브젝트가 도메인 계층을 벗어나서도 사용되게 할지 말지 결정해야 한다.

- 하지만 도메인 계층의 오브젝트는 매우 짧은 시간 동안만 존재했다가 사라지는 것을 반복한다.
- 싱글톤으로 계속 존재하는 다른 계층의 오브젝트와 달리,
각 사용자의 요청별로 독립적으로 도메인 계층을 이루는 오브젝트들이 생성됐다가 해당 요청을 처리하고 나면 버려진다.
때론 하나의 복잡한 작업 흐름을 따라서 오래 존재하는 경우도 있지만
도메인 오브젝트는 사용자별 요청에 대해 독립적인 상태를 유지하고 있어야 하므로 여전히 그 생명주기는 짧다.
또한 상태정보를 담고 있기 때문에 여러 스레드가 공유하는 싱글톤이 될 수가 없다. - 이런 여러 가지 제약과 불편을 감수하면서라도 이 방식을 택해야 하는 경우는 매우 복잡하고 변경이 잦은 도메인을 가졌을 때다.
복잡한 도메인의 구조와 로직을 최대한 도메인 계층의 오브젝트에 반영하고,
도메인 모델과 설계에 변경이 발생했을 때 도메인 계층의 오브젝트도 빠르게 대응해서 변경해주기 위해서다.
도메인 계층은 응집도가 매우 높기 때문에 단위 테스트를 작성하기가 편리하고
객체지향적인 설계의 모든 장점을 동원해서 도메인이 가진 복잡함을 가장 유연한 방법으로 대응할 수 있다. - 반면에 그만큼 복잡하지 않은 애플리케이션이라면 이런 방식을 선택하는 것 자체가 오히려 과도한 부담을 줄 수도 있다.
따라서 도메인 계층을 이용하는 방식을 선택할 때는, 오브젝트 중심 아키텍처의 기본 두 가지 방식을 충분히 경험해보고
오브젝트 중심의 개발 방식에 익숙해진 뒤에 조심스럽게 접근해야 한다.
- 싱글톤으로 계속 존재하는 다른 계층의 오브젝트와 달리,
- 오브젝트 중심 아키텍처는 애플리케이션 내의 모든 정보를 항상 도메인 오브젝트에 담고 다녀야할까? 꼭 그렇지는 않다.
- 도메인 계층 방식의 경우 도메인 계층을 벗어난 정보를 DTO라고 불리는 특정 계층에 종속되지 않는
정보 전달의 목적을 가진 단순 오브젝트에 담아 사용하기도 한다. 그 외의 방법에서도 DTO의 사용이 꼭 필요할 때가 있다. - 대표적인 예는 리포트 쿼리라고 불리는 DB 쿼리의 실행 결과를 담는 경우다.
리포트 쿼리를 리포트를 출력하기 위해 생성하는 쿼리라는 의미이며
여러 테이블에 걸쳐 존재하는 자료를 분석하고 그에 따른 분석/통계 결과를 생상하는 쿼리이다.
이런 쿼리의 결과는 DB 테이블에 담긴 필드의 내용보다는 그 합계, 평균과 같은 계산 값이거나
아니면 여러 테이블의 필드를 다양한 방식으로 조합해서 만들어진다.
따라서 DB 쿼리의 실행 결과를 담을 만한 적절한 도메인 오브젝트를 찾을 수 없다.
그래서 이런 리포트 쿼리의 결과는 DTO라고 불리는 단순한 자바빈이나 아니면 키와 값 쌍을 갖는 맵에 담아서 전달해야 한다. - 때론 웹 서비스 등의 시스템과 자료를 주고받을 때 전송 규약에 맞춰서 도메인 오브젝트에 담긴 정보를 가공해야 하는데
이런 경우에도 DTO나 맵을 이용해 해당 형식에 맞도록 변경하는 작업이 필요하다.
- 도메인 계층 방식의 경우 도메인 계층을 벗어난 정보를 DTO라고 불리는 특정 계층에 종속되지 않는
스프링 애플리케이션을 위한 아키텍처 설계
- 3계층 구조는 스프링을 사용하는 엔터프라이즈 애플리케이션에서 가장 많이 사용되는 구조다.
- 스프링의 주요 모듈과 기술을 살펴보면 3계층 구조에 적합하도록 설계되어 있다.
- 단, 3계층이라는 것은 논리적이고 개념적인 구분이지 꼭 오브젝트 단위로 딱 끊어져서 만들어지는 게 아님을 염두에 둬야 한다.
예를 들어 서비스 계층을 굳이 도입하지 않아도 될 만큼 비즈니스 로직이 단순한 애플리케이션이라면
서비스 계층과 데이터 액세스 계층을 통합할 수도 있다. 이때는 트랜잭션 경계설정 위치를 DAO 메소드로 삼으면 된다.
반대로 프레젠테이션 계층에 서비스 계층을 통합하는 방법도 가능하다.
하지만 프레젠테이션 계층의 오브젝트는 트랜잭션 단위로 삼기에는 너무 크고 트랜잭션 전파를 통해 조합하기가 애매하므로
이 방법이 불가능하진 않지만 스프링에서는 그리 권장되지 않는다. - 따라서 3계층을 단순화해서 2계층으로 만든다면 서비스 계층과 데이터 액세스 계층을 통합하는 편이 낫다.
물론 이때도 논리적으로 서비스 계층과 데이터 액세스 계층의 경계를 분명하게 하는 게 좋다.
같은 오브젝트에 담겨 있다고 할지라도 비즈니스 로직을 적용한다면 각각 독립적으로 메소드를 분리해두는 것이 바람직하다. - 프레젠테이션 계층은 보통 MVC 패턴 또는 아키텍처를 주로 사용하며
이 MVC 중 가장 부담을 많이 지고 있는 컨트롤러에 해당하는 부분을 또 다시 세분화해서
여러 단계의 오브젝트로 만들 수 있도록 설계되어 있다. 이런 식으로 계층 내의 역할을 좀 더 세분화화는 경우도 있다. - 또한 프레젠테이션 계층은 그 경계를 애플리케이션이 배치된 서버를 떠나서 클라이언트까지 확장하기도 한다.
SOFEA라고 불리는 아키텍처는 프레젠테이션 계층의 코드가 서버에서 클라이언트로 다운로드돼서
클라이언트 장치 안에서 동작하면서 서버에 존재하는 서비스 계층 또는 부분 프레젠테이션 계층과 통신하는 구조로 만들어진다.
이때는 프레젠테이션 계층이 가졌던 사용자와의 인터페이스, 화면 흐름에 대한 제어,
서비스 계층과의 통신, 상태정보의 유지 등을 클라이언트에 다운로드된 코드에 대부분 담당하게 된다. - 스프링을 처음 학습하고 도입하는 입장이라면 일단은 가장 전통적인 서버 기반의 3계층 구조에 먼저 익숙해지는 것이 좋다.
프레젠테이션 계층은 SpringMVC를 이용하고 서비스 계층은 POJO로 구현하면서 트랜잭션 AOP를 적용하고,
데이터 액세스 계층은 JDBC를 비롯해서 스프링의 데이터 액세스 전략이 적용된 JPA, 하이버네이트, JDO 등을 활용하도록 한다.
처음에는 이 3계층 방식의 아키텍처 위에서 스프링의 철학과 가치를 따라서 코드를 만드는데 익숙해진 다음,
차차 다양한 방식으로 계층구조의 통합과 분산을 시도해보자.
- 스프링의 주요 모듈과 기술을 살펴보면 3계층 구조에 적합하도록 설계되어 있다.
- 스프링의 기본 기술에 가장 잘 들어맞고 쉽게 적용해볼 수 있는 것은 오브젝트 중심 아키텍처의 도메인 오브젝트 방식이다.
- 일단은 빈약한 도메인 오브젝트 방식으로 시작하는 게 가장 쉽다.
도메인 오브젝트를 계층 간의 정보 전송을 위해 사용하고, 이를 각 계층의 코드에서 활용한다.
DAO는 기술이 어떤 것이든 상관없이 서비스 계층에서 요청을 받거나 결과를 돌려줄 때 도메인 오브젝트 형태를 유지하게 만든다.
서비스 계층의 비즈니스 로직 또한 도메인 오브젝트를 이용해 작성한다.
프레젠테이션 계층에서도 이 도메인 오브젝트를 직접 활용하도록 만든다.
이렇게 도메인 오브젝트를 사용해 애플리케이션의 정보를 일관된 형태로 유지하는 게 스프링에 가장 잘 들어맞는 방식이다. - DB와 SQL에 많은 비즈니스 로직을 담고 있는 레거시 시스템을 스프링으로 전환하는 경우라면,
일단 데이터 중심의 아키텍처를 사용해도 무방하다.
3계층의 기본 구조로 잘 분리할 수만 있다면 DB 중심의 접근 방법을 사용해서 기존에 만들어 사용해봤던 SQL을 재사용한다.
그리고 전환 작업을 마치고 검증이 끝난 후에 단계적으로 로직을 DB에서 애플리케이션으로 가져오고,
오브젝트 중심으로 전환하는 시도를 하면 된다.
레거시 시스템의 기능을 가져오되 구현은 새롭게 처음부터 시작할 수 있다면
스프링의 철학과 개념에 가장 잘 들어맞는 도메인 오브젝트 중심의 아키텍처에 도전해보는 편이 나을 것이다. - 객체지향적인 도메인 분석과 모델링에 자신이 있고
도메인 오브젝트 설계와 구현, 독립적인 테스트를 자유롭게 적용할 수 있다면 과감하게 도메인 계층 방식을 도입할 수도 있다.
다만 도메인 계층에 DI를 적용하기 위해 스프링의 고급 기술을 활용해야 하고
여러 가지 고려할 점이 많으므로 충분한 사전 학습과 점검이 먼저 진행돼야 한다.
- 일단은 빈약한 도메인 오브젝트 방식으로 시작하는 게 가장 쉽다.
- 아키텍처 설계에서 한 가지 더 신경 써야 할 사항은 상태 관리다.
- 크게는 사용자 로그인 세션 관리부터, 작게는 하나의 단위 작업이지만 여러 페이지에 걸쳐 진행되는 위저드 기능까지
애플리케이션은 하나의 HTTP 요청의 범위를 넘어서 유지해야 하는 상태정보가 있다. - 하나의 엔터프라이즈 애플리케이션은 동시에 수많은 사용자의 요청을 처리하게 하기 위해
매번 간단한 요청을 받아서 그 결과를 돌려주는 방식으로 동작한다.
따라서 서버의 자원이 특정 사용자에게 일정하게 할당되지 않으며
서버 기반의 애플리케이션은 지속적으로 유지되는 상태를 갖지 않는다는 특징을 가진다.
클라이언트로부터의 요청을 처리하는 매우 짧은 시간 동안만 도메인 오브젝트와 같은 정보저장소에 현재 상태정보가 보관되지만,
이는 요청 결과를 클라이언트에 돌려주고 나면 바로 폐기된다.
그 덕분에 수많은 동시 사용자의 요청을 제한된 서버 리소스를 가지고 처리할 수 있다. - 하지만 어떤 식으로든 애플리케이션의 상태와 장시간 진행되는 작업정보는 유지돼야 한다.
이를 위해서 웹 클라이언트에 URL, 파라미터, 폼 히든 필드, 쿠키 등을 이용해 상태정보
또는 서버에 저장된 상태정보에 키 값 등을 전달해야 한다.
클라이언트와 서버 사이에서 많은 양의 정보를 계속해서 주고받을 수는 없으므로
중요한 상태정보는 파일 시스템, 데이터그리드, DB 등에 저장되기도 한다.
또는 제약이 있기는 하지만 HTTP 세션과 같은 서블릿 컨테이너가 제공하는 저장공간을 활용하기도 한다.
이렇게 상태를 저장, 유지하는데 어떤 방식으 사용할지 결정하는 일은 매우 중요하다. - 스프링은 기본적으로 상태가 유지되지 않는 빈과 오브젝트를 사용하는 것을 권장한다.
웹의 생리에 가장 잘 들어맞고 개발하기 쉬우며 서버를 여러 대로 확장하기가 매우 쉽다.
반면에 웹 클라이언트에 폼 정보를 출력하고 이를 수정하는 등의 작업을 위해서는 HTTP 세션을 적극 활용하기도 해
작업 중인 폼의 내용을 짧은 동안에라도 서버에 저장해두록 한다. - 상태는 클라이언트, 백엔드에 저장해두거나 서블릿의 HTTP 세션 정도에 일시적으로 저장해두는 것이 대부분이지만
경우에 따라서는 장기간 유지되며 중첩될 수 있는 상태를 다루는 고급 상태 관리 기법을 이용할 수도 있다.
애플리케이션의 특징에 따라서 스프링을 이용해서 상태유지 스타일의 애플리케이션을 얼마든지 만들 수 있다. - 스프링에서는 싱글톤 외에도 다른 스코프를 갖는 빈을 간단히 만들 수 있다.
빈의 스코프를 잘 활용하면 스프링이 관리하는 빈이면서 사용자별로 또는 단위 작업별로
독립적으로 생성되고 유지되는 오브젝트를 만들어 상태를 저장하고 이를 DI를 통해 서비스 빈에서 사용하게 만들 수 있다.
- 크게는 사용자 로그인 세션 관리부터, 작게는 하나의 단위 작업이지만 여러 페이지에 걸쳐 진행되는 위저드 기능까지
- 스프링은 거의 대부분의 자바 표준 기술과 함께 사용될 수 있으며
표준 기술 외에도 많이 사용되는 오픈소스 프레임워크, 라이브러리나 상용 제품도 함께 사용할 수 있다.- 기본적으로는 J2EE 1.4와 JavaEE 5.0을 지원한다.
스프링이 제공하는 많은 API는 표준 JavaEE의 인터페이스를 사용할 수 있도록 설계되어 있어
스프링이 만든 애플리케이션은 JSP, JSF, EJB, JNDI, JTA, JCA, JAX-WS, JMS, JavaMail, JPA와 같은
JavaEE의 세부 기술과 함께 사용될 수 있다. - 표준 기술 외에도 많이 사용되는 오픈소스 프레임워크, 라이브러리나 상용 제품도 스프링과 함께 사용할 수 있으므로
이런 기술을 스프링과 함께 사용할 때는 먼저 스프링이 공식적으로 지원하는 기술인지 확인해본다.
스프링의 의존 라이브러리로 등록된 100여 개의 각종 라이브러리를 살펴보면
스프링이 직접 API나 추상화 서비스 등을 통해 지원하는 표준 또는 오픈소스, 상용 기술에는 어떤 것이 있는지 알 수 있다.
- 기본적으로는 J2EE 1.4와 JavaEE 5.0을 지원한다.
- 스프링이 지원하는 기술이란 무슨 의미일까?
- 첫째, 해당 기술을 스프링의 DI 패턴을 따라 사용할 수 있다는 의미이다.
프레임워크나 라이브러리의 핵심 클래스를 빈으로 등록할 수 있게 지원해주므로
코드를 이용해 초기화해야만 사용할 수 있는 기능을 빈을 등록하는 것만으로 바로 사용할 수 있다.
프레임워크의 핵심 오브젝트를 빈의 형태로 등록해둘 수 있다면 프로퍼티를 이용해 세부 설정을 조정할 수 있고,
DI를 통해 다른 오브젝트에서 손쉽게 활용할 수 있다.
예를 들어 하이버네이트의 기능을 사용하려면 설정파일에 대한 정보, 설정 값을 담은 프로퍼티와 DB 연결에 대한 정보를 담은
Configuration 오브젝트를 만들고 이를 이용해 SessionFactory 오브젝트를 만드는 등의 작업이 필요하다.
반면 스프링에서는 하이버네이트의 SessionFactory를 스프링이 제공하는 빈인 LocalSessionFactoryBean을 제공하므로
이를 빈으로 등록하고 적절한 프로퍼티 설정을 해주면 간단히 SessionFactorya 오브젝트가 만들어져 빈으로 등록된다.
이처럼 스프링 외의 기술을 접목할 때는 가장 먼저 스프링의 빈으로 등록해서 DI 방식으로 통해 사용 가능한지 살펴봐야 한다.
만약 스프링이 지원하는 프레임워크나 라이브러리일 경우에는 스프링 빈의 설정만으로 등록하고 사용 가능하도록 팩토리 빈 클래스가 제공되지만, 그렇지 않다면 스프링 빈으로 등록돼서 사용하기에 적합하도록 만들어주는 팩토리 빈을 직접 도입해야 한다. - 둘째, 스프링의 서비스 추상화가 적용됐다는 의미이다.
서비스 추상화를 적용하는 경우 비슷한 기능을 제공하는 기술에 대한 일관된 접근 방법을 정의해주어
이를 통해 서드파티 프레임워크를 적용할 수 있을 뿐만 아니라 필요에 따라 호환 가능한 기술로 손쉽게 교체해서 사용할 수 있다.
스프링이 제공하는 서비스 추상화는 이미 존재하는 다양한 기술의 공통점을 분석해서 추상화했으므로
추상 서비스 인터페이스를 구현해서 각 기술과 연동하게 해주는 어댑터 클래스가 필요하다.
추상 서비스 인터페이스를 구현한 클래스들은 모두 스프링의 빈으로 등록되도록 만들어졌고,
세부 기술의 특성에 맞는 설정이 손쉽게 가능하도록 다양한 프로퍼티를 제공하고 있다. - 셋째, 스프링이 지지하는 프로그래밍 모델을 적용했다는 의미이다.
예를 들어 스프링의 데이터 액세스 지원 기능을 사용하면
데이터 액세스 기술의 종류에 상관없이 일관된 예외 계층구조를 따라서 예외가 던져진다.
여기에는 기술에 독립적인 DAO를 만들 수 있도록 데이터 액세스 예외를 추상화하고,
불필요하게 예외를 처리하는 코드를 피하도록 런타임 위주의 예외를 사용한다는 스프링의 개발철학이 적용된 것이다.
이를 통해, 서비스 계층의 비즈니스 로직을 담은 코드가 데이터 액세스 계층의 기술에 종속되지 않도록 만들어준다. - 넷째, 템플릿/콜백이 지원된다는 의미이다.
스프링은 JDBC, JMS, JCA를 비롯한 20여 가지 기술을 지원하는 템플릿/콜백을 제공한다.
스프링은 이런 기술을 간편하게 사용할 수 있도록 템플릿/콜백 기능을 제공하므로
대부분의 템플릿 클래스는 빈으로 등록해서 필요한 빈에서 DI 받아 사용할 수 있다. - 즉, 스프링이 어떤 기술을 지원한다는 것은
스프링이 지지하는 개발철학과 프로그래밍 모델을 따르면서 해당 기술을 사용할 수 있다는 의미이다.
물론 이런 방법을 따르지 않고도 스프링에서 여타 프레임워크를 사용하거나 라이브러리를 이용할 수는 있다.
그 대신 스프링의 장점을 포기해야 하고, 일관된 구조의 유연하고 확장 가능한 코드를 만들기가 힘들 수 있다.
- 첫째, 해당 기술을 스프링의 DI 패턴을 따라 사용할 수 있다는 의미이다.
- 물론 스프링에 세상에 나와 있는 모든 기술과 프레임워크를 지원하진 않는다.
- 스프링이 직접 지원하는 수십여 가지의 표준, 오픈소스, 상용 기술은
엔터프라이즈 애플리케이션 개발에 보편적으로 사용되는 유명한 것들로 제한된다.
기술이나 프레임워크가 자체적으로 스프링에서 손쉽게 사용될 수 있도록 관련 기능을 제공하는 경우도 적지 않다. - 대신 스프링의 기술 지원 방법은 스프링의 핵심 기술과 프로그래밍 모델만 잘 이용하면 어떤 기술에든지 손쉽게 적용이 가능하다.
따라서 스프링이 직접 지원하는 기술이 아니라도 스프링이 지원하는 기술이라는 의미의 네 가지 방법을 따라서 만들 수 있다. - 가장 먼저 스프링에 새로운 기술을 연동하려면 스프링의 빈으로 해당 기술의 핵심 오브젝트가 등록되도록 만드는 것이다.
필요에 따라 팩토리 빈을 사용해서 오브젝트 생성과 초기화 작업, 프로퍼티 노출 등을 해주어
애플리케이션 내의 빈들이 새로운 기술에 대해 빈을 DI 하는 방법으로 접근할 수 있게 하는 것이다.
반대로 기존 빈 오브젝트를 새로 추가할 기술에 사용할 수 있게 해줄 필요도 있다. - 때로는 서비스 추상화를 시도할 경우도 있으므로
어떤 경우는 스프링이 이미 제공하는 추상 인터페이스의 새로운 구현으로 만들어질 수 있고,
아예 새로운 추상화 인터페이스를 정의해서 적용할 수도 있다.
유연한 설정과 더불어 테스트를 위해서라도 새로운 인터페이스를 추가할 필요가 있다. - 네트워크 접근이나 파일 IO처럼 실패할 가능성이 있는 시도를 하는 기술이라면 템플릿/콜백 방식의 적용도 적극 고려해보자.
기술을 사용할 때마다 반복적으로 try/catch/finally 블록이 필요한 기술이라면 템플릿/콜백이 제격이다. - 때로는 AOP나 예외 전환을 적용할 수도 있다.
보통 외부에서 가져와서 사용하는 기술에서 손을 대지 않고 사용 방법을 개선하는 작업에 사용된다.
특정 예외가 던져졌을 때에 대한 포인트컷을 만들어두고 어드바이스에서 예외를 추상화된 런타임 예외로 바꿔서 다시 던져준다.
그러면 해당 API를 사용하는 코드를 전혀 손대지 않고도 손쉽게 예외 변환이 가능하다. - 스프링이 직접 지원 기능을 제공해주지 않으면
무엇인가 시도해볼 생각도 없이 스프링 이전의 방식대로 코드를 만들어서 외부 기술을 사용하는 건 부끄러운 일이다.
스프링을 사용하려면 스프링의 프로그래밍 모델과 그에 담긴 개발 철학을 따르는 일관된 코드를 만드는데 관심을 기울여야 한다.
- 스프링이 직접 지원하는 수십여 가지의 표준, 오픈소스, 상용 기술은
'Java-Spring > 토비의 스프링 3.1' 카테고리의 다른 글
[토비의 스프링 3.1] Vol.1 스프링의 이해와 원리 - 스프링 모듈(1) (0) | 2024.05.18 |
---|---|
[토비의 스프링 3.1] Vol.1 스프링의 이해와 원리 - 스프링 모듈(0) (0) | 2024.05.18 |
[토비의 스프링 3.1] Vol.1 스프링의 이해와 원리 - 스프링 프로젝트 시작하기 (2) (0) | 2024.04.25 |
[토비의 스프링 3.1] Vol.1 스프링의 이해와 원리 - 스프링 프로젝트 시작하기 (1) (0) | 2024.04.22 |
[토비의 스프링 3.1] Vol.1 스프링의 이해와 원리 - 스프링 프로젝트 시작하기 (0) (0) | 2024.04.22 |
9.3) 애플리케이션 아키텍처
아키텍처
- 클라이언트와 백엔드 시스템의 종류와 사용 기술, 연동 방법을 결정했다면 시스템 레벨의 아키텍처는 대략 구성된 셈이다.
다음으로 결정할 사항은 스프링 웹 애플리케이션의 아키텍처다.- 아키텍처의 가장 단순한 정의는 어떤 경계 안에 있는 내부 구성요소들이 어떤 책임을 갖고 있고,
어떤 방식으로 서로 관계를 맺고 동작하는지를 규정하는 것이라고 할 수 있다. - 아키텍처는 단순히 정적인 구조를 나타내는 것으로만 생각하기 쉽지만
실제로는 그 구조에서 일어나는 동적인 행위와 깊은 관계가 있다.
- 아키텍처의 가장 단순한 정의는 어떤 경계 안에 있는 내부 구성요소들이 어떤 책임을 갖고 있고,
계층형 아키텍처
- 지금까지는 주로 오브젝트 레벨에서 분리의 문제에 대해 생각해보며 DI를 기반으로한 유연한 설계와 구현 전략을 해왔다.
- 관심, 책임, 성격, 변하는 이유와 방식이 서로 다른 것들을 분리함으로써
분리된 각 요소의 응집도는 높여주고 서로의 결합도를 낮추었다. - 이러한 원리는 아키텍처 레벨에서 좀 더 큰 단위에 대해서도 동일하게 적용할 수 있다.
또한 오브젝트보다 작은 단위인 메소드 레벨에서도 같은 원리를 적용할 수 있다. - 성격이 다른 것을 아키텍처 레벨에서 분리해줄 경우
독자적으로 개발과 테스트가 가능해서 개발과 변경 작업이 모두 빨라질 수 있다.
또 구현 방법이나 세부 로직은 서로 영향을 주지 않고 변경될 수 있을 만큼 유연하다. 전체를 이해하기도 상대적으로 쉽다. - 이렇게 책임과 성격이 다른 것을 크게 그룹으로 만들어 분리해두는 것을 아키텍처 차원에서는 계층형 아키텍처라고 부른다.
또는 계층이라는 의미를 가진 영어 단어인 티어를 써서 멀티 티어 아키텍처라고 한다. - 보통 웹 기반의 엔터프라이즈 애플리케이션은 세 개의 계층을 갖는다고 해서 3계층 애플리케이션이라고도 한다.
물론 반드시 모든 엔터프라이즈 애플리케이션을 3계층으로 만들어야만 하는 것은 아니다.
경우에 따라서 전통적인 3계층 방식이 아닌 다른 구분 방법을 선택하기도 한다.
또 각 계층을 좀 더 세분화해서 더 작은 단위의 계층으로 나눌 수도 있다.
- 관심, 책임, 성격, 변하는 이유와 방식이 서로 다른 것들을 분리함으로써
- 3계층 아키텍처는 데이터 액세스 계층, 서비스 계층, 프레젠테이션 계층으로 구분한다.
- 그런데 이 3계층 아키텍처의 각 계층을 부르는 이름은 워낙 다양하지만
각각 의미가 있는 이름이기 때문에 모두 기억해두고 사용하는 것이 좋다.
- 그런데 이 3계층 아키텍처의 각 계층을 부르는 이름은 워낙 다양하지만

- 데이터 액세스 계층은 백엔드의 DB나 레거시 시스템과 연동하는 인터페이스 역할을 한다.
- 데이터 액세스 계층은 DAO 패턴을 보편적으로 사용하기 때문에 DAO 계층이라고도 불린다.
- 또한 DB 외에도 ERP, 레거시 시스템, 메인프레임 등에 접근하는 역할을 하기 때문에 EIS 계층이라고도 한다.
- 하지만 대개는 장기적인 데이터 저장을 목적으로 하는 DB 이용이 주된 책임이다.
- 데이터 액세스 계층은 사용 기술에 따라서 다시 세분화된 계층으로 구분될 수 있다.
데이터 액세스 계층 안에서 다시 세분화하는 경우는 추상화 수준에 따른 구분이므로 수직적인 계층이라고 부르기도 한다. - 기본 3계층은 기술 계층보다는 역할에 따라 구분한 것이므로 보통 그림으로 나타낼 때 가로로 배열한다.
반면에 이처럼 같은 책임을 가졌지만 추상화 레벨에 따라 구분하는 경우는 세로로 배열해서 표현한다. - 예를 들어 스프링의 JdbcTemplate을 사용하는 DAO 계층은 JdbcTemplate이 추상화를 위한 계층으로 사용돼서
항상 JdbcTemplate을 통해 로우레벨의 기반 계층에 존재하는 JDBC와 드라이버,
스프링의 트랜잭션 추상화 서비스의 동기화 기능을 간접적으로 이용하게 만든다. - 또한 또 하나의 추상 계층을 추가해 하위 계층의 종류가 다른 서비스를 일관된 방식으로 접근할 수 있게도 할 수 있다.
이렇게 새로운 계층을 추가하면 개발자의 애플리케이션 코드에 지대한 영향을 주기 때문에 매우 신중하게 결정해야 한다.
한번 새로운 계층과 API를 만들어 적용하면 이를 최대한 유지하도록 하위 계층의 변화에 대응해야 하는 책임도 갖게 된다. - 그러므로 만약 추상 계층을 새로 추가하는 것은 부담스럽고
경우에 따라서 유연하게 하위 계층의 API를 활용할 필요가 있다면
공통적인 기능을 분리해서 유틸리티나 헬퍼 메소드 또는 오브젝트로 제공해주는 것도 좋은 방법이다.
- 데이터 액세스 계층은 DAO 패턴을 보편적으로 사용하기 때문에 DAO 계층이라고도 불린다.


- 서비스 계층은 비즈니스 로직을 담는다.
- 엔터프라이즈 애플리케이션에서 가장 중요한 자산은 도메인의 핵심 비즈니스 로직이 들어 있는 서비스 계층이어야 한다.
- 잘 만들어진 스프링 애플리케이션의 서비스 계층 클래스는 이상적인 POJO로 작성된다.
POJO로 만든다면 객체지향적인 설계 기법이 적용된 코드를 통해서 비즈니스 로직의 핵심을 잘 담아내고,
이를 쉽게 테스트하고 유연하게 확장할 수 있다. - 서비스 계층은 DAO 계층을 호출하고 이를 활용해서 만들어진다.
- 때론 데이터 액세스를 위한 기능 외에도 서버나 시스템 레벨에서 제공하는 기반 서비스를 활용하기도 한다.
일반적으로는 서비스 계층이 필요에 따라 기반 서비스 계층의 API를 호출해서 이용한다.
예) 원격 호출을 통해 정보 가져오기, 메일 또는 메시징 서비스 이용
하지만 반대로 서비스 계층의 코드를 기반 서비스 계층에서 실행히시키는 경우도 있다.
예) 스케줄링 - 이런 기반 서비스는 3계층 어디에서나 접근이 가능하도록 만들 수도 있고,
아키텍처를 설계하기에 따라서 반드시 서비스 계층을 통해 사용되도록 제한할 수도 있다. - 서비스 계층은 특별한 경우가 아니라면 추상화 수직 계층구조를 가질 필요가 없다.
단순히 POJO 레벨에서 비즈니스 로직을 모델링하다가 상속구조를 만들 수 있을진 몰라도
기술 API를 직접 다루는 코드가 아니기 때문에 기술에 일관된 방식으로 접근하게 하거나
편하게 사용하게 해주는 추상화는 필요 없기 때문이다. - 기반 서비스 계층을 사용하는 경우에도 데이터 액세스 계층을 사용하는 경우와 마찬가지로
독립된 계층의 서비스를 이용하는 것으로 봐야 한다.
비즈니스 로직을 담은 서비스 계층과 엔터프라이즈 서비스를 제공하는 기반 서비스 계층은 완전히 다르다. - 이때 원칙적으로 서비스 계층 코드가 기반 서비스 계층의 구현에 종속되면 안 되므로
서비스 계층의 코드는 추상화된 기반 서비스 인터페이스를 통해서만 접근하도록 만들어서
특정 구현과 기술에 대한 종속성을 제거해야 한다.
또는 AOP를 통해서 서비스 계층의 코드를 침범하지 않고 부가기능을 추가하는 방법을 활용해야 한다.

- 프레젠테이션 계층은 주로 웹 기반의 UI를 만들어내고 그 흐름을 관리한다.
- 프레젠테이션 계층은 매우 다양한 기술과 프레임워크의 조합을 가질 수 있다.
- 대부분의 엔터프라이즈 애플리케이션을 사용하는 클라이언트들은 HTTP 프로토콜을 선호하므로
이런 클라이언트와 연결돼서 동작하는 엔터프라이즈 애플리케이션의 프레젠테이션 계층은
클라이언트의 종류와 상관없이 HTTP 프로토콜을 처리하는 가장 기본 엔진이 서블릿 기술을 바탕으로 한다. - 프레젠테이션 계층은 다른 계층과 달리 클라이언트까지 그 범위를 확장될 수도 있다.
초기 클라이언트 모델은 단순히 HTML로 만들어진 결과를 사람이 볼 수 있게 그려주고, 폼을 통해 입력받은 값을 전달했다.
모든 프레젠테이션 로직은 서버의 프레젠테이션 계층의 컴포넌트에서 처리되어 화면 흐름을 결정하는 것이나
사용자 입력 값에 대한 검증, 서비스 계층의 호출과 전달되는 값의 포맷의 변화, 화면을 어떻게 그릴지에 대한 로직 등이
모두 서버에서 처리되고 클라이언트는 단순히 서버 프레젠테이션 계층의 기능에 대한 사용자 인터페이스에 불과했다.
하지만 최근에는 점점 많은 프레젠테이션 로직이 클라이언트로 이동하고 있다. - 스프링은 웹 기반의 프레젠테이션 계층을 개발할 수 있는 전용 웹 프레임워크를 제공한다.
동시에 다양한 서드파티 웹 기술을 지원하기도 하며
아예 프레젠테이션 계층을 통째로 스프링이 아닌 다른 웹 기술을 가져다 사용할 수도 있다.
스프링 애플리케이션에 적용할 수 없는 웹 기술은 없다고 봐도 좋다.
- 오브젝트와 그 관계에 적용했던 대부분의 객체지향 설계의 원칙은 아키텍처 레벨의 계층과 그 관계에도 동일하게 적용할 수 있다.
- 각 계층은 응집도가 높으면서 다른 계층과는 낮은 결합도를 유지할 수 있어야 한다.
- 각 계층은 자신의 계층의 책임에만 충실해야 한다.
이를 위해 자신과 관련된 기술이 아닌 다른 기술 API의 사용을 삼가해야 한다. - 자신의 역할과 기술에만 충실한 계층을 만들면 각 계층 사이의 결합도는 자연스럽게 낮아지며
각 계층이 자신의 책임에만 충실하게 작성되어 있다면 필요한 그 밖의 작업은 다른 계층에 요청하게 될 것이다.
이때는 계층 레벨에 정의한 인터페이스를 통해서 요청을 하게 되고,
계층 간에 사용되는 인터페이스 메소드에는 특정 계층의 기술이 최대한 드러나지 않게 만들어야 한다.
그렇지 않으면 계층 사이에 결합도가 높아질 뿐만 아니라, 계층 간의 기술이나 역할이 서로 침범하는 일이 일어난다. - 계층 간 강한 결합이 생기면 유연성이 떨어지므로 각 계층의 내부 구현이 변화되면 다른 계층의 코드도 함께 수정해야 한다.
또한 코드의 중복이 일어날 가능성이 높고 전체 코드를 이해하기는 힘들어진다.
오브젝트 간의 강한 결합이 있을 때 발생하는 문제와 성격이 유사하고 그 파장은 훨씬 심각하다.
- 스프링의 DI는 기본적으로 오브젝트 사이의 관계를 다룬다.
- 따라서 계층 사이의 경계나 그 관계에 직접적으로 관여하지 않는다.
- 하지만 모든 경계에는 오브젝트가 존재하고 그 사이의 관계도 오브젝트 대 오브젝트로 정의되기 마련이다.
그런 면에서 스프링의 DI가 계층 사이의 관계에도 적용된다고 볼 수 있다. - 하지만 DI는 계층을 구분해주지 않기 때문에 빈 사이의 의존관계를 만들 때 다른 계층과의 관계에 주의해야 한다.
애플리케이션 정보 아키텍처
- 애플리케이션을 사이에 두고 흘러다니는 정보를 어떤 식으로 다룰지를 결정하는 일도 아키텍처를 결정할 때 매우 중요하다.
- 엔터프라이즈 애플리케이션에 존재하는 정보를 단순히 데이터로 다루는 경우(데이터 중심 아키텍처)와
오브젝트로 다루는 경우(오브젝트 중심 아키텍처), 두 가지 기준으로 구분해볼 수 있다.
- 엔터프라이즈 애플리케이션에 존재하는 정보를 단순히 데이터로 다루는 경우(데이터 중심 아키텍처)와
데이터 중심 아키텍처
- 데이터 중심 아키텍처는 엔터프라이즈 애플리케이션에 존재하는 정보를 단순히 데이터로 다룬다.
- 데이터 중심 아키텍처는 애플리케이션에 흘러다니는 정보를
단순히 값이나 값을 담기 위한 목적의 오브젝트 형태로 취급하는 구조로,
DB나 백엔드 시스템에서 가져온 정보를 값으로 다루고 그 값을 취급하는 코드를 만들어 로직을 구현하고
값을 그대로 프레젠테이션 계층의 뷰, 즉 사용자가 보는 화면과 연결해주는 것이다. - 그러므로 비즈니스 로직이 DB 내부의 저장 프로시저나 SQL에 담겨 있는 경우가 많으며
보통 DB에서 돌려주는 내용을 그대로 맵이나 단순 결과 저장용 오브젝트에 넣어서 전달한다. - 데이터 중심 아키텍처는 핵심 비즈니스 로직을 어디에 많이 두는지에 따라
DB에 무게를 두는 구조와 서비스 계층에 무게를 두는 구조로 구분할 수 있다.
- 데이터 중심 아키텍처는 애플리케이션에 흘러다니는 정보를
- 데이터 중심 구조의 특징은 하나의 업무 트랜잭션에 모든 계층의 코드가 종속되는 경향이 있다는 점이다.
- 예를 들어 사용자의 이름으로 사용자 정보를 검색해서 일치하는 사용자의 아이디, 비밀번호, 이름, 가입일자만을
보여주는 작업이 있다면 이것이 하나의 업무 단위가 되고 모든 계층의 코드가 이 기준에 맞춰서 만들어진다.
즉, 사용자 조회라는 단위 업무를 위해서만 존재하는 각 계층의 코드가 만들어진다는 뜻이다. - 이때 검색조건은 SQL로 만들어지며 사용자 정보를 웹 페이지에 나타낼 때 가입일자 중에서 연도만 보여줘야 한다면
SQL의 날짜처리 함수를 이용해야 한다. 하지만 이럴 경우 SQL은 이미 화면에 어떤 식으로 출력이 될지 알고 있는 셈이다. - 또한 SQL의 결과는 컬럼 이름을 키로 갖는 맵에 저장되거나 조회 페이지에 필요한 네 가지 정보를 담을 수 있는
단순한 오브젝트를 저쟁돼서 전달되므로 서비스 계층은 별로 할일이 없게 된다. - 이때 만약 새로운 필드가 추가되거나 DB 테이블의 컬럼 이름이 변경됐다면, 그에 따라서 맵이나 오브젝트에 저장될
엔트리 또는 프로퍼티 이름이 바뀌거나 추가될 것이고 그에 맞게 뷰의 내용도 변경된다. - 뿐만 아니라 모든 계층의 코드는 업무에 종속되므로 업무의 내용이 바뀌면 모든 계층의 코드가 함께 변경되게 되므로
종속적일 뿐 아니라 배타적이어서 다른 단위 업무에 재사용되기 힘들다. - 자바 코드의 로직은 기껏해야 사용자 요청에 따라서 어떤 SQL을 가진 DAO를 실행할지를 결정하는 정도일 것이다.
- 이로 인해 대부분의 코드는 대응되는 작업 단위에 1:1로 매핑되어
업무 트랜잭션 단위로 코드를 묶어서 만들어 기능을 세분화해서 분리하고 재사용하기 쉽지 않아
여러 작업에서 반복되는 기능이 있다면 코드는 중복되기 쉽다.
- 예를 들어 사용자의 이름으로 사용자 정보를 검색해서 일치하는 사용자의 아이디, 비밀번호, 이름, 가입일자만을
- 데이터 중심 아키텍처는 하나의 업무 트랜잭션을 모두 담은 서비스 계층 코드와
해당 업무에 특화된 SQL을 담은 하나 또는 여러 개의 DAO 메소드로 구성된다.- 서비스 계층이 프레젠테이션 계층에 전달하는 결과의 포맷은 보통 DAO의 SQL 결과와 같고,
웹 페이지의 출력 내용과도 1:1로 대응되게 된다. - 이런 식의 개발 방법과 아키텍처는 사실 자바 기술이 발전하기 이전의 엔터프라이즈 시스템에서 흔히 발견할 수 있다.
굳이 자바라는 객체지향 언어와 프로그래밍 기술을 사용하지 않더라도 3계층 구조로 만드는 전통적인 개발 방법이다.
또는 서비스 계층이 별 의미가 없으므로 주요 로직을 클라이언트의 독립 프로그램에 담아두고
DB 처리 로직만 DB에 분리해둔 2계층 구조에서도 비슷하게 발견할 수 있다. - 이런 방식은 개발하기 쉽다는 장점이 있으며 각 업무의 핵심을 담은 SQL을 중심으로 DAO부터 사용자가 보는 화면까지
한 벌씩 만들면 되기 때문에 툴이나 코드 생성기를 이용해서 자동화하는데도 유리하다. - 하지만 이런 방식은 자바 코드를 단지 DB와 웹 화면을 연결해주는 단순한 인터페이스 도구로 전략시키는 것이다.
자바의 오브젝트는 단지 HTTP 서비스 채널을 만들어주고
JDBC를 이용해 DB 기능을 사용하게 해주는 스크립트 정도로 역할이 축소된다.
굳이 자바를 쓰지 않고 전통적인 언어나 단순한 스크립트 언어 또는 DB 등에서 제공하는 툴로 대치하더라도 차이가 없다. - 이렇게 DB 중심의 업무 단위로 코드를 만들면 애플리케이션 내에서 흘러다니는 정보는 항상 단순한 포맷의 데이터다.
그러므로 이런 코드는 항상 SQL과 그 결과에 종속되기 때문에 SQL의 변화가 일어나면 같이 변경돼야 한다. - 이는 겉으로 보기에는 각 계층이 독립적으로 보이지만, 그 사이를 이동하는 데이터가 강한 결합을 만들게 된다.
- 서비스 계층이 프레젠테이션 계층에 전달하는 결과의 포맷은 보통 DAO의 SQL 결과와 같고,

- 스프링을 사용하면 이런 데이터 중심의 코드를 만들 수 있을 뿐만 아니라, 실제로 매우 흔하게 발견된다.
- 데이터와 업무 트랜잭션 중심의 개발에 익숙한 사람들이 많고 이런 아키텍처를 의도적으로 선호하는 개발자도 많다.
- 이로 인해 최소한의 공통 모듈정도만 제공되는 것을 사용하고, 그 외의 기능은 단위 업무 또는 웹 화면 단위로 만들어진다.
- 하지만 이런 개발 방식은 객체지향의 장점이 별로 활용되지 못하는데다
각 계층의 코드가 긴밀하게 연결되어 있어 변화에 매우 취약하다. - 이로 인해 필드 하나가 달라도 거의 비슷한 DAO 메소드를 새로 만들게 되어 로직을 DB와 SQL에 많이 담게 되면
점점 확장성이 떨어져 한계가 있으며 매우 큰 비용이 든다.
또한 복잡한 SQL을 누구나 쉽게 이해하고 필요에 따라 유연하게 변경하기 힘들어진다. - 상대적으로 애플리케이션 서버와 그 안에 담긴 오브젝트는 비용이 적게 든다.
서버는 늘려 쉽게 확장이 가능하고 서버의 배용은 매우 빠르게 하락하고 있으므로
오브젝트를 만들고 코드를 동작시키는 비용은 DB에서 비슷한 작업을 할 때에 비해 저렴하다.
따라서 로직을 DB보다는 애플리케이션으로 가져오는 편이 유리한 점이 많다. - 또한 SQL이나 저장 프로시저에 담긴 로직은 테스트하기 힘든 반면에
오브젝트에 담긴 로직은 간단히 검증할 수 있으므로 안정성도 높아진다. - 따라서 DB에는 부하를 가능한 한 주지 않는 간단한 작업만 하고
복잡한 로직은 오브젝트에 담아서 애플리케이션 내에서 처리하도록 만드는 편이 낫다.
- DB에서 가져온 데이터가 애플리케이션에 흘러다니는 정보의 중심이 되는 아키텍처긴 하지만
DB에 많은 로직을 두는 개발 방법의 단점을 피하면서 애플리케이션 코드의 비중을 높이는 방법이 있다.- 즉, DB에는 부하가 걸리지 않도록 저장 프로시저의 사용을 자제하고 복잡한 SQL을 피하면서,
주요 로직은 서비스 계층의 코드에서 처리하도록 만드는 것이다. - 여전히 SQL의 결과를 그대로 담고 있는 단순한 오브젝트 또는 맵을 이용해 데이터를 주고받지만
대신 많은 비즈니스 로직을 DB의 저장 프로시저나 SQL에서 서비스 계층의 오브젝트로 옮겨왔기 때문에
애플리케이션 코드의 비중이 커져 그만큼 구조는 단순해지고 객체지향 개발의 장점을 살릴 기회가 많아진다. - 비즈니스 로직을 DB나 SQL에 담는 경우에는 항상 최종 결과만 DAO에서 서비스 계층으로 전달되지만
반면 이러한 방식에서는 DAO에서 좀 더 단순한 결과를 돌려준다.
이러한 DAO가 돌려준 정보를 분석, 가공하면서 비즈니스 로직을 적용하는 것이 서비스 계층 코드의 책임이 된다.
이로 인해 DAO와 SQL은 상대적으로 단순해지고, 그중 일부는 여러 서비스 계층 코드에서 재사용이 가능해진다. - 하지만 비즈니스 로직이 복잡해지면 서비스 계층의 코드도 매우 복잡해지고 커진다.
업무 트랜잭션 단위로 서비스 계층의 메소드가 만들어진 가능성이 높은데, 그러면 하나의 메소드가 매우 거대해지기도 한다.
이처럼 상대적으로 단순한 DAO 로직을 사용하고,
비즈니스 로직의 대부분을 서비스 계층에 집중하는 이런 접근 방법은 결국 거대한 서비스 계층을 만들게 된다. - 서비스 계층의 코드는 여전히 업무 트랜잭션 단위로 집중돼서 만들어지기 때문에
DAO를 공유할 수 있는 것을 제외하면 코드의 중복도 적지 않게 발생한다. - 거대 서비스 계층 방식은 애플리케이션 코드에 비즈니스 로직이 담겨 있기 때문에
자바 언어의 장점을 활용해 로직을 구현할 수 있고 테스트하기도 수월하다.
또한 각 단위 업무별로 독립적인 개발이 가능하므로 초기 개발 속도가 빠르고,
개발자 사이에 간섭 없이 독립적인 개발이 가능하다. - 하지만 데이터 액세스 계층의 SQL은 서비스 계층의 비즈니스 로직의 필요에 따라 만들어지므로 계층 간의 결합도가 크다.
또한 서비스 계층의 메소드는 DAO가 제공해주는 값 포맷에 따라 취급하는 방법이 달라져 공통 기능을 일반화하기 힘들다.
- 즉, DB에는 부하가 걸리지 않도록 저장 프로시저의 사용을 자제하고 복잡한 SQL을 피하면서,

- 이러한 데이터 중심 아키텍처의 특징은 계층 사이의 결합도가 높은 편이고 응집도는 떨어진다는 점이다.
화면을 중심으로 하는 업무 트랜잭션 단위로 코드가 모이기 때문에 처음엔 개발하기 편하지만
중복이 많아지기 쉽고 장기적으로 코드를 관리하고 발전시키기 힘들다는 단점이 있다.
오브젝트 중심 아키텍처
- 오브젝트 중심 아키텍처는 엔터프라이즈 애플리케이션에 존재하는 정보를 오브젝트로 다룬다.
- 오브젝트 중심 아키텍처는 데이터 중심 아키텍처와 달리
도메인 모델을 반영하는 오브젝트 구조를 만들어서 그것을 각 계층 사이에서 정보를 전송하는데 사용한다. - 그래서 오브젝트 중심 아키텍처는 객체지향 분석과 모델링의 결과로 나오는 도메인 모델을 오브젝트 모델로 활용한다.
대개 도메인 모델은 DB의 엔티티 설계에도 반영되기 때문에 관계형 DB의 엔티티 구조와도 유사한 형태일 가능성이 높다. - 이렇게 오브젝트를 만들고 오브젝트 구조 안에 정보를 담아 계층 사이에 전달하게 만드는 것이 오브젝트 중심 아키텍처다.
- 도메인 모델은 애플리케이션 전 계층에서 동일한 의미를 가지므로
도메인 모델이 반영된 도메인 오브젝트도 전 계층에서 일관된 구조를 유지한 채로 사용될 수 있다.
그러므로 SQL이나 웹 페이지의 출력 포맷, 입력 폼 등에 종속되지 않는 일관된 형식의 애플리케이션 정보를 다룰 수 있다. - 이를 위해 먼저 도메인 모델의 구조를 따라서 의미 있는 타입과 정보를 가진 클래스를 정의한다.
이 구조는 단순히 특정 SQL에 대응되는 맵과 배열, 매번 달리지는 SQL 결과를 담기 위해 급조해서 만든 오브젝트와 달리,
애플리케이션 어디에서도 사용될 수 있는 일관된 형식의 도메인 정보를 담고 있다.
이로 인해 DB에서 SQL 결과로 가져온 값을 그대로 사용하는 경우와 다르게,
도메인 모델을 반영하는 오브젝트를 사용하면 자바 언어의 특성을 최대한 활용할 수 있도록 정보를 가공할 수 있다. - 대표적으로 오브젝트 사이의 관계를 나타내기 위해 키의 조합을 통해
그때그때 테이블을 조인해서 의미 있는 관계를 만들어내던 것과 달리,
레퍼런스 변수를 이용해서 다른 오브젝트를 참조할 수 있으며,
하나 이상의 오브젝트와 관계를 가지려면 컬렉션을 이용할 수도 있다.
그러므로 데이터 중심 방식에서 SQL을 이용해 조인한 다음 하나의 맵에 뭉뚱그려서 가져온 것과 달리,
오브젝트 중심 방식에서는 테이블의 정보와 그 관계를 유지한 채로 정확한 오브젝트를 만들어 사용한다. - 이렇게 도메인 모델을 따르는 오브젝트 구조를 만들려면
DB에서 가져온 데이터를 도메인 오브젝트 구조에 맞게 변환해줄 필요가 있다.
한 번 변환되면 그 이후의 작업은 수월해지므로 DAO는 자신이 DB에서 가져와서
도메인 모델 오브젝트에 담아주는 정보가 어떤 업무 트랜잭션에서 어떻게 사용될지는 신경 쓰지 않아도 된다.
서비스 계층 또한 DAO에서 어떤 SQL을 사용했는지는 몰라도 되므로
단순히 필요한 정보를 조건에 맞게 조회해서 도메인 모델 오브젝트 형태로 돌려주는 DAO를 이용하기만 하면 된다.
프레젠테이션 계층에 전달할 때도 어떤 DAO가 사용됐고, 어떤 비즈니스 로직을 거쳤는지에 관해서
프레젠테이션 계층은 알 필요가 없어 단순히 전달된 도메인 오브젝트를 활용해 필요한 정보를 화면에 출력하기만 하면 된다.
- 오브젝트 중심 아키텍처는 데이터 중심 아키텍처와 달리
Category | ||
필드명 | 타입 | 설정 |
CategoryId | int | Primary Key |
Description | varchar(100) |
Product | ||
필드명 | 타입 | 설정 |
ProductId | int | Primary Key |
Name | varchar(100) | |
Price | int | |
CategoryId | int | Foreign Key (Category) |
/* 조건에 맞는 모든 카테고리와 상품 정보를 가져오는 데이터 중심 아키텍처 */
/* 1. JOIN을 이용해 두 개의 정보를 조합해서 2차원 구조의 정보를 만들어야 함 */
select c.categoryid, c.description, p.productid, p.name, p.price
from product p join category c
on p.categoryid = c.categoryid
/* 2. 이때 SQL 실행 결과는 맵이나 배열에 담을 수 있지만 배열에 담으려면 인덱스별로 필드 이름을 일일이 기억해야 하므로
맵에 필드 이름과 값을 함께 담고 맵의 리스트를 만들어 돌려주도록 함
하지만 이 경우 서비스 계층에 전달되는 것이 List<Map<String, Object>> 타입이므로 안에 담긴 내용이 어떤 것인지 알 수 없음
그러므로 어떠한 테이블을 조인해서 어떤 필드의 값을 가져오고, 필드 이름을 키로 갖는 맵에 값을 저장했음을 알아야 사용할 수 있음
만약 DAO에서 SQL을 필드 개수나 순서, 이름을 바꾼다면 서비스 계층, 프레젠테이션 계층의 코드도 같이 변경돼야 함 */
while(rs.next()) {
Map<String, Object> resMap = new HashMap<String, Object>();
resMap.put("categoryid", rs.getString(1));
resMap.put("description", rs.getString(2));
...
list.add(resMap);
}
/* 조건에 맞는 모든 카테고리와 상품 정보를 가져오는 오브젝트 중심 아키텍처 */
/* 1. 도메인 모델의 구조를 따라서 의미 있는 타입과 정보를 가진 클래스를 정의 */
public class Category {
int categoryid;
String description;
Set<Product> products; // 0 ~ N개의 Product를 참조하고 있는 컬렉션
// 접근자, 수정자
...
}
public class Product {
int productid;
String name;
int price;
Category category; // 1개의 Category를 가리키는 레퍼런스를 직접 가짐
// 접근자, 수정자
...
}
/* 2. 레퍼런스 변수를 통한 상호 참조가 가능하기 때문에
원한다면 Category 오브젝트에서 Category에 속한 Product를 간단히 가져올 수도 있음 */
Set<Product> products = myCategory.getProducts();
- 오브젝트 중심 방식에서 비즈니스 로직의 구현이 얼마나 간단하고 명확한지 살펴보자.
- 어떤 카테고리에 포함된 상품의 모든 가격을 계산해야 하는 로직이 필요하다면
서비스 계층의 오브젝트 안에서 메소드를 만들어서 사용하면 된다. - 이때 어떤 DAO를 이용해서 Category를 가져왔는지는 중요하지 않으며
어떻게든 Category 오브젝트를 가지고 있다면 메소드를 호출해서 카테고리에 담긴 모든 상품 가격의 합을 계산할 수 있다. - 도메인 모델을 알고 있다면 메소드가 무슨 작업을 하는지 이해하기 어렵지 않으며
테스트를 만들어 검증하기도 간단하고, 로직이 변경될 때 코드를 수정하기도 수월하다.
또한 Category 자체가 독립된 오브젝트이므로 서비스 계층 어디에서든지 이 메소드를 사용할 수 있다. - 이처럼 오브젝트 구조로 정보를 갖고 있으면 어떤 식으로든 활용하기 편리하다.
자바에서는 '.'을 이용해 레퍼런스 변수를 따라가면 관련된 정보를 손쉽게 이용할 수 있다. - 반면에 데이터 중심 방식에서라면 이런 식의 재사용 가능한 메소드를 만들어 사용하기가 쉽지 않으며 테스트하는 것도 복잡하다.
- 어떤 카테고리에 포함된 상품의 모든 가격을 계산해야 하는 로직이 필요하다면
// 서비스 계층
public int calcTotalOfProductPrice(Category cate) {
int sum = 0;
// Category는 도메인 모델을 따라 만들어진 오브젝트이므로 Category에 포함된 모든 Product를 간단히 가져올 수 있음
for (Product prd : cate.getProducts()) {
sum += prd.getPrice();
}
return sum;
}
- 반면 최적화된 SQL을 매번 만들어 사용하는 경우에 비해 성능 면에서 조금은 손해를 감수해야 할 수도 있다.
- DAO는 비즈니스 로직의 사용 방식을 알지 못하므로, 도메인 오브젝트의 모든 필드 값을 다 채워서 전달하는 경우가 대부분이다.
그런데 하나의 오브젝트에 담긴 필드의 개수가 많아지다 보면 그중에는 드물게 사용되는 필드도 있을 수 있다.
이처럼 비즈니스 로직에 따라서 필요한 정보가 달라질 수 있기 때문에 모든 필드 정보를 채워서 전달하는 문제가 발생하게 된다. - 또한 오브젝트 관계에서도 단순히 Product 정보만 필요한 비즈니스 로직이 있을 때
DAO는 Product 오브젝트에는 관계를 갖고 있는 Category 오브젝트도 함께 담겨 있을 것이므로
이를 다 조회해서 오브젝트로 만들어서 가져오는 것은 상당한 낭비다. - 이를 위해 Product 정보를 가져올 때 Category가 필요한 경우와 그렇지 않은 경우를 구분해서 DAO를 만들 수도 있으나
이처럼 최적화를 고려해서 DAO를 작성하려면 DAO는 비즈니스 로직에서 각 오브젝트를 어디까지 사용해야 하는지
어느 정보 알고 있어야 하므로 DAO와 비즈니스 로직 코드의 결합도가 높아지는 문제가 발생할 수 있다. - 이런 문제를 해결하는 접근 방법으로는 지연된 로딩 기법, 별도의 오브젝트 정의, 오브젝트/RDB 매핑 기술이 있다.
- 지연된 로딩 기법을 이용하면 일단 최소한의 오브젝트 정보만 읽어두고
관계하고 있는 오브젝트가 필요한 경우에만 다이내믹하게 DB에서 다시 읽어올 수 있다.
도메인 오브젝트를 사용하는 코드는 이런 사실을 전혀 의식하지 않고
처음부터 모든 오브젝트의 정보가 다 제공된다고 생각하고 작성하면 된다. - 필드가 너무 많은 테이블이 있다면 그중에서 가장 사용되는 것을 골라내서 별도의 오브젝트로 정의해두고
필요에 따라 구분해서 사용하게 할 수 있다.
물론 그에 따라 DAO 메소드가 추가돼야 하고, 어느 DAO를 사용할지를 서비스 계층에서 알고 있어야 하기 때문에,
약하긴 하지만 계층 사이의 결합이 발생한다. - 가장 이상적인 방법은 JPA, JDO, 하이버네이트, TopLinK와 같은 오브젝트/RDB 매핑 기술(ORM)을 사용하는 것이다.
이런 데이터 액세스 기술은 기본적으로 지연된 로딩 기법 등을 제공해주기 때문에
번거로운 코드를 만들지 않고도 도메인 오브젝트의 생성을 최적화할 수 있다.
또한 SQL 결과를 가지고 도메인 오브젝트를 만들고 값을 채우는 등의 복잡한 DAO 코드를 만들지 않아도 되며,
내부적으로 최적화된 SQL을 사용하도록 세밀히 튜닝할 수도 있다.
자주 변경되지 않으면서 많은 로직을 참조하는 레퍼런스 테이블이 있다면 오브젝트 캐시에 담아두고 사용할 수도 있다. - 그러므로 도메인 오브젝트를 사용하는 오브젝트 중심 아키텍처에서는 가능하다면
ORM과 같은 오브젝트 중심 데이터 액세스 기술을 사용하는 것을 권장한다.
ORM을 사용하지 않고 JDBC를 이용하는 경우라면 지연된 로딩 기법을 제공하는 코드를 추가해주거나,
사용되는 필드의 종류와 사용되는 관련 오브젝트의 범위에 따라서 여러 개의 DAO 메소드를 만들어 사용해야 할 수도 있다. - 그런데 도메인 오브젝트는 자바오브젝트다.
오브젝트는 원래 데이터를 저장하기 위해서만 사용하는 것이 아니라 내부의 정보를 이용하는 기능도 함께 갖고 있어야 한다.
클래스는 속성과 행위의 조합이므로 필드와 그에 대한 접근자, 수정자만 갖고 있는 오브젝트는 반쪽짜리다.
그러므로 가능하다면 이를 더 적극적으로 활용하게 만들어야 한다.
- DAO는 비즈니스 로직의 사용 방식을 알지 못하므로, 도메인 오브젝트의 모든 필드 값을 다 채워서 전달하는 경우가 대부분이다.
- 오브젝트 중심 아키텍처는 오브젝트 활용 방법을 기준으로 빈약한 도메인 오브젝트 방식, 풍성한 도메인 오브젝트 방식으로 구분된다.
- 도메인 오브젝트에 정보만 담겨 있고, 정보를 활용하는 아무런 기능도 갖고 있지 않는 오브젝트를 빈약한 오브젝트라고 부른다.
물론 도메인 모델을 반영한 오브젝트에 정보를 담아 활용하는 편이 도메인 오브젝트를 전혀 사용하지 않는 것보다는 훨씬 낫다.
이는 계층 사이의 독립성을 확보하기 위해 특정 계층에 종속되지 않으면서 애플리케이션 전반에서 사용될 수 있는 정보를 담는다. - 그렇다면 도메인 오브젝트에 넣을 수 있는 기능은 어떤 것일까?
도메인 모델을 반영해서 만들어진 도메인 오브젝트이니 그 기능이라고 하면 도메인의 비즈니스 로직이라고 볼 수 있다.
이러한 비즈니스 로직은 서비스 계층에 존재하게 되므로
사실 다루는 정보의 구조가 다를 뿐이지 빈약한 도메인 오브젝트 방식도 거대한 서비스 계층 방식의 하나라고 보면 된다.
도메인 오브젝트는 3개의 계층에는 독립적으로 존재하면서 일관된 구조의 정보를 담아서 계층 간에 전달하는데 사용된다.
비록 도메인 오브젝트라는 훨씬 유연하고 간결하지만,
여전히 서비스 계층의 메소드에 대부분의 비즈니스 로직이 들어 있어 로직의 재사용성이 떨어지고 중복의 문제가 발생하기 쉽다.
하지만 비즈니스 로직이 복잡하지 않다면 가장 만들기 쉽고 3계층 구조의 특징을 잘 살려서 개발할 수 있는 유용한 아키텍처다. - 풍성한 도메인 오브젝트 또는 영리한 도메인 오브젝트 방식은 빈약한 도메인 오브젝트의 단점을 극복하고
도메인 오브젝트의 객체지향적인 특징을 잘 사용할 수 있도록 개선한 것이다.
어떤 비즈니스 로직이 특정 도메인 오브젝트나 그 관련 오브젝트가 가진 정보와 깊은 관계가 있을 때
서비스 계층의 코드가 아니라 도메인 오브젝트에 넣어주고, 서비스 계층의 비즈니스 로직에서 재사용하게 만드는 것이다. - 앞선 calcaTotalOfProductPrice()는 Category라는 오브젝트와 그 관련 Product의 정보만을 사용한 간단한 로직이므로
굳이 서비스 계층의 메소드에 별도로 만들지 않고 Category 클래스의 메소드에 넣을 수도 있다.
이렇게 도메인 오브젝트 안에 로직을 담아두면 데이터와 그것을 사용하는 기능이 한 곳에 모여 있으므로
이 로직을 서비스 계층의 메소드에 따로 만드는 경우보다 응집도가 높다.
또한 이를 서비스 계층에 작성한 후 이 서비스 오브젝트 외의 서비스 계층 오브젝트에서 필요할 경우
그때마다 Category 오브젝트를 파라미터로 해서 서비스 오브젝트의 메소드를 호출하기 위해 DI도 해줘야 한다.
다른 모듈의 비즈니스 로직을 작성하고 있는 개발자는 이 기능을 몰라서 같은 기능을 가진 코드를 스스로 만들어 쓸지도 모른다.
하지만 이 로직을 Category 오브젝트 안에 넣어뒀다면 Category 오브젝트에게 직접 필요한 계산 작업을 요청하면 되므로
DI 등의 번거로운 작업이 필요 없고 중복도 나타나지 않는다.
그러므로 특정 도메인 오브젝트에 종속되는 비즈니스 로직은 서비스 계층의 오브젝트가 아닌 도메인 오브젝트 안에 넣으면 된다. - 하지만 도메인 오브젝트는 직접 데이터 액세스 계층이나 기반 계층
또는 다른 서비스 계층의 오브젝트에 접근할 수 없기 때문에 서비스 계층이 필요하기도 하다.
대개는 비즈니스 로직을 처리하는 중에 DB에서 정보를 가져오거나 결과를 다시 DB나 외부 시스템에 전송하는 작업이 필요하다.
그러러면 서비스 계층의 오브젝트와 같이 DAO 오브젝트를 DI 받아서 사용할 수 있어야 하지만 도메인 오브젝트는 그럴 수 없다.
3계층의 오브젝트는 모두 스프링의 빈으로 등록되기 때문에 필요에 따라 서로 DI 할 수 있다.
하지만 도메인 오브젝트는 스프링의 빈이 아니라 필요할 때마다 새롭게 만들어지므로
스프링이 생성하거나 관리하는 오브젝트가 아니므로 DI 받을 수 없다.
결국 이런 도메인 오브젝트는 DAO와 기반계층 오브젝트를 DI 받아 사용할 수 있는 서비스 계층의 코드가 필요하다.
서비스 계층은 도메인 오브젝트를 DB나 외부 리소스에서 가져오고 변경된 정보나 새로 등록된 정보를 DB에 반영하는 등의
작업과 함께 도메인 오브젝트가 갖고 있는 기능이 있다면 이를 활용해서 비즈니스 로직을 처리해야 한다.
그러므로 스프링의 빈으로 관리되는 3계층의 오브젝트들은 도메인 오브젝트를 자유롭게 이용할 수 있지만 반대는 안 된다.
- 도메인 오브젝트에 정보만 담겨 있고, 정보를 활용하는 아무런 기능도 갖고 있지 않는 오브젝트를 빈약한 오브젝트라고 부른다.


// 도메인 오브젝트
public class Category {
...
List<Product> products;
public int calcTotalOfProductPrice() { // Category를 따로 파라미터로 받을 필요 없이 자신이 가진 정보를 직접 사용
int sum = 0;
for (Product prd : this.products()) { // 오브젝트가 가진 내부 정보를 활용해서 로직을 수행
sum += prd.getPrice();
}
return sum;
}
}
// 서비스 계층에서 사용
public class InventoryService {
public void complexInventoryAnalysis() {
...
int total = category.calcTotalOfProdictPrice();
}
}
- 도메인 오브젝트가 스스로 필요한 정보를 DAO를 통해 가져올 수 있고,
생성이나 변경이 일어났을 때 직접 DAO에게 변경사항을 반영해달라고 요청할 수는 없을까?
DAO 외에도 다양한 기반계층의 서비스를 이용하도록 할 방법은 없을까?- 도메인 계층의 역할과 비중을 극대화하려다 보면 기존의 풍성한 도메인 오브젝트 방식으로는 만족할 수 없으므로
도메인 오브젝트가 기존 3계층과 같은 레벨로 격상되어 하나의 계층을 이루게 하는 도메인 계층 방식이 등장한다. - 도메인 오브젝트가 독립된 계층을 이뤘기 때문에 기존 방식과는 달리
도메인에 종속적인 비즈니스 로직의 처리는 서비스 계층이 아니라 도메인 계층의 오브젝트 안에서 진행되게 된다.
기존에는 서비스 계층에서 사용자가 입력한 정보를 바탕으로 새로운 도메인 오브젝트를 만들거나
데이터 액세스 계층을 통해 도메인 오브젝트를 가져와 도메인 오브젝트에게 비즈니스 로직의 처리를 요청했지만,
도메인 계층으로 들어가면 서비스 계층의 도움 없이도 비즈니스 로직의 대부분의 작업을 수행할 수 있다. - 또한 도메인 오브젝트가 기존 데이터 액세스 계층이나 기반 계층의 기능을 직접 활용할 수 있게 된다.
이때 도메인 오브젝트는 스프링에 등록돼서 싱글톤으로 관리되는 빈이 아니기 때문에 다른 빈을 DI 받을 수 없다고 했지만,
이러한 스프링이 관리하지 않는 오브젝트에도 간단한 설정인 AOP을 추가해 DI를 적용할 수 있다.
스프링 AOP는 부가기능을 추가할 수 있는 위치가 메소드 호출 과정에 한정되고 AOP의 적용 대상도 스프링의 빈 오브젝트뿐이다.
하지만 AspectJ AOP를 사용하면 클래스의 생성자가 호출되면서 오브젝트가 만들어지는 시점을 조인 포인트로 사용할 수 있고
스프링 빈이 아닌 일반 오브젝트에도 AOP 부가기능을 적용할 수 있다.
이를 이용해서 도메인 오브젝트가 생성되는 시점에 오브젝트의 수정자 메소드나 DI용 애노테이션을 참고해서
스프링의 빈 오브젝트를 스프링 컨테이너에서 찾아 DI 하도록 하면 된다.|
이 덕분에 도메인 오브젝트 기능의 제약이 사라져 이전의 어떤 방식보다 도메인 오브젝트에 많은 비즈니스 로직을 담아낼 수 있다. - 그럼에도 서비스 계층의 역할이 완전히 사라지는 건 아니다.
때로는 여러 도메인 오브젝트의 기능을 조합해서 복잡한 작업을 진행해야 하는 경우가 있다.
특정 도메인 오브젝트에 담길 수 없는 이런 작업은 서비스 계층에서 도메인 계층과 협력을 통해 진행하는 것이 바람직하다.
또는 도메인 계층을 거치지 않고 바로 데이터 액세스 계층으로부터 정보를 가져와 클라이언트에게 제공해야 하는 경우도 있다.
이럴 때도 서비스 계층이 인터페이스 역할을 담당하다.
대신 서비스 계층의 비중과 규모는 단순히 도메인 오브젝트를 사용하는 방식에 비해 훨씬 작다.
- 도메인 계층의 역할과 비중을 극대화하려다 보면 기존의 풍성한 도메인 오브젝트 방식으로는 만족할 수 없으므로
- 이러한 도메인 오브젝트를 독립적인 계층으로 만들려고 할 때 고려해야 할 중요한 사항이 있다.
- 도메인 오브젝트가 도메인 계층을 벗어나서도 사용되게 할지 말지 결정해야 한다.
도메인 오브젝트가 계층을 이루기 전에는 모든 계층에 걸쳐 사용되는 일종의 정보 전달 도구 같은 역할을 했다.
하지만 독자적인 계층을 이뤘을 때는 상황이 달라질 수 있으므로 선택할 수 있는 방법은 두 가지가 있다. - 첫 번째 방법은 여전히 모든 계층에서 도메인 오브젝트를 사용하는 것이다. 가장 손쉽고 편한 방법이다.
도메인 오브젝트를 이용해 도메인 로직을 적용하면 도메인 계층에서 진행되지만,
그 결과를 DB에 반영할 때나 화면에 출력하거나 페이지 이동을 위한 정보로 활용하기 위해
프레젠테이션 계층에서 참조할 때도 도메인 오브젝트를 활용할 수 있어
도메인 모델을 따르는 오브젝트 구조를 활용하는 면에서 오브젝트 중심 아키텍처의 장점을 그대로 누릴 수 있다. - 하지만 도메인 오브젝트의 메소드는 이제 단순한 값의 조작이나 분석, 변환 정도가 아니라
중요한 도메인/비즈니스 로직을 담당하게 되므로 심각한 혼란을 초래할 수 있다.
이런데 이런 막강한 기능을 가진 도메인 오브젝트를 프레젠테이션 계층이나 뷰 등에서 사용하게 해주면
이를 함부로 사용하는 위험이 뒤따를 수 있다.
이를 피하기 위해서는 철저한 개발 가이드라인을 만들어두고 이를 강력하게 적용해야 한다.
그럼에도 불구하고 이런 규정을 어기는 개발자가 있으므로 코딩 정책의 적용을 분석할 수 있는 툴을 이용해 검증하거나
AspectJ의 정책/표준 강제화 기능을 사용해 간단한 포인트컷 표현식만으로
특정 계층의 오브젝트가 사용할 수 있는 메소드의 범위를 제한하는 등을 할 수 있다. - 두 번째 방법은 도메인 오브젝트는 도메인 계층을 벗어나지 못하게 하는 것이다.
도메인 계층 밖으로 전달될 때는 별도로 준비된 정보 전달용 오브젝트에 도메인 오브젝트의 내용을 복사해 넘겨주는 것이다.
이는 데이터 전달을 위해 사용된다고 해서 DTO라고 불리며 상태의 변화를 허용하지 않고 읽기 전용으로 만들어지기도 한다.
반대로 사용자가 등록한 값이나 외부 시스템으로부터 전달받은 정보를 도메인 계층으로 전달하는 경우도 DTO를 이용할 수 있다.
DTO는 기능을 갖지 않으므로 사용하기 안전하며 외부 계층의 코드로부터 보호해준다.
하지만 도메인 오브젝트와 비슷한 구조를 가진 오브젝트를 따로 만들어야 하고 이를 매번 변환해줘야 하는 번거로움이 있다.
따라서 AOP와 같은 방법을 이용해 변환을 자동으로 해주도록 만들 필요가 있다.
- 도메인 오브젝트가 도메인 계층을 벗어나서도 사용되게 할지 말지 결정해야 한다.

- 하지만 도메인 계층의 오브젝트는 매우 짧은 시간 동안만 존재했다가 사라지는 것을 반복한다.
- 싱글톤으로 계속 존재하는 다른 계층의 오브젝트와 달리,
각 사용자의 요청별로 독립적으로 도메인 계층을 이루는 오브젝트들이 생성됐다가 해당 요청을 처리하고 나면 버려진다.
때론 하나의 복잡한 작업 흐름을 따라서 오래 존재하는 경우도 있지만
도메인 오브젝트는 사용자별 요청에 대해 독립적인 상태를 유지하고 있어야 하므로 여전히 그 생명주기는 짧다.
또한 상태정보를 담고 있기 때문에 여러 스레드가 공유하는 싱글톤이 될 수가 없다. - 이런 여러 가지 제약과 불편을 감수하면서라도 이 방식을 택해야 하는 경우는 매우 복잡하고 변경이 잦은 도메인을 가졌을 때다.
복잡한 도메인의 구조와 로직을 최대한 도메인 계층의 오브젝트에 반영하고,
도메인 모델과 설계에 변경이 발생했을 때 도메인 계층의 오브젝트도 빠르게 대응해서 변경해주기 위해서다.
도메인 계층은 응집도가 매우 높기 때문에 단위 테스트를 작성하기가 편리하고
객체지향적인 설계의 모든 장점을 동원해서 도메인이 가진 복잡함을 가장 유연한 방법으로 대응할 수 있다. - 반면에 그만큼 복잡하지 않은 애플리케이션이라면 이런 방식을 선택하는 것 자체가 오히려 과도한 부담을 줄 수도 있다.
따라서 도메인 계층을 이용하는 방식을 선택할 때는, 오브젝트 중심 아키텍처의 기본 두 가지 방식을 충분히 경험해보고
오브젝트 중심의 개발 방식에 익숙해진 뒤에 조심스럽게 접근해야 한다.
- 싱글톤으로 계속 존재하는 다른 계층의 오브젝트와 달리,
- 오브젝트 중심 아키텍처는 애플리케이션 내의 모든 정보를 항상 도메인 오브젝트에 담고 다녀야할까? 꼭 그렇지는 않다.
- 도메인 계층 방식의 경우 도메인 계층을 벗어난 정보를 DTO라고 불리는 특정 계층에 종속되지 않는
정보 전달의 목적을 가진 단순 오브젝트에 담아 사용하기도 한다. 그 외의 방법에서도 DTO의 사용이 꼭 필요할 때가 있다. - 대표적인 예는 리포트 쿼리라고 불리는 DB 쿼리의 실행 결과를 담는 경우다.
리포트 쿼리를 리포트를 출력하기 위해 생성하는 쿼리라는 의미이며
여러 테이블에 걸쳐 존재하는 자료를 분석하고 그에 따른 분석/통계 결과를 생상하는 쿼리이다.
이런 쿼리의 결과는 DB 테이블에 담긴 필드의 내용보다는 그 합계, 평균과 같은 계산 값이거나
아니면 여러 테이블의 필드를 다양한 방식으로 조합해서 만들어진다.
따라서 DB 쿼리의 실행 결과를 담을 만한 적절한 도메인 오브젝트를 찾을 수 없다.
그래서 이런 리포트 쿼리의 결과는 DTO라고 불리는 단순한 자바빈이나 아니면 키와 값 쌍을 갖는 맵에 담아서 전달해야 한다. - 때론 웹 서비스 등의 시스템과 자료를 주고받을 때 전송 규약에 맞춰서 도메인 오브젝트에 담긴 정보를 가공해야 하는데
이런 경우에도 DTO나 맵을 이용해 해당 형식에 맞도록 변경하는 작업이 필요하다.
- 도메인 계층 방식의 경우 도메인 계층을 벗어난 정보를 DTO라고 불리는 특정 계층에 종속되지 않는
스프링 애플리케이션을 위한 아키텍처 설계
- 3계층 구조는 스프링을 사용하는 엔터프라이즈 애플리케이션에서 가장 많이 사용되는 구조다.
- 스프링의 주요 모듈과 기술을 살펴보면 3계층 구조에 적합하도록 설계되어 있다.
- 단, 3계층이라는 것은 논리적이고 개념적인 구분이지 꼭 오브젝트 단위로 딱 끊어져서 만들어지는 게 아님을 염두에 둬야 한다.
예를 들어 서비스 계층을 굳이 도입하지 않아도 될 만큼 비즈니스 로직이 단순한 애플리케이션이라면
서비스 계층과 데이터 액세스 계층을 통합할 수도 있다. 이때는 트랜잭션 경계설정 위치를 DAO 메소드로 삼으면 된다.
반대로 프레젠테이션 계층에 서비스 계층을 통합하는 방법도 가능하다.
하지만 프레젠테이션 계층의 오브젝트는 트랜잭션 단위로 삼기에는 너무 크고 트랜잭션 전파를 통해 조합하기가 애매하므로
이 방법이 불가능하진 않지만 스프링에서는 그리 권장되지 않는다. - 따라서 3계층을 단순화해서 2계층으로 만든다면 서비스 계층과 데이터 액세스 계층을 통합하는 편이 낫다.
물론 이때도 논리적으로 서비스 계층과 데이터 액세스 계층의 경계를 분명하게 하는 게 좋다.
같은 오브젝트에 담겨 있다고 할지라도 비즈니스 로직을 적용한다면 각각 독립적으로 메소드를 분리해두는 것이 바람직하다. - 프레젠테이션 계층은 보통 MVC 패턴 또는 아키텍처를 주로 사용하며
이 MVC 중 가장 부담을 많이 지고 있는 컨트롤러에 해당하는 부분을 또 다시 세분화해서
여러 단계의 오브젝트로 만들 수 있도록 설계되어 있다. 이런 식으로 계층 내의 역할을 좀 더 세분화화는 경우도 있다. - 또한 프레젠테이션 계층은 그 경계를 애플리케이션이 배치된 서버를 떠나서 클라이언트까지 확장하기도 한다.
SOFEA라고 불리는 아키텍처는 프레젠테이션 계층의 코드가 서버에서 클라이언트로 다운로드돼서
클라이언트 장치 안에서 동작하면서 서버에 존재하는 서비스 계층 또는 부분 프레젠테이션 계층과 통신하는 구조로 만들어진다.
이때는 프레젠테이션 계층이 가졌던 사용자와의 인터페이스, 화면 흐름에 대한 제어,
서비스 계층과의 통신, 상태정보의 유지 등을 클라이언트에 다운로드된 코드에 대부분 담당하게 된다. - 스프링을 처음 학습하고 도입하는 입장이라면 일단은 가장 전통적인 서버 기반의 3계층 구조에 먼저 익숙해지는 것이 좋다.
프레젠테이션 계층은 SpringMVC를 이용하고 서비스 계층은 POJO로 구현하면서 트랜잭션 AOP를 적용하고,
데이터 액세스 계층은 JDBC를 비롯해서 스프링의 데이터 액세스 전략이 적용된 JPA, 하이버네이트, JDO 등을 활용하도록 한다.
처음에는 이 3계층 방식의 아키텍처 위에서 스프링의 철학과 가치를 따라서 코드를 만드는데 익숙해진 다음,
차차 다양한 방식으로 계층구조의 통합과 분산을 시도해보자.
- 스프링의 주요 모듈과 기술을 살펴보면 3계층 구조에 적합하도록 설계되어 있다.
- 스프링의 기본 기술에 가장 잘 들어맞고 쉽게 적용해볼 수 있는 것은 오브젝트 중심 아키텍처의 도메인 오브젝트 방식이다.
- 일단은 빈약한 도메인 오브젝트 방식으로 시작하는 게 가장 쉽다.
도메인 오브젝트를 계층 간의 정보 전송을 위해 사용하고, 이를 각 계층의 코드에서 활용한다.
DAO는 기술이 어떤 것이든 상관없이 서비스 계층에서 요청을 받거나 결과를 돌려줄 때 도메인 오브젝트 형태를 유지하게 만든다.
서비스 계층의 비즈니스 로직 또한 도메인 오브젝트를 이용해 작성한다.
프레젠테이션 계층에서도 이 도메인 오브젝트를 직접 활용하도록 만든다.
이렇게 도메인 오브젝트를 사용해 애플리케이션의 정보를 일관된 형태로 유지하는 게 스프링에 가장 잘 들어맞는 방식이다. - DB와 SQL에 많은 비즈니스 로직을 담고 있는 레거시 시스템을 스프링으로 전환하는 경우라면,
일단 데이터 중심의 아키텍처를 사용해도 무방하다.
3계층의 기본 구조로 잘 분리할 수만 있다면 DB 중심의 접근 방법을 사용해서 기존에 만들어 사용해봤던 SQL을 재사용한다.
그리고 전환 작업을 마치고 검증이 끝난 후에 단계적으로 로직을 DB에서 애플리케이션으로 가져오고,
오브젝트 중심으로 전환하는 시도를 하면 된다.
레거시 시스템의 기능을 가져오되 구현은 새롭게 처음부터 시작할 수 있다면
스프링의 철학과 개념에 가장 잘 들어맞는 도메인 오브젝트 중심의 아키텍처에 도전해보는 편이 나을 것이다. - 객체지향적인 도메인 분석과 모델링에 자신이 있고
도메인 오브젝트 설계와 구현, 독립적인 테스트를 자유롭게 적용할 수 있다면 과감하게 도메인 계층 방식을 도입할 수도 있다.
다만 도메인 계층에 DI를 적용하기 위해 스프링의 고급 기술을 활용해야 하고
여러 가지 고려할 점이 많으므로 충분한 사전 학습과 점검이 먼저 진행돼야 한다.
- 일단은 빈약한 도메인 오브젝트 방식으로 시작하는 게 가장 쉽다.
- 아키텍처 설계에서 한 가지 더 신경 써야 할 사항은 상태 관리다.
- 크게는 사용자 로그인 세션 관리부터, 작게는 하나의 단위 작업이지만 여러 페이지에 걸쳐 진행되는 위저드 기능까지
애플리케이션은 하나의 HTTP 요청의 범위를 넘어서 유지해야 하는 상태정보가 있다. - 하나의 엔터프라이즈 애플리케이션은 동시에 수많은 사용자의 요청을 처리하게 하기 위해
매번 간단한 요청을 받아서 그 결과를 돌려주는 방식으로 동작한다.
따라서 서버의 자원이 특정 사용자에게 일정하게 할당되지 않으며
서버 기반의 애플리케이션은 지속적으로 유지되는 상태를 갖지 않는다는 특징을 가진다.
클라이언트로부터의 요청을 처리하는 매우 짧은 시간 동안만 도메인 오브젝트와 같은 정보저장소에 현재 상태정보가 보관되지만,
이는 요청 결과를 클라이언트에 돌려주고 나면 바로 폐기된다.
그 덕분에 수많은 동시 사용자의 요청을 제한된 서버 리소스를 가지고 처리할 수 있다. - 하지만 어떤 식으로든 애플리케이션의 상태와 장시간 진행되는 작업정보는 유지돼야 한다.
이를 위해서 웹 클라이언트에 URL, 파라미터, 폼 히든 필드, 쿠키 등을 이용해 상태정보
또는 서버에 저장된 상태정보에 키 값 등을 전달해야 한다.
클라이언트와 서버 사이에서 많은 양의 정보를 계속해서 주고받을 수는 없으므로
중요한 상태정보는 파일 시스템, 데이터그리드, DB 등에 저장되기도 한다.
또는 제약이 있기는 하지만 HTTP 세션과 같은 서블릿 컨테이너가 제공하는 저장공간을 활용하기도 한다.
이렇게 상태를 저장, 유지하는데 어떤 방식으 사용할지 결정하는 일은 매우 중요하다. - 스프링은 기본적으로 상태가 유지되지 않는 빈과 오브젝트를 사용하는 것을 권장한다.
웹의 생리에 가장 잘 들어맞고 개발하기 쉬우며 서버를 여러 대로 확장하기가 매우 쉽다.
반면에 웹 클라이언트에 폼 정보를 출력하고 이를 수정하는 등의 작업을 위해서는 HTTP 세션을 적극 활용하기도 해
작업 중인 폼의 내용을 짧은 동안에라도 서버에 저장해두록 한다. - 상태는 클라이언트, 백엔드에 저장해두거나 서블릿의 HTTP 세션 정도에 일시적으로 저장해두는 것이 대부분이지만
경우에 따라서는 장기간 유지되며 중첩될 수 있는 상태를 다루는 고급 상태 관리 기법을 이용할 수도 있다.
애플리케이션의 특징에 따라서 스프링을 이용해서 상태유지 스타일의 애플리케이션을 얼마든지 만들 수 있다. - 스프링에서는 싱글톤 외에도 다른 스코프를 갖는 빈을 간단히 만들 수 있다.
빈의 스코프를 잘 활용하면 스프링이 관리하는 빈이면서 사용자별로 또는 단위 작업별로
독립적으로 생성되고 유지되는 오브젝트를 만들어 상태를 저장하고 이를 DI를 통해 서비스 빈에서 사용하게 만들 수 있다.
- 크게는 사용자 로그인 세션 관리부터, 작게는 하나의 단위 작업이지만 여러 페이지에 걸쳐 진행되는 위저드 기능까지
- 스프링은 거의 대부분의 자바 표준 기술과 함께 사용될 수 있으며
표준 기술 외에도 많이 사용되는 오픈소스 프레임워크, 라이브러리나 상용 제품도 함께 사용할 수 있다.- 기본적으로는 J2EE 1.4와 JavaEE 5.0을 지원한다.
스프링이 제공하는 많은 API는 표준 JavaEE의 인터페이스를 사용할 수 있도록 설계되어 있어
스프링이 만든 애플리케이션은 JSP, JSF, EJB, JNDI, JTA, JCA, JAX-WS, JMS, JavaMail, JPA와 같은
JavaEE의 세부 기술과 함께 사용될 수 있다. - 표준 기술 외에도 많이 사용되는 오픈소스 프레임워크, 라이브러리나 상용 제품도 스프링과 함께 사용할 수 있으므로
이런 기술을 스프링과 함께 사용할 때는 먼저 스프링이 공식적으로 지원하는 기술인지 확인해본다.
스프링의 의존 라이브러리로 등록된 100여 개의 각종 라이브러리를 살펴보면
스프링이 직접 API나 추상화 서비스 등을 통해 지원하는 표준 또는 오픈소스, 상용 기술에는 어떤 것이 있는지 알 수 있다.
- 기본적으로는 J2EE 1.4와 JavaEE 5.0을 지원한다.
- 스프링이 지원하는 기술이란 무슨 의미일까?
- 첫째, 해당 기술을 스프링의 DI 패턴을 따라 사용할 수 있다는 의미이다.
프레임워크나 라이브러리의 핵심 클래스를 빈으로 등록할 수 있게 지원해주므로
코드를 이용해 초기화해야만 사용할 수 있는 기능을 빈을 등록하는 것만으로 바로 사용할 수 있다.
프레임워크의 핵심 오브젝트를 빈의 형태로 등록해둘 수 있다면 프로퍼티를 이용해 세부 설정을 조정할 수 있고,
DI를 통해 다른 오브젝트에서 손쉽게 활용할 수 있다.
예를 들어 하이버네이트의 기능을 사용하려면 설정파일에 대한 정보, 설정 값을 담은 프로퍼티와 DB 연결에 대한 정보를 담은
Configuration 오브젝트를 만들고 이를 이용해 SessionFactory 오브젝트를 만드는 등의 작업이 필요하다.
반면 스프링에서는 하이버네이트의 SessionFactory를 스프링이 제공하는 빈인 LocalSessionFactoryBean을 제공하므로
이를 빈으로 등록하고 적절한 프로퍼티 설정을 해주면 간단히 SessionFactorya 오브젝트가 만들어져 빈으로 등록된다.
이처럼 스프링 외의 기술을 접목할 때는 가장 먼저 스프링의 빈으로 등록해서 DI 방식으로 통해 사용 가능한지 살펴봐야 한다.
만약 스프링이 지원하는 프레임워크나 라이브러리일 경우에는 스프링 빈의 설정만으로 등록하고 사용 가능하도록 팩토리 빈 클래스가 제공되지만, 그렇지 않다면 스프링 빈으로 등록돼서 사용하기에 적합하도록 만들어주는 팩토리 빈을 직접 도입해야 한다. - 둘째, 스프링의 서비스 추상화가 적용됐다는 의미이다.
서비스 추상화를 적용하는 경우 비슷한 기능을 제공하는 기술에 대한 일관된 접근 방법을 정의해주어
이를 통해 서드파티 프레임워크를 적용할 수 있을 뿐만 아니라 필요에 따라 호환 가능한 기술로 손쉽게 교체해서 사용할 수 있다.
스프링이 제공하는 서비스 추상화는 이미 존재하는 다양한 기술의 공통점을 분석해서 추상화했으므로
추상 서비스 인터페이스를 구현해서 각 기술과 연동하게 해주는 어댑터 클래스가 필요하다.
추상 서비스 인터페이스를 구현한 클래스들은 모두 스프링의 빈으로 등록되도록 만들어졌고,
세부 기술의 특성에 맞는 설정이 손쉽게 가능하도록 다양한 프로퍼티를 제공하고 있다. - 셋째, 스프링이 지지하는 프로그래밍 모델을 적용했다는 의미이다.
예를 들어 스프링의 데이터 액세스 지원 기능을 사용하면
데이터 액세스 기술의 종류에 상관없이 일관된 예외 계층구조를 따라서 예외가 던져진다.
여기에는 기술에 독립적인 DAO를 만들 수 있도록 데이터 액세스 예외를 추상화하고,
불필요하게 예외를 처리하는 코드를 피하도록 런타임 위주의 예외를 사용한다는 스프링의 개발철학이 적용된 것이다.
이를 통해, 서비스 계층의 비즈니스 로직을 담은 코드가 데이터 액세스 계층의 기술에 종속되지 않도록 만들어준다. - 넷째, 템플릿/콜백이 지원된다는 의미이다.
스프링은 JDBC, JMS, JCA를 비롯한 20여 가지 기술을 지원하는 템플릿/콜백을 제공한다.
스프링은 이런 기술을 간편하게 사용할 수 있도록 템플릿/콜백 기능을 제공하므로
대부분의 템플릿 클래스는 빈으로 등록해서 필요한 빈에서 DI 받아 사용할 수 있다. - 즉, 스프링이 어떤 기술을 지원한다는 것은
스프링이 지지하는 개발철학과 프로그래밍 모델을 따르면서 해당 기술을 사용할 수 있다는 의미이다.
물론 이런 방법을 따르지 않고도 스프링에서 여타 프레임워크를 사용하거나 라이브러리를 이용할 수는 있다.
그 대신 스프링의 장점을 포기해야 하고, 일관된 구조의 유연하고 확장 가능한 코드를 만들기가 힘들 수 있다.
- 첫째, 해당 기술을 스프링의 DI 패턴을 따라 사용할 수 있다는 의미이다.
- 물론 스프링에 세상에 나와 있는 모든 기술과 프레임워크를 지원하진 않는다.
- 스프링이 직접 지원하는 수십여 가지의 표준, 오픈소스, 상용 기술은
엔터프라이즈 애플리케이션 개발에 보편적으로 사용되는 유명한 것들로 제한된다.
기술이나 프레임워크가 자체적으로 스프링에서 손쉽게 사용될 수 있도록 관련 기능을 제공하는 경우도 적지 않다. - 대신 스프링의 기술 지원 방법은 스프링의 핵심 기술과 프로그래밍 모델만 잘 이용하면 어떤 기술에든지 손쉽게 적용이 가능하다.
따라서 스프링이 직접 지원하는 기술이 아니라도 스프링이 지원하는 기술이라는 의미의 네 가지 방법을 따라서 만들 수 있다. - 가장 먼저 스프링에 새로운 기술을 연동하려면 스프링의 빈으로 해당 기술의 핵심 오브젝트가 등록되도록 만드는 것이다.
필요에 따라 팩토리 빈을 사용해서 오브젝트 생성과 초기화 작업, 프로퍼티 노출 등을 해주어
애플리케이션 내의 빈들이 새로운 기술에 대해 빈을 DI 하는 방법으로 접근할 수 있게 하는 것이다.
반대로 기존 빈 오브젝트를 새로 추가할 기술에 사용할 수 있게 해줄 필요도 있다. - 때로는 서비스 추상화를 시도할 경우도 있으므로
어떤 경우는 스프링이 이미 제공하는 추상 인터페이스의 새로운 구현으로 만들어질 수 있고,
아예 새로운 추상화 인터페이스를 정의해서 적용할 수도 있다.
유연한 설정과 더불어 테스트를 위해서라도 새로운 인터페이스를 추가할 필요가 있다. - 네트워크 접근이나 파일 IO처럼 실패할 가능성이 있는 시도를 하는 기술이라면 템플릿/콜백 방식의 적용도 적극 고려해보자.
기술을 사용할 때마다 반복적으로 try/catch/finally 블록이 필요한 기술이라면 템플릿/콜백이 제격이다. - 때로는 AOP나 예외 전환을 적용할 수도 있다.
보통 외부에서 가져와서 사용하는 기술에서 손을 대지 않고 사용 방법을 개선하는 작업에 사용된다.
특정 예외가 던져졌을 때에 대한 포인트컷을 만들어두고 어드바이스에서 예외를 추상화된 런타임 예외로 바꿔서 다시 던져준다.
그러면 해당 API를 사용하는 코드를 전혀 손대지 않고도 손쉽게 예외 변환이 가능하다. - 스프링이 직접 지원 기능을 제공해주지 않으면
무엇인가 시도해볼 생각도 없이 스프링 이전의 방식대로 코드를 만들어서 외부 기술을 사용하는 건 부끄러운 일이다.
스프링을 사용하려면 스프링의 프로그래밍 모델과 그에 담긴 개발 철학을 따르는 일관된 코드를 만드는데 관심을 기울여야 한다.
- 스프링이 직접 지원하는 수십여 가지의 표준, 오픈소스, 상용 기술은
'Java-Spring > 토비의 스프링 3.1' 카테고리의 다른 글
[토비의 스프링 3.1] Vol.1 스프링의 이해와 원리 - 스프링 모듈(1) (0) | 2024.05.18 |
---|---|
[토비의 스프링 3.1] Vol.1 스프링의 이해와 원리 - 스프링 모듈(0) (0) | 2024.05.18 |
[토비의 스프링 3.1] Vol.1 스프링의 이해와 원리 - 스프링 프로젝트 시작하기 (2) (0) | 2024.04.25 |
[토비의 스프링 3.1] Vol.1 스프링의 이해와 원리 - 스프링 프로젝트 시작하기 (1) (0) | 2024.04.22 |
[토비의 스프링 3.1] Vol.1 스프링의 이해와 원리 - 스프링 프로젝트 시작하기 (0) (0) | 2024.04.22 |