|
|
Below are some attempts to get to grips with the part of the NCIP XML Schema relating to borrower information requests. Specifically, I've concentrated on the LookupUser request and LookupUserResponse. I wanted to work out whether there is some mapping from REST to NCIP (and vice versa). This could help determine the services which Jangle connectors should provide, and give some ideas about the type of data each service should receive and return.
First version of the LookupUser XML
I used Eclipse's XML Schema tools to generate the XML for a LookupUser request. This is what I got:
<?xml version="1.0" encoding="UTF-8"?>
<LookupUser xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.niso.org/ncip/v1_0/imp1/xtd/ncip_v1_0.xsd">
<InitiationHeader>
<FromAgencyId>
<UniqueAgencyId>
<Scheme>Scheme</Scheme>
<Value>Value</Value>
</UniqueAgencyId>
</FromAgencyId>
<ToAgencyId>
<UniqueAgencyId>
<Scheme>Scheme</Scheme>
<Value>Value</Value>
</UniqueAgencyId>
</ToAgencyId>
</InitiationHeader>
<UniqueUserId>
<UniqueAgencyId>
<Scheme>Scheme</Scheme>
<Value>Value</Value>
</UniqueAgencyId>
<UserIdentifierValue>UserIdentifierValue</UserIdentifierValue>
</UniqueUserId>
</LookupUser>Notice that a large part is an InitiationHeader, which specifies the agencies involved in making and responding to this request. The UniqueUserId element contains the query for this request: basically asking for information about a specified user.
The main issues are what has to go into the UniqueAgencyId elements.
Filling in the UniqueAgencyId
I interpolated UniqueAgencyIds using a presentation, which gives some examples of valid NCIP requests. (By the way, this presentation is the best one I've found for a succinct explanation of NCIP with examples.)
I also found this document, which gives sample requests and responses for a subset of the NCIP spec..
The presentation gives an example of how to represent a UniqueAgencyId using Scheme and Value elements:
<UserPrivilege>
<UniqueAgencyId>
<Scheme>http://www.librarylist.org</Scheme>
<Value>Needleman Library</Value>
</UniqueAgencyId>
<AgencyUserPrivilegeType>
<Scheme>http://www.needleman.com/patrons</Scheme>
<Value>Platinum User</Value>
</AgencyUserPrivilegeType>
.
.
.
</UserPrivilege>Here are the official, informal definitions of Scheme and Value (from NCIP part 1):
Scheme is a text string used to identify the authority determining the list of values for the enumeration and Value is a number, code, word, or phrase from that scheme.
My inference is that these function in a similar way to an XML schema: but rather than defining the structure for a class of documents, a Scheme enumerates the range of values a particular class of data can be instantiated from.
In the case of our implementation, we have three UniqueAgencyIds to contend with, specified inside the following parent elements:
- FromAgencyId: The agency which originates the request. In most cases, this is the client of the web service. The agency ID value in this case could be, for example:
- The IP address of the client.
- An API key belonging to the client.
- An arbitrary name assigned to the system making the request. (Same as an API key, really.)
- ToAgencyId: The agency for which the request is intended. The value for this could be:
- A network identifier for the instance of the application, e.g. the root URI of the API.
- The name of the organisation where the API resides.
- Some other arbitrary identifier for the API.
- UniqueUserId: The UniqueIdentifierValue is defined in the NCIP spec. as a "Text string; specifies a User within an Agency"; example values are "patron bar code, patron record number, university identification number". If this is the case, the UniqueAgencyId in this case should determine the type of identifier represented by the UniqueIdentifierValue.
Taking this into account, here are some example settings for these elements:
| Element | Setting |
| FromAgencyId/UniqueAgencyId/Scheme | http://tools.ietf.org/html/rfc791 |
| FromAgencyId/UniqueAgencyId/Value | 10.0.0.4 |
| ToAgencyId/UniqueAgencyId/Scheme | http://tools.ietf.org/html/rfc3986 |
| ToAgencyId/UniqueAgencyId/Value | http://api.jangle.org/ |
| UniqueUserId/UniqueAgencyId/Scheme | http://jangle.org/schemes/borrowerIdentifierType |
| UniqueUserId/UniqueAgencyId/Value | Barcode |
Notes:
- The FromAgencyId could be set to the client's IP address, and the scheme set to the RFC which defines valid IP addresses.
- The ToAgencyId can be defined using a URI (the location of the API): hence the scheme for this can be set to the URI of the RFC which describes the syntax for URIs(!).
- http://jangle.org/schemes/borrowerIdentifierType references a scheme which describes valid values for Jangle borrower identifier types (in line with the approach used for UserElementType elsewhere in the NCIP spec.)
Plugging these into the sample request gives:
<?xml version="1.0" encoding="UTF-8"?>
<LookupUser xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.niso.org/ncip/v1_0/imp1/xtd/ncip_v1_0.xsd">
<InitiationHeader>
<FromAgencyId>
<UniqueAgencyId>
<Scheme>http://tools.ietf.org/html/rfc791</Scheme>
<Value>10.0.0.4</Value>
</UniqueAgencyId>
</FromAgencyId>
<ToAgencyId>
<UniqueAgencyId>
<Scheme>http://tools.ietf.org/html/rfc3986</Scheme>
<Value>http://api.jangle.org/</Value>
</UniqueAgencyId>
</ToAgencyId>
</InitiationHeader>
<UniqueUserId>
<UniqueAgencyId>
<Scheme>http://jangle.org/schemes/borrowerIdentifierType</Scheme>
<Value>Barcode</Value>
</UniqueAgencyId>
<UserIdentifierValue>00000001</UserIdentifierValue>
</UniqueUserId>
</LookupUser>We now have a request from some client identified by the IP address 10.0.0.4, to the API identified located at http://api.jangle.org/, asking for a lookup on the user identified by the barcode 00000001.
Flattening out the request XML
Imagine we want Jangle's REST interface to be able to comprehend this request format. But at the same time we don't want a client to have to jump through hoops to generate this much XML. What can we do to make the client's life simpler while preserving all the information required to make a LookupUser request in NCIP format? Here are some suggestions:
- The API URI is implicit: it is part of the URL we call when requesting the service over HTTP. So when the client's call reaches the REST API, Jangle can infer this.
- The client's IP address is available as part of the HTTP request. It, too, can be inferred and added into the NCIP request by Jangle.
- The Borrower Identifier Type and Value will need to be passed with the GET request, but could be set to default values if not supplied.
- The UserIdentifierValue identifies a borrower resource to Jangle: it should be part of the path.
- The enclosing LookupUser element identifies the type of service which should be invoked on the ToAgencyId target API. This should be encoded into the URL which the client requests.
The fallout from this is that we "flatten" the LookupUser request into a simple GET request on the API which looks like this:
GET /LookupUser/00000001?BorrowerIdentifierType=Barcode HTTP/1.1 Host: api.jangle.org ...
Note that the IP address is not visible in the HTTP request, but is available to the application.
Jangle could then expand this simplified REST request into a full NCIP-compliant LookupUser service request. This gives two implementation scenarios:
- Jangle acting as a thin REST translation layer between a client and an NCIP-compliant back-end service. Jangle receives REST requests, translates them into NCIP requests, and passes them through to the underlying back-end service.
- Jangle receiving NCIP requests and converting them into their equivalent REST requests, before calling them on the Jangle service layer.
Implications
This suggests some general translation rules to turn NCIP requests into REST requests:
- The name of the top-level XML element provides the name of the service in the requested API path.
- The from and to agencies are inferred from the network locations of the client and API respectively.
- UniqueAgencyIds used within the main part of the request (not part of the InitiationHeader) will be specific to the type of request. They should be supplied by the client, as querystring variables in the case of a GET or DELETE, or as body parameters for POST and PUT.
- Lookup services should respond to GETs; Update services should respond to POSTs; Create services should respond to PUTs; Cancel services should respond to DELETEs.
LookupUserResponse
I think the above demonstrates that a subset of the LookupUser request can be readily (even automatically) translated into a RESTful equivalent.
Next, I decided to work out what kind of response a RESTful service should return when it receives a LookupUser request.
Here's what NCIP specifies as a minimum (again ignoring optional elements for now):
<?xml version="1.0" encoding="UTF-8"?>
<LookupUserResponse xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.niso.org/ncip/v1_0/imp1/xtd/ncip_v1_0.xsd">
<ResponseHeader>
<FromAgencyId>
<UniqueAgencyId>
<Scheme>http://tools.ietf.org/html/rfc3986</Scheme>
<Value>http://api.jangle.org/</Value>
</UniqueAgencyId>
</FromAgencyId>
<ToAgencyId>
<UniqueAgencyId>
<Scheme>http://tools.ietf.org/html/rfc791</Scheme>
<Value>10.0.0.4</Value>
</UniqueAgencyId>
</ToAgencyId>
</ResponseHeader>
<UniqueUserId>
<UniqueAgencyId>
<Scheme>http://jangle.org/schemes/borrowerIdentifierType</Scheme>
<Value>Barcode</Value>
</UniqueAgencyId>
<UserIdentifierValue>00000001</UserIdentifierValue>
</UniqueUserId>
</LookupUserResponse>
Notice that this just returns the same data we sent in with the request (other than we get a response header instead of an initiation header). This isn't much use.
To get more data back, the LookupUser request has to specify the fields the service should return. This is done using UserElementType elements inside the root LookupUser element, e.g.
<UserElementType> <Scheme>Scheme</Scheme> <Value>Value</Value> </UserElementType>
The Scheme for UserElementType elements is defined in http://www.niso.org/ncip/v1_0/schemes/userelementtype (which produces a 404, but is listed as the Scheme in NCIP part 1).
The Values available in this scheme are:
- Authentication Input
- Block Or Trap
- Date Of Birth
- Name Information
- User Address Information
- User Language
- User Privilege
- Visible User Id
For example, to get the borrower's date of birth and name, the request should look like this:
<?xml version="1.0" encoding="UTF-8"?>
<LookupUser xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.niso.org/ncip/v1_0/imp1/xtd/ncip_v1_0.xsd">
<InitiationHeader>
<FromAgencyId>
<UniqueAgencyId>
<Scheme>http://tools.ietf.org/html/rfc791</Scheme>
<Value>10.0.0.4</Value>
</UniqueAgencyId>
</FromAgencyId>
<ToAgencyId>
<UniqueAgencyId>
<Scheme>http://tools.ietf.org/html/rfc3986</Scheme>
<Value>http://api.jangle.org/</Value>
</UniqueAgencyId>
</ToAgencyId>
</InitiationHeader>
<UniqueUserId>
<UniqueAgencyId>
<Scheme>http://jangle.org/schemes/borrowerIdentifierType</Scheme>
<Value>Barcode</Value>
</UniqueAgencyId>
<UserIdentifierValue>00000001</UserIdentifierValue>
</UniqueUserId>
<UserElementType>
<Scheme>http://www.niso.org/ncip/v1_0/schemes/userelementtype</Scheme>
<Value>Date Of Birth</Value>
</UserElementType>
<UserElementType>
<Scheme>http://www.niso.org/ncip/v1_0/schemes/userelementtype</Scheme>
<Value>Name Information</Value>
</UserElementType>
</LookupUser>The response now includes a UserOptionalFields element containing the requested information:
<?xml version="1.0" encoding="UTF-8"?>
<LookupUserResponse xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.niso.org/ncip/v1_0/imp1/xtd/ncip_v1_0.xsd">
<ResponseHeader>
<FromAgencyId>
<UniqueAgencyId>
<Scheme>http://tools.ietf.org/html/rfc3986</Scheme>
<Value>http://api.jangle.org/</Value>
</UniqueAgencyId>
</FromAgencyId>
<ToAgencyId>
<UniqueAgencyId>
<Scheme>http://tools.ietf.org/html/rfc791</Scheme>
<Value>10.0.0.4</Value>
</UniqueAgencyId>
</ToAgencyId>
</ResponseHeader>
<UniqueUserId>
<UniqueAgencyId>
<Scheme>http://jangle.org/schemes/borrowerIdentifierType</Scheme>
<Value>Barcode</Value>
</UniqueAgencyId>
<UserIdentifierValue>00000001</UserIdentifierValue>
</UniqueUserId>
<UserOptionalFields>
<NameInformation>
<PersonalNameInformation>
<UnstructuredPersonalUserName>Elliot Smith</UnstructuredPersonalUserName>
</PersonalNameInformation>
</NameInformation>
<DateOfBirth>2001-12-31T12:00:00</DateOfBirth>
</UserOptionalFields>
</LookupUserResponse>Flattening out this more complex request
We need room to specify two more pieces of data in the REST request, representing the names of the optional fields we want to get back in the response. The easiest way to do this would be to add them onto the querystring as UserElementType variables, e.g.
/LookupUser/00000001?BorrowerIdentifierType=Barcode&UserElementType=NameInformation&UserElementType=DateOfBirth
Notice that I've elided the Scheme for each of these UserElementTypes, as the NCIP spec. fixes this enumeration to values in a single scheme (http://www.niso.org/ncip/v1_0/schemes/userelementtype), and is therefore redundant information.
I would probably go further and rename the querystring variables to make the URIs more "fluent":
/user/00000001?By=Barcode&Show=Name&Show=DateOfBirth
Handling missing users
If there is a problem responding to a request, NCIP specifies that a response specifying either a MessagingError or ProcessingError should be returned. The difference is:
- A MessagingError is used where the client sends a broken request, e.g. missing a required XML element.
- A ProcessingError is returned where the service cannot fulfil the request. This would be used for situations where the requested data cannot be found or is otherwise unavailable. This type of error also covers situations where a service is unwilling to respond to a request (e.g. client lacks proper authentication).
The latter of these two seems most appropriate where a client requests details for a user and their details are not available. The structure of a response containing ProcessingErrors looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<LookupUserResponse xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://www.niso.org/ncip/v1_0/imp1/xtd/ncip_v1_0.xsd">
<ResponseHeader>
<FromAgencyId>
<UniqueAgencyId>
<Scheme>http://tools.ietf.org/html/rfc3986</Scheme>
<Value>http://api.jangle.org/</Value>
</UniqueAgencyId>
</FromAgencyId>
<ToAgencyId>
<UniqueAgencyId>
<Scheme>http://tools.ietf.org/html/rfc791</Scheme>
<Value>10.0.0.4</Value>
</UniqueAgencyId>
</ToAgencyId>
</ResponseHeader>
<Problem>
<ProcessingError>
<ProcessingErrorType>
<Scheme>Scheme</Scheme>
<Value>Value</Value>
</ProcessingErrorType>
<ProcessingErrorElement>
<ElementName>ElementName</ElementName>
</ProcessingErrorElement>
</ProcessingError>
</Problem>
</LookupUserResponse>The scheme to be used for ProcessingErrorType relating to LookupUser is specified (in Part 2 of the NCIP standard as being located at:
http://www.niso.org/ncip/v1_0/schemes/processingerrortype/lookupuserprocessingerror.scm
Unfortunately, this URL isn't available. I decided to go with "Unknown User" as a reasonable value, which is listed in the NCIP spec. under a different scheme.
The final issue is the ProcessingErrorElement. This is one area which is particularly nasty, as this element should contain nested XML elements representing the part of the original request which caused the error.
Sign in to add a comment
