How can temporal and activity-specific state data be efficiently persisted?
Services required to repeatedly exchange larger amounts of the same data can impose unnecessary runtime performance and bandwidth burden.
Services are designed to leverage built-in caching features provided by the transport communications framework in order to defer and persist some forms of state data.
Available caching features of an application protocol (most commonly HTTP) are incorporated into service and composition designs.
Additional infrastructure may be required to effectively apply this pattern in high usage environments.
ArchitectureInventory, Composition, Service
ContributorsRaj Balasubramanian, Jim Webber, Thomas Erl, David Booth
Typical services are dynamic in nature, but there are instances when a subset of the information processed seldom changes. Repeatedly transmitting relatively static, state-specific data can be wasteful.
Then there are instances when the information is stable for a given period of time. In this case, the service is required to keep the data in memory while it perhaps waits for other processing to complete. This can be equally wasteful and can impede the service's scalability.
A transport communications framework with built-in caching features can be leveraged as a runtime state deferral mechanism. This allows the service to temporarily off-load static or idle state data until it needs to retrieve it at a later point. The result is that less data needs to be repeatedly transmitted and the service¡¦s overall memory consumption is reduced.
Within contemporary service inventory architectures, this pattern is most commonly applied using the caching features provided by HTTP.
HTTP supplies a set of headers and associated rules that can be used to identify a cache, how (and how long) to cache state data and when it should be cached. By using these features, there are three common cache types that can be employed:
- Gateway Caching - The service can using the centralized HTTP caching facility that is built into any Web server. This caching model is based on a reverse proxy that makes the decision as to whether to forward a request from the consumer to the service or to look it up in its cache. There are several variations of this caching model available in supplementary proxy products and appliances.
- Intermediary Proxy Caching - Service consumers can use an intermediary facility based on a standard proxy server that provides a shared HTTP cache. Consumers will have to point to this proxy server before making any requests to the service itself.
- Client-side Caching - With this approach the cache resides in the presentation layer, usually as part of a rich application interface or Web browser. This option only pertains to services with which client-side programs interact directly. A common example is the use of REST services that help assemble presentation-centric mashups.
In addition to the caching options or strategies mentioned above, there are specific directives that the HTTP specification makes available to developers to implement caching, specifically leveraging HTTP headers, as follows:
- The Response header can be used to dictate whether or not to cache a body of state data. In HTTP 1.1 it is represented by the Expires and Cache-Control header elements.
- The Last-Modified response header and the If-Modified-Since request headers can be used to implement caching logic depending on when the state data was altered. The corresponding headers in HTTP 1.1 are the ETag response header and the If-None-Match request header. The decision to use a given header is made based on the benefits to the overall service architecture.
The caching functionality provided by a transport communications framework can be convenient and efficient, but it may require additional infrastructure and associated caching configurations. Furthermore, there may be security implications to having data reside on the transport level, rather than behind the service contract as when State Repository or Stateful Services are applied.
Case Study Example
Alleywood Lumber has been asked by all of its contractors to provide a service to summarize orders filled within the last month. Alleywood decides to publish a REST service along with a corresponding specification that indicates that only the GET and HEAD methods will be made available. The service contract design from this specification is shown in the following table:
In its initial release, all access to the resource URI is protected by HTTP Basic Authentication and over HTTP (instead of HTTPS), since most of the consumers are required to access the service over a secure VPN.
Because the service implements an XML-based representation for OrderHistory. The schema is shown below-
<xsd:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://alleywood/entity/orderhistory"> <xsd:element name="orders"> <xsd:complexType> <xsd:sequence> <xsd:element maxOccurs="unbounded" name="order"> <xsd:complexType> <xsd:sequence> <xsd:element name="placedOn" type="xsd:string"/> <xsd:element name="placedBy" type="xsd:string"/> <xsd:element name="totalPrice" type="xsd:string"/> <xsd:element name="items"> <xsd:complexType> <xsd:sequence> <xsd:element maxOccurs="unbounded" name="item"> <xsd:complexType> <xsd:sequence> <xsd:element name="name" type="xsd:string"/> <xsd:element name="qty" type="xsd:unsignedByte"/> <xsd:element name="unitprice" type="xsd:string"/> </xsd:sequence> </xsd:complexType> </xsd:element> </xsd:sequence> </xsd:complexType> </xsd:element> </xsd:sequence> <xsd:attribute name="num" type="xsd:unsignedInt" use="required"/> <xsd:attribute name="acct" type="xsd:string" use="required"/> </xsd:complexType> </xsd:element> </xsd:sequence> <xsd:attribute name="customer_id" type="xsd:string" use="required"/> </xsd:complexType> </xsd:element> </xsd:schema>
A typical response to the OrderHistory service might produce a message like this:
<orders customer_id="abc2781" xmlns="http://alleywood/entity/orderhistory"> <order num="0101001092" acct="19289a"> <placedOn>10/10/01</placedOn> <placedBy>John C Randall</placedBy> <totalPrice>$280.00</totalPrice> <items> <item> <name>GradeA Posts</name> <qty>10</qty> <unitprice>$14.00</unitprice> </item> <item> <name>GradeC Posts</name> <qty>20</qty> <unitprice>$7.00</unitprice> </item> </items> </order> <order num="10118" acct="19289a"> <placedOn>10/10/02</placedOn> <placedBy>John C Randall</placedBy> <totalPrice>$1400.00</totalPrice> <items> <item> <name>GradeA Posts</name> <qty>100</qty> <unitprice>$14.00</unitprice> </item> </items> </order> </orders>
Because the order history for the previous month can be generated on the first day of the following month and then doesn't change during that following month, the development team considers using Transport Caching to optimize the usage of this data.
In order to define their caching strategy, they look at when the order history needs to be cached and when the cache needs to expire. They devise a simple caching rule:
If the order history for the previous month exists then reply with the order history Else create the order history for the previous month respond with the created order history set the Expires HTTP header in the response to be the end of the current month
The Expires element in the response header looks like this:
Expires: Mon, 14 April 2009 16:00:00 GMT
Therefore, a service consumer with a customer ID of ab1829 accessing the OrderHistory service using the URI /service/ab1829/orders on April 14, 2009, will receive the orders from March 2009 with the Expires header set to "Wed, 30 April 2009 23:59:59 GMT".