Pages

Tuesday, February 11, 2014

Copy as HTML plugin in RubyMine

Just a quick note: the Copy as HTML plugin does not show up in the RubyMine 6 plugins listing. But if you download it from here, and install it from disk through the Plugins preferences page, it will work flawlessly in RubyMine. Not sure why it does get listed, but it's a great plugin nonetheless.

Using Teacup to style individual UIView components

I've been doing a bunch of RubyMotion development lately and I'm continually amazed at the power of this framework. One of the many great tools available to RubyMotion developers is Teacup, a UI view layout and styling domain-specific library (DSL). I think DSLs are one of the big advantages that RubyMotion has over traditional native iOS development using Objective-C and Xcode. More information on Teacup here. There's tons of documentation around using Teacup with UIViewControllers, but scant documentation on using Teacup with custom UIView components. There are times that you cannot style a UIView or subclass when the view is initially rendered. Examples include table cells and table headers. Well, don't fear, because you can always mix-in the Teacup layout behavior into any old Ruby class and get that functionality. Below is a table view header helper class that creates new UIView objects with a UILabel subview. Notice how I mix in the Teacup::Layout behavior into the helper and then I have access to all the Teacup stylesheet and layout functionality.

 1 class TableViewHeaderHelper
 2     include Teacup::Layout
 3 
 4     stylesheet :table_view_header
 5 
 6     def create(frame, title)
 7         view = UIView.alloc.initWithFrame(frame)
 8         view.stylename = :root
 9         layout(view) do
10             label = subview(UILabel, :label)
11             label.text = title
12         end
13         view
14     end
15 
16 end

In this above example, the factory method takes a frame and a title for the header. I create the root UIView and then pass that to the Teacup layout to do the rest of the composite magic. Since the title is dynamic, I get a reference to the created UILabel and set the text of the label to the title string passed into the factory method.

Next up is the Teacup stylesheet. This sits in app/styles/styles.rb in my RubyMotion application. I defined a couple of global UIColor objects and then define the table_view_header style for use in the TableViewHeaderHelper class previously shown. The rest of this is standard Teacup functionality, so I won't repeat what they have already documented.

 1 sectionBackgroundColor = UIColor.colorWithRed(221/255.0, 
 2                                               green: 238/255.0, 
 3                                               blue: 249/255.0, 
 4                                               alpha: 1.0)
 5 headerTextColor = UIColor.colorWithRed(50/255.0, 
 6                                        green: 50/255.0, 
 7                                        blue: 50/255.0, 
 8                                        alpha: 1.0)
 9 
10 Teacup::Stylesheet.new :table_view_header do
11 
12     style :root,
13           backgroundColor: sectionBackgroundColor
14 
15     style :label,
16           top: 1, 
17           left: 15, 
18           width: 500, 
19           height: 40,
20           font: :bold.uifont(20),
21           textColor: headerTextColor
22 end

Finally are the UITableViewDelegate protocol methods that I implemented to get a custom header for my UITableView. Note that the frame height used for constructing the UIView in the TableViewHeaderHelper is ignored, and the UITableViewDelegate heightForHeaderInSection:section method is used to determine the header height instead. Kind of strange, but it works.

 1     def tableView(tableView, viewForHeaderInSection: section)
 2         frame = CGRectMake(0, 0, tableView.frame.size.width, 1)
 3         title = "#{@league.name}: My Stations by date"
 4         TableViewHeaderHelper.new.create(frame,
 5                                          title)
 6     end
 7 
 8     def tableView(tableView, heightForHeaderInSection:section)
 9         50
10     end

Thursday, January 02, 2014

Google App Engine\Python: Using the task queue with a reverse proxy or SSH tunnel

This is more a note for myself. When using forward, a SSH tunnel Ruby gem from the folks at forwardhq.com, Google App Engine\Python devserver will barf trying to resolve the Host header when dispatching tasks on the task queue. In my case, my tunnel hostname was coming through in the Host header, causing the KeyError in _port_registry.get(port) invocation.

dispatcher.py


  def _resolve_target(self, hostname, path):
    if self._port == 80:
      default_address = self.host
    else:
      default_address = '%s:%s' % (self.host, self._port)
    if not hostname or hostname == default_address:
      return self._module_for_request(path), None

    default_address_offset = hostname.find(default_address)
    if default_address_offset > 0:
      prefix = hostname[:default_address_offset - 1]
      # The prefix should be 'module', but might be 'instance.version.module',
      # 'version.module', or 'instance.module'. These alternatives work in
      # production, but devappserver2 doesn't support running multiple versions
      # of the same module. All we can really do is route to the default
      # version of the specified module.
      if '.' in prefix:
        logging.warning('Ignoring instance/version in %s; multiple versions '
                        'are not supported in devappserver.', prefix)
      module_name = prefix.split('.')[-1]
      return self._get_module_with_soft_routing(module_name, None), None

    else:
      if ':' in hostname:
        port = int(hostname.split(':', 1)[1])
      else:
        port = 80
      try:
        _module, inst = self._port_registry.get(port)
      except KeyError:
        raise request_info.ModuleDoesNotExistError(hostname)
        _module, inst = None, None
    if not _module:
      _module = self._module_for_request(path)
    return _module, inst

The line in red is the line that is barfing. Comment out the raise error line and set _module and inst to None, allowing execution to continue and the next line will test if _module hasn't been set and will go ahead and resolve it. Found the temporary fix from https://github.com/dylanvee/homebrew-gae_sdk. Hopefully this is something the GAE people can fix in upcoming versions of GAE\Python SDK.

Friday, December 20, 2013

Rubymotion: Controlling Info.plist generation with environment variables

I've been doing a fair amount of Rubymotion development these days and one trick that I'm using in my local development is to control the generation of the Info.plist via environment variables and some Ruby coding in the Rakefile. I can't stop raving about the excellent tools and development cadence that Rubymotion affords the iOS developer. I really like that I'm working out of Rubymine and not Xcode and using standard Ruby tools like rake and Bundler.

Like I said, using rake, you have the opportunity to change your Info.plist because it's generated through the rake process. I have a login view that needs a username and password entered into the text fields. I've rigged it up that these are prepopulated when working in dev, using environment variables and adding some logic to my Rakefile and the view controller. First up are the environment variables:

export DEV_MODE="true"
export DEV_USERNAME="chris.bartling@mycompany.net"
export DEV_PASSWORD="fahj2734hfjg86776dg$48df676"

Nothing earth shattering here. Normal environment variable assignments. Next up is the Rakefile changes:

Motion::Project::App.setup do |app|
    
	...    

    if ENV['DEV_MODE']
        puts '==========================================='
        puts '===> Using DEV_MODE Info.plist values <===='
        puts '==========================================='

        app.info_plist['DEV_USERNAME'] = ENV['DEV_USERNAME']
        app.info_plist['DEV_PASSWORD'] = ENV['DEV_PASSWORD']
    end

    app.pods do
        ....
    end
end

Again, pretty simple Ruby stuff here. If the DEV_HOME environment variable is set, add the username and password to the Info.plist during generation. Now, in your app, you can reference these values in your viewDidLoad method of your view controller, prepopulating the UI elements in your views:

dev_username = NSBundle.mainBundle.objectForInfoDictionaryKey('DEV_USERNAME')
dev_password = NSBundle.mainBundle.objectForInfoDictionaryKey('DEV_PASSWORD')
@email_field.text = dev_username || ''
@password_field.text = dev_password || ''

I probably sound like a broken record, but if you haven't used Rubymotion, definitely give it a try. Much different developer experience using Rubymotion tools vs. Apple's Xcode tooling.

Retrieving logs from your deployed Google App Engine Java application

If you need to see your application logs from your deployed Google App Engine (GAE) Java application, you can use the GAE appcfg.sh tool to do so. Issue the following command from your GAE app directory:

appcfg.sh --num_days=0 --severity=0 request_logs ./web ./logs/gae.log

where:

--num_days=0 will retrieve all of the logs available,
--severity=0 will retrieve DEBUG and above log levels,
./web is where the ./WEB-INF/appengine-web.xml descriptor file can be found, and
./logs/gae.log is the local log file to write records to.

There are other options available for this command. Execute appcfg.sh help request_logs to see more information on the options available for request logs command. This will dump all of your logs and they will not be truncated like they are in the Logs view of the GAE administration application.

Thursday, December 19, 2013

CoffeeScript Application Development by Ian Young

I had the opportunity to read CoffeeScript Application Development by Ian Young recently and thought I would put together a book review.

The author does a nice job describing why parentheses are required for executing CoffeeScript no-argument functions. This is an idiom that I have seen many developers trip over when first coming to CoffeeScript. CoffeeScript preserves JavaScript’s view of functions as first-class citizens. Parentheses are optional except when necessary to avoid ambiguity.

The author gives some nice examples of loop comprehensions, one of the snazzier features of CoffeeScript. Loop comprehensions come from Python and they make for a more readable way to iterate a list and selectively act on list elements which meet a certain criteria. I’m always looking for more examples of loop comprehensions in CoffeeScript and this book has some nice examples.

The CoffeeScript switch statement is explained thoroughly. This is a handy flow-control statement that works really well in its incarnation in CoffeeScript. There are numerous examples in the book where different usage scenarios are demonstrated. Very handy and welcomed.

I found the author’s treatment of classes and inheritance in CoffeeScript to be a nice, gentle introduction. The examples that are given in the book work well and the explanations that accompany the examples are clear and concise. It would have been nice to get an explanation of the boilerplate code that CoffeeScript generates for you when defining a class, but I guess that’s considered part of the magic of CoffeeScript. It isn’t until the discussion on inheritance that the author starts to poke his head under the hood to investigate the generated JavaScript. The inheritance discussion is extremely valuable and a big plus for this book. If you get this book for anything, it’s for this discussion. CoffeeScript is doing a whole bunch of interesting stuff when creating classes and implementing inheritance, and this is one of the first times that I have seen the generated JavaScript described line by line.

In typical fashion, the author introduces the fat arrow syntax in a gentle manner, clearly explaining the reasoning for such a feature. The author then gives a very good explanation why you should not overuse the fat arrow syntax in your CoffeeScript (hint, it’s due to memory usage). He also includes a very succinct definition and example around memoization in CoffeeScript. This is a feature that I have not had much exposure to, so it was great to see it described and used in an example.

IcedCoffeeScript is introduced in the chapter on Going Asynchronous. I have not used IcedCoffeeScript, so that was an interesting exploration into an extension to CoffeeScript for managing asynchronous invocations. Looks interesting.

The topic of debugging CoffeeScript is broached. This is an interesting subject, as I have seen a few developers really get frustrated with the mapping of generated JavaScript back to the original CoffeeScript. Luckily the author introduces source maps, which does this work for us. The author shows us how to set this feature up in Firefox and Chrome developer tools. Your mileage will vary on this feature, but it is an interesting tool for easing the inertia of moving to CoffeeScript. This discussion comes with a lot of screenshots that help you understand how the source maps feature can be used in the developer tools.

Overall I really liked this book and it’s a worthy addition to my other documentation on CoffeeScript. Link to the book

Tuesday, November 12, 2013

Debugging Before and After hooks in Cucumber

Had a heck of time trying to figure out why all my Cucumber steps were completing successfully, but my Cucumber scenario would fail. Seems that I had an issue with the web app that I was testing which would hit a REST endpoint asynchronously during the Cucumber scenario, causing an error. Cucumber seems to eat this normally, but running with the command line option of --format pretty dumps the backtrace of the exception. Blogging about this so I don't forget about it. Reference: https://groups.google.com/forum/#!topic/cukes/WTJDVWGQkTM

Monday, November 04, 2013

Python Imaging Library (PIL) on OS X Mavericks

Upgraded to Mavericks (10.9) over the weekend, not taking into account my current Python/Google App Engine development work. Thus, this Monday morning, I'm spending some quality time getting my Python environment back up and running. One of the things that I needed to re-install in this new environment is PIL or the Python Imaging Library. Unfortunately, doing a naïve pip install results in an error when looking for an X11 header file. This StackOverflow comment solved my problem, so I'm sharing with others who might get hung up on PIL on Mavericks. The pre-built PIL stuff doesn't seem to install on Mavericks, so the pip install seems to be the way to go for 10.9/Mavericks.

Monday, August 19, 2013

MySQL useCursorFetch property and Grails 2.2.4

Just hit a problem with using the useCursorFetch property in Grails 2.2.4. It may be present in earlier versions of Grails 2, but this was working up through Grails 1.3.9. I'm upgrading a Grails 1.3.9 application to Grails 2.2.4 and hit the following runtime exception when the JDBC URL has the useCursorFetch property set to true.


| Error 2013-08-19 09:05:50,365 [localhost-startStop-1] ERROR context.GrailsContextLoader  - Error initializing the application: java.lang.LinkageError: Illegal class file encountered. Try running with -Xverify:all in method executeBatchSerially
Message: java.lang.LinkageError: Illegal class file encountered. Try running with -Xverify:all in method executeBatchSerially
   Line | Method
->> 308 | evaluateEnvironmentSpecificBlock in grails.util.Environment
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
|   301 | executeForEnvironment            in     ''
|   277 | executeForCurrentEnvironment . . in     ''
|   303 | innerRun                         in java.util.concurrent.FutureTask$Sync
|   138 | run . . . . . . . . . . . . . .  in java.util.concurrent.FutureTask
|   895 | runTask                          in java.util.concurrent.ThreadPoolExecutor$Worker
|   918 | run . . . . . . . . . . . . . .  in     ''
^   680 | run                              in java.lang.Thread

Caused by LinkageError: Illegal class file encountered. Try running with -Xverify:all in method executeBatchSerially
->> 4566 | prepareStatement                 in com.mysql.jdbc.ConnectionImpl
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
|   4479 | prepareStatement                 in     ''
|   281 | prepareStatement . . . . . . . . in org.apache.commons.dbcp.DelegatingConnection
|   313 | prepareStatement                 in org.apache.commons.dbcp.PoolingDataSource$PoolGuardConnectionWrapper
|   111 | methodMissing . . . . . . . . .  in org.grails.datastore.gorm.GormStaticApi
|    56 | loadUsersAndRoles                in com.vocabra.eventlogging.services.DefaultDataLoaderService
|    22 | load . . . . . . . . . . . . . . in     ''
|   165 | doCall                           in GrailsMelodyGrailsPlugin$_closure4_closure15_closure16
|    18 | doCall . . . . . . . . . . . . . in BootStrap$_closure1
|   308 | evaluateEnvironmentSpecificBlock in grails.util.Environment
|   301 | executeForEnvironment . . . . .  in     ''
|   277 | executeForCurrentEnvironment     in     ''
|   303 | innerRun . . . . . . . . . . . . in java.util.concurrent.FutureTask$Sync
|   138 | run                              in java.util.concurrent.FutureTask
|   895 | runTask . . . . . . . . . . . .  in java.util.concurrent.ThreadPoolExecutor$Worker
|   918 | run                              in     ''
^   680 | run . . . . . . . . . . . . . .  in java.lang.Thread

Removing the property from the JDBC URL resolves the issue. Need to do more research into why this is happening.

Wednesday, July 31, 2013

When is a user story done?

I am beginning to hate the word "done". It means so many different things in software development and causes so much confusion that I am trying not to use the term, instead opting for statements which convey much more information and provide more transparency into our product development efforts.

In the past, I have been asked to figure out why two development groups under a common work area have radically different velocities and thus are being viewed much differently by management. The first group is chewing up stories, the other, not so much. Looking at the user stories of each group, differences between the two groups definitely exist. The "higher performing" group have simple stories with a few acceptance criteria. The acceptance criteria seem very high level; my gut feeling is that the acceptance criteria would be difficult to turn into actual acceptance tests. The "lower performing" group has more acceptance criteria which are more concrete--I can visualize actual acceptance tests with these criteria. Digging into the groups a bit more, it becomes apparent that the "higher performing" group's quality assurance effort is behind the development group in completion, but the group is considering stories "done" when the developers reach code complete. The "lower performing" group does not claim a story completed until it has been verified by quality assurance and the customer has signed off on the functionality (usually through some demonstration of the functionality).

So which group is really the high performer here? Saying a story is done when code complete is a misnomer and is likely building a false reality of where the group really is at. This "higher performing" group is setting themselves up for a big fall--there is no sense of quality assurance complete or customer complete.

Some in the agile community call this "done, done, done". I guess that's OK, but I think we need to be really careful with our terminology as we communicate within our development groups and outside of the development group to shareholders. I feel we should use specific terminology to describe where a story currently resides in the "done" spectrum. Daniel Gullo has a good article about this. He enumerates criteria for completion:

  • “Code complete”: Development completed, including accompanying unit tests (assumption here is that everyone is operating under test-driven development).
  • “Unit tested”: Unit tests completed. I don't really care for this designation, especially since my groups engage in test-driven development. I would call this Acceptance tested or something like that. Basically, demonstrate that we have satisfied the conditions for completion (aka acceptance criteria). Hopefully most of these acceptance tests are automated and have been written concurrently during the iteration by QA with the cooperation of the developers.
  • “Peer reviewed”: Developer code reviews completed. I like this concept when paired with feature branching and pull requests.
  • “QA complete”: QA testing, automated and exploratory, completed. I like having this as a separate stage-gate, allowing for exploratory testing by QA.
  • “Documented”: As needed. There's probably another blog post here around documentation and the agile process, but I will leave that alone for a while.

Now there is little ambiguity as to where a story stands when using this terminology. I may throw another level in there, customer complete, when the customer signs off on the newly developed feature. Thoughts? How do you communicate when a story is complete?

Sunday, June 09, 2013

Connecting DbVisualizer to Heroku PostgreSQL database

Just a quick note on how to connect DbVisualizer to a Heroku PostgreSQL database. The trick is to get the SSL stuff to work, as DbVisualizer needs to be told to use a SSL factory (via the sslfactory driver property). I used the 'org.postgresql.ssl.NonValidatingFactory' SSL factory. I set this property and the ssl property (to true) in the Properties tab of the connection information. The rest of the connection information you need to get from Heroku, in particular the Heroku database configuration. Go to your Heroku dashboard, drill into your app of interest and click on the Heroku Postgres link (mine was labelled "Heroku Postgres Dev"). This will take you to the herokupostgres setting for your application. Click on the bi-directional arrows icon (Connection Settings) and you can find your connection setting there. I used the JDBC properties and filled out the Server Info settings format. This format seems to work better in DbVisualizer. Also allows you to ping the database server.

Tuesday, April 23, 2013

Jasmine spying on jQuery selectors

Quick blog post about how to spy for jQuery selectors. Say I want to write something like the following:
var $element = $('div.my-element');
How would you write a Jasmine specification to drive this line of code in a Backbone.View function? Here it is:
it("find the element using a jQuery selector", function() {
   var spy = spyOn(jQuery.fn, 'find');
   this.view.doSomething();
   expect(spy).toHaveBeenCalledWith('div.my-element');
});
The reason you can do this is that $(selector, context) becomes $(context).find(selector). By default, selectors perform their searches within the DOM starting at the document root. However, an alternate context can be given for the search by using the optional second parameter to the $() function (from http://api.jquery.com/jQuery/#jQuery1). Had to write this down in a blog so I remember it again some day. Cheers!

Wednesday, February 20, 2013

Disabling hashListening in jQuery Mobile 1.2

Just a quick note. Spending a little time today integrating Backbone.js with jQuery Mobile. Both frameworks have routing solutions which do not work together. However, there is some simple configuration that you can use to turn off the jQuery Mobile routing in favor of Backbone.js routing:
$(document).bind("mobileinit", function () {
    $.mobile.ajaxEnabled = false;
    $.mobile.linkBindingEnabled = false;
    $.mobile.hashListeningEnabled = false;
    $.mobile.pushStateEnabled = false;
});
The trick to getting this to work is loading this bit of code before you load jQuery Mobile. More information at http://jquerymobile.com/demos/1.2.0/docs/api/globalconfig.html.

Friday, February 08, 2013

Disappearing USB ports on Mac Pro (Early 2009)

Just a quick note before bed. Recently I've been having issues with my Mac Pro not recognizing my HiFiMan headphone amp through a USB connection. I thought it might be the amp, but I tried it on my MacBook Pro and it recognized it immediately. Tonight, I noticed that my iPod would not connect iTunes on my Mac Pro. Searching around, I found an easy solution:

  1. Shut down your computer.
  2. Unplug the computer from power and wait about 15 seconds or so.
  3. Plug the computer power cord back into a power outlet.
  4. Restart your computer.


Voila! I have my USB ports back and my system is recognizing both the HiFiMan amp and my iPod.

Thursday, February 07, 2013

Effective use of the Rails has_and_belongs_to_many association

Just a note to myself and others using the Rails has_and_belongs_to_many association:
  • The naming of the association table is by alphabetical convention. For example, a many-to-many relationship between Assemblies and Parts models would result in an association table named assemblies_parts. The migration will look like:
    class CreateAssembliesPartsAssociationTable < ActiveRecord::Migration
    
        def self.up
            create_table :assemblies_parts, :id => false do |t|
                t.integer :assembly_id, :null => false
                t.integer :part_id, :null => false
            end
    
            add_foreign_key(:assemblies_parts, :assemblies)
            add_foreign_key(:assemblies_parts, :parts)
        end
    
        def self.down
            drop_table :assemblies_parts
        end
    
    end
    		
    Note that I am using the foreigner gem to implement foreign keys in my migrations.
  • Use this association type when you want a direct many-to-many mapping of models without any intervening association model.
  • The model mapping looks like the following:
    class Assembly < ActiveRecord::Base
    
        has_and_belongs_to_many :parts
    end
    
    and
    class Part < ActiveRecord::Base
    
        has_and_belongs_to_many :assemblies
    end
    
I just got caught by the alphabetically ordering of the association table name. Thought I would write something up about it.

Thursday, December 13, 2012

Pinning Rails 3.2 to a specific time zone

I've been wrestling with some issues of dates and times in Rails. Being that I'm still pretty new to Rails, I been soaking up knowledge about this platform and today was no different. ActiveRecord, by default, converts timestamps and dates to UTC and interacts with them that way. UTC is also the format that the values will be stored in the database. If this is not what you want, you can pin your Rails system to a specific time zone by setting the following two configuration options in config/application.rb:
    config.time_zone = 'Central Time (US & Canada)'
    config.active_record.default_timezone = :local
Hope this helps others that discover that the default behavior of UTC is not what is desired. Official documentation on these configuration items can be found at http://guides.rubyonrails.org/configuring.html#rails-general-configuration and http://guides.rubyonrails.org/configuring.html#configuring-active-record.

Thursday, September 13, 2012

Resolving connectivity issues with Verizon 4G LTE JetPack 4510L MiFi and your Apple devices

I've been dealing with the fact that my iPhone 4S and my first generation iPad could not connect to my Verizon 4G LTE JetPack 4510L MiFi hotspot. They did connect to it when I got this Mifi hotspot back in September 2011, but now they seem to flake out and ultimately never connect to it. Not sure if it was a MiFi issue or a device issue. I was getting to point of just returning the hotspot but thought a last ditch effort to resolve the issue may do the trick. Luckily, it seems to. Here's what I did.
  1. Make sure the firmware on the hotspot is up-to-date. Update if not.
  2. Changed the radio protocol to 802.11n. It was set to 802.11g.
  3. Changed the network key on the hotspot.
  4. Forget the network on the Apple device.
  5. Connect to other network, selecting your MiFi hotspot network. When prompted for network key, enter the new network key.
  6. Done!

Tuesday, July 10, 2012

Adding HTTP headers to AJAX calls in Backbone.js

Just a quick note about setting custom header in the HTTP request that Backbone.js manages. This is super easy to do. In the example code below, I have a Backbone.Collection assigned to messagesCollection variable and I'm triggering a fetch on that collection: messagesCollection.fetch({ headers: { 'x-my-custom-header-1':'foobar', 'x-my-rest-api-version':'1.0' } });

Wednesday, May 23, 2012

Potential issue when mocking in Groovy

The groovy.mock.interceptor.MockFor and grails.test.GrailsMock allow for mock objects in Groovy and Grails, respectively. I've been using both of these classes with good success for a long time. But recently, a small refactoring involving the the removal of a parameter from a method signature has caused me to re-evaluate the mock object usage in a dynamic language like Groovy.

As I said, there was a refactoring done on the public contract of a service, where the method name stayed the same, but a parameter was removed from the method signature. The contract unit tests for this service were changed to drive the refactoring ("test-driving the refactoring"). However, the collaboration unit tests, where this service is now acting a dependency, were not changed and they continued to pass successful. I tried cleaning the old .class files and compiling the Groovy tests, but to no avail, the unit tests which mocked this service continued to pass successfully, even though the method signature no longer existed on the real service implementation. After perusing the javadoc documentation, there does not seem to be any functionality in either of these classes to verify that the type that is being mocked has a method signature that matches the method signature being mocked. Therefore, these classes can mock methods which are non-existent on the real dependency implementations. Fixing the issue involved finding the method signature using a text search.

The whole episode was a bit unsettling; we have a lot of unit tests and we may be testing scenarios which are not representative of the real world. In my case, the real world scenario manifested itself as a runtime exception stating that the method was missing. In the case of Java and Mockito, the method signature change would result in a compilation error where the changed method signature was mocked in unit tests. My takeaway was to be more diligent with my refactoring and really ensure that I have changed all places in the code where a particular method is referenced.

Friday, May 04, 2012

Allocating business logic in Grails

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.