Groovy

Grails Tutorial for Beginners – Grails Service Layer

This tutorial will discuss the importance of the service layer in Grails and how to work with it. It also explains transaction management and how to utilize it.

Introduction

Separation Of Concerns

Consider this analogy: Imagine a company where employees are assigned tasks on different nature of work. For example, say there is an employee named John Doe with the following responsibilities:
 

  • Handle accounting and releasing of check payments
  • Take calls from customers for product support
  • Handle administrative tasks such as booking flights plus accommodation of executives
  • Manage the schedule of the CEO

As you could see, John’s work is too complicated because he needs to multi-task on very different type of tasks. He needs to change his frame of mind when switching from one task to another. He is more likely to be stressed out and commit many mistakes. His errors could cost a lot of money in the long run. Another problem is it’s not easy to replace John later on as he is too involved in a complicated setup.

Likewise in software engineering, it is not good idea to write a class that has different nature of responsibilities. The general consensus of experts is that a single class or source file should only be involved in only one nature of task. This is called the “separation of concerns“. If there is a lot of things going on, this will only introduce bugs and problems later as the application will be very complicated to maintain. Although this concept is so simple to state, the effect on a project is enormous!

Consider a controller in Grails. Inside a controller, we can do the ff:

  • Handle routing logic
  • Invoke GORM operations to manipulate data in the database
  • Render text and show it to the user.

However, it is not advisable that we do all those things inside a controller. Grails allows a developer to do all these things together for flexibility, but it should be avoided. The real purpose of a controller is to deal with routing logic- which means:

  1. Receive requests from users
  2. Invoke the most appropriate business logic
  3. Invoke the view to display the result

View logic should be taken care of inside Groovy Server Pages (GSP) files. Read this previous tutorial about GSPs if you are unfamiliar with it.

For business logic, they should be implemented inside the service layer. Grails has a default support and handling for the service layer.

Don’t Repeast Yourself (DRY) Principle

Another benefit of using a service layer is you could reuse a business logic in multiple places without code duplication. Having a single copy of a particular business logic will make a project shorter (in terms of lines of codes) and easier to maintain. Changing the business logic will just require a change in only one particular place.

Not having to duplicate code is a part of another best practice called Don’t Repeast Yourself (DRY) Principle

Create a Service

g2
To create a service class, invoke the create-service command from the root folder of your project. For example, use the following command inside a command line or terminal:

grails create-service asia.grails.sample.Test

You can also create a service class inside the GGTS IDE.

Just right click the project, select New and then Service.
 
zz01

Provide the name and click Finish:
 
zz02

Below is the resulting class created. A default method is provided as an example. This is where we will write business logic.

package asia.grails.sample
class TestService {
    def serviceMethod() {
    }
}

Just add as many functions as needed that pertains to business logic and GORM operations. Here is an example:

package asia.grails.sample
class StudentService {
  Student createStudent(String lastName, String firstName) {
    Student student = new Student()
    student.lastName = lastName
    student.firstName = firstName
    student.referenceNumber = generateReferenceNumber()
    student.save()
    return student
  }

  private String generateReferenceNumber() {
    // some specific logic for generating reference number
    return referenceNumber
  }
}

Injecting a Service

The service is automatically injected inside a controller by just defining a variable with the proper name. (use studentService variable to inject StudentService instance). Example:

package asia.grails.sample
class MyController {
  def studentService

  def displayForm() {
  }

  def handleFormSubmit() {
    def student = studentService.createStudent(params.lastName, params.firstName)
    [student:student]
  }
}

If you are not familiar with Spring and the concept of injection, what it means above is that you don’t need to do anything special. Just declare the variable studentService and the Grails framework will automatically assign an instance to it. Just declare and use right away.

You can also inject a service to another service. For example:

package asia.grails.sample
class PersonService {
  Person createPerson(String lastName, String firstName) {
    Person p = new Person()
    p.lastName = lastName
    p.firstName = firstName
    p.save()
    return p
  }
}

package asia.grails.sample
class EmployeeService {
  def personService
  Employee createEmployee(String lastName, String firstName) {
    Person person = personService.createPerson(lastName, firstName)
    Employee emp = new Employee()
    emp.person = person
    emp.employmentDate = new Date()
    emp.save()
    return emp
  }
}

You can also inject a service inside Bootstrap.Grovy. For example:

class BootStrap {
  def studentService
  def init = { servletContext ->
    if ( Student.count() == 0 ) { // if no students in the database, create some test data
      studentService.createStudent("Doe", "John")
      studentService.createStudent("Smith", "Jame")
    }
  }
}

You can also inject a service inside a tag library. For example:

package asia.grails.sample
class StudentService {
  def listStudents() {
    return Student.list()
  }
}

class MyTagLib {
  StudentService studentService
  static namespace = "my"

  def renderStudents = {
    def students = studentService.listStudents()
    students.each { student ->
      out << "<div>Hi ${student.firstName} ${student.lastName}, welcome!</div>"
    }
  }
}

Transaction Management

If you are are new to working with databases, transaction is a very important concept. Usually, we wish certain sequence of database changes to be all successful. If not possible, we want no operation to happen at all. For example, consider this code to transfer funds between two bank accounts:

class AccountService {
  def transferFunds(long accountFromID, long accountToID, long amount) {
    Account accountFrom = Account.get(accountFromID)
    Account accountTo = Account.get(accountToID)
    accountFrom.balance = accountFrom.balance - amount
    accountTo.balance = accountTo.balance + amount
  }
}

This code deducts money from one account (accountFrom.balance = accountFrom.balance – amount), and adds money to another account (accountTo.balance = accountTo.balance + amount). Imagine if something happened (an Exception) after deducting from the source account and the destination account was not updated. Money will be lost and not accounted for. For this type of codes, we want an “all or nothing” behavior. This concept is also called atomicity.

For the scenario given above, transaction management is required to achieve the desired behavior. The program will start a conversation with the database where any update operations are just written on a temporary space (like a scratch paper). The program will later on need to tell the database if it wants to make the changes final, or to scratch out all it did earlier.

Since Grails supports transactions, it automatically do these things to us when we declare a service to be transactional:

  • If all db operations are successful, reflect the changes to the database (this is also called commit)
  • If one db operation result in exception, return to the original state and forget/undo all the previous operations (this is also called rollback)

Transaction Declaration

Grails supports transaction management inside services. By default, all services are transactional. So these 3 declarations have the same effect.

class CountryService {
}
class CountryService {
    static transactional = true
}
@Transactional
class CountryService {
}

For readability, I suggest declaring the static transactional at the top of each service.

Note that not all applications needs transaction. Non sensitive programs like a blog software can survive without it. Transactions are usually required when dealing with sensitive data such as financial information.

Using transactions introduce overhead. Here is an example of how to disable it in a service:

class CountryService {
    static transactional = false
}

How To Force A Rollback

One of the most important thing to remember is what code to write to force Grails to rollback a current succession of operations. To do that, just raise a RuntimeException or a descendant of it. For example, this will rollback the operation accountFrom.balance = accountFrom.balance – amount

class AccountService {
  def transferFunds(long accountFromID, long accountToID, long amount) {
    Account accountFrom = Account.get(accountFromID)
    Account accountTo = Account.get(accountToID)
    accountFrom.balance = accountFrom.balance - amount
    throw new RuntimeException("testing only")
    accountTo.balance = accountTo.balance + amount
  }
}

But this code will not:

class AccountService {
  def transferFunds(long accountFromID, long accountToID, long amount) {
    Account accountFrom = Account.get(accountFromID)
    Account accountTo = Account.get(accountToID)
    accountFrom.balance = accountFrom.balance - amount
    throw new Exception("testing only")
    accountTo.balance = accountTo.balance + amount
  }
}

Meaning the line with subtraction will be committed, but the line with addition will not be reached.

Jonathan Tan

Jon is a professional software engineer currently working in financial trading systems. He has worked in a wide variety of projects that includes mobile games, 3D visualization, artificial intelligence, banking and trading systems. He loves to teach people and expresses his passion through blogging.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button