4

I'm building a RESTful interface on a Grails 2.1.1 application. How should I implement search operations? I don't want to repeat huge amounts of code, which my current thinking would require.

The server structure is quite normal Grails-MVC: domain classes represent data, controllers offer the interface and services have the business logic. I use command objects for data binding in controllers but not on service methods. The client is a web UI. My goal is to have search URLs like this:

/cars/?q=generic+query+from+all+fields
/cars/?color=red&year=2011

(I'm aware of the debate on the RESTfulness of this kind of URLs with query strings: RESTful URL design for search. While I think this is the best model for my purpose, I'm open to alternatives if they make the API and the implementation better.)

As you can see from the code examples below my problem is with the second kind of URL, the field-specific search. In order to implement this kind of search operation for several domain classes with lots of fields my method signatures would explode.

There probably is a "Groovy way" to do this but I'm still a bit of a n00b in finer Groovy tricks :)

Domain:

class Car {
    String color
    int year
}

Controller:

class CarsController {
    def carService
    def list(ListCommand cmd) {
        def result
        if (cmd.q) {
            result = carService.search(cmd.q, cmd.max, cmd.offset, cmd.order, cmd.sort)
        }
        else {
            result = carService.search(cmd.color, cmd.year, cmd.max, cmd.offset, cmd.order, cmd.sort)
        }
        render result as JSON
    }

    class ListCommand {
        Integer max
        Integer offset
        String order
        String sort
        String q
        String color // I don't want this field in command
        int year // I don't want this field in command

        static constraints = {
            // all nullable
        }
    }
    // show(), save(), update(), delete() and their commands clipped
}

Service:

class CarService {
    List<Car> search(q, max=10, offset=0, order="asc", sort="id") {
        // ...
    }

    List<Car> search(color, year, max=10, offset=0, order="asc", sort="id") {
        // ...
    }
}

UrlMappings:

class UrlMappings {
    static mappings = {
        name restEntityList: "/$controller"(parseRequest: true) {
            action = [GET: "list", POST: "save"]
        }
        name restEntity: "/$controller/$id"(parseRequest: true) {
            action = [GET: "show", PUT: "update", POST: "update", DELETE: "delete"]
        }
    }
}
Community
  • 1
  • 1
apa64
  • 1,578
  • 1
  • 17
  • 26

1 Answers1

1

You can get all this parameters from params, like:

result = carService.search(params.color, params.year as Integer, cmd.max, cmd.offset, cmd.order, cmd.sort)

All values of params map are strings, so you should convert it to appropriate data structures in controller (and it's better to check that params.year is actual number)


Update

If you don't want to writer field names, you can pass it as a Map:

resutl = carService.search(params)

where

List<Car> search(Map params)
Igor Artamonov
  • 35,450
  • 10
  • 82
  • 113
  • I'd like to pass all fields for the search method without explicitly writing them in the method signature. I would have to change the service interface when I change the fields in the domain object if the service method signature has the fields. I could use the REST interface command object for the service interface too but it doesn't feel right. – apa64 Dec 11 '12 at 13:01
  • It seems that passing the params map to the service method is the easiest way. My service method signature is now: `List search(params=null, max=10, offset=0, order="asc", sort="id")` I remove the controller parameters I don't need for the search: `params?.remove("action")` Later I build a `findAllWhere()` query from the param members which I want to use in the search. – apa64 Jan 05 '13 at 11:08