ObjectWeb Consortium
Print

Advanced Search - Powered by Google

  Consortium     Activities     Projects     Forge     Events 

Fractal


Project Links
· Home
· Documentation
· Download
· License
· What's New
· Wiki

Developers' Corner
· Workplan
· SVN Repository
· ObjectWeb Forge Site

About
· Team
· Users
· Partners
· Mailing List Archive

Comanche description

Overview

What follows is a generic description of the Comanche example, as it may be found (more or less adapted) in various Fractal implementation examples distributions.

Design

Before programming a component based software system with Fractal, one must first design it with components and, in particular, identify the components to be implemented. Note that this component oriented design task is quite independent from the subsequent component oriented programming task: at programming time, it is possible to merge several or even all the design time components into a single, monolithic piece of code, if desired (but then, of course, one loses the advantages of component oriented programming: modularity, adaptability, ...).

This section explains how to design component based applications, by using a concrete example. The next sections reuse this example to illustrate how to implement and deploy Fractal component based applications. This example application is Comanche, an extremely minimal HTTP server. A classical, non component oriented implementation of this application, in pseudo-code, is shown below:

Server {
  Socket server_socket;
  Socket worker;

  while true do
    server_socket = create server socket.
    worker_socket = accept(server_socket, port)
    // accept is a blocking call, it returns only when it has accepted a connection
    create new worker thread, passing it worker_socket
  end while
}

WorkerThread {
  read request on socket

  print first line of request 
  // analyse request
  if (request starts with "GET /") then
    get actual filename // up to the second " " of the first line of a well-formed request
    if ((file exists) and (file is not a directory)) then
      write "HTTP/1.0 200 OK\n\n" to socket
      write contents of file to socket
    else
      write "HTTP/1.0 404 Not Found\n\n" to socket
      write "<html>Document not found.</html>" to socket
    end if
  end if
  close socket
}
			

Finding the components

In a component based application, some components are dynamic , i.e they can be created and destroyed dynamically, possibly quite frequently, while other components are static , i.e. their life time is equal to the life time of the application itself. The dynamic components generally correspond to datas , while the static ones generally correspond to services .

In order to identify components in an application, it is easier to begin by identifying its static components. In other words, one should begin by identifying the services that are used in the application. In the case of Comanche, we can immediately identify two main services, namely a request receiver service and a request processor service (corresponding to the content of the two methods of the Server class). But it is also possible to identify other, lower level services. For example, we can see that the request receiver service uses a thread factory service, to create a new thread for each request. This thread factory service can be generalized into a scheduler service that can be implemented in several ways: sequential, multi thread, multi thread with a thread pool, and so on. Likewise, we can see that the request processor uses a request analyzer service, and a logger service, before effectively responding to a request. This response is itself constructed by using a file server service, or an error manager service. This can be generalized into a request dispatcher service that dispatches requests to several request handlers sequentially, until one handler can handle the request (we can then imagine file handlers, servlet handlers, and so on).

After the services have been specified, one can look for the main data structures, in order to identify the dynamic components. But the identification of the dynamic components is not mandatory, and is generally not done, because dynamic components themselves are rarely used (this means that, at programming time, data structures are generally not implemented as components, but as ordinary objects - if the programming language is object oriented). Indeed components do not have many benefits in the case of highly dynamic, short lived structures (introspection and dynamic reconfiguration, for instance, are not very useful in this case). In the case of Comanche we can consider sockets, HTTP requests, files, streams, and even threads as such data structures. But we will not map them to dynamic components.

After the services have been specified, one must assign them to components. Each component can provide one or more services but, unless two services are very strongly coupled, it is better to use one component per service. In the case of Comanche, we will use one component per service. We therefore have the seven following components: request receiver, request analyzer, request dispatcher, file request handler, error request handler, scheduler and logger.

Defining the component achitecture

After the components have been identified, it is easy to find the dependencies between them, and to organize them into composite components. Indeed the service dependencies are generally identified at the same time as the services themselves. If it is not the case, dependencies can also be found by looking at some use cases or scenarios. Likewise, services are generally identified from high level to lower level services (or vice versa). It is then easy to find the dependencies and the abstraction level of each component, since the components correspond to the previous services.

This is particularly clear in the case of Comanche: indeed, by re reading the previous section, one can see that the service dependencies have already been identified. For example, the request receiver service uses the scheduler service and the request analyzer, the request analyzer uses the request dispatcher, which itself uses the file and error request handlers. One can also see that the abstraction levels have already been found: the request receiver is "made of" the request receiver itself, plus the scheduler; the request processor is made of the request analyzer, the logger, and the request handler; the request handler is itself made of the request dispatcher, and of the file and error request handlers. All this can be summarized in the following component architecture:

Defining the components contracts

After the services have been found and organized into components, and after the component hierarchy and the component dependencies have been found, only one thing remains to be done to finish the design phase, namely to define precisely the contracts between the components, at the syntaxic and semantic level (if possible with a formal language - such as pre and post conditions, temporal logic formulas, and so on). Classical object oriented design tools, such as scenarios and use cases, can be used here.

The component contracts must be designed with care, so as to be the most stable as possible (changing a contract requires to change several components, and is therefore more difficult than changing a component). In particular, these contracts must deal only with the services provided by the components: nothing related to the implementation or configuration of the components themselves should appear in these contracts. For example, a setLogger operation has nothing to do in a component contract definition: this operation is only needed to set a reference between a component and a logger component. In other words, contracts must be defined with separation of concerns in mind: contracts must deal only with functional concerns; configuration concerns will be dealt with separately, as well as other concerns such as security, life cycle, transactions...

In the case of Comanche, we will use minimalistic contracts:

  • The logger service will provide a single log method, with a single parameter representing a string (String in Java, char * in C);
  • The scheduler component will provide a single schedule method, with a single parameter representing a programming-language-specific entity (e.g. a Runnable object in Java, or a struct containing a function pointer and a pointer to arguments in C). The role of this schedule method is to execute the given entity at some time after the method has been called, possibly in a different thread than the caller thread;
  • The request analyzer, the request dispatcher and the file and error request handlers will all provide a single handleRequest method, with a single parameter representing a programming-language-specific entity (e.g. a class containing a Socket, an InputStream, an OutputStream and an URL in Java; a socket file descriptor or a char * in C). This single handleRequest method will be implemented in several ways, in the different components:
    • the request analyzer will read the request to get the requested URL;
    • the request dispatcher will forward each request to its associated request handlers, sequentially, until one request handler successfully handles the request;
    • the file request handler will try to find and to send back to the client the file whose name corresponds to the requested URL, if it exists;
    • the error request handler will send back to the client an error message, and will always succeed.

Summary

The first step to design a component based application is to define its components. This is done by finding the services used in this application. The second step is to find the dependencies and hierarchical levels of these components. The last step is to define precisely the contracts between the components.

Copyright © 1999-2009, OW2 Consortium | contact | webmaster | Last modified at 2012-12-03 09:57 PM