Extra Grails Naming Conventions

The Grails framework is a covention over configuration framework. Through the years, I have created extra naming conventions.

Exisiting naming conventions

There are several conventions around naming already:

  • Controller is the suffix for Controllers. For example: BookController.groovy. They are in the folder grails-app/controllers.
  • Service is the suffix for services. For example: BookInventoryService. They are in the folder grails-app/services.
  • TagLib is the suffix for tag libraries. For example: BootstrapTagLib. They are in the folder grails-app/taglib
  • UrlMappings is the suffix for the URL mappings. You can have multiple files ending with UrlMappings and they are in the folder grails-app/controllers.
  • Spec for Spock specifications. They are in the folders src/test/groovy or src/integrationTest/groovy

Extra naming conventions suffixes

I use these suffixes:

  • Entity
  • GormService
  • Listener
  • ListenerService
  • JobService
  • Utils
  • Client
  • GebSpec
  • Page
  • Module
  • Create
  • Save
  • Update

Entity suffix for Domain classes

  • Domain classes are in the folder grails-app/domain. I like to suffix them with Entity and remove the Entity part from the table name using the mapping static property
class BookEntity {

    String title
    String about
    static mapping = {
        about type: 'text'
        table 'book'
    }
}

GormService suffix for GORM related services

I encapsulate GORM logic in a few services in the grails-app/services folder. I like to suffix them with GormService. I have used DataService in the past but lately I have settled with GormService. E.g.

@Service(BookEntity)
interface BookService {
    BookEntity save(String title, String about)
}

Suffix Listener for Kakfa, RabbmitMQ listeners

If I integrate a Grails application with Micronaut Kafka or Micronaut RabbitMQ I use the Listener suffix for those classes.

@KafkaListener
class AnalyticsListener {

    @Inject
    BookAnalyticsGormService bookAnalyticsGormService

    @Topic('analytics')
    void updateAnalytics(Map payload) {
        ...
    }
}

These listeners are in the folder src/main/groovy

Suffix Listener for classes subscribing to events

I suffix with ListenerService services containing methods annotated with @Subscriber.

class BookSavedListenerService {
    @Transactional
    @Subscriber 
    void saveBook(Book book) {
    ...

Also, classes extending AbstractPersistenceEventListener such as the UserPasswordEncoderListener which you get when you execute s2-quickstart with Grails Spring Security Core Plugin

I use the suffix ListenerService for classes in grails-app/services.

I use the suffix Listener for classes in src/main/groovy.

Suffix JobService for Jobs

I use the suffix JobService for classes in grails-app/services with methods annotated with org.springframework.scheduling.annotation.Scheduled:

@Slf4j
@CompileStatic
class DailyEmailJobService  {

    static lazyInit = false 

    EmailService emailService 

    @Scheduled(cron = "0 30 4 1/1 * ?") 
    void execute() {
    ...
}

Suffix Utils for utility classes.

I use the suffix Utils for classes in grails-app/utils. For example EAN13Utils.groovy. Utils classe are mostly final classes with static methods.

Sufix Client for HTTP Clients.

I use the suffix Client for declarative Micronaut HTTP Client in src/main/groovy.

package example.grails

import io.micronaut.http.annotation.Get
import io.micronaut.http.client.annotation.Client

@Client(id = "itunes") 
interface ItunesClient {

    @Get("/search?limit=25&media=music&entity=album&term={term}") 
    SearchResult search(String term)

Suffix GebSpec for Geb integration tests

I use the suffix GebSpec for Grails Integration tests which use Geb. They are in the folder src/integrationTest/groovy. For example:

@Integration
class ContactPageGebSpec extends GebSpec {

    void "go to contacts index page"() {
        when:
        to(ContactPage)

        then:
        at(ContactPage)
}

Suffix Page for Geb Pages

I use the suffix Page for Geb Page Objects. They are in the folder src/integrationTest/groovy. For example:

class ContactPage extends Page {
    static url = '/'
    static at = {
        $('div#container-contact')
    }

    static content = {
        navbar { $('#contact-navbar').module(AddContactModule) }
    }

    void addContact() {
        navbar.add()
    }
}

Suffix Module for Geb Modules

I use the suffix Module for Geb Modules. They are in the folder src/integrationTest/groovy. For example:

import geb.Module

class AddContactModule extends Module {
    static content = {
        addContactLink(to: AddContactPage) { $('a', text: contains('Add Contact')) }
    }

    void add() {
        addContactLink.click()
    }
}

Suffix Create, Update and Save for POGOs used in the create, update and save actions

I like to use a different POGO for the create action and the save action.

The Create POGO normally allows anything to be null and it is the object that the controller passes to the view (e.g. the GSP) as the model.

import grails.compiler.GrailsCompileStatic
import grails.validation.Validateable

@GrailsCompileStatic
class ContactCreate implements Validateable {
    String name
    String nameInvalid

    static constraints = {
        name nullable: true
        nameInvalid nullable: true
    }
}

The Save POGO contains the constraints which are validated before passing the object into the service layer:

import grails.compiler.GrailsCompileStatic
import grails.validation.Validateable

@GrailsCompileStatic
class ContactSave implements Validateable {
    String name

    static constraints = {
        name blank: false
    }
}

This is an example of how I use them in a controller:

class ContactController {
    ...
    def create() {
        [contact: new ContactCreate()]
    }

    def save(ContactSave contactSave) {
        if (contactSave.hasErrors()) {
        String invalid = errorMessage(contactSave, "name").orElse(null)
        ContactCreate contactCreate = new ContactCreate(name: contactSave.name,
                                                        nameInvalid: invalid)
            render view: 'create', model: [contact: contactCreate]
            return
        }
        String id = contactService.save(contactSave)
        redirect(action: 'show', id: id)
    }
}

Don't make me think

I became aware of the Book Don't make me think while attending a attending a usability class at the unversity. It had a big impact on me when I read it. I always felt the same about Grails naming conventions, they don't make you think and they keep me productive.

Tags: #grails #gorm