|
Hi,
I just came around to blog post showing a way to build up the controller (one with service in background). As I tested these solutions a bit I was wondering if there are more ways to do this? Can someone state if these are good solutions or where the pros and cons are? a) http://www.kromhouts.net/blog/grails/grails-service-and-controller-interface/ b) http://mrpaulwoods.wordpress.com/2011/01/23/a-pattern-to-simplify-grails-controllers/ in b) I tried to use this withDomainClassName but I get some errors. Is this still working in grails 1.3.7? Thanks for discussion best Sebastian -- Geschäftsführer (CEO) Haiku Internet GmbH Schlegelstraße 26 10115 Berlin Tel.: +49 30 868702020 Fax: +49 30 57708224 Mobil: +49 179 7923088 E-Mail: [hidden email] http://www.thechicken.com http://www.facebook.com/pages/The-Chicken/162425640445977 http://twitter.com/the_chicken_com Handelsregister: HRB 126354 B Amtsgericht Berlin-Charlottenburg Geschäftsführung: Adrian Haß, Sebastian Kurt --------------------------------------------------------------------- To unsubscribe from this list, please visit: http://xircles.codehaus.org/manage_email |
|
Hi Sebastian,
IMHO designing the controller and service layer is a bit of a preference question. For me, the first blog post is describing an anti pattern, overusing the service layer for trivial data fetching logic and thereby bloating the service layer (not to speak of the loss of object orientation). True, you should not leave too much business logic specific code in the controller, but why not put it in the domain class? I think its best kept there, since it belongs to a specific domain (in most cases). It is a (mainly jee) myth, that you always need a service layer for ALL kinds of business logic. Evans' "Domain driven design" shed some light on this topic for me. Also a good read is Fowlers anemic domain model anti pattern: http://martinfowler.com/bliki/AnemicDomainModel.html. I really like the second blog post. I think I will try this in my next project :-) Ok, leaving theoretical fuss behind, the question on how to move some of the controller logic into domain classes is still unanswered. Josh (basejump) was getting tired of having too much repetition in his service layer and came up with a great plugin (using a well known pattern, the data access object): https://github.com/basejump/grails-dao. Why not give this a try... I did a little plugin for dealing with concurrent updates: https://github.com/drosowski/grails-concurrent-update. I ususally keep all kinds of trivial data fetching logic inside the domain classes (perhaps as static methods) and I have a service layer for all data manipulation concerns. The controller is just for gathering data for the views and navigation purposes. Cheers, Daniel
Agrenon GmbH
www.agrenon.com |
|
The second blog is really nice thanks for sharing. The first one which put all the params into the service is really bad code because your methods are not really well defined. Its always better to use Command Objects for any kind of data-binding whether its domain related or not. Data binding is not just limited to domain classes you can use it binding the params for example the I always use to do the pagination by creating the Command object.
Grails itself has very nice support of command object and data-binding we just need to leverage this feature, your controller will automatically become the dump routers :) On Thu, Aug 4, 2011 at 3:00 PM, drosowsk <[hidden email]> wrote: Hi Sebastian, -- Regards ##Uday Pratap Singh## Intelligrape software (P)Ltd. |
|
Hi,
thanks for discussing. I read your lines, write a first answer [1], thought about it, find it was "wrong" and now I am convinced there is a way working with Command Objects does make sense. :) But I am not sure what the pattern is. What qualifies a constraint to stay in Command Object and not in Domain Class? thanks Sebastian [1] Command Objects are nice, but they do have some IMHO cons: * you have to declare constraints already given in the domain again * if the new/updated object does not validate with command objects it is more difficult to display what is wrong. lets compare two examples [A] and [B] in a controller. With [A] if there is an error in the given data, it will render the form again and mark the property that does not validate. [B] must have [A] def update = { Foo foo = Foo.get(params.id) bindData(foo, params) if (!foo.save()) { render "/foo/edit", model:[foo:foo] } ... } [B] def update = { FooCO fooCO -> Foo foo = Foo.get(params.id) bindData(foo, fooCO) if (!foo.save()) { render "/foo/edit", model:[foo:foo] } ... } Am 04.08.2011 19:35, schrieb Uday Pratap Singh: > The second blog is really nice thanks for sharing. The first one which > put all the params into the service is really bad code because your > methods are not really well defined. Its always better to use Command > Objects for any kind of data-binding whether its domain related or not. > Data binding is not just limited to domain classes you can use it > binding the params for example the I always use to do the pagination by > creating the Command object. > > Grails itself has very nice support of command object and data-binding > we just need to leverage this feature, your controller will > automatically become the dump routers :) > > On Thu, Aug 4, 2011 at 3:00 PM, drosowsk <[hidden email] > <mailto:[hidden email]>> wrote: > > Hi Sebastian, > > IMHO designing the controller and service layer is a bit of a preference > question. For me, the first blog post is describing an anti pattern, > overusing the service layer for trivial data fetching logic and thereby > bloating the service layer (not to speak of the loss of object > orientation). > True, you should not leave too much business logic specific code in the > controller, but why not put it in the domain class? I think its best > kept > there, since it belongs to a specific domain (in most cases). It is a > (mainly jee) myth, that you always need a service layer for ALL kinds of > business logic. Evans' "Domain driven design" shed some light on > this topic > for me. Also a good read is Fowlers anemic domain model anti pattern: > http://martinfowler.com/bliki/AnemicDomainModel.html. > > I really like the second blog post. I think I will try this in my next > project :-) > > Ok, leaving theoretical fuss behind, the question on how to move > some of the > controller logic into domain classes is still unanswered. Josh > (basejump) > was getting tired of having too much repetition in his service layer and > came up with a great plugin (using a well known pattern, the data access > object): https://github.com/basejump/grails-dao. Why not give this a > try... > I did a little plugin for dealing with concurrent updates: > https://github.com/drosowski/grails-concurrent-update. > > I ususally keep all kinds of trivial data fetching logic inside the > domain > classes (perhaps as static methods) and I have a service layer for > all data > manipulation concerns. The controller is just for gathering data for the > views and navigation purposes. > > Cheers, > Daniel > > ----- > Agrenon GmbH > www.agrenon.com <http://www.agrenon.com> > -- > View this message in context: > http://grails.1312388.n4.nabble.com/controller-pattern-best-practice-tp3717900p3718112.html > Sent from the Grails - user mailing list archive at Nabble.com. > > --------------------------------------------------------------------- > To unsubscribe from this list, please visit: > > http://xircles.codehaus.org/manage_email > > > > > > -- > Regards > ##Uday Pratap Singh## > Intelligrape software (P)Ltd. -- Geschäftsführer (CEO) Shopotainment by Haiku Internet GmbH Schlegelstraße 26 10115 Berlin Tel.: +49 30 868702020 Fax: +49 30 57708224 Mobil: +49 179 7923088 E-Mail: [hidden email] http://www.shopotainment.de http://facebook.com/shopotainment http://twitter.com/shopotainment Handelsregister: HRB 126354 B Amtsgericht Berlin-Charlottenburg Geschäftsführung: Adrian Haß, Sebastian Kurt --------------------------------------------------------------------- To unsubscribe from this list, please visit: http://xircles.codehaus.org/manage_email |
|
Hi,
Well, being two different concepts (CO not persisted, domain classes persisted) you should first make up your mind about that. If you need the data persisted, use a domain class (and naturally all constraints stay in this domain class). If you need an object solely for transfering and validating user input but don't need the user input persisted (e.g. a searchrequest), use a CO. In this case all the validation logic stays in the CO. Cheers, Daniel
Agrenon GmbH
www.agrenon.com |
|
http://mrpaulwoods.wordpress.com/2011/01/23/a-pattern-to-simplify-grails-controllers/ creates one of those "why didn't I think of that?" moments. Definitely should be abstracted into a plugin if not core.
John 2011/8/9 drosowsk <[hidden email]> Hi, |
|
Absolutely, excellent thread, certainly cleans up the crud so to speak, and dovetails nicely with REST.
I broke out into an abstract base class (in 2.0 no need for controller base class to be a controller itself), so given this src/groovy class: abstract class Crud { Class domain Integer entityID // usually sequence ID, can def if you use assigned string keys String pkey // id, orderID, etc. abstract def setup() Crud() { setup() } def self(Closure c) { def entity = domain.get(entityID) if(!entity) { i18n this.pkey, 'not found', true withFormat { html { redirect action:"list" } json { response.status = 404 } xml { response.status = 404 } } } else { entity.properties = params return c.call(entity) } } def withRest(LinkedHashMap<String, Object> entityMap) { entityMap?.each{ name, entity-> withFormat { html { return ["$name":entity] } json { render entity as JSON } xml { render entity as XML } } } } } You can do this in your crud enabled controllers with 2 lines (extends and setup()): class NodeController extends Crud { def setup() { domain = Node } // or whatever domain you'd like to use def show() { self({ Node entity-> withRest node:entity }) // parens omitted if you prefer } def create() { withRest node:new Node(params) } def update() { self({ Node entity-> if(!entity.hasErrors() && entity.save(flush:true)) { withFormat { i18n pkey, 'updated' html { redirect action:"show", id:entity."${pkey}" } json { response.status = 200; render entity as JSON } xml { response.status = 200; render entity as XML } } } else { withFormat { i18n pkey, 'not updated' html { render view:"edit", model:[node: entity] } json { response.status = 409; render entity.errors as JSON } xml { response.status = 409; render entity.errors as XML } } } }) } } |
|
Wow! this looks as "conventional" as grails is coding by convention.
It probably saves a lot of unneccessary we write it again and agian lines of code. If grails would be developed via github you should commit this and add a pull request! thanks Sebastian 2011/8/19 virtualeyes <[hidden email]>: > Absolutely, excellent thread, certainly cleans up the crud so to speak, and > dovetails nicely with REST. > > I broke out into an abstract base class (in 2.0 no need for controller base > class to be a controller itself), so given this src/groovy class: > > abstract class Crud { > > Class domain > Integer entityID // usually sequence ID, can def if you use > assigned string keys > String pkey // id, orderID, etc. > > abstract def setup() > Crud() { > setup() > } > > def self(Closure c) { > def entity = domain.get(entityID) > if(!entity) { > i18n this.pkey, 'not found', true > withFormat { > html { redirect action:"list" } > json { response.status = 404 } > xml { response.status = 404 } > } > } > else { > entity.properties = params > return c.call(entity) > } > } > > def withRest(LinkedHashMap<String, Object> entityMap) { > entityMap?.each{ name, entity-> > withFormat { > html { return ["$name":entity] } > json { render entity as JSON } > xml { render entity as XML } > } > } > } > } > > You can do this in your crud enabled controllers with 2 lines (extends and > setup()): > > class NodeController extends Crud { > > def setup() { domain = Node } // or whatever domain you'd like to use > > def show() { > self({ Node entity-> withRest node:entity }) // parens omitted if you > prefer > } > > def create() { > withRest node:new Node(params) > } > > def update() { > self({ Node entity-> > if(!entity.hasErrors() && entity.save(flush:true)) { > withFormat { > i18n pkey, 'updated' > html { redirect action:"show", id:entity."${pkey}" } > json { response.status = 200; render entity as JSON } > xml { response.status = 200; render entity as XML } > } > } > else { > withFormat { > i18n pkey, 'not updated' > html { render view:"edit", model:[node: entity] } > json { response.status = 409; render entity.errors as JSON } > xml { response.status = 409; render entity.errors as XML } > } > } > }) > } > } > > -- > View this message in context: http://grails.1312388.n4.nabble.com/controller-pattern-best-practice-tp3717900p3754977.html > Sent from the Grails - user mailing list archive at Nabble.com. > > --------------------------------------------------------------------- > To unsubscribe from this list, please visit: > > http://xircles.codehaus.org/manage_email > > > -- Geschäftsführer (CEO) Haiku Internet GmbH Schlegelstraße 26 10115 Berlin Tel.: +49 30 868702020 Fax: +49 30 57708224 Mobil: +49 179 7923088 E-Mail: [hidden email] http://www.thechicken.com http://www.facebook.com/pages/The-Chicken/162425640445977 http://twitter.com/the_chicken_com Handelsregister: HRB 126354 B Amtsgericht Berlin-Charlottenburg Geschäftsführung: Adrian Haß, Sebastian Kurt --------------------------------------------------------------------- To unsubscribe from this list, please visit: http://xircles.codehaus.org/manage_email |
|
Thanks Skurt, but proceed with caution, my background is LAMP stack ;-)
You'll probably want to change a couple of things to suit your needs (for example, using a beforeInterceptor {} instead of abstract class' ctor in order to access controller scoped vars like controllerName), not to mention implementing some flavor of the i18n method: def i18n(String key, String msgCode = 'default.not.found.message', Boolean renderMsg = false) { // convert abbreviated code like 'updated' or 'not found' to 'default.updated.message' if(msgCode.split('\\.').size() == 1) { msgCode = { def(a,b,c) = ['default',null,'message'] b = msgCode.contains('not ')? msgCode.split(' ').join('.'): msgCode [a,b,c].join('.') }() } Map msgArgs = [ code: msgCode, args: [message(code:"${entityName.lower()}.label", default:"$entityName"), entityID] ] withFormat { html { flash.message = message(msgArgs) } json { if(renderMsg) render message(msgArgs) } xml { if(renderMsg) render message(msgArgs) } } } Would be nice if Groovy supported named args a la Python -- so boolean renderMsg arg could be called like: i18n pkey, 'not found', renderMsg:true (instead of just "true" which reveals little about the arg's purpose) |
|
On 19/08/2011 19:37, virtualeyes wrote:
> Would be nice if Groovy supported named args a la Python -- so boolean > renderMsg arg could be called like: > i18n pkey, 'not found', renderMsg:true (instead of just "true" which reveals > little about the arg's purpose) The Groovy approach is to make the method take a Map parameter i18n(key:pkey, code:'not found', renderMessage:true) def i18n(Map args) { String key = args.key?.toString() String msgCode = args.code ?: 'default.not.found.message' def renderMsg = args.renderMessage ?: false //... } Ian -- Ian Roberts | Department of Computer Science [hidden email] | University of Sheffield, UK --------------------------------------------------------------------- To unsubscribe from this list, please visit: http://xircles.codehaus.org/manage_email |
|
Ian, right, interesting.
Of course, now looking at method signature you know nothing about the intent of Map args, not to mention being unable to enforce args.foo Type. On the other hand, method calls are explicit, no mystery at all as to what the intent is. You can mix and match named & non-named method params, but I believe the named param will always be called first, so your first arg and any other to-be-named arg must be defined before non-named args in method signature. I'd like my cake and eat it too, however, so typed method signature with option to use named params where desired would be ideal -- perhaps in Groovy 2.0. |
| Powered by Nabble | Edit this page |
