Sponsored Links


Resources

Enterprise Java
Research Library

Get Java white papers, product information, case studies and webcasts


A RESTful Core for Web-like Application Flexibility - Part 2 - Microkernel

August 2008

Discussion


Introduction

In the first article we began exploring how application software could made to be as flexible as the World Wide Web by following a REST or RESTful design approach [1]. To make it clear that we are not talking about REST using the HTTP protocol and its methods (GET, POST, etc.) we will adopt the term "Resource Oriented Computing" or ROC to refer to our approach. This name comes from a fundamental point about the approach: resources are identified by URI addresses and are requested by clients. You might think of ROC as a refinement or evolution of RESTful design thinking, applied to application software.

To recap the first article, we described a software architecture in which clients, service endpoints and an Intermediary work together to provide a logical computing environment. We grounded our discussion at the physical level by showing prototypical code for a client and endpoint. In the following Java code, a request is created and issued to the Intermediary. The request comprises a logical URI address, a request verb and (optionally) a physical class specifying the form of the returned resource representation. The Intermediary is accessed through the variable "context":

Request req;
Representation rep;

req = context.createRequest("resource:/customers/");
req.setVerb(Request.SOURCE);
req.setType(DOM.class);
rep = context.issueRequest(req);

It is the job of the Intermediary to then:

  1. Resolve the logical URI address to a physical endpoint,
  2. Send the request to the endpoint,
  3. Accept the representation returned by the endpoint and send it back to the requesting client,
  4. Drop any connection it established between the client and the endpoint.

The responsibility of the endpoint is to return a copy of the requested information in the form of an immutable representation of the information (called a "resource representation" or, more simply, a "representation").

In the first article, we argued that this architecture provides an environment for application software construction that mirrors the Web. By exclusively using logical coupling between client code and service endpoints, application software becomes more flexible. Just as web sites can be added, changed and dropped without disrupting the Web as a whole, analogous changes can be made to logically bound application software - even in a running system.

In this article we look at services that can be provided by the Intermediary. As we do, it will become clear that referring to the Intermediary as a "microkernel" may be more appropriate.

Microkernel Services

Logical Address Resolution

The most important service the microkernel can provide is logical address resolution. When client software needs information (or an information processing service), it issues a request to the microkernel containing a logical URI address. The microkernel searches for a mapping from the requested logical URI address to physical code (an endpoint) within a logical address space (also known as a context).

Note: programming at the logical level using URI addresses, logical address spaces, and SOA-like microservices is the topic of a future article in this series.

In the Web there is only a single, global logical address space. In the ROC system we are designing there is no reason to have such a limitation. Instead, our architecture can support multiple address spaces, each with its own static resources and endpoints. Each logical address space is defined, implemented, and managed by an entity called a module. A module can:

  • Map logical addresses to physical endpoints: A module contains physical code (endpoints) and physical resource representations (such as static HTML pages or configuration control files). Declarations within a module define the mapping from the logical address space of the module to these physical entities.
  • Export a portion of the logical address space: To facilitate information hiding, each module locates its resources within a private address space. To facilitate information sharing, each module contains an export declaration which defines the portion of the private address space that other modules can import.
  • Import other logical address spaces: Each module can import and utilize the logical address space of zero or more other modules. This allows a variety of different architectures to be constructed from the same basic building blocks.
  • Load Java classes to function as endpoints: Since each module can contain endpoints implemented physically as Java code, each module will need a classloader. To keep modules truly separate from each other, each module implements its own custom classloader that loads code and static resources into a private area and that respects export and import statements.

Modules have a well-defined way of resolving logical addresses to physical endpoints within a private address space and an import/export mechanism for logical addresses. Application software architects can use these capabilities when linking modules together to compose the layers of a software system design.

Looking at our prototype client code again:

req = context.createRequest("resource:/customers/");
req.setVerb(Request.SOURCE);
req.setType(DOM.class);
rep = context.issueRequest(req);

We know that the microkernel searches for a mapping from the address "resource:/customers/" to an endpoint. The endpoint might exist in the current address space, an imported address space, or even in an address space further up the request stack. The developer who writes the client code does not need to know anything about the large scale structure of the application. The developer only needs to make a request for the information required. It is up to the application composer or architect to determine the layering and large scale structure of the application. In fact, the application structure can changed after both the client and endpoint code is written with no impact whatsoever to the existing Java code.

Transrepresentation

The advantages of logical address mapping can be further augmented with the idea of transrepresentation, as introduced in the first article. With support for transrepresentation (also known as transreption), client and endpoint code is decoupled even with respect to type.

If a client requests that information be returned in a particular representation type, then the microkernel will forward that type information to the endpoint. The endpoint may or may not be able to deliver the information in the requested form. If it cannot, the endpoint will return whatever representation type it knows how to return. (Many endpoints may, in fact, be written to return only one form of information).

If the endpoint does not return the information in the requested form, the microkernel will search for a transreption service (transreptor) that can transform the returned representation type automatically. This transreption will occur transparently to both the client and the service. In our example, if the endpoint mapped to "resource:/customers/" returns information as a JSON object, then the microkernel will search for a transreption service that can change the form of the information from JSON to XML DOM.

If the microkernel cannot locate an appropriate transreptor or if the transreptor fails, an error is returned to the client. This signals to the client that the system could not provide the information in the form requested. It is important to note that a transreption service does not change the information itself, it only changes the physical form (representation) of the information. Thus, there is never a chance that information will be returned to the client that the client did not request.

There is no equivalent to the idea of transreption in the World Wide Web - this is an innovation that currently applies only to application software development. From the broader perspective of general computing, however, transreption is an interesting idea that goes beyond just providing additional decoupling between client and endpoint code. Many computing tasks which transform information from one representation into another can be considered to be transreption tasks. This includes processes which have, traditionally, been implemented as standalone tools such as parsers, compilers, etc. By treating these information transformers as transreptors, they can be smoothly and transparently incorporated into our ROC architecture as just another logically addressed service.

Caching

According to the REST design approach, returned resource representations are copies of the information located at a resource address. And, importantly, they are immutable copies of the information which was current at the time of the request. Because the representation is immutable, there is no difference between recomputing the representation and reading the immutable copy - as long as the information managed by the endpoint has not changed.

This leads to the idea of using a cache to save computed resource representations and somehow keep track of the validity of the information. Our microkernel can cache returned representations by simply associating the URI address of the request with the representation. The URI becomes the cache key and the representation is stored as the cache value. With a cache in place, client requests may be satisfied by retrieving a cached representation without ever resending the request to an endpoint. The cache can also maintain a dependency hierarchy which will atomically and transparently invalidate all cache entries that are dependent on a resource that has become invalid.

Scheduling

Our ROC architecture also has several important ramifications for process scheduling. When a client sends a resource request to the microkernel, the microkernel is free to use the provided thread or to schedule processing on one or more other threads. It is also free to determine when to schedule processing at the endpoint, which endpoint to use, and even the priority of the request relative to other requests.

With full decoupling of clients and endpoints, the microkernel is able to schedule work across CPU cores analogous to the way a load balancer distributes work across servers in a large web site's server farm. Just as a browser has no clue whether a web site has a single server or ten thousand servers, client code in our architecture is unaware of whether the machine executing its request has only a single core or sixty-four cores. And that means that application software can scale up with the addition of CPU cores without the software developer writing any thread aware code. This is a very significant result because, as many know by now, writing code to perform well on multiple CPU cores is a challenging task.

Implications

In the first and second articles we have described an architecture that brings the flexibility of the Web to application software. Realizing an implementation of this architecture leads to a combination of technologies and ideas which has several interesting implications, including:

Decoupling - a long sought characteristic that leads to flexibility - is provided for both physical coupling and type coupling.

Multi-core scaling. Writing software to take advantage of emerging multi-core computers is very challenging. Our approach mitigates this issue, both by allowing application software to scale with cores and by not requiring developers to write multi-threaded code.

Caching and Dependency Tracking eliminates mindless redundant computation across whole applications and systems. Since all requests are based on logical addresses they are all candidates for caching. This optimization is transparent across entire systems and can greatly reduce server overhead. In addition, caching facilitates the automatic rebalancing of a server as the workload changes.

Container Management is Simplified. As physical containers, modules organize code (endpoints) and static resources and provide a private logical address space. Because resources in modules are logically coupled to other parts of the system, they can be replaced (updated, rolled back, etc.) as the system runs. (All that is required is to have the microkernel temporarily queue requests to a module for the time it takes to do the physical swap). Modules can also have version numbers, allowing different versions to be run simultaneously. The microkernel will always resolve a versioned request to the correct version of a module.

Conclusion

This article rounds-out the discussion of a RESTful approach to application software design at the physical code level. The Resource Oriented Computing approach is not only simple, powerful, and elegant but it also leads to more flexible software applications and systems. Upcoming articles will move towards the logical level to examine how to integrate different programming languages as services and how to compose layered applications using ROC. We will also explore the new set of architectural and design patterns that become available to us at the logical level.

References

[1] A RESTful Core for Web-like Application Flexibility - Part 1


PRINTER FRIENDLY VERSION


News | Blogs | Discussions | Tech talks | Patterns | Reviews | White Papers | Downloads | Articles | Media kit | About
Java Solutions
All Content Copyright ©2007 TheServerSide Privacy Policy
Site Map