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.