행복한 아빠

[Grails1.0 사용자 가이드] 8. 서비스 계층 본문

Grails

[Grails1.0 사용자 가이드] 8. 서비스 계층

행복한아빠 2008. 3. 12. 12:20


8. 서비스 계층

웹 계층과 함께 Grails는 서비스 계층 개념을 정의한다. Grails 팀은 핵심 애플리케이션 로직이 컨트롤러 안에 포함되지 않는 것을 권고한다. 핵심 로직이 컨트롤러에 있을 경우 재사용과 관심의 분리(separation of concerns)를 증진시킬 수 없다.

Grails의 서비스를 애플리케이션의 대부분의 로직을 넣는 장소로 여기고 컨트롤러에는 리다렉트 등의 요청을 다루는 것만 남겨둔다.

서비스 만들기
명령 프롬프트 안의 프로젝트 루트에서 create-service 명령어를 실행하여 Grails 서비스를 만들 수 있다.

grails create-service simple

이 명령어는 grails-app/services/SimpleService.groovy 위치에 서비스를 생성한다. 서비스의 이름은 약속에 의해 Service로 끝나며 서비스는 평범한 Groovy 클래스이다.

class SimpleService {
}


8.1 선언적 트랜잭션
서비스는 보통 도메인 클래스간의 상호작용하는 로직을 가진다. 따라서 종종 큰 오퍼레이션으로 나눠진 저장 기능들을 수행한다. 이러한 서비스의 본질 때문에 트랜잭션이 자주 필요하다. 물론 withTransaction 메소드를 이용하여 프로그램상에서 트랜잭션을 사용할 수 있지만 이것은 반복적이고 Spring이 내재한 트랜잭션 추상화의 힘을 충분히 활용하지 못하는 경우다.

서비스는 트랜잭션 경계를 사용할 수 있으며 이것은 근본적으로 서비스의 모든 메소드가 트랜잭션에 참여할 수 있게(transactional) 만드는 선언적인 방법이다. 기본으로 모든 서비스에 트랜잭션 경계를 만들수 있다. 이것을 쓰지 않으려면 단순히 transactional 속성을 false로 한다.

class CountryService {
    static transactional = false
}

물론 미래에 기본설정이 바뀔 경우 이 속성을 true로 바꿀 수 있다. 또는 서비스가 의도적으로 transactional하다고 명확히 하기 위해 사용할 수 있다.

사용자 삽입 이미지
주의: 선언적인 트랜잭션이 작동하기 위한 유일한 방법은 의존성 주입(dependency injection)이다. new BookService()와 같은 new 연산자를 사용하면 트랜잭션 서비스를 사용할 수 없을 것이다.

그 결과 모든 메소드를 트랜잭션으로 감싸고 메소드중 하나라도 예외를 발생하면 자동으로 롤백한다. 트랜잭션의 전파 수준(propagation level)은 기본으로 PROPAGATION_REQUIRED로 설정된다.

8.2 서비스 범위
기본으로, 서비스 메소드 접근은 동기화되어 있지 않다. 따라서 이런 메소드를 동시에 실행하는 것을 막지를 못한다. 사실, 서비스는 singleton이고 동시에 사용하기 때문에 상태를 서비스에 저장하는 것은 매우 주의를 요하는 것이다. 아니면 결코 상태를 서비스에 저장하지 않는 쉬운(보다 나은) 방법을 취한다.

서비스를 특정 범위에 넣음으로써 이러한 동작을 바꿀 수 있다. 지원하는 범위는 다음과 같다.

  • prototype: 매번 새로운 서비스를 생성하고 이것은 다른 클래스에 매번 넣어진다(injected).
  • request: Request마다 새 서비스를 생성한다.
  • flash: 오직 현재와 다음 request에만을 위해 새 서비스를 생성한다.
  • flow: 웹 플로우에서 플로우 범위마다 새 서비스를 생성한다.
  • conversation: 웹 플로우에서 대화 범위마다 서비스가 존재한다. 즉 루트 플로우와 그 하위 플로우에 대해서만..
  • session: 사용자 세션마다 서비스를 생성한다.
  • singleton (default): 오직 하나의 서비스 인스턴스만 존재한다.
사용자 삽입 이미지
서비스가 flash, flow 또는 conversation 범위에 있을 경우 이 서비스는 java.io.Serializable을 구현해야 한다. 그리고 오직 웹 플로우 상황에서만 사용할 수 있다.

범위 중 하나를 사용하려면 클래스에 static scope 속성을 위 범위 중 하나의 값으로 설정한다.

static scope = "flow"


8.3 의존성 주입(Dependency Injection)과 서비스

의존성 주입 기본
Grails 서비스의 핵심 중 하나는 Spring 프렘임워크의 의존성 주입 능력의 잇점을 취했다는 것이다. Grails는 약속에 의한 의존성 주입을 지원한다. 바꿔 말하면 서비스의 클래스 이름으로 속성이름을 사용하면 자동으로 서비스를 컨트롤러나 태그 라이브러리 등에 넣어(주입)준다.

예로 BookService라는 서비스가 있다고 치자. 다음과 같이 컨트롤러안에 bookService라는 속성을 정의하면

class BookController {
    def bookService
    ...
}

이 경우 Spring 컨테이너는 BookService의 인스턴스를 설정된 범위에서 자동으로 BookController에 넣어준다. 모든 의존성 주입은 이름에 의해 이루어진다. Grails는 타입에 의한 의존성 주입은 지원하지 않는다. 또한 다음과 같이 타입도 지정할 수는 있다.

class AuthorService {
    BookService bookService
}

하지만 이것은 개발 모드에서 BookService가 변경되었을 경우 리로딩 시 에러를 발생하는 불리한 점이 있긴하다.

의존성 주입과 서비스
동일한 기법으로 다른 서비스에도 서비스를 주입할 수 있다. BookService가 필요한 AuthorService가 있다고 하자. 다음과 같이 AuthorService를 선언하여 서비스를 사용할 수 있다.

class AuthorService {
    def bookService
}


의존성 주입과 도메인 클래스
도메인 클래스에도 서비스를 주입
할 수 있다. 이것은 풍부한(rich) 도메인 모델 개발을 도와준다.

class Book {
    ...
    def bookService
    def buyBook() {
        bookService.buyBook(this)
    }
}


8.4 자바에서 서비스 사용하기
서비스의 강력한 점 중 하나는 이것이 재사용 가능한 로직으로 포장되었기에 자바 클래스는 물론 다른 클래스에서 그것들을 사용할 수 있다는 것이다. 몇가지 방법으로 자바에서 서비스를 재사용할 수 있다. 가장 간단한 방법은 서비스를 grails-app/services 디렉토리안의 패키지로 옮기는 것이다. 기본 패키지(package 선언이 없는 경우)인 경우 그 심각성 때문에 자바에서 클래스로 임포트(import)할 수 없다. 따라서 아래의 BookService는 자바에서 그대로 사용할 수 없다.

class BookService {
    void buyBook(Book book) {
        // 로직
    }
}

어쨌거나 이 클래스를 grails-app/services/bookstore와 같이 하위 디렉토리로 옮기고 package 선언을 수정하여 패키지에 넣음으로써 바로잡을 수 있다.

package bookstore
class BookService {
    void buyBook(Book book) {
        // 로직
    }
}

패키징을 위한 다른 방법은 서비스가 구현할  자바 인터페이스를 특정 패키지에 작성하고

package bookstore;
interface BookSerice {
    void buyBook(Book book);
}

Grails 서비스는 이것을 구현한다.

class BookService implements bookstore.BookStore {
    void buyBook(Book b) {
        // 로직
    }
}

자바 쪽에는 오직 인터페이스의 레퍼런스만 가지고 구현 클래스는 없기에 어쩌면 뒤의 기법이 더 깨끗하다. 두 방법 모두 자바에서 사용하는 목적을 위해 컴파일 시에 정적으로 클래스(또는 인터페이스)를 결정한다. 이제 src/java 패키지안에 자바 클래스를 생성하고 Spring에서 사용하는 타입과 bean의 이름을 이용하여 setter를 제공하면 된다.

package bookstore;
// note: this is Java class
public class BookConsumer {
    private BookStore store;
    public void setBookStore(BookStore storeInstance) {
        this.store = storeInstance;
    }
    …
}

이것이 되었다면 자바 클래스를 grails-app/conf/spring/resources.xml에 Spring의 빈으로 설정할 수 있다. (더 자세한 정보는 Grails와 Spring의 관련 장을 보라)

<bean id="bookConsumer" class="bookstore.BookConsumer">
    <property name="bookStore" ref="bookService" />
</bean>

---
원문: 8. The Service Layer

0 Comments
댓글쓰기 폼