0

I'm not a programming savvy person, so please bear with me.

I've read blog entries and docs about command object. I've never used it and was wondering if I should. (I probably should...)

My project requires parsing, sorting, calculating, and saving results into database when users upload files.

So according to one of the blog entries I read and its corresponding github code,

1) SERVICE should receive file uploads, parse uploaded files (mainly docs and pdfs), sort parsed data using RegEx, and calculate data,

2) COMMAND OBJECT should call SERVICE, collect results and send results back to controller, and save results into the database,

3) CONTROLLER should receive request from VIEW, get results from COMMAND OBJECT, and send results back to VIEW.

Did I understand correctly?

Thanks.

monty_bean
  • 494
  • 5
  • 25

3 Answers3

2

I found this to be the best setup. Here is an example that I use on production:

Command Object (to carry data and ensure their validity):

@grails.validation.Validateable
    class SearchCommand implements Serializable {
    // search query
    String s
    // page
    Integer page

    static constraints = {
        s                   nullable: true
        page                nullable: true
    }

}

Controller (directs a request to a Service and then gets a response back from the Service and directs this response to a view):

class SomeController {

    //inject service
    def someService

    def search(SearchCommand cmd) {
        def result = someService.search(cmd)
        // can access result in .gsp as ${result} or in other forms
        render(view: "someView", model: [result: result])
    }
}

Service (handles business logic and grabs data from Domain(s)):

class SomeService {
    def search(SearchCommand cmd) {
        if(cmd.hasErrors()) {
            // errors found in cmd.errors
            return
        }
        // do some logic for example calc offset from cmd.page
        def result = Stuff.searchAll(cmd.s, offset, max)
        return result
    }
}

Domain (all database queries are handled here):

class Stuff {
    String name
    static constraints = {
        name               nullable: false, blank: false, size: 1..30
    }

    static searchAll(String searchQuery, int offset, int max) {
        return Stuff.executeQuery("select s.name from Stuff s where s.name = :searchQuery ", [searchQuery: searchQuery, offset: offset, max:max])
    }
}
Multithreader
  • 878
  • 6
  • 14
  • Thanks for the example. I understand better now. Also, the way you used the `domain class` is intriguing. – monty_bean Apr 14 '16 at 18:09
  • The way I use Domain is not standard. However, I see it as a natural fit since Domain already handles database querying by default (e.g. `.findBy()`, `.get(id)`). This way, you have complete separation for database logic and you free services from handling database logic and make them focus on business logic solely. – Multithreader Apr 17 '16 at 17:58
1

Yes, you understood it correctly except the one thing: command object shouldn't save the data to DB - let service to do that. The other advantage of command object is data binding and validation of data from the client. Read more about command objects here grails command object docs You can also find helpful information regarding your question in this article grails best practices

Taras Kohut
  • 2,505
  • 3
  • 18
  • 42
  • So unless I want to validate the result, I don't really need `command object` in my project, correct? If saving result to the database is done in `service`, I don't think I need to use `command object`. Thanks. – monty_bean Apr 14 '16 at 17:16
1

I guess not. Its not really related to whether the save is done in a service it should always attempt to carry out complex stuff and specifically db stuff in a service. so that is regardless. I tend to not use command object but have got hooked on helper classes aka beans that sit in src/main/groovy and do all of the validation and formatting. I just did a form and in it has feedback and reason.

Initially I thought I would get away with

def someAction(String feedback, String reason) { someService.doSomething(feedback,reason) }

But then I looked closed and my form was firstly a textarea then the selection objects were bytes so above was not working and to simply fix it without adding the complexity to my controller/service I did this:

packe some.package
import grails.validation.Validateable

class SomeBean implements Validateable {

    User user

    byte reason
    String feedback

    static constraints = {
        user(nullable: true)
        reason(nullable:true, inList:UsersRemoved.REASONS)
        feedback(nullable:true)
    }

    void setReason(String t) {
        reason=t as byte
    }

    void setFeedback(String t) {
        feedback=t?.trim()
    }
}

Now my controller

class SomeController {
  def userService
  def someService
 def doSomething(SomeBean bean){
        bean.user = userService.currentUser
        if (!bean.validate()) {
 flash.message=bean.errors.allErrors.collect{g.message([error : it])}
            render view: '/someTemplate', model: [instance: bean,template:'/some/template']
            return
        }

        someService.doSomeThing(bean)
}
}

Now my service

Class SomeService {
def doSomeThing(SomeBean bean) {

if (bean.user=='A') { 
.....
}


}

All of that validation would have still had to have been done somewhere, you say no validation but in a good model you should do validation and set things to be stored in proper structures to reduce overloading your db over time. difficult to explain but in short i am talking about your domain class objects and ensuring you are not setting up String something string somethingelse and then not even defining their lenghts etc. be strict and validate

if you have a text area this will be stored in the back end - so you will need to trim it like above - you will need to ensure the input does not exceed the max character of the actual db structure which if not defined will probably be 255

and by doing

  static constraints = {
            user(nullable: true)
            reason(min:1, max:255, nullable:true, inList:UsersRemoved.REASONS)

Has already invalidated it through the bean.validate() in the controller if the user exceeded somehow my front end checks and put in more than 255.

This stuff takes time be patient

Edited to finally add in that example byte - is one to be careful of -

When adding any String or what ever I have started to define the specific like this and in the case of byte if it is a boolean true false - fine if not then define it as a tinyint

static mapping = {
 //since there is more than 1 type in this case
 reason(sqlType:'tinyint(1)')
 feedback(sqlType:'varchar(1000)')
// name(sqlType:'varchar(70)')
}

If you then look at your tables created in the db you should find they have been created as per definition rather than standard 255 varchar which I think is the default for a declared String.

V H
  • 8,382
  • 2
  • 28
  • 48