Welcome to Adaptation

Adaptation is a framework that tries to facilitate data interchange between applications. Applications send and receive xml messages through a message oriented middleware (mom) using a publish/subscribe pattern:

Example:

Application A publishes messages on topic "messages from A", and all 
applications interested on reading messages from A subscribe to that topic.

Adaptation focuses on facilitate building adaptors. adaptors are programs that are executed for an application subscribed to a topic, when a message is received on that topic.

Adaptation is highly inspired by Ruby on Rails web framework, so adaptors are built in a similar way that web apps are built using the RoR framework.

When building an adaptor, logic will be stored into Adaptation::Adaptor objects, and mapping of xml data messages will be performed by Adaptation::Message objects.

Adaptation can use ActiveRecord based models for data interaction with databases.

Installation

Adaptation is available as a ruby gem, so the easiest way should be:

> gem install adaptation

Usage

  1. At the command prompt, start a new adaptation adaptor using the adaptation command and your adaptor name:

    > adaptation myadaptor
    

    This will generate a an adaptor file tree under folder myadaptor.

  2. If no message oriented middleware (mom) has been already set, you can start one typing:

    > mom
    

    This will start a mom in localhost (default), listening on port 8080 (default).

  3. Subscribe your adaptor to the mom, so it will be executed when a message is received on a topic your adaptor is interested in:

    > ruby script/subscribe
    

    By default this will try to subscribe to a mom listening on localhost:8080, using port 8081 to subscribe (subscribing means starting a new server that listens for message publication notifications). These values can be changed editing config/mom.yml. In mom.yml you can also specify wich topics your adaptor is interested in.

  4. Right now you should have a mom listening for messages on localhost:8080, and an adaptor subscribed to that mom and listening on localhost:8081, and interested in all topics available.

    This environment can be tested by executing the following from myadaptor folder:

    > ruby script/publish NEWS '<helloworld/>'
    

    The previous command should publish de xml <helloworld/> message into topic NEWS from the mom. This message should be displayed in the subscriber terminal when delivered by the mom. Nothing would be executed, because a Helloworld message to map this xml message and a HelloworldAdaptor[link:../rdoc/classes/Adaptation/Adaptor.html] to process it don’t exist yet. Since these classes aren’t implemented, Adaptation will pass the message as an Adaptation::Message object to the default ApplicationAdaptor adaptor, but its process method is still empty, so nothing will happen.

    To see something happening the process method in the default ApplicationAdaptor could be implemented, editing file myadaptor/app/adaptors/application.rb:

    class ApplicationAdaptor < Adaptation::Adaptor
    
      def process message
        logger.info "Received message #{message}"
      end
    
    end
    

    Now, if the previous <helloword/> message is published, that should be visible in log/development.log.

    The other way this can be done is by creating Helloworld Adaptation::Message class to map the xml data:

    > ruby script/generate message helloworld
        exists  app/messages/
        exists  test/unit/
        exists  test/fixtures/
        create  app/messages/helloworld.rb
        create  test/unit/helloworld_test.rb
        create  test/fixtures/helloworld.xml
    

    The file we care about right now is app/messages/helloworld.rb:

    class Helloworld < Adaptation::Message
    end
    

    We can leave it like this by now, and proceed to generate the HelloworldAdaptor Adaptation::Adaptor class:

    > ruby script/generate adaptor helloworld
        exists  app/adaptors/
        exists  test/functional/
        create  app/adaptors/helloworld_adaptor.rb
        create  test/functional/helloworld_adaptor_test.rb
    

    and to edit app/adaptors/helloworld_adaptor to make something happen when a message is received:

    class HelloworldAdaptor < ApplicationAdaptor
    
      def process helloworld
        logger.info "Received message: #{helloworld.to_xml.to_s}"
      end
    
    end
    

    We can notice that helloworld variable is an instance of Helloworld class (because Adaptation has been able to map it to a subclass of Adaptation::Message object), and that the HelloworldAdaptor inherits from ApplicationAdaptor, so functionality repeated in different Adaptors[link:../rdoc/classes/Adaptation/Adaptor.html] can be placed in ApplicationAdaptor.

Moms

By default, Adaptation will try to use druby to execute the built-in Ruby mom. This mom is suitable for development, but not for production. For a production environment a more stable solution like Xmlblaster should be chosen.

Different message brokers can be configured in config/mom.yml, and example configuration for supported moms are present in the same file when an adaptor is generated with the adaptation command.

When we want to publish/subscribe to a mom different than the default druby, we can do so by adding the MOM=mom_type option:

> ruby script/subscribe MOM=xmlblaster
> ruby script/publish MOM=xmlblaster topic message

Description of an adaptor file tree:

app

Holds all the code that's specific to this particular adaptor.

app/adaptors

Holds adaptors that should be named like messagename_adaptor.rb for
automated mapping. All adaptors should descend from Adaptation::Adaptor.

app/messages

Holds messages that should be named like messagename.rb.
Messages descend from Adaptation::Message.

app/models

Holds models that should be named like post.rb.
Most models will descend from ActiveRecord::Base.

config

Configuration files for the Adaptation environment, the mom, the adapted application, 
the database, and other dependencies.

db

Contains database related scripts.

doc

This directory is where your adaptor documentation will be stored.

lib

Application specific libraries. Basically, any kind of custom code that doesn't
belong under adaptors, models, or messages.

public

The directory available for calling the adaptor (contains dispatch.rb).

script

Helper scripts for automation and generation.

test

Unit and functional tests along with fixtures. When using the script/generate scripts, template
test files will be generated for you and placed in this directory.

Debugging

Adaptation includes a console from wich we can access Adaptation::Message and ActiveRecord classes defined in the adaptor we are working on.

To open the console type:

> ruby script/console [environment]

From the command prompt that should appear, we can instantiate ActiveRecord objects defined in app/models and Adaptation::Message objects defined in app/messages. With the previous example, where an Adaptation::Message subclass called Helloworld was defined in app/messages, we could do:

> ruby script/console test
$ >> Loading test environment (Adaptation X.X.X)
$ hw = HelloWorld.new("<helloworld>hello</helloworld>")
$ hw.content
$ >> "hello"
$ hw.to_xml
$ >> "<helloworld>hello</helloworld>"

We could edit app/messages/hellworld.rb to add a validation:

class Helloworld < Adaptation::Message
  validates_presence_of :atr
end

and check if it works with the console:

> ruby script/console
$ >> Loading test environment (Adaptation X.X.X)
$ hw = HelloWorld.new("<helloworld>hello</helloworld>")
$ hw.valid?
$ >> false 
$ hw = HelloWorld.new("<helloworld atr="something">hello</helloworld>")
$ hw.valid?
$ >> true

If we define ActiveRecord::Base subclasses in app/models we can do the same mapping database rows instead of xml messages, if config/database.yml is properly configured to access the database. If we had a users table in our database, we could generate a model to access it with ActiveRecord:

> ruby script/generate model user
    exists  app/models/
    exists  test/fixtures/
    create  app/models/user.rb

and use it from the console:

> ruby script/console
$ >> Loading test environment (Adaptation X.X.X)
$ u = User.find(:first)

Testing

As it can be seen when generating adaptors and messages, adaptation automatically generates empty functional tests for adaptors and empty unit tests for messages.

Tests can be run typing:

> ruby test.rb

Tests can use fixtures stored in test/fixtures folder. There are two kind of fixtures:

  • database fixtures: the same database fixtures a rails application uses to fill its tables in tests. These fixtures are usually stored in *.yml files, and are used to reset database contents before each test, so it is important to configure the test database properly in config/database.yml. More about ActiveRecord fixtures here.

  • xml fixtures: these fixtures are plain xml strored in *.xml, and are used to build Adaptation::Message objects and probably process them with our adaptor in the tests. These fixtures can also be created dynamically with Erb templetes. This is an example of a xml fixture built with Erb:

    <20_people>
      <% 20.times do |c| %>
        <person num="<%= c %>"/>
      <% end %>
    </20_people>
    

For the HellworldAdaptor in our example, we should find a functional test in test/functional/helloworld_adaptor_test.rb, and for the Helloworld message a unit test in test/unit/helloworld_test.rb.

Tests usually perfrom assertions. There are a couple of methods that may be useful when testing adaptors, href="../../../../../rdoc/classes/ActiveSupport/TestCase.html#M000044"> and href="../../../../../rdoc/classes/ActiveSupport/TestCase.html#M000043">:

  • get_message_from_fixture: Returns an Adaptation::Message object from a fixture, without processing it (or an instance of the corresponding subclass, if it’s defined).

  • message: Builds a message from a xml fixture file and processes it the same way messages from the mom are processed by Adaptation, but using the test environment. Messages published with publish will be published to a mocked MOM (and can be checked with assert_message_published).