일관된 의미를 가지고 유연하며 애플리케이션 전반에 공유 가능한 도메인 모델을 따르는 오브젝트로 정보를 다루면 많은 장점이 있다. 코드는 이해하기 쉽고 로직을 작성하기도 수월하다. 하지만 단점도 있다.
최적화된 SQL을 매번 만들어 사용하는 경우에 비해 성능 면에서 조금은 손해를 감수해야 할 수도 있다.
오브젝트 관계에도 문제가 있다.
그래서 도메인 오브젝트를 사용하는 오브젝트 중심 아키텍처에서는 가능하다면 ORM과 같은 오브젝트 중심 데이터 액세스 기술을 사용하는 것을 권장한다.
도메인 오브젝트에 정보만 담겨 있고, 정보를 활용하는 아무런 기능도 갖고 있지 않다면 이는 온전한 오브젝트라고 보기 힘들다. 그래서 이런 오브젝트를 빈약한 오브젝트라고 부른다.
물론 이렇게라도 도메인 모델을 반영한 오브젝트에 정보를 담아 활용하는 편이 도메인 오브젝트를 사용하지 않는 것보다는 훨씬 낫다.
도메인 오브젝트에 넣을 수 있는 기능은 어떤 것일까?
그렇다면 빈약한 도메인 오브젝트 방식에서는 비즈니스 로직이 어디에 존재할까?
빈약한 도메인 오브젝트 방식의 한계는 거대 서비스 계층 방식과 유사하다. 비록 도메인 오브젝트라는 일관된 오브젝트를 활용하기 때문에 SQL에 의존적인 데이터 방식 보다는 유연하고 간결하지만, 여전히 서비스 계층의 메소드에 대부분의 비즈니스 로직이 들어 있기 때문에 로직의 재사용성이 떨어지고 중복의 문제가 발생하기 쉽다. 하지만 비즈니스 로직이 복잡하지만 않다면 가장 만들기 쉽고 3계층 구조의 특징을 잘 살려서 개발할 수 있는 유용한 아키텍처다.
풍성한 도메인 오브젝트 또는 영리한 도메인 오브젝트 방식은 빈약한 도메인 오브젝트의 단점을 극복하고 도메인 오브젝트의 객체지향적인 특징을 잘 사용할 수 있도록 개선한 것이다. 어떤 비즈니스 로직은 특정 도메인 오브젝트나 그 관련 오브젝트가 가진 정보와 깊은 관계가 있다. 이런 로직을 서비스 계층의 코드가 아니라 도메인 오브젝트에 넣어주고, 서비스 계층의 비즈니스 로직에서 재사용하게 만드는 것이다.
서비스 계층 코드에서 만들었던 calcTotalOfProductPrice()는 Category와 Product의 정보만 사용하는 간단한 로직이다. 이것을 굳이 서비스 계층에 두지 않고 Category 클래스안에 둘 수있다.
public class Category {
List<Product> products;
// Category를 따로 파라미터로 받을 필요가 없다. 자신이 가진 정보를 직접 사용하면 되기 때문이다.
public int calcTotalOfProductPrice() {
int sum = 0;
for (Product p : this.products) { // 오브젝트가 가진 내부 정보를 활용해서 필요한 계산이나 로직을 수행하면 된다.
sum += p.getPrice();
}
return sum;
}
}
이렇게 만드는 경우는 서비스 계층의 메소드에 따로 만드는 것보다 응집도가 높다. 데이터와 그것을 사용하는 기능이 한곳에 모여있기 때문이다.
물론 도메인 오브젝트에 비즈니스 로직을 넣는다고 해서 비즈니스 로직을 담고 있던 서비스 계층 오브젝트가 필요 없어지는 건 아니다.
1. 여러 종류의 도메인 오브젝트 기능을 조합해서 복잡한 비즈니스 로직을 만드는 경우는 서비스 계층의 오브젝트에 두는 것이 적당하다.
2. 도메인 오브젝트는 직접 데이터 액세스 계층이나 기반 계층 또는 다른 서비스 계층의 오브젝트에 접근할 수 없기 때문에 서비스 계층이 필요하기도 하다.
지금까지 살펴본 바로는 도메인 모델을 따르는 오브젝트를 만들고 이를 활용하는 방법에는 한계가 있다.
도메인 오브젝트에 담을 수 있는 비즈니스 로직은 데이터 액세스 계층에서 가져온 내부 데이터를 분석하거나, 조건에 따라 오브젝트 정보를 변경, 생성 하는 정도에 그칠 수 밖에없다. 이렇게 변경된 정보가 다시 DB등에 반영되려면 서비스 계층 오브젝트의 부가적인 작업이 필요하다.
도메인 오브젝트가 스스로 필요한 정보는 DAO를 통해 가져올 수 있고, 생성이나 변경이 일어났을 때 직접 DAO에게 변경사항을 반영해달고 요청할 수는 없을까?
도메인 계층의 역할과 비중을 극대화하려다 보면 기존의 풍성한 도메인 오브젝트의 방식으로는 만족할 수 없다. 그래서 등장한 것이 바로 도메인 오브젝트가 기존 3계층과 같은 레벨로 격상되어 하나의 계층을 이루게 하는 도메인 계층 방식이다. 개념은 간단한다. 도메인 오브젝트들이 하나의 독립적인 계층을 이뤄서 서비스 계층과 데이터 액세스 계층의 사이에 존재하는 것이다.
도메인 오브젝트가 독립된 계층을 이뤘기 때문에 기존 방식과는 다른 두 가지 특징을 갖게 된다.
스프링이 관리하지 않는 도메인 오브젝트에 DI를 적용하기 위해서는 AOP가 필요하다. 물론 AOP의 적용 대상도 스프링의 빈 오브젝트 뿐이다. 하지만 스프링 AOP대신 AspectJ AOP를 사용하면 클래스의 생성자가 호출되면서 오브젝트가 만들어지는 시점을 조인 포인트로 사용할 수 있고 스프링 빈이 아닌 일반 오브젝트에도 AOP 부가기능을 적용할 수 있다. 이를 이용해서 도메인 오브젝트가 생성되는 시점에 특별한 부가기능을 추가하게 만들어 줄 수 있다.
도메인 계층 방식은 이전의 어떤 방식보다 도메인 오브젝트에 많은 비즈니스 로직을 담아낼 수 있다. 그럼에도 서비스 계층의 역할이 완전히 사라지는 건 아니다. 때로는 여러 도메인 오브젝트의 기능을 조합해서 복잡한 작업을 진행해야 하는 경우가 있다. 특정 도메인 오브젝트에 담길 수 없는 이런 작업은 서비스 계층에서 도메인 계층과 협력을 통해 진행하는 것이 바람직하다.
도메인 오브젝트를 독립적인 계층으로 만들려고 할 때 고려해야 할 중요한 사항이 있다. 도메인 오브젝트가 도메인 계층을 벗어나서도 사용되게 할지 말지 결정해야 한다. 도메인 오브젝트가 계층을 이루기 전에는 모든 계층에 걸쳐 사용되는 일종의 정보전달 도구 같은 역할을 했다. 하지만 독자적인 계층을 이뤘을 때는 상황이 달라질 수 있다.
선택할 수 있는 방법은 두 가지가 있다.
1. 여전히 모든 계층에서 도메인 오브젝트를 사용한다.
- 도메인 계층은 물론이고 서비스, 프레젠테이션, 뷰에서도 도메인 오브젝트를 사용할 수 있게 하는 것이다.
- 하지만, 막강한 기능을 가진 오브젝트를 프레젠테이션, 뷰 등에서 사용하게 해주면 이를 함부로 사용하는 위험이 뒤따를 수 있다.
- 이런 문제를 피하려면 어떻게 해야할까? 철저한 개발 가이드를 만들어 강력하게 적용하는 것이다.
2. 도메인 오브젝트는 도메인 계층을 벗어나지 못하게 하는 것이다.
- 도메인 계층 밖으로 전달될 때는 별도로 준비된 정보 전달용 오브젝트에 도메인 오브젝트의 내용을 복사해서 넘겨줘야 한다. 이런 오브젝트는 데이터 전달에 사용된다고 해서 DTO라고한다.
- DTO는 변화를 허용하지 않고 읽기전용으로 만들어지기도 한다.
- DTO는 기능을 갖지 않으므로 사용하기 안전하다.
스프링의 기본 기술에 가장 잘 들어맞고 쉽게 적용햅볼 수 있는 것은 오브젝트 중심 아키텍처의 도메인 오브젝트 방식이다. 일단은 빈약한 도메인 오브젝트 방식으로 시작하는게 가장 쉽다.
도메인 오브젝트를 계층 간의 정보 전송을 위해 사용하고, 이를 각 계층의 코드에서 활용한다.
DAO는 그 기술이 어떤 것이든 상관없이 서비스 계층에서 요청을 받거나 결과를 돌려줄때 도메인 오브젝트 형태를 유지하게 만든다.
서비스 계층의 비즈니스 로직 또한 도메인 오브젝트를 사용해서 작성한다.
프레젠테이션 계층에서도 도메인 오브젝트를 직접 활용하도록 만든다. 프레젠테이션 계층의 MVC 아키텍처에서도 모델은 도메인 오브젝트를 그대로 사용한다. 뷰에 전달하는 정보도 물론 도메인 오브젝트를 사용하는 모델이고, 사용자가 입력하는 폼의 정보도 도메인 오브젝트로 변환해서 사용한다.
이렇게 도메인 오브젝트를 사용해 애플리케이션의 정보를 일관된 형태로 유지하는게 스프링에 가장 잘 들어맞는 방식이다.
스프링이 지원하는 기술이란 무슨 의미일까?
1. 해당 기술을 스프링의 DI 패턴을 따라 사용할 수 있다.
2. 스프링의 서비스 추상화가 적용됐다.
3. 스프링이 지지하는 프로그래밍 모델을 적용했다.
4. 템플릿/콜백이 지원된다.
스프링이 어떤 기술을 지원한다는 건, 결국 스프링이 지지하는 개발철학과 프로그래밍 모델을 따르면서 해당 기술을 사용할 수 있다는 의미다.