(Quick Reference)

11 サービスレイヤー - Reference Documentation

Authors: Graeme Rocher, Peter Ledbrook, Marc Palmer, Jeff Brown, Luke Daley, Burt Beckwith

Version: 2.3.0

Translated by: T.Yamamoto, Japanese Grails Doc Translating Team. Special thanks to NTT Software.
【注意】このドキュメントの内容はスナップショットバージョンを元に*意訳*されているため、一部現行バージョンでは未対応の機能もあります。

11 サービスレイヤー

Grails defines the notion of a service layer. The Grails team discourages the embedding of core application logic inside controllers, as it does not promote reuse and a clean separation of concerns.
Grailsではサービスレイヤの概念を持っています。再利用や関心事の明確な分離を妨げるといった理由から、Grailsチームは、コントローラ内にアプリケーションのコアロジックを持ち込まないことを推奨します。

Services in Grails are the place to put the majority of the logic in your application, leaving controllers responsible for handling request flow with redirects and so on.
Grailsのサービスは、アプリケーション・ロジックの大部分を置く場所と考えられます。コントローラには、リダイレクトなどを介してリクエストの流れを処理する、といった責務が残ります。

サービスを作成する
Creating a Service

You can create a Grails service by running the create-service command from the root of your project in a terminal window:
ターミナルウィンドウで、プロジェクトのルートからcreate-serviceコマンドを実行すると、Grailsのサービスを作成することができます:

grails create-service helloworld.simple

If no package is specified with the create-service script, Grails automatically uses the application name as the package name.
create-serviceスクリプト実行した際にパッケージ名を指定しなかった場合は、アプリケーション名をパッケージ名として使用します。

The above example will create a service at the location grails-app/services/helloworld/SimpleService.groovy. A service's name ends with the convention Service, other than that a service is a plain Groovy class:
上記の例では、grails-app/services/helloworld/SimpleService.groovyというサービスが作成されます。サービスの名前は、規約でServiceで終わることになっており、作成されるサービスは、普通のGroovyクラスです

package helloworld

class SimpleService { }

11.1 断定的なトランザクション

デフォルトの宣言的トランザクション
Default Declarative Transactions

Services are typically involved with coordinating logic between domain classes, and hence often involved with persistence that spans large operations. Given the nature of services, they frequently require transactional behaviour. You can use programmatic transactions with the withTransaction method, however this is repetitive and doesn't fully leverage the power of Spring's underlying transaction abstraction.
サービスは、典型的にはドメインクラス間の調整的なロジックに関わりますが、それゆえしばしば大規模な操作に渡る永続性に関わります。頻繁にトランザクションの振る舞いを必要とするサービスの性質を考えてください。もちろんwithTransactionメソッドを使ってプログラム的なトランザクションを行うことができますが、これは同じことを何度も繰り返すことになり、Springが持つ基本的なトランザクション抽象化の恩恵を十分活用することができません。

Services enable transaction demarcation, which is a declarative way of defining which methods are to be made transactional. All services are transactional by default. To disable this set the transactional property to false:
サービスは、トランザクション境界を使用することができ、サービス内のすべてのメソッドをトランザクションにすると宣言する宣言方法です。すべてのサービスは、デフォルトでトランザクション境界が可能です、不可にするにはtransactionalプロパティにfalseを設定します:

class CountryService {
    static transactional = false
}

You may also set this property to true to make it clear that the service is intentionally transactional.
サービスが意図的にトランザクションであるということを明確にするために、このプロパティにtrueを設定することもできます。

Warning: dependency injection is the only way that declarative transactions work. You will not get a transactional service if you use the new operator such as new BookService()
警告:依存性の注入は、宣言的トランザクションが機能する唯一の方法です。new BookService()のようにnew演算子を使用した場合、トランザクションなサービスを取得するできません。

The result is that all methods are wrapped in a transaction and automatic rollback occurs if a method throws a runtime exception (i.e. one that extends RuntimeException) or an Error. The propagation level of the transaction is by default set to PROPAGATION_REQUIRED.
その結果、すべてのメソッドがトランザクションでラップされ、例外がメソッドからスローされると、トランザクションは自動的にロールバックされます。トランザクションの伝播レベルは、デフォルトでPROPAGATION_REQUIREDが設定されます。

Checked exceptions do not roll back transactions. Even though Groovy blurs the distinction between checked and unchecked exceptions, Spring isn't aware of this and its default behaviour is used, so it's important to understand the distinction between checked and unchecked exceptions.

カスタムトランザクションの設定
Custom Transaction Configuration

Grails also fully supports Spring's Transactional annotation for cases where you need more fine-grained control over transactions at a per-method level or need specify an alternative propagation level.
メソッド単位のレベルでトランザクションのよりきめの細かいコントロールを必要とする場合や、別のトランザクション伝播レベルを指定する必要がある場合、GrailsはSpringのTransactionalアノテーションもフルサポートしています:

Annotating a service method with Transactional disables the default Grails transactional behavior for that service (in the same way that adding transactional=false does) so if you use any annotations you must annotate all methods that require transactions.
Transactionalアノテーションをメソッドに指定すると、デフォルトの書式(transactional=falseでの定義)でのトランザクションは無効になります。アノテーションを使用する場合は、トランザクションが必要な全てのメソッドに定義する必要があります。

In this example listBooks uses a read-only transaction, updateBook uses a default read-write transaction, and deleteBook is not transactional (probably not a good idea given its name).
この例ではlistBooksがread-onlyトランザクションを使用、updateBookがデフォルトのread-writeトランザクションを使用、deleteBookにはトランザクションは指定されていません(このメソッドの名前的に良くはありませんが)。
import org.springframework.transaction.annotation.Transactional

class BookService {

@Transactional(readOnly = true) def listBooks() { Book.list() }

@Transactional def updateBook() { // … }

def deleteBook() { // … } }

You can also annotate the class to define the default transaction behavior for the whole service, and then override that default per-method. For example, this service is equivalent to one that has no annotations (since the default is implicitly transactional=true):
クラスにアノテーションを指定することで、サービス全体にデフォルトトランザクションを指定して、さらにそれぞれのメソッドでオーバーライドすることができます。例として、このサービスはアノテーションを使用していない物と同等になります(transactional=trueがデフォルトなので):

import org.springframework.transaction.annotation.Transactional

@Transactional class BookService {

def listBooks() { Book.list() }

def updateBook() { // … }

def deleteBook() { // … } }

This version defaults to all methods being read-write transactional (due to the class-level annotation), but the listBooks method overrides this to use a read-only transaction:
オーバーライドした、read-onlyトランザクションのlistBooksメソッド以外の、全メソッドがread-writeトランザクションになります(クラスレベルのアノテーションの定義によって)。

import org.springframework.transaction.annotation.Transactional

@Transactional class BookService {

@Transactional(readOnly = true) def listBooks() { Book.list() }

def updateBook() { // … }

def deleteBook() { // … } }

Although updateBook and deleteBook aren't annotated in this example, they inherit the configuration from the class-level annotation.
updateBookdeleteBookはアノテーション指定されていませんが、クラスレベルアノテーションの定義が受け継がれます。

For more information refer to the section of the Spring user guide on Using @Transactional.
さらなる情報はSpringユーザガイドのトランザクションセクションを参照してください。

Unlike Spring you do not need any prior configuration to use Transactional; just specify the annotation as needed and Grails will detect them up automatically.
Springと違いTransactionalを使用するための特別な定義は必要有りません。必要なアノテーションを記述すればGrailsが自動的に認識します。

11.1.1 トランザクションロールバックとセッション

Understanding Transactions and the Hibernate Session

When using transactions there are important considerations you must take into account with regards to how the underlying persistence session is handled by Hibernate. When a transaction is rolled back the Hibernate session used by GORM is cleared. This means any objects within the session become detached and accessing uninitialized lazy-loaded collections will lead to LazyInitializationExceptions.

To understand why it is important that the Hibernate session is cleared. Consider the following example:

class Author {
    String name
    Integer age

static hasMany = [books: Book] }

If you were to save two authors using consecutive transactions as follows:

Author.withTransaction { status ->
    new Author(name: "Stephen King", age: 40).save()
    status.setRollbackOnly()
}

Author.withTransaction { status -> new Author(name: "Stephen King", age: 40).save() }

Only the second author would be saved since the first transaction rolls back the author save() by clearing the Hibernate session. If the Hibernate session were not cleared then both author instances would be persisted and it would lead to very unexpected results.

It can, however, be frustrating to get LazyInitializationExceptions due to the session being cleared.

For example, consider the following example:

class AuthorService {

void updateAge(id, int age) { def author = Author.get(id) author.age = age if (author.isTooOld()) { throw new AuthorException("too old", author) } } }

class AuthorController {

def authorService

def updateAge() { try { authorService.updateAge(params.id, params.int("age")) } catch(e) { render "Author books ${e.author.books}" } } }

In the above example the transaction will be rolled back if the Author's age exceeds the maximum value defined in the isTooOld() method by throwing an AuthorException. The AuthorException references the author but when the books association is accessed a LazyInitializationException will be thrown because the underlying Hibernate session has been cleared.

To solve this problem you have a number of options. One is to ensure you query eagerly to get the data you will need:

class AuthorService {
    …
    void updateAge(id, int age) {
        def author = Author.findById(id, [fetch:[books:"eager"]])
        ...

In this example the books association will be queried when retrieving the Author.

This is the optimal solution as it requires fewer queries then the following suggested solutions.

Another solution is to redirect the request after a transaction rollback:

class AuthorController {

AuthorService authorService

def updateAge() { try { authorService.updateAge(params.id, params.int("age")) } catch(e) { flash.message "Can't update age" redirect action:"show", id:params.id } } }

In this case a new request will deal with retrieving the Author again. And, finally a third solution is to retrieve the data for the Author again to make sure the session remains in the correct state:

class AuthorController {

def authorService

def updateAge() { try { authorService.updateAge(params.id, params.int("age")) } catch(e) { def author = Author.read(params.id) render "Author books ${author.books}" } } }

Validation Errors and Rollback

A common use case is to rollback a transaction if there are validation errors. For example consider this service:

import grails.validation.ValidationException

class AuthorService {

void updateAge(id, int age) { def author = Author.get(id) author.age = age if (!author.validate()) { throw new ValidationException("Author is not valid", author.errors) } } }

To re-render the same view that a transaction was rolled back in you can re-associate the errors with a refreshed instance before rendering:

import grails.validation.ValidationException

class AuthorController {

def authorService

def updateAge() { try { authorService.updateAge(params.id, params.int("age")) } catch (ValidationException e) { def author = Author.read(params.id) author.errors = e.errors render view: "edit", model: [author:author] } } }

11.2 サービスのスコープ

By default, access to service methods is not synchronised, so nothing prevents concurrent execution of those methods. In fact, because the service is a singleton and may be used concurrently, you should be very careful about storing state in a service. Or take the easy (and better) road and never store state in a service.
デフォルトでは、サービスが持つメソッドへのアクセスは同期されないので、サービスが持つメソッドの同時実行を防ぐことはできません。実際には、サービスはシングルトンで同時に使用することが出来るため、サービスに状態を格納する場合は注意が必要です。簡単な(そしてより良い)方法は、サービスに状態を決して格納しないことです。

You can change this behaviour by placing a service in a particular scope. The supported scopes are:
特定のスコープにサービスを置くことで、この振る舞いを変えることができます。サポートされているスコープは次のとおりです:

  • prototype - A new service is created every time it is injected into another class
  • request - A new service will be created per request
  • flash - A new service will be created for the current and next request only
  • flow - In web flows the service will exist for the scope of the flow
  • conversation - In web flows the service will exist for the scope of the conversation. ie a root flow and its sub flows
  • session - A service is created for the scope of a user session
  • singleton (default) - Only one instance of the service ever exists

  • prototype - クラスへの注入時にサービスが生成される
  • request - リクエスト毎にサービスが生成される
  • flash - 現在とその次のリクエスト用にサービスが生成される
  • flow - フローの開始から終了まてのサービス(サブフロー含まず)
  • conversation - フローの開始から終了まてのサービス(サブフロー含む)
  • session - ユーザセッション毎にサービスが生成される
  • singleton (デフォルト) - サービスが1つのみ生成される

If your service is flash, flow or conversation scoped it must implement java.io.Serializable and can only be used in the context of a Web Flow
サービスのスコープがflashflowあるいはconversationの場合、java.io.Serializableを実装する必要があり、Webフローのコンテキストの中でだけ使うことができます。

To enable one of the scopes, add a static scope property to your class whose value is one of the above, for example
スコープのいずれかを有効にするには、クラスにstaticのscopeプロパティを追加し、プロパティの値を上記のいずれかにします:
static scope = "flow"

11.3 依存注入とサービス

依存性の注入の基本
Dependency Injection Basics

A key aspect of Grails services is the ability to use Spring Framework's dependency injection features. Grails supports "dependency injection by convention". In other words, you can use the property name representation of the class name of a service to automatically inject them into controllers, tag libraries, and so on.
Grailsのサービスの重要な側面は、Springフレームワークが持つ依存性注入機能を活用するための能力です。Grailsは、「規約による依存性の注入」をサポートしています。言い換えれば、サービスのクラス名のプロパティ名表現を使用することで、自動的にコントローラ、タグライブラリなどにサービスが注入されます。

As an example, given a service called BookService, if you define a property called bookService in a controller as follows:
例として、BookServiceというサービスが与えられた場合、次のようにコントローラの中にbookServiceという名前のプロパティを配置します:

class BookController {
    def bookService
    …
}

In this case, the Spring container will automatically inject an instance of that service based on its configured scope. All dependency injection is done by name. You can also specify the type as follows:
この場合、Springコンテナが設定されたスコープに基づいてサービスのインスタンスを自動的に注入します。すべての依存性の注入は、名前によって行われます。次のように型を指定することもできます:

class AuthorService {
    BookService bookService
}

NOTE: Normally the property name is generated by lower casing the first letter of the type. For example, an instance of the BookService class would map to a property named bookService.
注:通常、プロパティ名は、型の最初の文字を小文字のにして生成します。たとえば、BookServiceクラスのインスタンスは、bookServiceという名前のプロパティにマップされます。

To be consistent with standard JavaBean conventions, if the first 2 letters of the class name are upper case, the property name is the same as the class name. For example, the property name of the JDBCHelperService class would be JDBCHelperService, not jDBCHelperService or jdbcHelperService.
標準のJavaBean規約と整合をとるため、クラス名の最初の2文字が大文字の場合、プロパティ名はクラス名と同じになります。たとえばJDBCHelperServiceクラスは、jDBCHelperServiceまたjdbcHelperServiceでは無く、JDBCHelperServiceという名前のプロパティにマップされます。

See section 8.8 of the JavaBean specification for more information on de-capitalization rules.
小文字への変換規則の詳細については、JavaBeanの仕様のセクション8.8を参照してください。

依存性注入とサービス
Dependency Injection and Services

You can inject services in other services with the same technique. If you had an AuthorService that needed to use the BookService, declaring the AuthorService as follows would allow that:
同じ方法で他のサービスにサービスを注入することができます。たとえば、AuthorServiceBookService使用する場合、 次のようにAuthorServiceを宣言することで使用することができます:

class AuthorService {
    def bookService
}

依存性注入とドメインクラス/タグライブラリ
Dependency Injection and Domain Classes / Tag Libraries

You can even inject services into domain classes and tag libraries, which can aid in the development of rich domain models and views:
ドメインクラスとタグライブラリにもサービスを注入することでき、豊富なドメインモデルやビューの開発を支援することができます:

class Book {
    …
    def bookService

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

Service Bean Names

The default bean name which is associated with a service can be problematic if there are multiple services with the same name defined in different packages. For example consider the situation where an application defines a service class named com.demo.ReportingService and the application uses a plugin named ReportingUtilities and that plugin provides a service class named com.reporting.util.ReportingService. The default bean name for each of those would be reportingService so they would conflict with each other. Grails manages this by changing the default bean name for services provided by plugins by prefixing the bean name with the plugin name. In the scenario described above the reportingService bean would be an instance of the com.demo.ReportingService class defined in the application and the reportingUtilitiesReportingService bean would be an instance of the com.reporting.util.ReportingService class provided by the ReportingUtilities plugin. For all service beans provided by plugins, if there are no other services with the same name within the application or other plugins in the application then a bean alias will be created which does not include the plugin name and that alias points to the bean referred to by the name that does include the plugin name prefix. For example, if the ReportingUtilities plugin provides a service named com.reporting.util.AuthorService and there is no other AuthorService in the application or in any of the plugins that the application is using then there will be a bean named reportingUtilitiesAuthorService which is an instance of this com.reporting.util.AuthorService class and there will be a bean alias defined in the context named authorService which points to that same bean.

11.4 Javaからサービスを使う

One of the powerful things about services is that since they encapsulate re-usable logic, you can use them from other classes, including Java classes. There are a couple of ways you can reuse a service from Java. The simplest way is to move your service into a package within the grails-app/services directory. The reason this is important is that it is not possible to import classes into Java from the default package (the package used when no package declaration is present). So for example the BookService below cannot be used from Java as it stands:
サービスについての強力な事の一つは、サービスは再利用可能なロジックをカプセル化するため、Javaのクラスを含む他のクラスからサービスを使用することができるということです。Javaからサービスを再利用する方法はいくつかあります。最も簡単な方法は、grails-app/servicesディレクトリ下で、パッケージの中にサービスを移動することです。これが重要な手順である理由は、デフォルトパッケージ(パッケージの宣言がない場合に使用されるパッケージ)からJavaにクラスをインポートできないからです。したがって、たとえば以下のBookServiceは、そのままではJavaから使用できません:

class BookService {
    void buyBook(Book book) {
        // logic
    }
}

However, this can be rectified by placing this class in a package, by moving the class into a sub directory such as grails-app/services/bookstore and then modifying the package declaration:
しかし、このクラスをパッケージの中に置き、grails-app/services/bookstoreといったサブディレクトリの中にクラスを移動し、パッケージ宣言を修正することで、是正できます:

package bookstore

class BookService { void buyBook(Book book) { // logic } }

An alternative to packages is to instead have an interface within a package that the service implements:
パッケージに代わる別の方法として、パッケージ内のインターフェイスをサービスが実装することです:

package bookstore

interface BookStore { void buyBook(Book book) }

And then the service:
そしてサービスは:

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

This latter technique is arguably cleaner, as the Java side only has a reference to the interface and not to the implementation class (although it's always a good idea to use packages). Either way, the goal of this exercise to enable Java to statically resolve the class (or interface) to use, at compile time.
この後者の方法の方が、間違いなくクリーンです。Java側は、実装クラスではなくインタフェースへの参照を持てばよいからです。いずれにせよ、この実行の目的は、Javaがコンパイル時に使用するクラス(またはインターフェイス)を静的に解決できるようにすることです。

Now that this is done you can create a Java class within the src/java directory and add a setter that uses the type and the name of the bean in Spring:
これで、src/javaディレクトリのパッケージにJavaクラスを作ることができ、Springビーンの型と名前を使うセッターを提供することができます:

// src/java/bookstore/BookConsumer.java
package bookstore;

public class BookConsumer {

private BookStore store;

public void setBookStore(BookStore storeInstance) { this.store = storeInstance; } … }

Once this is done you can configure the Java class as a Spring bean in grails-app/conf/spring/resources.xml (for more information see the section on Grails and Spring):
一度こうすることで、grails-app/conf/spring/resources.xmlの中にSpringビーンとしてJavaクラスを設定することができます(詳細は、GrailsとSpringセクションを参照してください)

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

or in grails-app/conf/spring/resources.groovy:
または、 grails-app/conf/spring/resources.groovyに:

import bookstore.BookConsumer

beans = { bookConsumer(BookConsumer) { bookStore = ref("bookService") } }