blob: 4a9b281440136185058f4af86d7ef72ed3b3335e [file] [log] [blame]
==== Writing a Simple Data Service
In a Grails application you can create a Data Service in either `src/main/groovy` or `grails-app/services`. To write a Data Service you should create either an interface (although abstract classes can also be used, more about that later) and annotate it with the `grails.gorm.services.Service` annotation with the domain class the service applies to:
[source,groovy]
----
@Service(Book)
interface BookService {
Book getBook(Serializable id)
}
----
The `@Service` annotation is an AST transformation that will automatically implement the service for you. You can then obtain the service via Spring autowiring:
[source,groovy]
----
@Autowired BookService bookService
----
Or if you are using GORM standalone by looking it up from the `HibernateDatastore` instance:
[source,groovy]
----
BookService bookService = hibernateDatastore.getService(BookService)
----
TIP: The above example also works in Spock unit tests that extend `HibernateSpec`
==== How Does it Work?
The `@Service` transformation will look at the the method signatures of the interface and make a best effort to find a way to implement each method.
If a method cannot be implemented then a compilation error will occur. At this point you have the option to use an abstract class instead and provide an implementation yourself.
The `@Service` transformation will also generate a `META-INF/services` file for the service so it can be discovered via the standard Java service loader. So no additional configuration is necessary.
==== Advantages of Data Services
There are several advantages to Data Services that make them worth considering to abstract your persistence logic.
* *Type Safety* - Data service method signatures are compile time checked and compilation will fail if the types of any parameters don't match up with properties in your domain class
* *Testing* - Since Data Services are interfaces this makes them easy to test via https://spockframework.org/spock/docs/1.0/interaction_based_testing.html[Spock Mocks]
* *Performance* - The generated services are statically compiled and unlike competing technologies in the Java space no proxies are created so runtime performance doesn't suffer
* *Transaction Management* - Each method in a Data Service is wrapped in an appropriate transaction (a read-only transaction in the case of read operations) that can be easily overridden.
==== Abstract Class Support
If you come across a method that GORM doesn't know how to implement, then you can provide an implementation by using an abstract class.
For example:
[source,groovy]
----
interface IBookService {
Book getBook(Serializable id)
Date someOtherMethod()
}
@Service(Book)
abstract class BookService implements IBookService {
@Override
Date someOtherMethod() {
// impl
}
}
----
In this case GORM will implement the interface methods that have not been defined by the abstract class.
In addition, all _public_ methods of the domain class will be automatically wrapped in the appropriate transaction handling.
What this means is that you can define protected abstract methods that are non-transactional in order to compose logic. For example:
[source,groovy]
----
@Service(Book)
abstract class BookService {
protected abstract Book getBook(Serializable id) <1>
protected abstract Author getAuthor(Serializable id) <1>
Book updateBook(Serializable id, Serializable authorId) { <2>
Book book = getBook(id)
if(book != null) {
Author author = getAuthor(authorId)
if(author == null) {
throw new IllegalArgumentException("Author does not exist")
}
book.author = author
book.save()
}
return book
}
}
----
<1> Two `protected abstract` methods are defined that are not wrapped in transaction handling
<2> The `updateBook` method uses the two methods that are implemented automatically by GORM and being `public` is automatically made transactional.
TIP: If you have `public` methods that you do not wish to be transactional, then you can annotate them with `@NotTransactional`