7. 서비스 추상화
1. 서비스란 무엇인가?
스프링에 대한 오해
스프링은
@Controller,@Service,@Repository만 기계적으로 찍어내는 방법이다? -> 적어도 이 세가지 빈은 구분해서 만들어야 한다는 최소한의(!) 스프링의 가이드
스프링 애플리케이션 빈이 존재하는 계층 구조
3개의 전형적인(stereotype) 애노테이션을 사용하는 애플리케이션 빈의 위치

서비스는 일반적인 용어라서 쓰이는 곳(컨텍스트) 에 따라 다른 의미를 가진다.
이 때문에, 서비스라는 단어를 이해하기가 생각보다 어렵다 ;;
서비스(개발쪽에서, 특히 백엔드) 에 대한 통용되는 개념
서비스는 클라이언트에게 서비스를 제공해주는 오프젝트나 모듈
클라이언트가 존재해야 한다!
클라이언트가 없다면 서비스가 아니다.

서비스는 일반적으로 상태를 가지지 않는다.
상태를 가지지 않기 때문에, 싱글톤 빈으로 관리해도 된다.

서비스의 종류
애플리케이션 서비스(application service)
정의 : 애플리케이션 계층에 존재하는 서비스입니다. 주로 사용자의 요청(Use Case)을 처리하고, 도메인 객체나 도메인 서비스를 조합하여 비즈니스 로직을 수행하도록 조율(Orchestration)하는 역할을 한다.
특징
@Service 애노테이션: 스프링 프레임워크와 같은 환경에서 빈(Bean)으로 등록되어 관리될 때 흔히
@Service애노테이션을 사용한다.상태 비저장(Stateless) : 일반적으로 상태를 가지지 않고, 요청에 필요한 데이터를 받아 처리한 후 결과를 반환한다.
트랜잭션 경계 : 트랜잭션의 시작과 끝을 담당하는 경우가 많다.
도메인 서비스(domain service)
정의 : 특정 도메인 객체에 비즈니스 로직을 포함하기 애매할 때 사용하는 서비스이다. 즉, 하나의 엔티티(Entity)나 값 객체(Value Object)에 속하지 않는 도메인 로직을 처리한다.
특징
도메인 계층 소속: 애플리케이션 서비스와 달리, 도메인 모델의 일부로 간주된다.
상태 비저장(Stateless): 일반적으로 상태를 가지지 않는다.
도메인 객체 간의 상호작용: 여러 도메인 객체가 관련된 복잡한 비즈니스 규칙이나 계산을 수행할 때 사용된다.
인프라 서비스(infrastructure service)
정의 : 도메인 로직이나 애플리케이션 로직에 직접 참여하지 않고, 기술적인 기능을 제공하는 서비스이다. 서비스 추상화의 대상이 되기도 한다.
특징
인프라스트럭처 계층 소속: 애플리케이션의 핵심 비즈니스 로직과는 분리되어 기술적인 문제를 해결한다.
외부 시스템 연동: 데이터베이스 접근, 메시징 큐, 이메일 발송, 파일 시스템 접근, 외부 API 호출 등과 관련된 기능을 제공한다.
구현 기술에 의존: 특정 기술(예: SMTP, JDBC, JPA, Kafka)을 사용하여 구현된다.
추상화: 도메인 계층이나 애플리케이션 계층에서는 인프라 서비스의 구체적인 구현 기술을 알 필요 없이, 인터페이스를 통해 사용하는 것이 좋다.. 이를 통해 기술 변화에 유연하게 대처할 수 있다.
2. 애플리케이션 서비스 도입
애플리케이션 서비스
@Service빈으로 구성Application/Service 계층에 존재
애플리케이션/도메인 로직 - 도메인 오브젝트/엔티티 활용
인프라 서비스의 도움이 필요
가장 중요한 도메인/애플리케이션 비즈니스 로직
인프라 레이어에 존재하는 기술에 가능한 의존하지 않도록 만들어야 함
추상화에 의존해야 하지, 구체적인 것에 의존해서는 안된다.
PaymentService-ExRateService에 적용된 DIP

기존의 DataClient 안에 있던 비지니스 로직에 서비스 레이어를 추가하자.
DataClient 안에 있던 비지니스 로직에 서비스 레이어를 추가하자. 기존
DataClient이름을OrderClinet로 변경하자.
3. 기술에 독립적인 애플리케이션 서비스
현재 OrderService 를 자세히 보면 JPA 에 굉장히 의존적이다. (변경에 취약하다)
OrderService 의 문제점
데이터 액세스 기술의 하나인 JPA 에 의존
JPA 를 사용하는
OrderRepository클래스에 의존JpaRepository라고 이름을 변경해도 이상하지 않다.
JPA Transaction Manger 에 의존
Order 의 문제점
@Entity가 붙은 JPA 엔티티로 작성@Entity라이브러리가 붙은 경우 컴파일 시점에만 JPA 라이브러리에 의존만약
Order클래스 안에 비지니스 로직을 추가할 경우 JPA 기술과 관련된 내용이 들어가지 않는다.애노테이션 외의 코드로 JPA 와 연관성이 없다.
JPA 를 사용하지 않고 다른 용도로 사용한다면? 런타임에는 JPA 라이브러리에 의존하지 않는다.
Order 에서 JPA 메타데이터 분리
애노테이션(
@Entiry) 은 컴파일타임 라이브러리 의존성만 가진다.엔티티 동작에는 영향을 주지 않기 때문에, 엔티티 클래스를 다른 데이터 기술에서 사용해도 된다.
그래도 제거하고 싶다면 외부 XML 디스크립터를 사용할 수 있다.
resources/META-INF/orm.xml
해당 경로에 orm.xml 파일을 만들자.
Order 내 JPA 어노테이션을 제거하자.
Order 내 JPA 어노테이션을 제거하자. XML 디스크립터 덕분에 애노테이션을 제거하고도 이전과 같이 코드가 잘 동작한다.
4. OrderRepository DIP
특정 기술(JPA) 에 의존하지 않는 애플리케이션 서비스 만들기
JPA Repository -
OrderRepository에 의존하지 않도록 변경추상화에 의존하도록 만들어 구체 클래스에 의존하지 않도록 변경해준다.

코드 수정
DIP 원칙을 지키기 위해서
OrderRepository는 상위 모듈(order) 패키지에 생성하자.상위 모듈은 하위 모듈에 의존해서는 안된다.
테스트 추가
기존에는
OrderClient실행을 통해서 코드가 잘 동작하는지 확인했다.이제는 테스트 코드를 추가해 기능을 테스트하자.
5. 트랜잭션 서비스 추상화
OrderService
JPA 를 사용하는 Repository 클래스에 의존 (지난 챕터에서 해결)JPA Transaction Manager 에 의존
Transaction 은 데이터 기술에 따라 방법이 다르다.
JDBC
JPA
MyBatis
Jooq
JPA 트랜잭션 예시
JDBC 트랜잭션
추상화
구현이 복잡함과 디테일을 감추고 중요한 것만 남기는 기법
여러 인프라 서비스 기술의 공통적이고 핵심적인 기능을 인터페이스로 정의하고 이를 구현하는 어댑터(interface) 를 만들어 일관된 사용이 가능하게 만드는 것이 서비스 추상화
우리가 만드는 코드는 추상화하기 쉽다! (
OrderRepository인터페이스)하지만, JPA 같은 라이브러리 기술들을 우리가 자유롭게 추상화하기 쉽지 않기 때문에, 어댑터(
PlatformTransactionManager)를 사용해야 한다.

코드 수정
어댑터들(
JpaTrasactionManager,DataSourceTransactionManager, ...) 은 대부분 스프링이 제공한다.
6. JDBC 데이터 엑세스 기술
이제 어뎁터(PlatformTransactionManager) 를 활용해 JPA 에서 JDBC 로 데이터 엑세스 기술을 변경해보자.
JdbcClient
Spring 6.1 에서 추가
SQL 을 사용하는 JDBC 데이터 처리 코드를 유연하게 작성하도록 도와줌
일종의 템플릿 / 콜백
스프링의
JdbcTemplate의 대체 기술
DataSourceTransactionManager
JDBC 의
Connection을 이용하는 트랜잭션 매니저Connection을 리턴하는DataSource오브젝트 필요
JDBC 데이터 엑시스용 구성 정보
DataSourceDataSourceTransactionManager
코드 수정
중요한 것은, 데이터 엑시스 기술을 JPA -> JDBC 로 변경했는데,
OrderService코드를 한줄도 건들지 않았다는 것이다!
7. 트랜젝션 테스트
OrderService 에서 기술 관련 코드 제거
현재
OrderSerivce코드에서 기술 관련 코드를 많이 제거하므로, 데이터 엑세스 기술이 변경되어도 기존 코드는 영향을 받지 않는다.하지만
TransactionTamplate,PlatformTransactionManager와 같은 기술과 관련된 코드가 계속 등장한다.트랜잭션의 시작과 종료는 보통 애플리케이션 서비스 메서드 실행 전후이기 때문에, 트랜잭션 로직은 서비스 레이어 안에 포함되어야 한다.
트랜잭션 테스트
트랜잭션이 필요한 곳에 정확하게 적용되었는지 테스트 하기는 매우 어려움
JDBC 처럼 자동 커밋이 되거나 Spring Data JPA 처럼 기본 레포지토리 구현에서 트랜잭션을 알아서 적용해주는 기술을 사용할 때는 트랜잭션이 바르게 적용되지 않는 것을 놓치기 쉽다.
모든 작업이 성공하면 하나의 트랜잭션으로 진행된 것인지 여러개의 트랜잭션으로 쪼개진 것인지 확인하기 어려움
트랜잭션 중간에 실패하는 케이스를 만들 수 있다면 롤백 여부로 확인할 수 있음
테스트 코드 작성
JDBC 는 트랜잭션 로직을 강제하지 않기 때문에,
OrderService.createOrder(),.createOrders()에서 트랜젝션 로직을 뺐다.그러니, 당연하게도 트랜잭션 테스트가 실패하고 만다..
트랜잭션 추가
트랜잭션을 추가함으로 테스트는 성공했다.
하지만, 다시
OrderService는TransactionTemplate()같은 기술적인 객체에 의존적인 코드로 변경되었다.
8. 트랜잭션 프록시
이전에 기술적인 객체에 의존적인 코드를 트랜잭션 프록시를 통해서 개선해보자.
데코레이터 패턴
오브젝트의 코드를 변경하지 않고 새로운 기능을 런타임에 부여하는 패턴

프록시 패턴
타킷을 대신해서 존재하며 접근을 제거하거나 보안, 지연, 원격 접속 등의 기능을 제공
내 생각에는 데코레이터, 프록시 패턴 모두 비슷한 기능을 하는 것으로 보여진다. (그냥 프록시라고 보자)

트랜잭션 프록시
OrderService인터페이스 추출트랜잭션 부가 기능을 제공하는
OrderServiceTxProxy프록시

코드 수정
프록시 패턴 적용 후 트랜잭션 테스트가 정상적으로 수행되는 것을 볼 수 있다.
결론적으로, 서비스 추상화를 통해 본인의 책임만을 수행하는 구조를 만들어나갈 수 있다.
9. @Transactional 과 AOP
트랜잭션 프록시 적용
동일한
OrderService인터페이스를 구현한 프록시를OrderClient에 주입

스프링이 만들어주는 트랜잭션 프록시
@Transactional애노테이션이 붙은 클래스의 메소드가 트랜잭션 안에서 실행되도록 프록시를 만들어줌지금까지 우리가 작업했던 복잡했던 코드를 대체해준다!
코드 수정
복잡했던 프록시 패턴을
@Transaction을 사용해 선언적으로 변경했다!직접 프록시 패턴을 사용한다면 수 많은 반복되는 코드를 만들어낸다.
스프링의 프록시 AOP (Aspect Oriented Programming)
AOP 는 스프링에서 그다지 성공하지 못한 핵심 기술 중의 하나
활용 용도가 제한적이면서 막상 사용하기가 매우 어렵다.. (난이도가 높다)
스프링이 만들어 놓은 트랜잭션과 보안 기술에서는 유용하게 활용된다.
직접 활용하려면 꽤 많은 학습이 필요하다!!
AOP 는 아니더라도 데코레이터/프록시 패턴의 동작원리를 이해하고 필요한 곳에 활용할 수 있다.
Last updated