How Revolution Health Group does Rails development
From WhyNotWiki
Contents |
[edit] Custom developer commands
- command to set up "runtime environment"
- deployment support tools
http://revolutiononrails.blogspot.com/2007/01/new-rhg-developers-illustrated-primer.html
First, he decides to install a copy of one of the applications (rop) locally. Armed with instructions from wiki, he installs the deployment support tools:
$ gem install rhg_deployment --remote --source http://gems.revolutionhealth.com:8808He now has two new commands - rhg and rhgcontrol. He sets up a runtime environment for the application:
$ rhgcontrol setup Setting up the runtime environment mkdir -p /opt/rhg/applications/etc mkdir -p /opt/rhg/applications/tmp ... mkdir -p /opt/rhg/applications/logHe downloads the latest version of the application with all its dependencies to his workstation:
$ rhg update rop Bulk updating Gem source index for: http://gems.revolutionhealth.com:8808 Installing [ actionwebservice, 1.1.6 ] Installing [ activesupport, 1.3.1 ] Installing [ rails, 1.1.6 ] ... Installing [ rhg_ui, 1.5.40766 ] Installing [ rhg_migrations, 1.0.37541 ] Installing [ rop, 1.4.40837 ]...
Dan plans to use both lighttpd and mongrel to host the application, but first he goes with the default one, lighttpd, using a config supplied with the deployment tools:
$ rhgcontrol add rop Adding lighttpd configuration for rop_8001 $ rhgcontrol start rop Executing on rop_8001 Executing: /usr/sbin/lighttpd -f /opt/rhg/applications/etc/rop_8001.confHe points his browser to http://localhost:8001 and plays with the application. He feels like doing some coding. He checks out the latest application code from subversion and runs mongrel from the top of the source tree:
$ mongrel_rails start -dHe fixes some code and navigates his browser to http://localhost:3000 to see the changes. It works but he needs to do some more fixes, this time in a shared component rhg_ui. He checks out the latest component code, sym-links it to the vendor/plugin directory of the application making it temporarily a plugin [?], and changes some code to see the immediate result. The bug is resolved, and, after running unit tests, he checks in the modified code for both the application and the component.
The changes he made are in a latent state. They are in the source tree but no gems were built off them yet. Dan knows that QA usually builds application gems off the latest code but not component ones. He decides to build a gem for rhg_ui himself. He navigates to the top of the component source tree and runs a command to tag, build, package, and publish the component as a gem to our local gem server:
$ rhg publish ... Committed revision 40967. ... Checking out tag to /tmp/rhg_ui-1.5.40966 Changing directory to /tmp/rhg_ui-1.5.40966 Building gem from tag ... Successfully installed rhg_ui, version 1.5.40966 Pushing gem to development gem server via rhg tool: rhg push rhg_ui Bulk updating Gem source index for: http://gems.revolutionhealth.com:8808 Bulk updating Gem source index for: http://gems.revolutionhealth.com:8808/archive SSH User: dsmith SSH Password: XXXXXXX Publishing gems... ... Refreshing the gem server indexes at /opt/rhg/gems
[edit] Custom deployment tools (not Capistrano)
Gem-based Deployment and Delivery: Part 1 - When Capistrano Is Not Enough (http://revolutiononrails.blogspot.com/2007/03/when-capistrano-is-not-enough.html).
In the beginning, we, as many other rails projects, were using Capistrano for deployment. This changed when it came time to start preparing builds for QA and production. Having formal QA and Operations teams, we had to adjust our approach to meet their requirements for deployment. [...]
[1] provides insight into how deployment tools are used on our projects. [...] It does not show all use cases; however, it gives up enough to see what might be going on later in QA and production. The teams there use rhgcontrol for any application or shared component deployment task. Wrapping up all deployment activities in a self-contained tool gives the development team better control over how applications are installed and deployed in those environments. In addition, it allows the addition of new features to the existing command set without changing the installation instructions.
[edit] The Deployment
Any application or shared component is potentially deployable. All that is required is a single file--
deployment.yml--in the config directory of the gem (since all applications at RHG are plugems). The content of the file is read and executed at deployment time whenrhg deployis run. The deployment configuration is simpler than a Capistrano recipe and contains just a few sections....
There are two types of the deployment tasks: copying over configuration files (the
filessubsection) and execution of commands (theexecssection).Some configuration files, such as database.yml, are often deployment environment specific. The deployment-time configuration directory (config/deployment) may contain such files, prefixed with either the host class (like dev) or the full hostname (like dev8-rails.) They are used to overwrite the copies under the config/ directory at deployment time.
Configuration-file overrides are a small piece of the multiple-configuration puzzle we've had to solve at RHG. We plan to have a separate post on this subject, which will cover runtime configuration as well.
There are two types of the execution commands--regular UNIX commands and macros. Macros are commands that are bundled with the rhg tool (when it makes sense to share them between different applications.) For example, the macros for hosting gems (@cmd_hosts_gems) looks like this:
[edit] Conclusion
While Capistrano is suitable for most of the rails projects out there, we had to build our own deployment and delivery tools to wrangle our multi-environment, multi-application, multi-component portal. We plan to extract and release the useful parts of the tool for Rails development teams that are isolated from their QA/Production environments.
[edit] Plugems
Gem-based Rails Dependency Loading: Part 0 - Introduction (http://revolutiononrails.blogspot.com/2007/01/gem-based-rails-dependency-loading-part.html).
Once you begin to build a lot of plugins (shared across numerous Rails applications), cross-plugin dependencies start to creep in. If you've ever renamed your plugins to effect load order, then you know what I'm talking about. Our idea, which has been extremely successful, is to package interesting dependencies as RubyGems. These dependencies include proprietary shared plugins as well as library dependencies.
Since our applications care to share a lot more than bundles of Ruby classes, we decided to beef up plugins as well.
- Part I: Justification
- Part II: Bundling a plugin as a Gem and getting it all to work with Rails
- Part III: Extracting views, partials, tasks and layouts into plugins and gems
- Part IV: Retaining developer convenience in a Gem-driven Rails application
Revolution On Rails: Gem-based Rails Dependency Loading: Part 1 - Justification (http://revolutiononrails.blogspot.com/2007/01/gem-based-rails-dependency-loading-part_24.html).
Justification 2: Maintenance versioning
If you've got multiple applications sharing your plugin, it can be awkward to maintain under the standard plugin model. You're going to want to continue development on the plugin, but you don't want to force all clients to be on 'trunk' (or force all clients to move to trunk to pick up critical bug fixes.)
With this in mind, you're going to need the ability to backport bug fixes to maintenance branches (and provide a mechanism for clients to absorb these backported fixes without absorbing unwanted non-backwards-compatible changes.)
With RubyGems, client applications need only use the right optimistic version lock, and they'll pick up critical fixes on their minor (or major) revision without committing to a boatload of unwelcome API changes.
We're very wary about overengineering our solutions, so keep in mind that things begin their life as plugins (and continue to work as usual.) We just take care to gem-up things that are not well suited for plugins (e.g. highly cross-dependent or highly versioned).
Seems like another solution would be to just have a "stable" branch for each release and svn:external each app to use the appropriate "stable" branch. Then they would absorb bug fixes automatically but you could continue development (API changes and such) on a separate branch and only switch the external over to the newest version when it is ready... (But maybe the excuse is that gem versions are easier to work with than constantly messing with svn:externals??)
Revolution On Rails: Plugems (http://revolutiononrails.blogspot.com/2007/01/plugems-rails-as-first-class-citizens.html).
We started with a small application. A few controllers, a few models. Soon the company grew, the development team grew, and with that the application grew. Luckily, we namespaced applications early on, so it was mostly clear where one application lived from another, but the rails project became too big. [...] It was time to split applications. But for a portal with a single look and feel, how do you share things across applications? Here is how we do it (this week).
We wanted to use gems, as described in Part I, but without gems being true 1st class citizens in rails, there was no way to make it happen. So what was important to share and more importantly version?
I. Code
Thats obvious, rubygems already does (most of) this for us.
II. Views
With a common header, or commonly shared assets, we'd like to leverage those.
III. Rake Tasks
We have lots of rake tasks to do all sorts of things, we need to share those too.
IV. Assets
We'll cover this later. This gets complicated, and needs deployment input.
V. Plugems as Plugins
Using gems, as plugins, in your plugin dir, and the A/B/C problem.
...
...
