CHAPTER 11

Services

A common pattern in the development of enterprise software is the so-called service layer that encapsulates a set of business operations. With Java web development, it is generally considered good practice to provide layers of abstraction and reduce coupling between the layers within an MVC application.

The service layer provides a way to centralize application behavior into an API that can be utilized by controllers or other services. Many good reasons exist for encapsulating logic into a service layer, but the following are the main drivers:

  • You need to centralize business logic into a service API.
  • The use cases within your application operate on multiple domain objects and model complex business operations that are best not mixed in with controller logic.
  • Certain use cases and business processes are best encapsulated outside a domain object and within an API.

If your requirements fall into one of these categories, creating a service is probably what you want to do. Services themselves often have multiple dependencies; for example, a common activity for a service is to interact with the persistence layer whether that is straight JDBC or an ORM system like Hibernate.

Clearly, whichever system you use, you are potentially dependent on a data source or a session factory or maybe just another service. Configuring these dependencies in a loosely coupled way has been one of the main challenges facing early adopters of the J2EE technology.

Like many other software development challenges, this problem is solved by a software design pattern called Inversion of Control (IoC), or dependency injection, and projects such as Spring implement this pattern by providing an IoC container.

Grails uses Spring to configure itself internally, and it is this foundation that Grails builds on to provide services by convention. Nevertheless, let's jump straight into looking at what Grails services are and how to create a basic service.

Service Basics

Services, like other Grails artefacts, follow a convention and don't extend any base class. For example, say you decide to move much of the gTunes application's business logic into a service; you would need to create a class called StoreService located in the grails-app/services/ directory.

Unsurprisingly, there is a Grails target that allows you to conveniently create services. Building on what was just mentioned, to create the StoreService you can execute the create-service target, which will prompt you to enter the name of the service, as demonstrated in Listing 11-1.

Listing 11-1. Running the create-service Target

$ grails create-service
Welcome to Grails 1.1 - http://grails.org/
Licensed under Apache Standard License 2.0
Grails home is set to: /Development/Tools/grails

Base Directory: /Development/Projects/gTunes
Running script /Development/Tools/grails/scripts/CreateService.groovy
Environment set to development
Service name not specified. Please enter:
com.g2one.gtunes.Store
     [copy] Copying 1 file to /.../gTunes/grails-app/services/com/g2one/gtunes
Created Service for Store
     [copy] Copying 1 file to /.../gTunes/test/integration/com/g2one/gtunes
Created Tests for Store

Here you can enter com.g2one.gtunes.Store as the name of the service, and the target will create the StoreService class automatically and put it in the right place. The result will resemble something like Listing 11-2.

Listing 11-2. grails-app/services/com/g2one/gtunes/StoreService.groovy

package com.g2one.gtunes
class StoreService {
    boolean transactional = true
    def serviceMethod() {
    }
}

The service contains one method, which is just a placeholder for a real method. The more interesting aspect is the transactional property, which is discussed in detail later in this chapter.

Services and Dependency Injection

It is important to note that services are singletons by default, which means there is only ever one instance of a service. So, how do you go about getting a reference to a service within a controller, for example? Well, as part of Spring's dependency injection support, Spring has a concept called autowiring that allows dependencies to automatically be injected by name or type.

Grails services can be injected by name into a controller. For example, simply by creating a property with the name storeService within the StoreController, the StoreService instance will automatically be available to the controller. Listing 11-3 demonstrates how this is done.

Listing 11-3. Injecting a Service Instance into a Controller

class StoreController {
    def storeService
    ...
}

Note The storeService property is dynamically typed in Listing 11-3. The property can be statically typed, and injection will work in the same way. It should be noted that using a dynamically typed reference allows for dummy versions of the service to easily be injected for the purpose of testing the controller.


The convention used for the name of the property is basically the property name representation of the class name. In other words, it is the class name with the first letter in lowercase following the JavaBean convention for property names. You can then invoke methods on the singleton StoreService instance, even though you have done nothing to explicitly look it up or initialize it. The underlying Spring IoC container handles all of this automatically.

You can use the same convention to inject services into other services, hence allowing your services to interact within one another.

It is important that you let Grails inject service instances for you. You should never be instantiating instances of service classes directly. Later in this chapter, we will discuss transactions, and you will see that there is some special magic going on when Grails is allowed to inject service instances for you. You will get none of those benefits if you are creating service instances yourself.

Now that you understand the basics of services, we'll show an example of implementing a service.

Services in Action

The StoreController in the gTunes application contains quite a bit of business logic and complexity at the moment. Pulling that logic out of the controller and into a service is a good idea. In general, you should strive to keep your Grails controllers tight and concise. You should not let a lot of business complexity evolve in a controller. When much complexity starts to evolve in a controller, that should be a red flag to you, and you should consider refactoring the controller to pull out a lot of that complexity. Much of that complexity will fit perfectly into a service or multiple services.

Let's take a look at one specific area of the controller that is a good candidate for some refactoring. That area is the showConfirmation step in the buyFlow flow. Listing 11-4 shows a relevant piece of the StoreController.

Listing 11-4. Web Flow Logic in StoreController

package com.g2one.gtunes
class StoreController {
    def buyFlow = {
        ...
        showConfirmation {
            on('confirm') {
                // NOTE: Dummy implementation of transaction processing
                // a real system would integrate an e-commerce solution
                def user = flow.user
                def albumPayments = flow.albumPayments
                def p = new Payment(user:user)
                flow.payment = p
                p.invoiceNumber = "INV-${user.id}-${System.currentTimeMillis()}"
                def creditCard = flow.creditCard
                assert creditCard.validate()
                // TODO: Use credit card to take payment
                // ...
                // Once payment taken update user profile
                for(ap in albumPayments) {
                    ap.user = user
                    // validation should never fail at this point
                    assert ap.validate()
                    p.addToAlbumPayments(ap)
                    assert p.save(flush:true)
                    ap.album.songs.each { user.addToPurchasedSongs(it) }
                    user.addToPurchasedAlbums(ap.album)
                    assert user.save(flush:true)
                }
            }.to 'displayInvoice'
        }
        ...
    }
    ...
}

There is a lot going on here, and this is just one step in a series of steps in a Web Flow. You should pull most of this code out of the controller and put it into a service.

Defining a Service

The code that is being refactored out of the StoreController should be put into a service called StoreService. The StoreService class should be defined in the grails-app/services/com/g2one/gtunes/ directory. That refactoring would yield a StoreService like the one shown in Listing 11-5.

Listing 11-5. The purchaseAlbums Method in the StoreService

package com.g2one.gtunes
class StoreService {
    static transactional = true
    Payment purchaseAlbums(User user, creditCard, List albumPayments) {
       def p = new Payment(user:user)
       p.invoiceNumber = "INV-${user.id}-${System.currentTimeMillis()}"
       if(!creditCard.validate()) {
           throw new IllegalStateException("Credit card must be valid")
       }
       // TODO: Use credit card to take payment
       // ...
       // Once payment taken update user profile
       for(ap in albumPayments) {
           ap.user = user
           // validation should never fail at this point
           if(!ap.validate()) {
               throw new IllegalStateException("Album payment must be valid")
           }
           p.addToAlbumPayments(ap)
           if(!p.save(flush:true)) {
               throw new IllegalStateException("Payment must be valid")
           }
           ap.album.songs.each { user.addToPurchasedSongs(it) }
           user.addToPurchasedAlbums(ap.album)
       }
       if(!user.save(flush:true)) {
           throw new IllegalStateException("User must be valid")
       }
       return p
   }
}

Using a Service

The StoreController can now take advantage of the purchaseAlbums method in the StoreService. To do this, the StoreController needs to define the storeService property and then invoke the purchaseAlbums method on that property, as shown in Listing 11-6.

Listing 11-6. Calling the purchaseAlbums Method in the StoreController

package com.g2one.gtunes
class StoreController {
    def storeService
    def buyFlow = {
        ...
        showConfirmation {
            on('confirm') {
                // NOTE: Dummy implementation of transaction processing,
                // a real system would integrate an e-commerce solution
                def user = flow.user
                def albumPayments = flow.albumPayments
                flow.payment =
                  storeService.purchaseAlbums(user,
                                              flow.creditCard,
                                              flow.albumPayments)
            }
        }.to 'displayInvoice'
    }
    ...
}

Transactions

As mentioned previously, services often encapsulate business operations that deal with several domain objects. If an exception occurs while executing changes, you may not want any earlier changes to be committed to the database.

Essentially, you want an all-or-nothing approach, also known as a transaction. Transactions are essential for maintaining database integrity via their ACID properties, which have probably been covered in every book that has used a relational database. Nevertheless, we'll give you a quick look at them here. ACID stands for atomicity, consistency, isolation, and durability:

  • Atomicity: This refers to how operations on data within a transaction must be atomic. In other words, all tasks within a transaction will be completed or none at all will be, thus allowing the changes to be rolled back.
  • Consistency: This requires that the database be in a consistent state before and after any operations occur. There is no point attempting to complete a transaction if the database is not in a legal state to begin with, and it would be rather silly if an operation left the database's integrity compromised.
  • Isolation: This refers to how transactions are isolated from all other operations. Essentially, this means other queries or operations should never be exposed to data that is in an intermediate state.
  • Durability: Once a transaction is completed, durability guarantees that the transaction cannot possibly be undone. This is true even if system failure occurs, thus ensuring the committed transaction cannot at this point be aborted.

Grails services may declare a static property named transactional. When the transactional property is set to true, the methods of the service are configured for transaction demarcation by Spring. What this does is create a Spring proxy that wraps each method call and provides transaction management.

Grails handles the entire automatic runtime configuration for you, leaving you to concentrate on writing the logic within your methods. If the service does not require any transaction management, set the transactional property to false to disable transactions.

If a service needs to impose its own fine-grained control over transaction management, that is an option as well. The way to do this is to assign the transactional property a value of false and take over the responsibility of managing transactions yourself. The static withTransaction method may be called on any domain class, and it expects a closure to be passed as an argument. The closure represents the transaction boundary. See Listing 11-7 for an example.

Listing 11-7. Using withTransaction in a Service

package com.g2one.gtunes

class GtunesService {

    // turn off automatic transaction management
    static transactional = false

    void someServiceMethod() {

        Album.withTransaction {

            // everything in this closure is happening within a transaction
            // which will be committed when the closure completes
        }

    }

}

If the closure that is passed to the withTransaction method throws an exception, then the transaction will be rolled back. Otherwise, the transaction is committed.

If you want to take explicit control over rolling back the transaction, that is simple to do as well. It turns out that an instance of the org.springframework.transaction.TransactionStatus interface is being passed as an argument to the closure. One of the methods defined by the TransactionStatus interface is setRollbackOnly().8 Calling the setRollbackOnly() method will ensure that the transaction gets rolled back. Listing 11-8 demonstrates how to take advantage of this.

Listing 11-8. Using the TransactionStatus Argument

package com.g2one.gtunes

class GtunesService {

    // turn off automatic transaction management
    static transactional = false

    void someServiceMethod() {

        Album.withTransaction { tx ->
            // do some work with the database
            // if the transaction needs to be rolled back for
            // any reason, call setRollbackOnly() on the
            // TransactionStatus argument...
            tx.setRollbackOnly()
       }

    }

}

Controllers and other Grails artefacts will, of course, need to get hold of a reference to the singleton StoreService. As described earlier in this chapter, the best way to get hold of a reference to a service is to take advantage of the automatic dependency injection provided by Grails.

Scoping Services

You must be careful about storing state in a service. By default all services are scoped as singletons and can be used concurrently by multiple requests. Further, access to service methods is not synchronized. For stateless services, none of that is a problem. If a service must maintain state, then it should be scoped to something other than singleton.

Grails supports several scopes for services. Which scope you use will depend on how your application uses the service and what kind of state is maintained in the service. The support scopes are as follows:

  • 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 requests 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, in other words, a root flow and its subflows.
  • session: A service is created for the scope of a user session.
  • singleton (default): Only one instance of the service ever exists.

Note If a service uses flash, conversation, or flow scope, then the service class must implement the java.io.Serializable interface. Services using these scopes can be used only within the context of a Web Flow. See Chapter 9 for more details about Web Flow.


If a service is to be scoped using anything other than singleton, the service must declare a static property called scope and assign it a value that is one of the support scopes listed earlier. See Listing 11-9.

Listing 11-9. A request-Scoped Service

class LoanCalculationService {


    boolean transactional = true

    // this is a request scoped service
    static scope = 'request'

    ...


}

Choose the service scope carefully, and make sure your scope is consistent with the application's expectations of the service. Prefer stateless services; for these, the default scope of singleton is almost always optimum. When a service must maintain state, choose the scope that satisfies the application's requirements.

Testing Services

Since much of your business logic and complexity is encapsulated in services, it is important that these components are tested. As far as your tests are concerned, a service is just another class and can be tested as such. Note that integration tests participate in automatic dependency injection, so service instances can be injected into an integration test. Unit tests do not participate in automatic dependency injection. A unit test should create its own instances of a service class as necessary.

When unit testing a controller (or any other component) that uses a service, if the service is dynamically typed in the component that is being tested, then that component should be easy to test independent of the service dependency. For example, a Map or Expando object could be passed to a controller constructor to act as a dummy version of the service. An approach like this allows individual components to be unit tested in isolation. Isolation testing is all about testing individual components independently from their dependencies. Dynamic typing is one aspect of Groovy that makes isolation testing much easier to achieve compared to statically typed languages such as Java.

Exposing Services

The services you write as part of a Grails application contain a large share of the business logic involved in the application. Those services are easily accessed from just about anywhere in the application using Grails' automatic dependency injection. It makes sense that a lot of that business logic may be useful to other Grails applications. In fact, it may be useful to other applications that may not be Grails applications. The automatic dependency injection works only within the application. There really isn't any way to inject those services into other applications. However, it is possible to access those services from other applications, and Grails makes that really easy to do.

Making a service available to other process is known as exposing the service. A number of Grails plugins are available that support exposing services using various remoting technologies. For example, there is a plugin that greatly simplifies exposing services using the Java Management Extensions (JMX) technology.9 JMX has been part of the Java Platform since the J2SE 5.0 release and provides a really simple mechanism for monitoring and managing resources within an application.

You can install the JMX plugin into a project using the install-plugin target, as shown in Listing 11-10.

Listing 11-10. Installing the JMX Plugin

$ grails install-plugin jmx

Welcome to Grails 1.1 - http://grails.org/
Licensed under Apache Standard License 2.0
Grails home is set to: /Development/Tools/grails

Base Directory: /Development/Projects/gTunes
Running script /Development/Tools/grails/scripts/InstallPlugin.groovy
Environment set to development
Reading remote plug-in list ...
Installing plug-in jmx-0.4
[mkdir] Created dir: /Users/jeff/.grails/1.1/projects/gTunes/plugins/jmx-0.4
    [unzip] Expanding: /.../plugins/grails-jmx-0.4.zip into /.../plugins/jmx-0.4
Executing remoting-1.0 plugin post-install script ...
Plugin jmx-0.4 installed

Like other remoting plugins that are available for Grails, the JMX plugin will look in all service classes for a property named expose. The expose property should be a list of Strings, and if the list contains the string jmx, then the plugin will expose that service using JMX.

Listing 11-11 shows a service in the gTunes application that has been exposed using JMX.

Listing 11-11. The GtunesService Is Exposed Using JMX

package com.g2one.gtunes

class GtunesService {

    static transactional = true

    static expose = ['jmx']
    int getNumberOfAlbums() {
        Album.count()
    }

    int getNumberOfAlbumsForGenre(String genre) {
        Album.countByGenre(genre)
    }
}

The GtunesService contains a single method called getNumberOfAlbums, which returns the number of Album objects that are currently in the database. The service may contain any number of methods. All of the methods in the service will be exposed as JMX operations.

In terms of code, the only thing you need to do to expose your services using JMX is include jmx in the value of the expose property. It could not be simpler! There is another step that does not involve code. The way to enable remote access to services that have been exposed using JMX is to set the com.sun.management.jmxremote system property when the Grails application starts. A simple way to do this is to assign a value to the JAVA_OPTS environment variable. The value should include -Dcom.sun.management.jmxremote. Note that the property does not need to be assigned a value; the property just needs to be set. For example, in a Bash shell you could interactively set the environment variable using the code shown in Listing 11-12.

Listing 11-12. Setting JAVA_OPTS in a Bash Shell

export JAVA_OPTS=-Dcom.sun.management.jmxremote

In a Windows shell you could use the code shown in Listing 11-13.

Listing 11-13. Setting JAVA_OPTS in a Windows Shell

set JAVA_OPTS=-Dcom.sun.management.jmxremote

The com.sun.management.jmxremote system property must be set when the Grails application starts. Setting it after the Grails application has started will not affect the application.

Versions 5.0 and later of the J2SE include the Java Monitoring and Management Console known as JConsole. The JConsole application is a GUI tool for interacting with beans that have been exposed using JMX.

With your Grails application up and running, start JConsole by running the jconsole command at a command prompt. The application should open with the dialog box shown in Figure 11-1.

This dialog box allows you select which agent you want to connect to. Typically you will see just one agent in the list. Find your Grails application in the list, select it, and click the Connect button.

Once you have connected to an agent, the main JConsole window should appear, as shown in Figure 11-2.

image

Figure 11-1. The Connect to Agent dialog box in JConsole

image

Figure 11-2. The main JConsole window

This main screen displays a lot of information about the Grails process. Click the "MBeans" tab at the top of the screen to view all the accessible beans. On that screen, you should see a list of all of your JMX exposed services under the "GrailsApp" folder on the left, as shown in Figure 11-3.

image

Figure 11-3. Grails services exposed using JMX

Notice the NumberOfAlbums property represented there. In Figure 11-3, that property has a value of 498. That value was just retrieved from the Grails application by invoking the getNumberOfAlbums method on the GtunesService. Just like that you have an entirely separate process communicating with your Grails service! In this case, the process is the JConsole application, but that process can be any JMX-aware client.

Select the "Operations" tab near the top of the screen. This tab will list all the operations that have been exposed by this bean, including all the methods defined in your service, as shown in Figure 11-4.

Notice that for operations that require parameters, JConsole provides a text box for you to define the value of the parameter. With that value filled in, you can click the button that contains the operation name. The operation will be invoked remotely, and the return value will be displayed.

image

Figure 11-4. JMX operations

The JMX plugin is one of several Grails plugins that support exposing services using various remoting technologies. There is an XML-RPC plugin, and there is a Remoting plugin that allows services to be exposed via RMI, Hessian, Burlap, and Spring's HttpInvoker. The XFire plugin and the Axis2 plugin each support exposing services via SOAP. The XFire plugin is covered in detail in Chapter 15.

All of the remoting plugins use the same expose property in a service class as the trigger for exposing a service using any particular technology. Listing 11-14 shows how you would expose the GtunesService using JMX and XFire.

Listing 11-14. Exposing a Service Using JMX and XFire

package com.g2one.gtunes
class GtunesService {
    static transactional = true
    static expose = ['jmx', 'xfire']
    int getNumberOfAlbums() {
        Album.count()
    }
int getNumberOfAlbumsForGenre(String genre) {
        Album.countByGenre(genre)
    }
}

Remember that in order for that to work, you need to have the JMX plugin and the XFire plugin installed.

Exposing Grails services is a great way to allow applications to access business logic inside a Grails application. In fact, you could build a Grails application that is just a service layer. That application might consist of nothing more than domain classes and services that provide access to the data, similar to the GtunesService shown earlier. The application would not necessarily need to have any controllers, any views, or anything else.

Summary

Services are an important component in almost any nontrivial Grails application. Services are where much of the application's business logic and complexity belong.

In this chapter, you saw how Grails helps simplify an application by encouraging the isolation of that complexity into services. You learned how you can easily take advantage of the power of Spring's dependency injection capabilities without the burden of having to write configuration files to instrument Spring.

You also saw how transaction management works with respect to Grails services. For most scenarios, the default method-level transaction demarcation is a perfect fit. For scenarios where the application needs more fine-grained control over transactions, Grails provides a really simple mechanism for dealing with those scenarios.

You have a lot of options for exposing Grails services using any number of remoting technologies.

You should make a habit of taking advantage of the power and flexibility provided by Grails services. If you do that, your applications will be easier to write, easier to understand, easier to maintain, and easier to test.



8. You can find the full documentation for the TransactionStatus interface at http://static.springframework.org/spring/docs/2.5.x/api/.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset