rbFeatures: Feature-Oriented Programming in Ruby

05-02-2012 | Category: Programming | Tags: Paradigms, Feature-Oriented-Programming, Metaprogramming, Ruby

Feature-Oriented Programming is a paradigm that addresses the configurability of your application. It helps you to modularize your source code according to its functional features. Features can be as coarse granular as complete modules or classes, or as fine granular as statements inside method bodies.

In this post, I present rbFeatures, an extension to the Ruby programming language that enables Feature-oriented programming. With the simple expression Feature.code you can encompass any Ruby code – complete classes, methods, or individual source code lines – to mark what belongs to a feature. Then you just specify which features your program should activate, and you receive an appropriate customized program.

Why I Created rbFeatures

rbFeatures is a language extensions to Ruby that serves as two proofs of concept. First, I wanted to understand how to build a DSL from scratch. Second, I wanted to see that you can add a non-native paradigm to Ruby. This helped me to get several publications, and eventually led to my PhD thesis.

Design Philosophy

I term my design philosophy for rbFeatures as essential minimalism, which means to craft something with sophistication yet reduction of its elements towards a minimalist core. For rbFeatures, this means that I …

  • do not want to modify the Ruby compiler, because future compiler patches or new Ruby versions could break compatibility with my compiler modifications
  • want a minimal code base, but a maximum test suite
  • want to make it easy to define features in an existing Ruby program without the need of large code rewrites

During several iterations and 43 commits to my private repository (and before you ask, it will stay private), this form could be achieved and makes rbFeatures an essential contribution to feature-oriented programming, especially in dynamic programming languages. Also, it shows the power of a custom DSL to communicate how a new paradigm intersects your existing program by a set of dedicated expressions.

Features

Features are defined by the individual stakeholders of your program. If the stakeholders are your users, then features describe the functionality of your software. To illustrate what features are, lets consider TAP, the Twitter Application that I described in an earlier post. This application enables you to add any existing Twitter user and their tweets to a local database, then to browse the stored tweets. In this application, two features are Add User or Delete User for the obvious intent, and All Tweets to show all stored tweets instead of the least 10 only.

The challenge with these features is a characteristic that computer science researchers call “crosscutting”: the source code that implements this feature is not contained in one part of the source code, but scattered over different classes, methods, or even individual source code lines. For example, the Add User feature in TAP consists of a dialog shown on the user configuration page, the HTTP post controller receiving new user names, the helper function that communicate with Twitter, and the helper functions that store the user and the tweets in the local database.

Enter the magic of rbFeatures. It embraces the cross-cutting nature of feature-related source code by allowing you to wrap all these individual source code parts with the simple expression Feature.code; right at their original place, without the need to rewrite your program. Features add another layer of modularity to your programs: Features can be activated and deactivated at runtime, immediately changing the behavior of your application.

Defining Features

The first task to use rbFeatures is very simple. In order to define a feature, we just need a class that includes the Feature module. This module defines all methods with which the feature can be activated, deactivated, and used to define feature containments.

We stay with the TAP example and define the necessary features with the following expressions:

class AddUser
  is Feature
end

class DeleteUser
  is Feature
end

class AllTweets
  is Feature
end

As you can see, I added a small Domain Method: Calling is is the same as Ruby’s include, but it better captures the intent of the code.

Adding Feature Containments

After the features are defined as entities, the next step is to define which parts of the program belong to this feature. As stated above, one of rbFeatures’ primary design goals is to minimize the amount of code changes in the original program. The vehicle that enables a seamless addition of features are called feature containments.

Feature containments consist of two parts: A condition and a body. The condition is introduced with the syntax Feature.code. Here, Feature is any Boolean-like expression that determines which features have to be activated or deactivated. Conditions can be trivial like “If feature A is activated”, which translates to A.code, or “If Feature A but not B is activated”, which translates to (A - B).code. After the condition, the containment body follows with Ruby’s do...end notation. The body can include any piece of Ruby code: definitions of classes and methods, single lines of code in the body of objects, or even individual parts inside a code line. This versatility to put coarse or fine-granular code in the containments distinguishes rbFeatures from other approaches.

Now let’s see how feature containments in TAP are defined for the AllTweets feature. There are two different source code parts that belong to this feature: A link at the navigation bar, and the handler that is triggered.

The location of the link is easy to spot in the page template (here using HAML). As you see, we create a HTML div for the navigation, and add a link entitles "All Tweets". We put this link into a feature containment with the condition that AllTweets is activated. See Line 4 in the following code snippet:

1 %div{:class => 'span-6 last', :id => 'navigation'}
2   %p
3     %a{:href=>'/'} Home
4   - @all_tweets_feature.code do
5     %p
6       %a{:href=>'/all'} All Tweets
7   %p

As we see from this template, the link will trigger the "/get+" handler to be called. We find this handler in the application and likewise put it into a feature containment with the same condition as before. See the following code snippet, Line 2—6:

1 get '/all' do
2   AllTweets.code do
3     @tweets = Tweet.all :order => [:date.desc]
4     @pagetitle = "All Tweets"
5     haml :index
6   end
7 end

These two changes are all that is necessary for the AllTweets feature to be available in our application. Now we are ready to start the application and to configure the application’s behavior via the features.

Configuring the program

Two commands control the behavior of the features: Feature.activate and Feature.deactivate. Changing the activation status of a feature triggers the re-evaluation of all feature containments. For each containment, the condition is checked. If it is true, then the body code is executed. If it is false, then the body code is not executed1.

Normally, you can execute these commands at the shell. For TAP, I created handlers to execute these commands. Issuing a HTTP get request to /activate/Tap::AllTweets will activate the AllTweets feature. For convenience, TAP also defines the following feature configuration dialog:

TAP -- Feature Configuration Dialog

Outlook

rbFeatures is still used in my research, and new paper are published. Check out my publications to stay up-to-date. A detailed explanation of rbFeatures can be found in my Science of Computer Programming Journal Paper, for which I provide the author’s version to download.

Furthermore, you can test my Twitter Application.


Footnotes

1 There are some exceptions to this rule. rbFeatures will always define the classes and methods included in feature containments. However, if the containment condition is false, then method bodies are changed to throw errors with a custom message explaining to the user what feature needs to be activated to execute this method normally.

Comments & Social Media

blog comments powered by Disqus