좌충우돌 개발

Toby 스프링3 1부 9장-2

|

9.3 애플리케이션 아키텍처

9.3.1 계층형 아키텍처

아키텍처와 SoC
3계층 아키텍처와 수직 계층
  • 데이터 액세스 계층 : 수직적 계층 구조, DAO 계층, 장기적인 데이터 저장을 목적으로 하는 DB 이용이 주된 책임이다. 또 외부 시스템을 호출해서 서비스에 이용하는 기반계층으로 따로 분류하기도 한다.

  • 서비스 계층: POJO, 엔터프라이즈 애플리케이션에서 가장 중요한 자산은 도메인의 핵심 비즈니스 로직이 들어 있는 서비스 계층이어야 한다.

  • 프리젠테이션 계층: 엔터프라이즈 애플리케이션의 프레젠테이션 계층은 클라이언트의 종류와 상관없이 HTTP 프로토콜을 사용하는 서블릿이 바탕이 된다.

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

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

화면을 중심으로 하는 업무 트랜잭션단위로 코드가 모이기 때문에 최초 개발하기 편하다.
=> 내가 보아온 SI 프로젝트 대부분이 위와 같이 구성 되어 있었다.
다른 개발자의 간섭없이 독립적인 개발이 가능하다.
계층간 결합도가 높고 응집력이 떨어지며, 중복 코드가 발생하기 쉽고 장기적으로 코드 관리하고 발전 시키기 어렵다.

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

비즈니스 로직이 SQL이나 저장 프로시저에 있다
장점
코드 생성기나 툴을 사용하여 자동화하기 유리하다

단점
DB 확장에는 한계가 있으며 고비용이 발생 한다.
테스트가 어렵다
중복제거가 쉽지 않다
변화에 취약 하다

거대한 서비스 계층 방식

비즈니스 로직이 서비스 계층에 있다.
장점
DAO와 SQL은 상대적으로 단순해지고, 그중 일부는 여러 서비스 계층 코드에서 재사용이 가능해 진다.
테스트가 DB 방식보다 수월해 진다.

단점
업무 트랜잭션 단위로 서비스 계층의 만들어질 가능성이 높다
본격적인 객체지향 설계는 어렵다
개발 능력이 떨어지는 경우 자바 코드로 구현한 비즈니스 로직이 복잡한 SQL보다 더 이해하기 힘들 수도 있다.

9.3.3 오브젝트 중심 아키텍처

도메인 모델을 반영하는 오브젝트 구조를 만들어두고 그것을 각 계층 사이에서 정보를 전송하는 데 사용한다.
오브젝트 중심 아키텍터는 객체지향 분석과 모델링의 결과로 나오는 도메인 모델을 오브젝트 모델을 활용한다.
코드는 이해하기 쉽고 로직을 작성하기 쉽다.
프레젠테이션 영역에서도 이미 정의된 도메인 오브젝트 구조만 알고 있다면 아직 DAO가 작성되지 않았어도 뷰를 미리 만들어 볼수도 있다.
코드의 재사용성이 높아지고 DAO는 더 작고 효율적으로 만들질 수 있다.

데이터와 오브젝트
도메인 오브젝트를 사용하는 코드
도메인 오브젝트 사용의 문제점
  • 최적화된 SQL을 매번 만들어 사용하는 경우에 비해 성능 면에서 조금은 손해를 감수해야 할 수도 있다. 비즈니스 로직 마다 필요한 정보가 달라질 수 있기 때문에 발생하는 문제이다.
  • 오브젝트 관계 문제 : 필요하지 않는 관계된 걕체의 정보까지고 가져올 수 있다. 최적화를 고려해서 DAO를 작성하려면 DAO는 비즈니스 로직에서 각 오브젝트를 어디까지 사용해야 하는지 어느 정도 알고 있어야 한다. 프리젠테이션 계층도 마찬가지이다.
  • 오브젝트 관계 문제의 해결 :
    • 지연 로딩기법을 이용하면 일단 최소한의 오브젝트 정보만 읽어두고 관계하고 있는 오브젝트가 필요할 경우에만 다이내믹하게 DB에서 다시 읽어들인다.
    • 가장 이상 적인 방법은 ORM기술 을 사용하는 것이다.
빈약한 도메인 오브젝트 방식

도메인 오브젝트에는 정보만 담겨 있고, 정보를 활용하는 아무런 기능도 갖고 있지 않다. 비즈니스 로직은 서비스 계층에 존재 한다.
문제점 : 거대 서비스 계층 방식과 유사하다. 데이터 방식보다는 유연하고 간결하지만, 서비스 계층의 메소드에 대부분의 비즈니스 로직이 득어 있기 때문에 로직의 재사용 성이 떨어지고 중복이 발생하기 쉽다.

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

비즈니스 로직이 특정 도메인 오브젝트와 관계가 깊다면 이 로직을 서비스 계층이 아닌 도메인 오브젝트에 넣어주고, 서비스 계층의 비즈니스 로직에서 재사용 하도록 만든다.
도메인 오브젝트는 직접 데이터 액세스 계층이나 기반 계층 또는 다른 서비스 계층의 오브젝트에 접근 할 수 없기 때문에 서비스 계층이 필요하다. (데이터 계층이나 서비스 계층은 스프링 빈으로 관리되기 때문이다. 해당 객체에 접근하려면 도메인 오브젝트도 스프링 빈으로 등록되어야 한다.)
도메인 오브젝트는 애플리케이션의 코드 또는 기타 프레임워크나 라이브러리, JDBC템플릿 등에 의해 필요할 때마다 새롭게 만들어 진다.
스프링의 빈으로 관리되는 3계층의 오브젝트들은 도메인 오브젝트를 자유롭게 이용할 수 있지만 그 반대는 안된다.

빈약한 도메인 오브젝트를 피하고 도메인 오브젝트가 스스로 처리 가능한 기능과 도메인 비즈니스 로직을 갖도록 만드는 것이 바람직하다.

장점

  • 빈약한 도메인 오브젝트 방식보다 서비스 계층의 코드가 간결하다.
  • 비즈니스 로직 코드를 이해하기도 쉽다.
도메인 계층 방식

풍성한 도메인 오브젝트 안의 비즈니스 로직은 데이터액세스 계층에 접근 할 수 없으므로 서비스계층의 오브젝트의 부가적인 작업이 필요 하다.
도메인 오브젝트들이 하나의 독립적인 계층을 이뤄서 서비스 계층과 데이터 액세스 계층의 사이에 존재하게 하는 것이다.

  • 도메인 계층으로 들어가면 서비스계층의 도움 없이도 비즈니스 로직의 대부분의 작업을 수행 할 수 있다.
  • 도메인의 오브젝트가 기존 데이터 액세스 계층이나 기반 계층의 기능을 직접 활용할 수 있다.

스프링이 관리하지 않는 도메인 오브젝트에 DI를 적용하기 위해서는 AOP가 필요하다.
AspectJ AOP를 사용하면 클래스 생성자가 호출되면서 오브젝트가 생성되는 시점에 조인 포인트로 사용할 수 있고 스프링 빈이 아닌 일반 오브젝트도 AOP 부가기능을 적용할 수 있다. 오브젝트의 수정자나 DI용 애노테이션을 참고해서 DI 가능한 대상을 스프링 컨테이너에서 찾아 DI해주는 기능을 추가한다. 도메인 오브젝트에 스프링의 빈 오브젝트를 DI받아 사용 할수 있도록 만든다.

서비스 계층의 역할

  • 서비스계층에서는 여러 오브젝트를 조합해서 사용하는 복잡한 작업을 하는 경우 사용 한다.
  • 데이터 액세스 계층에서 클라이언트로 정보를 제공할 때 인터페이스 역할을 한다.
  • 트랜잭션 경계를 설정하거나 특정 도메인 로직에 포함되지는 않지만 애플리케이션에서 필요로 하는 기반 서비스를 이용해야 하는 작업을 위해서도 필요하다.

도메인 계층의 고려 사항

  • 프리젠테이션 영역에서 도메인 객체를 사용하게 되면 문제가 발생할 수 있다.
  • 도메인 객체를 모든 계층에서 사용하게 하지만 개발가이드를 특정 메소드 말고는 접근을 막는 것이다. 이때 AspectJ를 활용한다.
  • 도메인 객체는 도메인 계층을 벗어나지 못하게 한다. 도메인 계층 밖으로 전달될 때는 별도로 준비된 정보 전달용 오브젝트(DTO)에 도메인 오브젝트의 내용을 복사하여 넘겨 준다.

도메인 계층 방식을 사용 할 때는 매우 복잡하고 변경이 잦은 도메인을 갖을 때 사용 한다.

DTO와 리포트 쿼리

리포트 쿼리 : 여러 테이블에 걸쳐 존재하는 자료를 분석하고 그에 따른 분석/통계 결과를 생성하는 쿼리
DB쿼리 결과를 담을 적절한 도메인 오브젝트가 없을 경우 DTO라고 불리는 단순한 자바 빈이나 맵에 담아 전달 해야 한다.

9.3.4 스프링 애필리케이션을 위한 아키텍처 설계

계층형 아키텍처

처음 학습 할때는 3계층 구조로 프레젠테이션 계층은 SpringMVC를 이용하고 서비스 계층은 POJO로 구현하면서 트랜잭션 AOP를 적용하고, 데이터 액세스 계층은 JDBC를 비롯해서 스프링의 데이터 액세스 전략이 적용된 JPA, 하이버네이트, JDO 등을 활용하는 것이 좋다. 코드가 익숙해지면 다양한 방식으로 계층구조를 통합과 분산을 시도해 보자.

정보 전송 아키텍처

빈약한 도메인 오브젝트 방식으로 시작해 보는게 가장 쉽다. 도메인 오브젝트를 계층 간의 정보 전송을 위해서 사용하고, 이를 각 계층의 코드에서 활용한다.
DAO는 그 기술이 어떤 것이든 상관없이 서비스 계층에서 요청을 받거나 결과를 돌려줄 때 도메인 오브젝트 형태를 유지하게 만든다.
서비스 계층의 비즈니스 로직 또한 도메인 오브젝트를 이용해서 작성한다. 가능하다면 도메인 오브젝트에 단순한 기능이라도 추가하도록 노력해보는 것이 좋다.
프레젠테이션 계층의 MVC아키텍처 에서도 모델은 도메인 오브젝트를 그대로 사용 한다. 뷰에 전달하는 정보도 물론 도메인 오브젝트를 사용하는 모델이고, 사용자가 입력하는 폼의 정보도 도메인 오브젝트로 변환해서 사용한다.

상태 관리와 빈 스코프

스프링은 기본적으로 상태가 유지되지 않는 빈과 오브젝트를 사용하는 것을 권장한다. 웹의 생리에 가장 잘 맞고 개발하기 쉽기 때문이다. 또, 서버를 여러 대로 확장하기가 매우 쉽다. 반면에 웹 클라이언트에 폼 정보를 출력하고 이를 수정하는 등의 작업을 위해서는 HTTP세션을 적극 활용하기도 한다. 대부분의 폼 등록, 수정 작업은 한페이지짜리 폼이라도 여러 번의 HTTP 요청에 걸쳐 일어나기 때문에 작업 중인 폼의 내용을 짧은 동안에라도 서버에 저장해둘 필요가 있다.

스프링에서는 싱글톤 외에도 다른 스코프를 갖는 빈을 간단히 만들 수 있다. 빈의 스코프를 잘 활용하면 스프링이 관리하는 빈이면서 사용자별로 또는 단위 작업별로 독립적으로 생성되고 유지되는 오브젝트를 만들어 상태를 저자하고 이를 DI를 통해 서비스 빈에서 사용하게 만들 수 있다.

서드파티 프레임워크, 라이브러리 적용

스프링이 지원하는 기술이란 ?

  • 첫째, 해당 기술을 스프링의 DI 패턴을 따라 사용할 수 있다.
  • 둘째, 스프링의 서비스 추상화가 적용됐다.
  • 셋째, 스프링이 지지하는 프로그래밍 모델을 적용했다.
  • 넷째, 템플릿/콜백이 지원된다.

9.4 정리

  • 스프링은 어떤 플랫폼에서도 사용될 수 있지만, 기본적으로 자바 엔터프라이즈 플랫폼(JavaEE)에 최적화되어 있다. HTTP를 통해 접근하는 웹 클라이언트와 백엔드 DB를 사용하는 애플리케이션에 적합하다.
  • 스프링 개발의 생산성을 증대시키고 품질을 높이려면 SpringIDE 플러그인이나 STS 같은 적절할 툴의 지원이 필요하다.
  • 스프링의 의존 라이브러리가 방대하기 때문에 라이브러리 관리와 의존관계를 관리하는 데 많은 노력을 기울여야 한다. 가능하면 스프링이 의존관계 정보를 제공해주는 Maven이나 Ivy같은 빌드 툴을 사용해 의존 라이브러리를 관리하는 것이 바람직하다.
  • 스프링 애플리케이션은 역할에 따라 3계층으로 구분되고, 다시 기술의 추상화에 따라 세분할 수 있다.
  • 스프링에 가장 잘 어울리는 아키텍처는 오브젝트 중심의 아키텍처이다.
  • 스프링이 직접 지원하지 않는 서드파티 기술도 스프링의 스타일의 접근 방식을 따라서 사용할 수 있도록 준비해둬야 한다.

Toby 스프링3 1부 7장 스프링 핵심기술의 응용 2

|

7.6 스프링 3.1의 DI

객체지향 코드의 장점 : 유연성과 확장성

자바 언어의 변화와 스프링

- 애노테이션의 메타정보 활용

자바 코드의 메타정보를 이용한 프로그래밍 방식으로 발전해 왔다.
애노테이션은 자바코드가 실행되는데 직접 참여하지 못 한다. 복잡한 리플랙션API를 이용해 애노테이션의 메타정보를 조회하고, 애노테이션 내에 설정된 값을 가져와 참고하는 방법이 전부이다.
그렇다면 왜 애노테이션의 활용성이 늘었을까?
스프링은 핵심로직을 담은 자바코드와 IOC프레임워크, 프레임워크가 참조하는 메타정보로 구성되어 있다.
IOC프레임워크는 메타정보를 통해 객체의 관계를 파악하고 핵심로직이 담긴 객체를 생성한다.

DAOFactory -> xml -> 애노테이션 순으로 발전

애노테이션은 xml이나 여타 외부파일과 달리 자바코드의 일부로 사용된다.
장점
a. xml은 애노테이션 하나를 자바코드에 넣는 것에 비해 작성한 정보가 많다.
b. 텍스트 정보이기에 오타가 발생할 수 있다.
c. 리펙토링이 쉽다.
단점
xml은 어느 환경에서나 손쉽게 편집이 가능하고 내용이 변경되어도 다시 빌드할 필요가 없다. 하지만 애노테이션은 재컴파일을 해줘야한다.

- 정책과 관례를 이용한 프로그래밍

애노테이션과 같은 메타정보를 활용하는 프로그래밍 방식은 코드를 이용해 명시적으로 동작 내용을 기술하는 대신 코드 없이 미리 약속한 규칙 또는 관례를 따라서 프로그램이 동작하도록 만드는 프로그래밍 스타일을 적극 포용하여 만들어 왔다.
장점 : 작성해야할 내용이 줄어든다.
단점 : 미리 정의된 규칙과 관례를 기억해야 하고, 메타정보를 보고 프로그램이 어떻게 동작할지 이해해야한다. 높은 학습비용이 필요하고 잘못 이해할 시에는 찾기 힘든 버그를 만들어낼 수있다.

현재는 자바 9과 스프링5에서는 위의 문제점을 개선한다고 한다.(링크 참조)

토비님 발표 자바9과 스프링 5


7.6.1 자바코드를 이용한 빈 설정

기존의 메타정보를 담은 xml로 관리하였으나 이를 java코드로 변환하는 실습

테스트 컨텍스트의 변경

@ContextConfiguration: 스프링 테스트가 테스트용 DI 정보를 어디서 가져와야 하는지 지정할 때 사용하는 애노테이션
@ContextConfiguration이 XML 대신 DI 정보를 담고 있는 클래스를 이용하도록 변경

  1. DI정보를 담은 클래스는 평범한 자바 클래스에 @Configruation 애노테이션을 달아주면 된다.
  2. 자바 클래스로 만들어진 DI 설정 정보에 xml의 설정 정보를 가져오게 할수 있다.
    @ImportResource 를 사용
<context: annotation-config/> 제거

<context: annotation-config/>는 \@PostConstruct를 붙인 메소드가 빈이 초기화된 후 자동 실행되도록 하는 용도로 사용되었다.

<bean>의 전환

<bean>은 @Bean이 붙은 public 메소드로 만들어주면 된다.
메소드 이름은 <bean>의 id값으로 한다.
메소드의 리턴타입은 빈이 주입 받아서 사용하는 빈에서 어떤 타입으로 사용 되는지 확인할 필요가 있다. 생성할 빈 오브젝트의 클래스는 <bean>의 class에 나온것을 그대로 사용하면 된다.
프로퍼티가 있는 경우엔 일단 로컬변수로 받아둬야 한다.
@bean메소드 내부에서는 빈의 구현 클래스에 맞는 프로퍼티값 주입이 필요하다. 프로퍼티는 구현 클래스에 의존적인 경우가 대부분이다. 따라서 빈 내부에서 new키워드를 사용해 빈 인스턴스를 만드는 경우 구현 클래스 타입으로 변수를 만들어야 한다.

@Configruation 자바 파일에서 생성한 빈과 xml로 생성한 빈은 서로 참조가 가능하다.

@Autowired 가 붙은 필드의 티입과 같은 빈이 있으면 해당 빈을 필드에 자동으로 넣어준다.

@Resource 는 @Autowired와 유사하지만 @Autowired는 타입기준으로 찾고 @Resource는 필드이름 기준으로 빈을 검색한다.

전용태그 전환

<jdbc: embedded-database> 전용 태그는 type에 지정한 내장형 DB를 생성하고 <jdbc:script>로 지정한 스크립트로 초기화한 뒤에 DataSource 타입 DB의 커낵션 오브젝트를 빈으로 등록한다.
자바 코드에서는 EmbeddedDatabaseBuilder 를 이용해 위의 작업을 진행한다.

<tx: annotation-driven/> 의 제거
AOP를 위해서 어드바이스와 포인트컷이 필요하고, 애노테이션 정보에서 트랜잭션 속성을 가져와 어드바이스에서 사용하게 해주는 역할을 한다.
스프링 3.1 부터는 \@EnableTransactionManagement 를 지원한다.

스프링 3.1 부터는 xml에서 자주 사용되는 전용 태그를 @Enable 로 시작하는 애노테이션으로 대체할 수 있도록 다양한 애노테이션을 제공한다.

7.6.2 빈스캐닝과 자동와이어링

@Autowired를 이용한 자동와이어링

빈의 프로퍼티에 다른 빈을 넣어서 런타임시 관계를 맺어 주려면 <bean>의 <property>를 사용해 빈을 정의하거나 자바코드로 직접 수정자 메소드를 호출해 줘야 했다.

@Autowired 는 자동와이어링기법을 이용해서 조건에 맞는 빈을 찾아 자동으로 수정자 메소드나 필드에 넣어 준다. 컨테이너가 자동으로 주입할 빈을 결정하기 어려운 경우도 있다. 이럴 경우에는 직접 프로퍼티에 주입할 대상을 지정하는 방법을 병행하면 된다.
필드의 접근 제한자가 private로 선언되어 문제가 되지 않는다 스프링은 리플랙션 API를 이용하여 접근 제한자를 우회하여 값을 넘겨 준다.

스프링과 무관하게 직접 오브젝트를 생성하고 다른 오브젝트에 주입해서 테스트 하는 순수 단위 테스트의 경우 수정자 메소드가 필요하다.

@Autowired와 같은 자동와이어링을 적절하게 사용의 장단점

장점: DI관련 코드를 대폭 줄일 수 있어서 관리가 편리하다.
단점: 설정정보를 보고 다른 빈과 의존관계가 어떻게 맺어져 있는지 한눈에 파악하기 힘들다

@Component를 이용한 자동 빈 등록
  • @Component가 붙은 클래스는 빈스캐너를 통해 자동으로 빈으로 등록된다.
  • @Component 애노테이션이 달린 클래스를 자동으로 찾아서 빈을 등록해주게 하려면 빈 스캐닝 기능을 사용하겠다는 애노테이션 정의가 필요하다. 빈 자동등록이 컨테이너가 디폴트로 제공하는 기능이 아니기 때문이다. 프로젝트내의 모든 클래스패스를 뒤져서 @Component 애노테이션이 달린 클래스를 찾는 것은 부담이 많이 가는 작업이다. 그래서 특정 패키지만 찾도록 기준이 되는 패키지를 지정할 필요가 있다. 이때 사용되는 애노테이션이 @ComponentScan 이다.
  • @Component가 붙은 클래스가 발견되면 새로운 빈을 자동으로 추가한다. 빈의 클래스는 @Component가 붙은 클래스이고, 빈의 아이디는 따로 지정하지 않는다면 클래스 이름의 첫글자를 소문자로 바꾸어 사용한다.
  • @Component에 의해 자동으로 빈을 등록하면 @Autowired에서 사용할 때는 별 문제가 안되지만 @Resource로 사용할 때는 아이디를 기준으로 찾음으로 문제가 발생할 수 있다. 만약 @Component가 붙은 클래스의 이름 대신에 다른 이름을 빈을 아이디로 사용하고 싶다면 @Component(“이름”) 을 넣어준다.

애노테이션은 @interface 키워드를 이용해 정의한다.

public @interface Component {
......
}

메타 애노테이션: 애노테이션의 정의에 부여된 애노테이션
여러개의 애노태이션에 공통의 속성을 부여할 때 메타 애노테이션을 이용한다.

@Repository : DAO 빈을 자동등록할 때 사용
@Service : 비즈니스 로직을 담고 있는 서비스 계층의 빈을 구분하기 위해 사용

7.6.3 컨텍스트 분리와 @Import

성격에 따른 DI 정보 분리하기

테스트용 컨텍스트 분리

DI 설정 정보를 분리하는 방법
DI 설정 클래스를 추가하고 관련된 빈설정 애노테이션, 필드 메소드를 옮기면 된다.
테스트용으로 들어난 빈은 설정정보가 드러나는 편이 좋다.

@Import

테스트용 설정 정보는 핵심설정정보와 깔끔하게 분리하는게 낫다.

  @Configruation
  @EnableTransactionManagement
  @ComponentScan(basePackage="springbook.practice.user")
  @Import(SqlServiceContext.class) // SQL서비스 설정 클래스를 따로 빼고 메인 설정에 import 시킨다.
  public class AppContext {
    ......
  }

7.4.2 프로파일

테스트 환경과 운영환경에서 각기 다른 빈 정의가 필요한 경우가 종종 있다.
이 문제를 해결하려면 운영환경에서는 반드시 필요하지만 테스트 실행 중에는 배제해야 하는 빈설정은 별도의 설정 클래스를 만들어 따로 관리할 필요가 있다.
(예를 들어 테스트에는 mock 메일 발송 클래스를 만들어 테스트 하지만 운영환경에서는 실제 메일을 발송해야하는 빈을 사용해야 한다)

//실제 운영환경에서 사용할 context
 @Configuration
 public class ProductionContext {
     @Bean
     public MailSender mailSender() {
         JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
         mailSender.setHost("mail.mycompany.com");
         return mailSender;
     }

 }

테스트 환경에서는 @ContextConfiguration에 AppContext와 TestAppContext를 지정해서 두 개 클래스의 빈 설정을 조합해서 테스트가 동작하게 만들고, 운영환경에서는 AppContext와 ProductionContext 두 개의 클래스가 DI정보로 설정하면 된다.
하지만 이런 식으로 실행환경이나 기능에 따라서 설정 파일이 여러개 쪼개지고 그중에 몇개를 선택해서 동작하도록 구성하는 일은 번거롭다 애플리케이션이 커질 수록 파일조합을 이용한 DI설정은 불편하다.

@ContextConfiguration(classes={TestAppContext.class, AppContext.class})
@Profile과 @ActiveProfile

스프링 3.1은 환경에 따라서 빈 설정정보가 달라져야 하는 경우에 파일을 여러 개로 쪼개고 조합하는 등의 번거로운 방법 대신 간단히 설정정보를 구성할 수 있는 방법을 제공한다. 실행환경에 따라 달라지는 내용을 프로파일로 정의해서 만들어두고, 실행 시점에 어떤 프로파일의 빈 설정을 사용할지 지정하는 것이다.

@Configuration
@Profile("test")
public class TestAppContext {

@Configuration
@Profile("production")
public class ProductionContext {

@import를 통해 모든 설정 클래스를 추가한다.

@Configuration
@EnableTransactionManagement
@ComponentScan(basePackage="springbook.practice.user")
@Import({ SqlServiceContext.class, TestAppContext.class, ProductionContext.class })
public class AppContext {

활성 프로파일 지정하기

@RunWith(SpringJUnit4ClassRunner.class)
@ActiveProfiles("test") //운영환경이라면 test를 production으로 한다.
@ContextConfiguration(classes = {AppContext.class })
public class UserServiceTest {
컨테이너의 빈 등록 정보 확인
@Autowired
DefaultListableBeanFactory bf;

@Test
public void beans() {
    for(String name : bf.getBeanDefinitionNames()) {
        System.out.println(name + "\t"+ bf.getBean(name).getClass().getName());
    }
}

스프링은 BeanFactory 인터페이스를 구현하고 있다.
BeanFactory의 구현 클래스중 DefaultListableBeanFactory이 있는데 거의 대부분의 스프링 컨테이너는 이 클래스를 이용해 빈을 등록하고 관리한다. 위의 코드와 같이 사용하여 등록된 빈의 정보를 조회할 수 있다.

중첩 클래스를 이용한 프로파일 적용

프로파일 에 따라 클래스를 파일을 분리하였지만 흩어져 있어 파악하기 힘들다. 이를 static 중첩 class를 사용하여 하나의 설정파일로 묶는다. AppContext의 내부 static 클래스로 테스트와 ProductionContext를 들어오게 만들면 아래와 같이 @Import항목에서 해당 내부 중첩 클래스를 삭제할 수 있음으로 단순화 되는 장점이 있다.

@Import({ SqlServiceContext.class, TestAppContext.class, ProductionContext.class })
        |
        V
@Import({ SqlServiceContext.class})

7.6.5 프로퍼티 소스

아직 AppContext에는 테스트 환경에 종속되는 정보가 남아 있다. 바로 DataSource의 DB 연결 정보이다.
운영환경이라면 JNDI를 이용해 서버가 제공하는 DataSource를 가져오거나 애플리케이션에 내장할 수 있는 DB 커넥션 풀을 이용할 필요가 있다.
DB 설정 정보는 필요에 따라 손쉽게 변경할 수 있으면 좋다. 이런 외부 서비스 연결에 필요한 정보는 자바 클래스에서 제거하고 손쉽게 편집할 수 있고 빌드 작업이 필요없는 XML 이나 프로퍼티 파일같은 텍스트 파일에 저장해두는 편이 낫다.

@PropertySource
@Configuration
@EnableTransactionManagement
@ComponentScan(basePackages = "springbook.practice.user")
@EnableSqlService
@PropertySource("/database.properties") //@PropertySource를 사용하여 프로퍼티 파일을 등록해 준다.
public class AppContext implements SqlMapConfig {

스프링 3.1은 빈 설정 작업에 필요한 프로퍼티 정보를 컨테이너가 관리하고 제공해 준다. 스프링 컨테이너가 지정된 정보 소스로부터 프로퍼티 값을 수집하고, 이를 빈 설정 작업 중에 사용할 수 있게 해준다. 컨테이너가 프로퍼티 값을 가져오는 대상을 프로퍼티 소스라고 한다.
@PropertySource로 등록한 리소스로부터 가져오는 프로퍼티 값은 컨테이너가 관리하는 Environment 타입의 환경 오브젝트에 저장된다. 환경 오브젝트는 @Autowired를 통해 필드로 주입받을 수 있다. 이 객체를 사용하여 프로퍼티 값에 접근하여 DB 연결정보를 세팅한다.

@Autowired Environment env;

@Bean
public DataSource dataSource() {
    SimpleDriverDataSource simpleDriverDataSource = new SimpleDriverDataSource();

    try{
        simpleDriverDataSource.setDriverClass(
          (Class<? extends java.sql.Driver>)Class.forName(
                  env.getProperty("db.driverClass")));      
                  //드라이버 클래스 이름을 String 값으로 가져오고 이 이름을 Class.forName() 넣어서 드라이버 클래스를 생성한다.
    } catch (ClassNotFoundException e) {
      throw new RuntimeException(e); // RuntimeException으로 매핑하여 예외를 넘김으로 써 상위 호출단에서 예외처리를 꼭하지 않아도 되게 해준다.
    }


    simpleDriverDataSource.setUrl(env.getProperty("db.url"));
    simpleDriverDataSource.setUsername(env.getProperty("db.username"));
    simpleDriverDataSource.setPassword(env.getProperty("db.password")); // 실제환경에서 프로퍼티에 암호를 입력할 때는 암호화 시켜서 노출 되지 않도록 한다.
    return simpleDriverDataSource;
}

getProperty()로 가져오는 정보는 스트링타입으로 만약 입력 값이 드라이버 클래스라면 Class.forName(“클래스이름”)를 사용하여 Class타입으로 변환하여 이용한다.
이제 연결 환경이 바뀌게 되면 프로퍼티 파일의 내용만 바꾸면 된다.

PropertySourcePlaceholderConfigurer

dataSource 빈의 프로퍼티는 빈 오브젝트가 아니므로 @Autowired를 사용할 수 없다. 대신 @Value 애노테이션을 사용할 수 있다.
@Value의 사용에는 여러가지 방법이 있으나 여기에서는 프로퍼티 소스로부터 값을 주입받을 수 있도록 치환자(placeholder)를 이용 하겠다.

@Configuration
@EnableTransactionManagement
@ComponentScan(basePackages = "springbook.practice.user")
@Import({SqlServiceContext.class})
@PropertySource("/database.properties")
public class AppContext implements SqlMapConfig {
    /**
     * DB 연결과 트랜잭션
     */

    @Value("${db.driverClass}")
    Class<? extends Driver> driverClass;
    @Value("${db.url}")
    String url;
    @Value("${db.username}")
    String username;
    @Value("${db.password}")
    String password;

    @Bean //프로퍼티 소스를 이용한 치환자 설정용 빈 : 꼭 추가해줘야 치환자를 사용할 수 있다.
    public static PropertySourcesPlaceholderConfigurer placeholderConfigurer() {
      return new PropertySourcesPlaceholderConfigurer();
    }

    @Bean
    public DataSource dataSource() {
        SimpleDriverDataSource simpleDriverDataSource = new SimpleDriverDataSource();

        simpleDriverDataSource.setDriverClass(this.driverClass);
        simpleDriverDataSource.setUrl(this.url);
        simpleDriverDataSource.setUsername(this.username);
        simpleDriverDataSource.setPassword(this.password);
        return simpleDriverDataSource;
    }

위에서 제시한 두 가지 방법 중 편한 것을 사용하면 된다.

7.6.6 빈 설정의 재사용과 @Enable*

빈 설정자

SQL 서비스를 재사용 가능한 독립적인 모듈로 만들려면 해결할 문제가 하나 남았다.
UserDao 인터페이스가 위치한 클래스패스로부터 sqlmap.xml을 가져오게 되어 있다.
SQL 서비스를 사용하는 각 애플리케이션은 SQL 매핑파일의 위치를 직접 지정할 수 있어야 한다.

public class OxmSqlReader implements SqlReader {
    private Unmarshaller unmarshaller;
    private final static String DEFAULT_SQLMAP_FILE = "/sqlmap.xml";
    private String sqlmapFile = DEFAULT_SQLMAP_FILE;

    private Resource sqlmapResource = new ClassPathResource(sqlmapFile, UserDao.class);
                              |
                              V
    private Resource sqlmapResource = new ClassPathResource(sqlmapFile);

위와 같이 변경하여 UserDao.class 클래스패스에 대한 종속성을 제거한다.
SQL 매핑 리소스는 빈 클래스 외부에서 설정할 수 있어야 한다.

@Bean
public SqlService sqlService() {
    OxmSqlService sqlService = new OxmSqlService();
    sqlService.setSqlRegistry(sqlRegistry());
    sqlService.setUnmarshaller(unmarshaller());

    sqlService.setSqlMapResource(new ClassPathResource("sqlmap.xml", UserDao.class));

    return sqlService;
}

하지만 아직 SqlServiceContext에 UserDao.class에 대한 의존성이 남아 있어 모듈로 제공하기 어렵다.
외존성을 제거하기 위하여 Resource를 제공 하는 인터페이스를 만든다.

import org.springframework.core.io.Resource;

public interface SqlMapConfig {
    Resource getSqlMapResource();
}

SqlMapConfig 인터페이스의 구현 클래스

import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;

public class SqlMapConfigApp implements SqlMapConfig {
    @Override
    public Resource getSqlMapResource() {
        return new ClassPathResource("/sqlmap.xml", UserDao.class);
        //인텔리J로 실습할때면 해당 xml파일이 빌드할때마다 삭제됨으로 xml파일은 Resources 아래 놓고 실습힌다.
        //UserDao.class 삭제
    }
}

SqlMapConfig 인터페이스를 의존하도록 수정 하고 @Autowired를 통해 해당 타입의 빈을 가져온다.

@Autowired
SqlMapConfig sqlMapConfig;

@Bean
public SqlService sqlService() {
    OxmSqlService sqlService = new OxmSqlService();
    sqlService.setSqlRegistry(sqlRegistry());
    sqlService.setUnmarshaller(unmarshaller());
    sqlService.setSqlMapResource(this.sqlMapConfig.getSqlMapResource());
    return sqlService;
}

SqlMapConfig 인터페이스의 빈 생성은 AppContext에서 설정 해준다. @Bean을 통해서 등록할수도 있고 AppContext 클래스가 SqlMapConfig를 인터페이스를 구현 하도록 만들 수도 있다. 해당 인터페이스를 구현 하도록 하면 자동으로 빈에 등록이 된다. @Configuration 애노테이션이 @Component를 메타애노테이션으로 가지고 있기 때문이다.

@Enable* 애노테이션
import org.springframework.context.annotation.Import;
import springbook.practice.SqlServiceContext;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME) //책의 실습 예제에는 이 부분이 없다. 이부분을 제외 시키면 빈생성중에 에러가 발생한다.
@Import(value = SqlServiceContext.class)
public @interface EnableSqlService {
}

애노테이션을 만들고 @Import({SqlServiceContext.class})@EnableSqlService 으로 바꾼다.

정리

  • SQL처럼 변경될 수 있는 텍스트로 된 정보는 외부 리소스에 담아두고 가져오게 만들면 편리하다.
  • 성격이 다른 코드가 한데 섞여 있는 클래스라면 먼저 인터페이스를 정의해서 코드를 각 인터페이스별로 분리하는게 좋다. 다른 인터페이스에 속한 기능은 인터페이스를 통해 접근하게 만들고, 간단히 자기참조 빈으로 의존관계를 만들어 검증한다. 검증을 마쳤으면 아예 클래스를 분리해도 좋다.
  • 자주 사용되는 의존 오브젝트는 디폴트로 미리 정의해두면 편리하다.
  • XML과 오브젝트 매핑은 스프링의 OXM추상화 기능을 활용한다.
  • 특정 의존 오브젝트를 고정시켜 기능을 특화하려면 맴버 클래스로 만드는 것이 편리하다.
  • 외부의 파일이나 리소스를 사용하는 코드에서는 스프링의 리소스 추상화와 리소스 로더를 사용한다.
  • DI를 의식하면서 코드를 작성하면 객체지향 설계에 도움이 된다.
  • DI에는 인터페이스를 사용한다. 인터페이슬르 사용하면 인터페이스 분리 원칙을 잘 지키는데도 도움이 된다.
  • 클라이언트에 따라서 인터페이스를 분리할 때, 새로운 인터페이스를 만드는 방법과 인터페이스를 상속하는 방법 두 가지를 사용할 수 있다.
  • 애플리케이션에 내장하는 DB를 사용할 때는 스프링의 내장형 DB추상화 기능과 전용 태그를 사용하면 편리하다.

Toby 스프링3 1부 7장 스프링 핵심기술의 응용 1

|

7.3.3 리소스 추상화

기존 코드의 문제점 : query 정보를 담은 xml파일의 위치가 [DAO classpath]에 제한되어 있다.
여기서 [DAO classpath]는 소스코드의 위치가 아닌 빌드된 위치 .clss 생성된 위치를 말한다.
실습 할때는 빌드시 해당 sqlmap.xml이 삭제되어 Resources디렉토리로 옮기고 읽어들이도록 하였다.

자바에서는 다양한 위치에 존재하는 리소스에 대해 단일화된 접근 인터페이스를 제공해주는 클래스가 없다.

URL (java.net.URL) 의 경우 원격만 가능하고 리소스 파일의 존재여부를 미리 알수가 없다.

그래서 스프링에서는 Resource라는 추상화 interface를 제공한다.

Resource

(org.springframework.core.io) Resource는 다른 서비스 추상화의 오브젝트와 달리, 빈이 아니라 값으로 취급된다. property의 ref가 아닌 value로 밖에 등록할 수 없다.

value로 취급함으로 객체로 변환해줄 장치가 필요하다 이게 ResourceLoader 이다.

ResourceLoader

(org.springframework.core.io) 스프링에서는 리소스의 종류와 위치를 문자열로 지정하면 이를 Resource 객체로 변환하는 ResourceLoader를 제공한다.

스프링의 ApplicationContext는 ResourceLoader를 상속받는다.
스프링에서 제공하는 빈으로 등록가능한 클래스에 파일 지정이 가능하다면 거의 모두 Resource type이다.

Resource 타입은 빈으로 등록하지 않고 의 value를 사용해 문자열 값으로 넣으면 ApplicationContext가 ResourceLoader의 역할을 하여 값을 변환 하고 property에 값을 주입한다.

7.4. 인터페이스 상속을 통한 안전한 기능 확장

기능 개선: Application을 재시작하지 않고 특정 SQL의 내용만 변경

7.4.1 DI와 기능의 확장

DI의 가치를 제대로 얻으려면, 먼저 DI에 적함한 오브젝트 설계가 필요하다.

DI를 의식하는 설계

많은 고민과 학습, 훈련, 경험이 필요하다. (즉, 시간과 노력이 필요하다.)
DI를 적용하려면 커다란 오브젝트만 존재해서는 안된다. ( 통짜 프로그램 이거하나면 다되요.) 최소한 두개 이상의 의존관계를 가지고 서로 협력해서 일하는 오브젝트가 필요하다. (레고 블록도 하나만 있으면 안된다. 2개 이상이 있어야 서로 맞물려 먼가를 만들지…) DI는 런타임시 의존 오브젝트를 다이내믹하게 연결해서 유연한 확장을 꾀하는게 목적이기 때문에 항상 확장을 염두에두고 오브젝트 사이의 관계를 생각해야 한다.

DI와 인터페이스 프로그래밍
  1. 다형성
  2. interface 분리원칙을 통해 클라이언트와 의존오브젝트 사이의 관계를 명확하게 해줄 수 있다.

interface 분리원칙: 오브젝트가 그 자체로 충분히 응집도가 높은 작은 단위로 설계됐더라도, 목적과 관심이 각기 다른 클라이언트가 있다면 인터페이스를 통해 이를 적절하게 분리할 필요가 있다.

미래 바뀔거 같은 기능이 있다 이는 interface로 정의 해주고 추후에 구현체로 바꿔줄 수 있게 만든다. ( 레볼 블록의 표준화된 규격을 생각하면 된다. 규격만 맞으면 어떤 레고 블록도 꽂을 수 있다.)

이러한 오브젝트는 여러 인터페이스를 동시게 갖을 수 있다. 물론 클래스는 동시 상속이 안되지만 인터페이스는 가능하다. 여러 인터페이스를 구현한 클래스 객체를 생성하고 다른 클라이언트에서 필요한 인터페이스에 맞춰 사용할 수 있다.

7.4.2 인터페이스 상속

인터페이스 상속을 통한 확장

기존 오브젝트를 확장하는 방법에는 interface를 새로 추가하거나 기존의 interface를 상속하는 방법이 있다.

7.5 DI를 이용한 다양한 구현방법 적용하기

운영중인 시스템에서 사용하는 정보를 실시간으로 변경하는 작업을 할 때 가장 고려사항은 동시성 문제이다.

7.5.1 ConcurrentHashMap을 이용한 수정 가능 SQL 레지스트리

HashMap은 멀티 스레드 환경에서는 예상하지 못한 결과를 발생 시킬 수 있다.
멀티 스레드 환경에서 안전하게 HashMap을 조작하려면 Collections.sychronizedMap()등을 이용하면되지만 빈번하게 서비스 요청이 있을 시 성능문제가 발생한다.

이럴 때는 ConcurrentHashMap을 사용한다. ConcurrentHashMap은 데이터 조작시 전체 데이터에 대해 락을 걸지 않고 조회에는 아예 조회를 걸지 않아 성능 향상을 꾀한다.

단위 테스트를 별도로 만든다.

  1. SQL 등록한 것이 잘 조회 되는가?
  2. SQL 수정이 잘 되는가?
  3. 존재하지 않는 SQL을 수정 시 적절한 예외를 발생시키는 가?

테스트를 만들 때 중요한건 코드를 작성한 다음 테스트를 만들어 검증하는 그 사이의 간격을 가능한 짧게하고, 예외 사항을 포함한 기능을 세세하게 검증하도록 테스트를 만드는 것이다.

테스트를 철저하게 만들어서 기능을 검증하고 구현방식이 변경 될 때마다 테스트를 실행해서 기능에 영향을 주는지 확인하는 일이 중요하다.

7.5.2 내장형 데이터베이스를 이용한 SQL 레지스트리 만들기

스프링의 내장형 DB 지원
내장형 DB 빌더 학습 테스트

실습 시 문제점 : SimpleJdbcTemplate는 더이상 사용되지 않는다. JdbcTemplate로 실습하면 된다.

내장형 DB를 이용한 SqlRegistry 만들기
XML 설정을 통한 내장형 DB의 생성과 적용

실습 시 문제점 : 실습 예제에 아래 코드 부분이 빠져 있다. 그래서 아래와 같은 에러가 발생한다.

Caused by: org.springframework.beans.factory.xml.XmlBeanDefinitionStoreException: Line 38 in XML document from class path resource [applicationContent.xml] is invalid; nested exception is org.xml.sax.SAXParseException; lineNumber: 38; columnNumber: 63; cvc-complex-type.2.4.c: 일치하는 와일드 카드 문자가 엄격하게 적용되지만 'jdbc:embedded-database' 요소에 대한 선언을 찾을 수 없습니다.

http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd 추가해주면 된다.

<?xml version="1.0" encoding="UTF-8"?>
<beans
 xmlns:jdbc="http://www.springframework.org/schema/jdbc"
 xsi:schemaLocation="http://www.springframework.org/schema/jdbc
                     http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd
">

xsd(XML SCHEMA Definition)란 XML스키마 정의를 말한다.
‘jdbc:embedded-database’ 여기에 대한 요소 정의가 xsd 파일에 있는데 이파일이 빠져서 해당 요소 정의를 찾지 못하여 Bean 생성 시 에러를 출력하게 된다.

7.5.3 트랜잭션 적용

앞에서의 실습 코드는 트랜잭션이 적용 되지 않아 맵으로 일괄 변경 작업이 일어날 때는 문제가 발생할 수 있다. 컬랙션에 트랜잭션을 적용하기는 어렵지만 내장형 DB에 트랜잭션을 적용하기는 상대적으로 쉽다.
스프링에서 트랜잭션을 적용할 때 트랜잭션 경계가 DAO밖에 있고 범위가 넓은 경우라면 AOP를 이용하는 것이 편리하지만 SQl레지스트리로 제한된 오브젝트내에 서비스 특화된 간단한 트랜잭션이 필요할 때는 간단한 트랜잭션 추상화 API를 직접 사용하는게 편리하다.

다중 SQL 수정에 대한 트랜잭션 테스트
코드를 이용한 트랜잭션 적용

PlatformTransactionManager를 직접사용해서 트랜잭션 처리 코드를 만들어도 되지만 그보다 간결하게 트랜잭션 적용 코드에 템플릿/콜백 패턴을 적용한 TransactionTemplate를 쓰는 것이 낫다.
일반적으로 트랜잭션 매니저를 싱글톤 빈으로 등록해서 사용하는데, 이유는 여러 개의 AOP를 통해 만들어지는 트랜잭션 프록시가 같은 트랜잭션 매니저를 공유하기 때문이다.
앞에서 만들 EmbeddedDbSqlRegistry는 내장형 DB를 사용함으로 트랜잭션 메니저를 공유할 필요가 없다.
TransactionTemplate은 멀티스레드 환경에서 공유해도 안전함으로 처음 만들때 인스턴스 변수에 저장하여 사용한다.

2017-08-31_TIL

|

2017년 08월 31일

비정상 기동 후 jekyll을 다시 빌드 하려고 하니 search.html에 권한에러가 발생.
jekyll 작업시 root 권한으로 빌드하여 몇몇 파일들이 root소유로 생성이 됨.

su 권한으로 로그인 하려니 패스를 잊어버림 아래명령어를 넣어 초기화

sudo passwd root

파일 소유권 변경 chown 소유권자:그룹식별자 파일명

  chown minigear:staff *

오라클 코드 토비님 발표

|

토비님 발표


1. 자바는 죽었나?

팩트체크 결과 아직 수요가 많다.


2. 위기와 변화
  • 1차 위기 과도한 기술 복잡해짐, EJB
    • 대안 : 자바와 객체지향의 기본으로 돌아간다
-> POJO, J2EE Development without EJB
  • 2차 위기 언어 발전의 요구와 호환성
    • 언어의 발전과 함께 코드 호환성도 지킨다. 플렛폼이 바뀌어도 빌드하지 않고 재사용할 수 있다.
  • 3차 위기 간결한 코드와 관례로 무장한 언어와 기술의 습격
    • 간결한 짧은 언어
    • 설정을 최소하고 관례를 우선하는 프레임워크
    • 엔터프라이즈 개발도 루비와 레일즈로 프레임워크
    • 해결책 : 애노테이션 기반의 메타프로그래밍과 영리한 디폴트로 무장한 관례의 적극 도입
  • 4차 위기 함수형 프로그래밍과 비동기 논블록킹 개발의 도전
    • 스칼라
    • 대용량 비동기 분산시스템 개발에 적함한 함수형 프로그래밍 필요성 대두
    • 해결책 : 함수형 프로그래밍 스타일의 자바와 비동기 논블록킹 지원 서블릿, 스프링 등장

3. 새로운 변화 : 자바9와 스프링5
  • 새로운 위기 : 애노테이션과 메타프로그래밍, 관례의 범람
    • 앤노테이션의 한계와 부작용
      • 컴파일러에 의해 검증 가능한 타입이 아님
      • 코드의 행위를 규정하지 않음
      • 상속, 확장 규칙의 표준이 없음
      • 이해하기 어려운 코드, 오해하기 쉬운 코드 가능성
      • 테스트가 어렵거나 불가능함
      • 커스트마이징하기 어려움
    • 리플렉션과 런타임 바이트코드 생성의 문제
      • 성능저하
      • 타입정보 제거
      • 디버깅 어려움
      • 툴이나 컴파일러에 의한 검증불가

  • 대안 : 업그레이드된 자바의 기본으로 돌아간다.
    • 함수형 프로그래밍이 도입된 자바의 기본으로 돌아가자
    • 함수형 스타일의 자바 웹 코드로 전환
      • 애노테이션과 리플랙션 제거
      • 명시적 코드로 모든 기능을 표현
      • 불변객체 사용
      • 함수의 조합을 이용
      • 리액티브
    • 스프링 5.0 - 새로운 함수형 스타일 웹 개발 지원
    • 서블릿의 의존성 제거
      • 서블릿 컨테이너를 비동기 HTTP서버로 활용가능
    • 새로운 HTTP요청과 응답의 추상화 - 불변 객체
      • ServerRequest
      • ServerResponse
    • 두 개의 함수를 이용해 개발
      • HandlerFunction
      • RouterFunction
    • Mono, Flux 리액티브 방식
    • 스프링 5.0- 함수형 스타일 애플리케이션
      • 독립형애플리케이션
      • 스프링 컨테이너 이용
    • 웹 핸들러(컨트롤러)가 웹 요청 처리하는 방식
      • 요청매핑
      • 요청 바인딩
      • 핸들러 실행
      • 핸들러 결과 처리(응답 생성)
    • 함수형 웹 개발
      • 람다식-메소드 상호 호환 - 메소드 레퍼런스
      • 핸들러를 람다식 대신 메소드로 작성할 수 있음
      • 기능 종류에 따라 클래스-메소드로 구조화
      • 함수의 조합

람다식과 같은 형식의 메소드 타입

  HandlerFunction helloHandler = (ServerRequest req)-> {
    String name = req.pathVariable("name");
    return ServerResponse.ok().syncBody("Hello"+name);
  };

  public Mono<ServerResponse> helloHandler(ServerRequest) {
    String name = req.pathVariable("name");
    return ServerResponse.ok().syncBody("Hello"+name);
  }

메소드 형태로 재작성된 핸들러, 라우터함수

Mono<ServerResponse> helloHandler(ServerRequest req) {
  String name = req.pathVariable("name");
  return ServerResponse.ok().syncBody("Hello" + name);
}

Mono<HandlerFunction<ServerResponse>> router(ServerRequest req) {
  return RequestPredicates.path("/hello/{name}").test(req)?
    Mono.just(this::helloHandler):Mono.empty();
}

void run() throws IOException {
  HttpHandler httpHandler = RouterFunctions.toHttpHandler(this::router);
}

함수형 웹개발 - 함수의 조합 핸들러를 메소드로 모아 놓은 클래스

  public class PersonHandler {
    private final PersonRepository repository;
    public PersonHandler(PersonRepository repository) { this.repository = repository; }

    public Mono<ServerResponse> getPerson(ServerRequest request) {
      int personId = integer.valueOf(request.pathVariable("id"));
      Mono<Person> personMono = this.repository.getPerson(personId);
      return personMono
        .flatMap(person->
          ServerResponse.ok().contentType(APPLICATION_JSON).body(fromObject(person))).switchIfEmpty(ServerResponse.notFound().build());
    }

    public Mono<ServerResponse> savePerson(ServerRequest request) {
      Mono<Person> person = request.bodyToMono(Person.class);
      return ServerResponse.ok().build(this.repository.savePerson(person));
    }
  }

Mono
ReactiveStreams의 publisher 타입
웹요청, 웹 응답, 핸들러, 라우터 등의 모든 함수에 적용

Mono - RouterFunction

  @FunctionalInterface
  public interface RouterFunction<T extends ServerResponse> {
    Mono<HandlerFunction<T>> route(ServerRequest request);
  }
Mono/Flux
  • 정보를 전달할 때 컨테이너 사용
  • Mono - 단일 오브젝트
  • Flux - 스트림 오브젝트
  • 데이터 가공, 변환, 조합을 위한 다양한 연산 제공
  • 스케쥴러를 이용해 비동기 작업 수행
  • 지연 연산, 자유로운 조합
  • 스프링 함수형 웹 개발의 모든 기능이 Mono/Flux 기반으로 재개발
  • 리액트비 프로그래밍 기반
    • ProjectReactor
    • RxJava
  • 함수형 웹 개발 외의 전 계층에 적용 가능
    • 서비스, 리포지토리, API 호출
    • 대부분의 스프링 서브 프로젝트에 적용 중
  • 비동기 논블록킹 방식으로 동작하는 고성능 코드
  • 함수형 스타일의 코드
JavaSE9 - Flow API와 ReactiveStreams
  • Flow API는 ReactiveStreams 표준과 호환
  • 스프링 Mono/Flux - Java 9의 Flow API를 함께 사용해서 개발 가능
  • 자바9의 Flow API가 많은 기술의 브릿지 역할을 담당
스프링5 함수형 웹 개발은 다양한 방식으로 가능
  • 순수 함수형 독립형
  • 순수 함수형 애플리케이션 코드 + 자바설정(@)
  • 하이브리드 함수형 애플리케이션 코드(@)