Unbuilt Rails Dependencies: How to design for loosely-coupled, highly-cohesive components within a Rails application

February 21, 2012 Mike Barinek

I’m sure a few of you have encountered this scenario…

You’re a year in and your Rails application is growing in size as well as complexity. It’s becoming increasingly difficult to navigate through a sea of models, not to mention that your test suite is grinding to a screeching halt.

You have a handful of 3rd-party services you integrate with so you start thinking about extracting Gems. Although you’re nervous, as you’ll now need to manage and version multiple Gems. You’re looking at a mountain of work with little feature development.

Here’s a solution that has worked on several projects that might help to avoid this scenario: move loosely-coupled, highly-cohesive components to unbuilt gems within your lib directory while maintaining a single Git repository.

Sounds strange? Here are a few important facts that might help clarify the solution…

There’s only one Git repository and dependent gems are never “built”, their gemspec is simply referenced via your Rails Gemfile. This defers the need for a Gem versioning strategy as you Rails application moves forward with multiple dependent components all under the same umbrella.

From within your Gemfile…

gem 'component_1', path: "lib/component_1"

All your source is in one place. This allows you to continue to develop within a single environment with cross-component refactorings, which is now supported by tools like RubyMine.

Components have their own Gemfile, test_helper, test suite, and continuous integration environment. The unique Gemfile and test helper allows you to remove any unnecessary dependencies (Rails for example). The test suite and continuous integration environment helps to ensure downward dependencies and avoid circular dependencies.

As a result, you tend to end up with loosely-coupled, highly-cohesive components and avoid accidental design. In addition, test suites tend to run faster as they’re loading far fewer Gems and you’ve honored the timeless way of building large applications.

Here’s an example that describes the project structure…

rails_app/
  app
  config
  lib/
    component_1/
      lib/component_1.rb
      lib/component_1
      test/lib
      test/test_helper.rb
      Gemfile
      component_1.gemspec
      Rakefile
    component_2/
      lib/component_2.rb
      lib/component_2
      test/lib
      test/test_helper.rb
      Gemfile
      component_2.gemspec
      Rakefile
  test/lib
  test/test_helper.rb

Probably the most compelling argument is prematurely trying to identify and extract components. 3rd-party service integrations seem like a reasonable place to start, however, more recently we’ve been teasing out a variety of components very early within the development lifecycle with much success.

About the Author

Biography

More Content by Mike Barinek
Previous
Rails Contained: A Container for Web Application Development and Deployment
Rails Contained: A Container for Web Application Development and Deployment

You might call this post Part 2 in a component based architecture series. The first post describes a soluti...

Next
Achievement unlocked! One month at Pivotal.
Achievement unlocked! One month at Pivotal.

There's always been an mix of excitement and fear when starting a new job for me. There are many questions ...

Enter curious. Exit smarter.

Learn More