Data Managers and Data Requests

Data managers and data requests provide the mechanism in Fiz for accessing and modifying the data used in a Web application. Typically the data is stored separately from the application, for example in a relational database. Two goals motivated the design of Fiz's mechanisms:

  • Support a variety of data sources. Most Web frameworks have focused on supporting relational databases but don't provide good support for other kinds of data. However, relational databases aren't scalable enough to meet the needs of the largest Web applications, so new data sources are appearing such as Bigtable and memcached. Fiz makes it easy for you to use these additional data sources as well as relational databases. You can also use the data manager mechanism to connect to other applications or access files on disk.
  • Allow asynchronous/concurrent requests. In complex Web applications a single Web page may require data from dozens or even hundreds of sources. In most frameworks the application must fetch this data sequentially, one source at a time; this can result in long page generation times. With Fiz data managers you can initiate many requests simultaneously, resulting in much faster rendering of complex pages.

Data managers

A data manager is a Java class that provides access to a particular kind of data; different data managers represent different kinds of data, such as relational databases, Excel spreadsheet files, a news feed, or an enterprise application. In most cases the data manager does not store the data itself; it simply provides access to data stored elsewhere. Fiz currently contains the following built-in data managers:

  • SqlDataManager: provides access to any relational database supported by the JDBC mechanism.
  • FileDataManager: provides access to Fiz datasets stored in files on disk.
  • RawDataManager: used to generate data requests with predetermined results; typically used for testing.

Data requests

The interactors and sections in a Fiz application communicate with data managers using objects of type DataRequest. Each DataRequest object represents a single operation carried out by a single data manager. There are three parties involved with each data request:

  • The creator: data requests are usually created by interactors to retrieve data that is needed to render a Web page. The interactor supplies parameters defining the request, such as the database table and rows that are desired.
  • The data manager: provides methods to create requests, and also executes the requests once they have been created.
  • The consumer: the result of a data request is typically used by one or more sections to render HTML.

A data request is created by invoking a method on a data manager object. Consider the following example:

DataRequest request = sql.newFindRequest("people", "state", "California");

In this example sql is a SqlDataManager object; the method returns a data request that will search the table named people and return all records whose state column is California. The request has not necessarily completed at the time newFindRequest returns; the data manager will execute the request in the background. Normally, all of the requests needed for a particular page get created at the beginning of the rendering process, so they can all execute in parallel.

When you reach a point where you need to use the result of a request, invoke the getResponseData method on the request:

Dataset result = request.getResponseData();

This method will wait for the request to complete and return a dataset containing the result of the request. All data requests always return a dataset as result; this common format makes it easy to interchange data managers and sections. In this case, the response dataset contains one child dataset named record for each matching record found in the database; the child dataset will contain name-value pairs with the contents of the record.

If an error occurs while executing a data request then getResponseData returns null. You can then invoke the getErrorData method on the request, which will return error information in the form of an array of datasets, one for each error that occurred during the request. Each error dataset contains one or more entries describing a particular problem. In general, the names and values in an error dataset can be different for every error and every data manager, but certain names are used consistently in Fiz:

message

A human-readable string describing what happened. This field must be present in every error dataset.

culprit

If a parameter or input value for the request had an illegal value, this field gives the name of that parameter or data item. It's used, for example, identify a particular form element in which bad data was entered, so that the error message can be displayed next to that element.

The DataRequest class includes several other methods for handling errors. For example, getErrorMessage will generate a single string describing all of the errors for the request, and getResponseOrAbort is identical to getResponseData except that if an error occurred it automatically generates a Java Error containing information about all the errors that occurred.

Passing data requests between interactors and sections

Most sections in Fiz are designed to work with data from a variety of sources; information about which data to use is provided to the sections when they are created. This can happen in two ways. The most common approach is for the interactor to create a data request, register it in the ClientRequest under a particular name, and pass the name to the section as a configuration property:

cr.addDataRequest("CaliforniaPeople",
        sql.newFindRequest("people", "state", "California"));
...
new TableSection(new Dataset("request", "CaliforniaPeople") ...);

When the section needs the data from the request, it invokes cr.getDataRequest("CaliforniaPeople") to retrieve the data request. The request begins executing at the time of the newFindRequest call.

The second approach is for the interactor to provide the section with the name of a factory method that will generate the data request on demand. The section then invokes this method to create the request. This allows the section to invoke multiple requests. In addition, the section can pass parameters to the factory method so that different requests can behave slightly differently. See the requestFactory method of the TreeSection class for an example of this usage.

Writing data managers

Creating a new data manager is fairly straightforward: just create a class with one method for each operation supported by the data manager. Each method has a name of the form newXyzRequest where Xyz is the name of the operation. The method returns a DataRequest object, or subclass thereof. Normally the method returns immediately and arranges for the request to execute in a background thread (though for some very simple operations it may make sense to complete the operation immediately). Once the operation has been completed, the data manager invokes the setComplete method on the data request to signal that the request has finished, and to provide the response dataset. If an error occurs, the data manager can invoke setError instead of setComplete.