Services and Dependency Injection

Harry · 13 Sep 2026 · 2 views

Why Services

Services are the business logic layer of a Grails application. The golden rule:

Controllers should be thin. Services should be rich.

Creating a Service

class UserService {
    def createUser(Map params) {
        new User(params).save(failOnError: true)
    }
}

Services live in grails-app/services, are singleton Spring beans, and their public methods are transactional by default.

Transactional Behavior

All public methods run inside a transaction. A runtime exception rolls everything back:

class UserService {
    def registerUser(Map params) {
        def user = new User(params)
        user.save(failOnError: true)
        assignRole(user, Role.findByAuthority("ROLE_USER"))
        user
    }
}

Disable transactions when not needed:

static transactional = false

Dependency Injection

Spring wires everything; no annotations or XML required:

class UserController {
    UserService userService
}

You can inject services, domain classes, configuration values, and other Spring beans.

Read-Only Transactions

Marking read-only operations helps the database optimize:

@Transactional(readOnly = true)
def listUsers() {
    User.list()
}

Propagation and Isolation

Advanced flows like audit logs may need independent transactions:

@Transactional(propagation = Propagation.REQUIRES_NEW)
def logAudit() { ... }

Error Handling

Throw exceptions for business failures; they trigger automatic rollback:

if (!user.save()) {
    throw new IllegalStateException("User creation failed")
}

Service Design Rules

  • Keep methods small and focused on one responsibility.
  • Avoid circular dependencies between services.
  • Keep services stateless; never store request data in fields.
  • Service-to-service calls are fine and common.

Testing Services

Write Spock specs for business logic:

class UserServiceSpec extends Specification {
    def userService

    void "test user creation"() {
        when:
        userService.createUser([username: "test"])

        then:
        User.count() == 1
    }
}

Common Mistakes

  • Business logic leaking into controllers.
  • Long transactional methods holding locks too long.
  • Circular dependencies.
  • Stateful services that break under concurrency.

Key Points

  • Services are transactional, singleton Spring beans.
  • DI is automatic - just declare the property.
  • Throw exceptions for failures and let them roll back.
  • Stateless, focused services scale and test best.
Share this post:

Comments (0)

Please login or register to comment.