The difference between J2ee and Ruby

来源:互联网 发布:足球先开球数据 编辑:程序博客网 时间:2024/04/29 11:19

Ruby on Rails is a Web application framework that aims to provide an easy path to application development. In fact, the framework's proponents claim that Ruby on Rails developers can be up to ten times more productive than they would be when using traditional J2EE frameworks. (Read the article titled "Rolling with Ruby on Rails" for more on this claim; see Resources). While this statement has been the source of considerable debate in the Rails and J2EE communities, little has actually been said about how Rails and J2EE architectures compare. This article will contrast the Rails framework against a typical J2EE implementation using common open source tools that are regularly found in enterprise applications.

What is Ruby on Rails?

To find a simple, one-sentence description of Rails, you need look no further than the project's home page:

Rails is a full-stack, open-source Web framework in Ruby for writing real-world applications with joy and less code than most frameworks spend doing XML sit-ups.

Although I can't guarantee that the framework will deliver on its promise of joy, the statement does a good job of summing up Rails' qualities. The full stack consists of a Web server, a framework for processing HTTP requests and responses, and a framework for easily persisting data to a relational database. Rails strives for development ease by eliminating complicated XML configuration files and using the very dynamic nature of the Ruby language to help minimize much of the repeating code often found in static typed languages.

Rails and a typical J2EE Web stack

Figure 1 compares the Rails stack to a typical J2EE Web stack comprised of the Tomcat servlet container, the Struts Web application framework, and the Hibernate persistence framework.


Figure 1. Comparison of Rails and J2EE stacks
Comparison of Rails and J2EE stacks

As you can see, the fundamental difference between the Rails stack and the components that make up a common J2EE-based Web application is small. Both have a container in which the application code will execute; an MVC framework that helps to separate the application's model, view, and control; and a mechanism to persist data.

The MVC framework

Model-View-Controller (MVC) is a design pattern that has been around for quite some time. It has its origins in Smalltalk; today, virtually all GUI frameworks, including Web and rich clients, are based on it. MVC has three parts: the model, which is responsible for the business logic, including application state and the actions to be performed upon that state; the view, which is used to render and present the model to the user (in the case of Web applications, the view is generally rendered as HTML); and the controller, which defines application behavior. For a more detailed explanation of the MVC pattern, check Resources.

The front controller

Struts' ActionServlet and Rails' DispatchServlet are both examples of the Front Controller pattern; as such, they both provide the same functionality. They accept HTTP requests, parse the URL, and forward processing of the request to an appropriate action. In the case of Struts, an action is a class that extends Action; for Rails, it is a class that extends ActionController. The main difference between the two front controllers is how they determine the action that processes a particular request.

With Struts, a developer needs to externalize the mappings of specific requests to Action classes in an XML configuration file. When the ActionServlet is first loaded, it parses this file and prepares to accept requests. By convention, HTTP requests that end in .do get redirected to ActionServlet for dispatching to the appropriate Action. The XML in Figure 2 is a typical mapping. It tells the ActionServlet to forward a request called deleteOrder.do to controllers.order.DeleteOrderAction for further processing.

Rails takes a different approach. Instead of relying upon a configuration file to map requests to actions, it discovers the appropriate action based on the URL requested. As you can see in Figure 2, the URL http://localhost/order/delete/4 indicates for Rails to invoke the delete method on an instance of OrderController and make 4 available as an instance variable. Rails is smart enough to know that /order maps to a controller class defined in a file named order_controller.rb. If there is a find method defined in the controller, that method can be invoked by simply replacing delete with find in the URL.


Figure 2. URL mappings in Rails and Struts
URL mappings in Rails and Struts

The action and the model

In both Rails and Struts, the action acts as a bridge between the front controller and the model. The developer provides an implementation of an action in order to provide application-specific processing of a request. The front controller is responsible for accepting the request and passing it off to a specific action. Figure 3 illustrates a basic action hierarchy for Rails and Struts.


Figure 3. Rails and Struts action hierarchy
Rails and Struts action hierarchy
Are actions models or controllers?

Action and ActionController are technically part of the controller in the MVC pattern, as they respond to events initiated by the client. However, in small applications, developers often code domain or business logic inside these classes, and as a result they can be considered part of the model as well in those cases. Best practices suggest that you should abstract domain logic away from the controller and placed in its own domain-specific classes.

Struts requires that the developer extend Action and override execute() in order to process the request. Generally, each Action class provides a very specific unit of work. Figure 3 illustrates three specific actions: SaveOrderAction, DeleteOrderAction, and ListOrdersAction. The front controller calls the execute() method and passes it a number of useful objects, including the HTTP request and response objects. ActionForm is a class that conveniently transfers and validates form-related input to and from the view, and ActionMapping contains the configuration information for the mapping as described in the XML in Figure 2.

The execute() method returns an ActionForward object that Struts uses to determine the component that continues processing the request. Generally, this component is a JSP, but ActionForward can also point in other actions. Developers must be aware that Struts will create a single instance of the Action and allow multiple threads to invoke execute(). This allows for faster request processing, as the framework is not continually creating new Action instances to handle each request. But because a single object is shared between multiple threads, you must observe proper threading considerations, as other threads are likely to pummel instance variables that hold state in the action.

In Rails, you must extend ActionController::Base for the model to participate in the processing of a request. Rails doesn't pool the instance of the ActionController; instead, it creates a new instance for each request. While this might have a negative impact on performance, it makes development easier. Developers need not be concerned with the threading issues that are present in Struts, and as a result, the session, request, header, and parameters are all accessible as instance members of the ActionController. ActionControllers are also a logical place to group all processing of specific domain logic. While Struts Action classes are fine-grained and provide very specific units of work, Rails ActionControllers are coarse-grained and model discreet units of work as methods.

Listing 1 and Listing 2 illustrate a typical Struts action and a typical Rails action, respectively.

Table 1 offers a comparison of the logic flow of the two methods, and illustrates what happens on specific lines in Listings 1 and 2. When you examine the execute() method of DeleteOrderAction and the delete method of OrderController, you can see that they are basically the same.


Table 1. execute() and delete methods compared
Step Struts Rails Framework calls action Line 03: execute() Line 07: delete ID retrieved from request Lines 06-07: Pulled from request object Line 08: Pulled from an instance hash of all parameters Order record is deleted from database Lines 09, 14-24: delete() method called, deleting the record using Hibernate Line 09: Deletes the record using ActiveRecord Redirecting to list the remaining orders Line 11: The ActionMapping object is used to look up the next component to forward processing to. The XML mapping in Figure 2 shows that success maps to /listOrders, which is another Action that is responsible for looking up the remaining orders and presenting them as a JSP. Line 10: The redirect_to method is called with a hash of the next action to invoke; in this case, it simply calls the list method of the same controller.

Back to top

The persistence frameworks

A persistence framework is used to move data to and from the database in the application layer. Both Hibernate and Rails persistence frameworks can be classified as object/relational mapping (ORM) tools, meaning that they take an object view of the data and map it to tables in a relational database. Both frameworks aim to reduce the development time associated with working with relational databases. However, Figure 4 illustrates some fundamental differences in how each is designed and configured.


Figure 4. Comparison of Active Record and Hibernate persistence frameworks
Comparison of Active Record and Hibernate persistence frameworks

Hibernate

Hibernate is based on the Data Mapper pattern, where a specific mapper class, the Session, is responsible for persisting and retrieving data to and from the database. Hibernate can persist any Java object as long as it conforms to JavaBean specifications. XML mapping files describe how a class maps to a particular table in the database, along with any relationships that the class has with other classes.

Listing 3 shows an example of a Hibernate mapping file. The class tag maps the Order object to the ORDERS table and has a number of sub tags that describe its properties, the ID and the order name, and a one-to-many relationship to models.Item. Listing 4 shows the Order class itself.


Listing 3. Order.hbm.xml
...01 <hibernate-mapping>02    <class name="models.Order" table="ORDERS"03        dynamic-update="true" dynamic-insert="false"04        discriminator-value="null">0506 <id name="id" column="id" type="java.lang.Long" 07             unsaved-value="null">08             <generator class="identity"/>09         </id>10 11         <set name="items" lazy="false" inverse="false"12            cascade="none" sort="unsorted">13             <key column="id"/>14             <one-to-many class="models.Item"/>15         </set>16 17         <property name="name" type="java.lang.String"18             update="true" insert="true"19             access="property" column="name"/>20     </class>21 </hibernate-mapping>


Listing 4. Order.java
01 public class Order {02    private Set items;03     private String name;04     private Long id;05 06     public Long getId() { return id;}07 08     public void setId(Long id) { this.id = id;}09 10     public Set getItems() { return items;}11 12     public void setItems(Set items) { this.items = items; }13 14     public String getName() { return name; }15 16     public void setName(String name) { this.name = name; }17 }

Active Record

Reflection and metaprogramming

Reflection is succinctly defined in Wikipedia (see Resources) as "the ability of a program to examine and possibly modify its high-level structure at runtime." The same source defines metaprogramming as "the writing of programs that write or manipulate other programs (or themselves) as their data or that do part of the work that is otherwise done at runtime during compile time."

The following code implements reflection:

01 obj = "some_string"02 if obj.respond_to?('length'):03   puts "obj length = #{obj.length}" 03 end>> obj length = 5

And this code implements metaprogramming:

01 class SomeClass02 end03 newMethod = %q{def msg() puts "A message!" end}04 SomeClass.class_eval(newMethod)05 aClass = SomeClass.new06 aClass.msg>> A message!

Rails' ORM framework is called Active Record and is based upon the design pattern of the same name. Martin Fowler describes an Active Record as "an object that wraps a row in a database table or view, encapsulates the database access, and adds domain logic on that data." In Rails, each domain object extends ActiveRecord::Base, which provides the CRUD operations.

Active Record doesn't require a mapping file, as Hibernate does; in fact, a developer working with Active Record doesn't need to code getters or setters, or even the properties of the class. Through some nifty lexical analysis, Active Record is able to determine that the Order class will map to the ORDERS table in the database. Using a combination of Ruby reflection and metaprogramming, the columns of the table become properties of the object. Accessors and mutators are also added.

Listing 5 shows the completed code for the Order class. The one line of code in the class body of Order defines its relationship to the Item object. has_many is a static method call for which the symbol :items is a parameter. ActiveRecord uses :items to discover the Item domain object and in turn maps the Item object back to the ITEMS table in the database.


Listing 5. order.rb
01 class Order < ActiveRecord::Base02has_many :items03 end

The Order class as coded in Listing 5 provides dozens of class and instance methods at runtime. Table 2 offers a partial list of operations and attributes available on Order:


Table 2. Attributes and operations available on Order
Class methods Instance methods Attributes
  • find(*args)
  • find_by_sql(sql)
  • exists?(id)
  • create(attributes)
  • update(id, attributes)
  • update_all(updates, conditions
  • delete(id)
  • delete_all(conditions)
  • ...
  • add_items
  • build_to_items
  • create_in_items
  • find_all_in_items
  • find_in_items
  • has_items?
  • items
  • items=
  • items_count
  • remove_items
  • id
  • name


Back to top

In summary

While Ruby on Rails is a very new and exciting framework that has generated considerable interest in the Web development community, the core architecture follows the basic patterns found in J2EE. It's the philosophical approach to development of Web applications that sets the two frameworks apart. Rails prefers explicit code instead of configuration files, and the dynamic nature of the Ruby language generates much of the plumbing code at runtime. Most of the Rails framework has been created as a single project and application development benefits from a set of homogeneous components. In contrast, the typical J2EE stack tends to be built from best-of-breed components that are usually developed independently of one another, and XML is often used for configuration and gluing the components together.

So, should you consider Rails for your next Web application? Well, why shouldn't you? It's a well-written stack of components that work well with each other and are based upon industry accepted enterprise patterns. The Ruby language allows for fast development and adds to the framework by generating much of the application plumbing. Those who are familiar with MVC and ORM frameworks available in the Java world will have no difficulty wrapping their minds around Rails.

Should you dispense with J2EE altogether in favor of Rails? Absolutely not. J2EE is a well-established standard with several solid implementations and, most importantly, is a proven technology. But I do suggest that you download a copy of Rails and start hacking away. Many of the tutorials that are available are introductory and will get you up and running in no time. Again, I don't guaranteeing that you'll experience joy as a result of working with Rails, but I do bet you'll find contentment.



Resources

  • Check out the Ruby on Rails Web site.
原创粉丝点击