The dilemma
I've been on a couple of larger Grails projects in the past year and half and I'm witnessing a disturbing phenomenon. The allocation of business logic responsibility across the abstractions that Grails provides today is causing considerable pain. Grails provides controllers, services and domain objects where business logic can reside. I'll contend in this blog entry that these abstraction categories work well for small- to medium-sized Grails projects, but thing quickly start to unravel once your application gets to be large.
Controllers
I see a lot of business logic code in controllers these days. Controllers shouldn't contain any business logic whatsoever. The controller's responsibility is to handle the web request and response. Anything else should be delegated to a collaborator. Don't do it!
Domain objects
The next logical place to put business logic is in the domain class. Allocating responsibility here works to a point, but you will quickly encounter issues when you need business logic that resides in services. I'm not a fan of injecting Grails services into domain classes. This situation quickly spirals out of control and makes unit testing very difficult to perform. For simple per-domain business logic, free free to allocate to the domain class. Anything more, and it belongs in a service (or something else, which we'll discuss in a bit).
Services
So most business logic seems to end up in Grails services these days. That's what the creators of Grails intended. I have no qualms about that. The beef I have with services is that a method is a crappy abstraction for representing business logic in the system. On larger Grails projects, the service approach seems to break down, as services seem to take on more and more responsibility, making them more difficult to test. I'm also witnessing a lot of code duplication; in it's current incarnation, there is no delineation of public API services versus private services which the public API services compose larger sets of business logic with. What we end up with is large, hard-to-test service methods that collaborate with too many dependencies and do too much.
The desire
I want an abstraction in Grails that promotes proper factoring of business logic into unit-testable abstractions. These abstractions can be composed into larger abstractions to provide the necessary logic to fulfill the requirements of the system. The
chain of responsibility design pattern may offer some value here. Individual commands that have a singular responsibility can be created, unit tested, and finally composed into "chains" of commands that provide the necessary functionality of the system. The command chains can be integration tested to ensure that the individual commands composition provides the functionality required by the customer/business. When new functionality is needed, a new command chain is created, reusing existing commands where appropriate and creating new commands where functionality does not exist. Spring Batch has a similar concept that is core to its design.
Conclusion
I hope to blog a bit more around this in the coming weeks. I really like Grails and would love to see its usage increase in the coming months and years. I think it has some really cool features that allow you to get up and running very quickly. The plugin system alone is a huge advantage to using Grails, because features like a Chain of Responsibility executor can easily be added to the core Grails system.
I think this is a common problem with web MVC frameworks in general. Rails has similar issues. I think the best solution is to think of the web tier as just a front end and offload all the 'hard' work to other applications entirely. Then the services layer would just be a facade around 3rd party services which could be REST, SOAP, or just using a jar or plugin. Thoughts?
ReplyDeleteKyle,
ReplyDeleteThanks for your thoughts on this. I have seen application partitioning like what you describe. I'm more interested in this blog posting around how do we build the business logic of the system. I'm becoming acutely aware that services don't always make the best abstractions for implementing business logic; there are other design patterns that would help alleviate the crushing force of "big, bloated service methods". I agree with you that partitioning the web tier from the services tier can also be done. I could see this happening when you have a suite of applications and you want reuse of the services layer. This is especially prevalent in mix platform environments (e.g. .NET front end consuming RESTful web services provided by a Grails backend).
-- chris --
This comment has been removed by the author.
ReplyDeleteThis comment has been removed by the author.
ReplyDeleteI think this a problem in all applications I have ever worked on. How to best put together the logic of the system so that it is composed in a way that it can be easily found and used. I think the problem you are seeing is that in Grails as well as Spring there is no thought about the granularity of the services and there is little up front thought on how to put them together and layer them. What I have typically done is to use the Domain model as the contract between services and then try to have Coarse grained services that operate on these items that aggregate and delegate to more fine grained services. I think your ideas about utilizing alternate patterns can work, but I think it is not quite that simple.
ReplyDeleteOne thing that Kyles approach above would force is a better overall design of your "Public" API which could help a bit.
Joe
As I was reading the post I found a common theme "code smell" that pertains to these MVC based applications. Could'nt agree more with Joe & Kyle. As always it was much harder to write test after design intent was expressed. Balanced pairing programming may also help alleviate the effects of code smell.
ReplyDeleteI inherited a grails app with a bloated service layer; To me it seems the conventions of grails made the java programers forget Object Oriented Principals / Design.
ReplyDeleteServices fall in that area of the framework being a little tiny bit too helpful. My thought for refactoring the app I inherited is to create OO architecture (POJO or POGO) to handle anything complex (e.g. use extract method to move complex bloated logic out of the service layer) and use the service layer as a facade for controllers to access the OO stuff.
In my experience rails has the same effect on developers where they forgot all that they had learned before using it. It's a MVC framework, but it doesn't restrict you from using classes, inheritance, encapsulation, etc to build nice OO layers.
Funny to have to remind people of something they were so good at before learning about Grails / Rails. :)
Thank you for posting this! Being a developer for over 20 years enforced a lot of deeper OO best practices in me. As a new convert to Grails I've been examining code from different sources and seeing the very same pattern of business logic in Controllers and not in Domain objects. Plus, the notes from you and the comments about lacking general OO principles is notably absent. Thanks for the post - it is the first one I've found to point this out.
ReplyDeleteNice post. Keep sharing. Thanks for sharing.
ReplyDeleteVisit Best Astrologer in Rajanna Sircilla
Amazing blog post,Thanks for sharing this.
ReplyDeletelook here
Best Sofa Repair Services in Bannerghatta Road