+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>\r
-<chapter xml:id="opensrf" xmlns="http://docbook.org/ns/docbook" version="5.0" xml:lang="EN"\r
- xmlns:xi="http://www.w3.org/2001/XInclude" xmlns:xlink="http://www.w3.org/1999/xlink">\r
- <chapterinfo>\r
- <title>OpenSRF</title>\r
- </chapterinfo>\r
- <abstract id="openSRF_abstract">\r
- <simpara>One of the claimed advantages of\r
- Evergreen over alternative integrated library systems is the underlying Open\r
- Service Request Framework (OpenSRF, pronounced "open surf") architecture. This\r
- article introduces OpenSRF, demonstrates how to build OpenSRF services through\r
- simple code examples, and explains the technical foundations on which OpenSRF\r
- is built. This chapter was taken from Dan Scott's <emphasis>Easing gently into OpenSRF</emphasis> article, June, 2010.</simpara>\r
- </abstract>\r
- <section id="_introducing_opensrf">\r
- <title>Introducing OpenSRF</title>\r
- <indexterm><primary>OpenSRF</primary></indexterm>\r
- <simpara>OpenSRF is a message routing network that offers scalability and failover\r
- support for individual services and entire servers with minimal development and\r
- deployment overhead. You can use OpenSRF to build loosely-coupled applications\r
- that can be deployed on a single server or on clusters of geographically\r
- distributed servers using the same code and minimal configuration changes.\r
- Although copyright statements on some of the OpenSRF code date back to Mike\r
- Rylander’s original explorations in 2000, Evergreen was the first major\r
- application to be developed with, and to take full advantage of, the OpenSRF\r
- architecture starting in 2004. The first official release of OpenSRF was 0.1 in\r
- February 2005 (<ulink url="http://evergreen-ils.org/blog/?p=21">http://evergreen-ils.org/blog/?p=21</ulink>), but OpenSRF’s development\r
- continues a steady pace of enhancement and refinement, with the release of\r
- 1.0.0 in October 2008 and the most recent release of 1.2.2 in February 2010.</simpara>\r
- <simpara>OpenSRF is a distinct break from the architectural approach used by previous\r
- library systems and has more in common with modern Web applications. The\r
- traditional "scale-up" approach to serve more transactions is to purchase a\r
- server with more CPUs and more RAM, possibly splitting the load between a Web\r
- server, a database server, and a business logic server. Evergreen, however, is\r
- built on the Open Service Request Framework (OpenSRF) architecture, which\r
- firmly embraces the "scale-out" approach of spreading transaction load over\r
- cheap commodity servers. The <ulink url="http://evergreen-ils.org/blog/?p=56">initial GPLS\r
- PINES hardware cluster</ulink>, while certainly impressive, may have offered the\r
- misleading impression that Evergreen requires a lot of hardware to run.\r
- However, Evergreen and OpenSRF easily scale down to a single server; many\r
- Evergreen libraries run their entire library system on a single server, and\r
- most OpenSRF and Evergreen development occurs on a virtual machine running on a\r
- single laptop or desktop image.</simpara>\r
- <simpara>Another common concern is that the flexibility of OpenSRF’s distributed\r
- architecture makes it complex to configure and to write new applications. This\r
- article demonstrates that OpenSRF itself is an extremely simple architecture on\r
- which one can easily build applications of many kinds – not just library\r
- applications – and that you can use a number of different languages to call and\r
- implement OpenSRF methods with a minimal learning curve. With an application\r
- built on OpenSRF, when you identify a bottleneck in your application’s business\r
- logic layer, you can adjust the number of the processes serving that particular\r
- bottleneck on each of your servers; or if the problem is that your service is\r
- resource-hungry, you could add an inexpensive server to your cluster and\r
- dedicate it to running that resource-hungry service.</simpara>\r
- <simplesect id="_programming_language_support">\r
- <title>Programming language support</title>\r
- <simpara>If you need to develop an entirely new OpenSRF service, you can choose from a\r
- number of different languages in which to implement that service. OpenSRF\r
- client language bindings have been written for C, Java, JavaScript, Perl, and\r
- Python, and service language bindings have been written for C, Perl, and Python.\r
- This article uses Perl examples as a lowest common denominator programming\r
- language. Writing an OpenSRF binding for another language is a relatively small\r
- task if that language offers libraries that support the core technologies on\r
- which OpenSRF depends:</simpara>\r
- <itemizedlist>\r
- <listitem>\r
- <simpara>\r
- <ulink url="http://tools.ietf.org/html/rfc3920">Extensible Messaging and Presence\r
- Protocol</ulink> (XMPP, sometimes referred to as Jabber) - provides the base messaging\r
- infrastructure between OpenSRF clients and services\r
- </simpara>\r
- <indexterm><primary>XMPP</primary></indexterm>\r
- </listitem>\r
- <listitem>\r
- <simpara>\r
- <ulink url="http://json.org">JavaScript Object Notation</ulink> (JSON) - serializes the content\r
- of each XMPP message in a standardized and concise format\r
- </simpara>\r
- </listitem>\r
- <listitem>\r
- <simpara>\r
- <ulink url="http://memcached.org">memcached</ulink> - provides the caching service\r
- </simpara>\r
- <indexterm><primary>memcached</primary></indexterm>\r
- </listitem>\r
- <listitem>\r
- <simpara>\r
- <ulink url="http://tools.ietf.org/html/rfc5424">syslog</ulink> - the standard UNIX logging\r
- service\r
- </simpara>\r
- <indexterm><primary>syslog</primary></indexterm>\r
- </listitem>\r
- </itemizedlist>\r
- <simpara>Unfortunately, the\r
- <ulink url="http://evergreen-ils.org/dokuwiki/doku.php?id=osrf-devel:primer">OpenSRF\r
- reference documentation</ulink>, although augmented by the\r
- <ulink url="http://evergreen-ils.org/dokuwiki/doku.php?id=osrf-devel:terms">OpenSRF\r
- glossary</ulink>, blog posts like <ulink url="http://evergreen-ils.org/blog/?p=36">the description\r
- of OpenSRF and Jabber</ulink>, and even this article, is not a sufficient substitute\r
- for a complete specification on which one could implement a language binding.\r
- The recommended option for would-be developers of another language binding is\r
- to use the Python implementation as the cleanest basis for a port to another\r
- language.</simpara>\r
- <indexterm><primary>Python</primary></indexterm>\r
- </simplesect>\r
- </section>\r
- <section id="writing_an_opensrf_service">\r
- <title>Writing an OpenSRF Service</title>\r
- <simpara>Imagine an application architecture in which 10 lines of Perl or Python, using\r
- the data types native to each language, are enough to implement a method that\r
- can then be deployed and invoked seamlessly across hundreds of servers. You\r
- have just imagined developing with OpenSRF – it is truly that simple. Under the\r
- covers, of course, the OpenSRF language bindings do an incredible amount of\r
- work on behalf of the developer. An OpenSRF application consists of one or more\r
- OpenSRF services that expose methods: for example, the <literal>opensrf.simple-text</literal>\r
- <ulink url="http://svn.open-ils.org/trac/OpenSRF/browser/trunk/src/perl/lib/OpenSRF/Application/Demo/SimpleText.pm">demonstration\r
- service</ulink> exposes the <literal>opensrf.simple-text.split()</literal> and\r
- <literal>opensrf.simple-text.reverse()</literal> methods. Each method accepts zero or more\r
- arguments and returns zero or one results. The data types supported by OpenSRF\r
- arguments and results are typical core language data types: strings, numbers,\r
- booleans, arrays, and hashes.</simpara>\r
- <simpara>To implement a new OpenSRF service, perform the following steps:</simpara>\r
- <orderedlist numeration="arabic">\r
- <listitem>\r
- <simpara>\r
- Include the base OpenSRF support libraries\r
- </simpara>\r
- </listitem>\r
- <listitem>\r
- <simpara>\r
- Write the code for each of your OpenSRF methods as separate procedures\r
- </simpara>\r
- </listitem>\r
- <listitem>\r
- <simpara>\r
- Register each method\r
- </simpara>\r
- </listitem>\r
- <listitem>\r
- <simpara>\r
- Add the service definition to the OpenSRF configuration files\r
- </simpara>\r
- </listitem>\r
- </orderedlist>\r
- <simpara>For example, the following code implements an OpenSRF service. The service\r
- includes one method named <literal>opensrf.simple-text.reverse()</literal> that accepts one\r
- string as input and returns the reversed version of that string:</simpara>\r
-<programlisting language="perl" linenumbering="unnumbered">\r
-#!/usr/bin/perl\r
-\r
-package OpenSRF::Application::Demo::SimpleText;\r
-\r
-use strict;\r
-\r
-use OpenSRF::Application;\r
-use parent qw/OpenSRF::Application/;\r
-\r
-sub text_reverse {\r
- my ($self , $conn, $text) = @_;\r
- my $reversed_text = scalar reverse($text);\r
- return $reversed_text;\r
-}\r
-\r
-__PACKAGE__->register_method(\r
- method => 'text_reverse',\r
- api_name => 'opensrf.simple-text.reverse'\r
-);\r
-</programlisting>\r
- <simpara>Ten lines of code, and we have a complete OpenSRF service that exposes a single\r
- method and could be deployed quickly on a cluster of servers to meet your\r
- application’s ravenous demand for reversed strings! If you’re unfamiliar with\r
- Perl, the <literal>use OpenSRF::Application; use parent qw/OpenSRF::Application/;</literal>\r
- lines tell this package to inherit methods and properties from the\r
- <literal>OpenSRF::Application</literal> module. For example, the call to\r
- <literal>__PACKAGE__->register_method()</literal> is defined in <literal>OpenSRF::Application</literal> but due to\r
- inheritance is available in this package (named by the special Perl symbol\r
- <literal>__PACKAGE__</literal> that contains the current package name). The <literal>register_method()</literal>\r
- procedure is how we introduce a method to the rest of the OpenSRF world.</simpara>\r
- <simplesect id="serviceRegistration">\r
- <title>Registering a service with the OpenSRF configuration files</title>\r
- <simpara>Two files control most of the configuration for OpenSRF:</simpara>\r
- <itemizedlist>\r
- <listitem>\r
- <simpara>\r
- <literal>opensrf.xml</literal> contains the configuration for the service itself, as well as\r
- a list of which application servers in your OpenSRF cluster should start\r
- the service.\r
- </simpara>\r
- </listitem>\r
- <listitem>\r
- <simpara>\r
- <literal>opensrf_core.xml</literal> (often referred to as the "bootstrap configuration"\r
- file) contains the OpenSRF networking information, including the XMPP server\r
- connection credentials for the public and private routers. You only need to touch\r
- this for a new service if the new service needs to be accessible via the\r
- public router.\r
- </simpara>\r
- <indexterm><primary>configuration files</primary><secondary>opensrf_core.xml</secondary></indexterm>\r
- </listitem>\r
- </itemizedlist>\r
- <simpara>Begin by defining the service itself in <literal>opensrf.xml</literal>. To register the\r
- <literal>opensrf.simple-text</literal> service, add the following section to the <literal><apps></literal>\r
- element (corresponding to the XPath <literal>/opensrf/default/apps/</literal>):</simpara>\r
- <indexterm><primary>configuration files</primary><secondary>opensrf.xml</secondary></indexterm>\r
-<programlisting language="xml" linenumbering="unnumbered">\r
-<apps>\r
- <opensrf.simple-text> <co id="CO1-1"/> \r
- <keepalive>3</keepalive><co id="CO1-2"/> \r
- <stateless>1</stateless><co id="CO1-3"/>\r
- <language>perl</language><co id="CO1-4"/> \r
- <implementation>OpenSRF::Application::Demo::SimpleText</implementation><co id="CO1-5"/> \r
- <max_requests>100</max_requests><co id="CO1-6"/> \r
- <unix_config>\r
- <max_requests>1000</max_requests> <co id="CO1-7"/> \r
- <unix_log>opensrf.simple-text_unix.log</unix_log> <co id="CO1-8"/> \r
- <unix_sock>opensrf.simple-text_unix.sock</unix_sock><co id="CO1-9"/> \r
- <unix_pid>opensrf.simple-text_unix.pid</unix_pid> <co id="CO1-10"/> \r
- <min_children>5</min_children> <co id="CO1-11"/> \r
- <max_children>15</max_children><co id="CO1-12"/> \r
- <min_spare_children>2</min_spare_children><co id="CO1-13"/> \r
- <max_spare_children>5</max_spare_children> <co id="CO1-14"/> \r
- </unix_config>\r
- </opensrf.simple-text>\r
-\r
- <!-- other OpenSRF services registered here... -->\r
-</apps>\r
-</programlisting>\r
- <calloutlist>\r
- <callout arearefs="CO1-1">\r
- <simpara>\r
- The element name is the name that the OpenSRF control scripts use to refer\r
- to the service.\r
- </simpara>\r
- </callout>\r
- <callout arearefs="CO1-2">\r
- <simpara>\r
- The <literal><keepalive></literal> element specifies the interval (in seconds) between\r
- checks to determine if the service is still running.\r
- </simpara>\r
- </callout>\r
- <callout arearefs="CO1-3">\r
- <simpara>\r
- The <literal><stateless></literal> element specifies whether OpenSRF clients can call\r
- methods from this service without first having to create a connection to a\r
- specific service backend process for that service. If the value is <literal>1</literal>, then\r
- the client can simply issue a request and the router will forward the request\r
- to an available service and the result will be returned directly to the client.\r
- </simpara>\r
- </callout>\r
- <callout arearefs="CO1-4">\r
- <simpara>\r
- The <literal><language></literal> element specifies the programming language in which the\r
- service is implemented.\r
- </simpara>\r
- </callout>\r
- <callout arearefs="CO1-5">\r
- <simpara>\r
- The <literal><implementation></literal> element pecifies the name of the library or module\r
- in which the service is implemented.\r
- </simpara>\r
- </callout>\r
- <callout arearefs="CO1-6">\r
- <simpara>\r
- (C implementations only): The <literal><max_requests></literal> element, as a direct child\r
- of the service element name, specifies the maximum number of requests a process\r
- serves before it is killed and replaced by a new process.\r
- </simpara>\r
- </callout>\r
- <callout arearefs="CO1-7">\r
- <simpara>\r
- (Perl implementations only): The <literal><max_requests></literal> element, as a direct\r
- child of the <literal><unix_config></literal> element, specifies the maximum number of requests\r
- a process serves before it is killed and replaced by a new process.\r
- </simpara>\r
- </callout>\r
- <callout arearefs="CO1-8">\r
- <simpara>\r
- The <literal><unix_log></literal> element specifies the name of the log file for\r
- language-specific log messages such as syntax warnings.\r
- </simpara>\r
- </callout>\r
- <callout arearefs="CO1-9">\r
- <simpara>\r
- The <literal><unix_sock></literal> element specifies the name of the UNIX socket used for\r
- inter-process communications.\r
- </simpara>\r
- </callout>\r
- <callout arearefs="CO1-10">\r
- <simpara>\r
- The <literal><unix_pid></literal> element specifies the name of the PID file for the\r
- master process for the service.\r
- </simpara>\r
- </callout>\r
- <callout arearefs="CO1-11">\r
- <simpara>\r
- The <literal><min_children></literal> element specifies the minimum number of child\r
- processes that should be running at any given time.\r
- </simpara>\r
- </callout>\r
- <callout arearefs="CO1-12">\r
- <simpara>\r
- The <literal><max_children></literal> element specifies the maximum number of child\r
- processes that should be running at any given time.\r
- </simpara>\r
- </callout>\r
- <callout arearefs="CO1-13">\r
- <simpara>\r
- The <literal><min_spare_children></literal> element specifies the minimum number of idle\r
- child processes that should be available to handle incoming requests. If there\r
- are fewer than this number of spare child processes, new processes will be\r
- spawned.\r
- </simpara>\r
- </callout>\r
- <callout arearefs="CO1-14">\r
- <simpara>\r
- The`<max_spare_children>` element specifies the maximum number of idle\r
- child processes that should be available to handle incoming requests. If there\r
- are more than this number of spare child processes, the extra processes will be\r
- killed.\r
- </simpara>\r
- </callout>\r
- </calloutlist>\r
- <simpara>To make the service accessible via the public router, you must also\r
- edit the <literal>opensrf_core.xml</literal> configuration file to add the service to the list\r
- of publicly accessible services:</simpara>\r
- <formalpara><title>Making a service publicly accessible in <literal>opensrf_core.xml</literal></title><para>\r
-<programlisting language="xml" linenumbering="unnumbered">\r
-<router><co id="CO2-1"/> \r
- <!-- This is the public router. On this router, we only register applications\r
- which should be accessible to everyone on the opensrf network -->\r
- <name>router</name>\r
- <domain>public.localhost</domain><co id="CO2-2"/>\r
- <services>\r
- <service>opensrf.math</service>\r
- <service>opensrf.simple-text</service> <co id="CO2-3"/> \r
- </services>\r
-</router>\r
-</programlisting>\r
- </para></formalpara>\r
- <calloutlist>\r
- <callout arearefs="CO2-1">\r
- <simpara>\r
- This section of the <literal>opensrf_core.xml</literal> file is located at XPath\r
- <literal>/config/opensrf/routers/</literal>.\r
- </simpara>\r
- </callout>\r
- <callout arearefs="CO2-2">\r
- <simpara>\r
- <literal>public.localhost</literal> is the canonical public router domain in the OpenSRF\r
- installation instructions.\r
- </simpara>\r
- </callout>\r
- <callout arearefs="CO2-3">\r
- <simpara>\r
- Each <literal><service></literal> element contained in the <literal><services></literal> element\r
- offers their services via the public router as well as the private router.\r
- </simpara>\r
- </callout>\r
- </calloutlist>\r
- <simpara>Once you have defined the new service, you must restart the OpenSRF Router\r
- to retrieve the new configuration and start or restart the service itself.</simpara>\r
- </simplesect>\r
- <simplesect id="_calling_an_opensrf_method">\r
- <title>Calling an OpenSRF method</title>\r
- <indexterm><primary>srfsh</primary></indexterm>\r
- <simpara>OpenSRF clients in any supported language can invoke OpenSRF services in any\r
- supported language. So let’s see a few examples of how we can call our fancy\r
- new <literal>opensrf.simple-text.reverse()</literal> method:</simpara>\r
- <simplesect id="_calling_opensrf_methods_from_the_srfsh_client">\r
- <title>Calling OpenSRF methods from the srfsh client</title>\r
- <simpara><literal>srfsh</literal> is a command-line tool installed with OpenSRF that you can use to call\r
- OpenSRF methods. To call an OpenSRF method, issue the <literal>request</literal> command and\r
- pass the OpenSRF service and method name as the first two arguments; then pass\r
- one or more JSON objects delimited by commas as the arguments to the method\r
- being invoked.</simpara>\r
- <simpara>The following example calls the <literal>opensrf.simple-text.reverse</literal> method of the\r
- <literal>opensrf.simple-text</literal> OpenSRF service, passing the string <literal>"foobar"</literal> as the\r
- only method argument:</simpara>\r
-<programlisting language="sh" linenumbering="unnumbered">\r
-$ srfsh\r
-srfsh # request opensrf.simple-text opensrf.simple-text.reverse "foobar"\r
-\r
-Received Data: "raboof"\r
-\r
-=------------------------------------\r
-Request Completed Successfully\r
-Request Time in seconds: 0.016718\r
-=------------------------------------\r
-</programlisting>\r
- </simplesect>\r
- <simplesect id="opensrfIntrospection">\r
- <title>Getting documentation for OpenSRF methods from the srfsh client</title>\r
- <simpara>The <literal>srfsh</literal> client also gives you command-line access to retrieving metadata\r
- about OpenSRF services and methods. For a given OpenSRF method, for example,\r
- you can retrieve information such as the minimum number of required arguments,\r
- the data type and a description of each argument, the package or library in\r
- which the method is implemented, and a description of the method. To retrieve\r
- the documentation for an opensrf method from <literal>srfsh</literal>, issue the <literal>introspect</literal>\r
- command, followed by the name of the OpenSRF service and (optionally) the\r
- name of the OpenSRF method. If you do not pass a method name to the <literal>introspect</literal>\r
- command, <literal>srfsh</literal> lists all of the methods offered by the service. If you pass\r
- a partial method name, <literal>srfsh</literal> lists all of the methods that match that portion\r
- of the method name.</simpara>\r
- <note><simpara>The quality and availability of the descriptive information for each\r
- method depends on the developer to register the method with complete and\r
- accurate information. The quality varies across the set of OpenSRF and\r
- Evergreen APIs, although some effort is being put towards improving the\r
- state of the internal documentation.</simpara></note>\r
-<programlisting language="sh" linenumbering="unnumbered">\r
-srfsh# introspect opensrf.simple-text "opensrf.simple-text.reverse"\r
---> opensrf.simple-text\r
-\r
-Received Data: {\r
- "__c":"opensrf.simple-text",\r
- "__p":{\r
- "api_level":1,\r
- "stream":0, <co id="CO3-1"/>\r
- "object_hint":"OpenSRF_Application_Demo_SimpleText",\r
- "remote":0,\r
- "package":"OpenSRF::Application::Demo::SimpleText", <co id="CO3-2"/>\r
- "api_name":"opensrf.simple-text.reverse",<co id="CO3-3"/>\r
- "server_class":"opensrf.simple-text",\r
- "signature":{ <co id="CO3-4"/>\r
- "params":[ <co id="CO3-5"/>\r
- {\r
- "desc":"The string to reverse",\r
- "name":"text",\r
- "type":"string"\r
- }\r
- ],\r
- "desc":"Returns the input string in reverse order\n", <co id="CO3-6"/>\r
- "return":{ <co id="CO3-7"/>\r
- "desc":"Returns the input string in reverse order",\r
- "type":"string"\r
- }\r
- },\r
- "method":"text_reverse", <co id="CO3-8"/>\r
- "argc":1 <co id="CO3-9"/>\r
- }\r
-}\r
-</programlisting>\r
- <calloutlist>\r
- <callout arearefs="CO3-1">\r
- <simpara>\r
- <literal>stream</literal> denotes whether the method supports streaming responses or not.\r
- </simpara>\r
- </callout>\r
- <callout arearefs="CO3-2">\r
- <simpara>\r
- <literal>package</literal> identifies which package or library implements the method.\r
- </simpara>\r
- </callout>\r
- <callout arearefs="CO3-3">\r
- <simpara>\r
- <literal>api_name</literal> identifies the name of the OpenSRF method.\r
- </simpara>\r
- </callout>\r
- <callout arearefs="CO3-4">\r
- <simpara>\r
- <literal>signature</literal> is a hash that describes the parameters for the method.\r
- </simpara>\r
- </callout>\r
- <callout arearefs="CO3-5">\r
- <simpara>\r
- <literal>params</literal> is an array of hashes describing each parameter in the method;\r
- each parameter has a description (<literal>desc</literal>), name (<literal>name</literal>), and type (<literal>type</literal>).\r
- </simpara>\r
- </callout>\r
- <callout arearefs="CO3-6">\r
- <simpara>\r
- <literal>desc</literal> is a string that describes the method itself.\r
- </simpara>\r
- </callout>\r
- <callout arearefs="CO3-7">\r
- <simpara>\r
- <literal>return</literal> is a hash that describes the return value for the method; it\r
- contains a description of the return value (<literal>desc</literal>) and the type of the\r
- returned value (<literal>type</literal>).\r
- </simpara>\r
- </callout>\r
- <callout arearefs="CO3-8">\r
- <simpara>\r
- <literal>method</literal> identifies the name of the function or method in the source\r
- implementation.\r
- </simpara>\r
- </callout>\r
- <callout arearefs="CO3-9">\r
- <simpara>\r
- <literal>argc</literal> is an integer describing the minimum number of arguments that\r
- must be passed to this method.\r
- </simpara>\r
- </callout>\r
- </calloutlist>\r
- </simplesect>\r
- <simplesect id="_calling_opensrf_methods_from_perl_applications">\r
- <title>Calling OpenSRF methods from Perl applications</title>\r
- <simpara>To call an OpenSRF method from Perl, you must connect to the OpenSRF service,\r
- issue the request to the method, and then retrieve the results.</simpara>\r
-<programlisting language="perl" linenumbering="unnumbered">\r
-#/usr/bin/perl\r
-use strict;\r
-use OpenSRF::AppSession;\r
-use OpenSRF::System;\r
-\r
-OpenSRF::System->bootstrap_client(config_file => '/openils/conf/opensrf_core.xml');<co id="CO4-1"/>\r
-\r
-my $session = OpenSRF::AppSession->create("opensrf.simple-text");<co id="CO4-2"/>\r
-\r
-print "substring: Accepts a string and a number as input, returns a string\n";\r
-my $result = $session->request("opensrf.simple-text.substring", "foobar", 3);<co id="CO4-3"/>\r
-my $request = $result->gather(); <co id="CO4-4"/>\r
-print "Substring: $request\n\n";\r
-\r
-print "split: Accepts two strings as input, returns an array of strings\n";\r
-$request = $session->request("opensrf.simple-text.split", "This is a test", " ");<co id="CO4-5"/>\r
-my $output = "Split: [";\r
-my $element;\r
-while ($element = $request->recv()) { <co id="CO4-6"/>\r
- $output .= $element->content . ", "; <co id="CO4-7"/>\r
-}\r
-$output =~ s/, $/]/;\r
-print $output . "\n\n";\r
-\r
-print "statistics: Accepts an array of strings as input, returns a hash\n";\r
-my @many_strings = [\r
- "First I think I'll have breakfast",\r
- "Then I think that lunch would be nice",\r
- "And then seventy desserts to finish off the day"\r
-];\r
-\r
-$result = $session->request("opensrf.simple-text.statistics", @many_strings); <co id="CO4-8"/>\r
-$request = $result->gather(); <co id="CO4-9"/>\r
-print "Length: " . $result->{'length'} . "\n";\r
-print "Word count: " . $result->{'word_count'} . "\n";\r
-\r
-$session->disconnect(); <co id="CO4-10"/>\r
-</programlisting>\r
- <calloutlist>\r
- <callout arearefs="CO4-1">\r
- <simpara>\r
- The <literal>OpenSRF::System->bootstrap_client()</literal> method reads the OpenSRF\r
- configuration information from the indicated file and creates an XMPP client\r
- connection based on that information.\r
- </simpara>\r
- </callout>\r
- <callout arearefs="CO4-2">\r
- <simpara>\r
- The <literal>OpenSRF::AppSession->create()</literal> method accepts one argument - the name\r
- of the OpenSRF service to which you want to want to make one or more requests -\r
- and returns an object prepared to use the client connection to make those\r
- requests.\r
- </simpara>\r
- </callout>\r
- <callout arearefs="CO4-3">\r
- <simpara>\r
- The <literal>OpenSRF::AppSession->request()</literal> method accepts a minimum of one\r
- argument - the name of the OpenSRF method to which you want to make a request -\r
- followed by zero or more arguments to pass to the OpenSRF method as input\r
- values. This example passes a string and an integer to the\r
- <literal>opensrf.simple-text.substring</literal> method defined by the <literal>opensrf.simple-text</literal>\r
- OpenSRF service.\r
- </simpara>\r
- </callout>\r
- <callout arearefs="CO4-4">\r
- <simpara>\r
- The <literal>gather()</literal> method, called on the result object returned by the\r
- <literal>request()</literal> method, iterates over all of the possible results from the result\r
- object and returns a single variable.\r
- </simpara>\r
- </callout>\r
- <callout arearefs="CO4-5">\r
- <simpara>\r
- This <literal>request()</literal> call passes two strings to the <literal>opensrf.simple-text.split</literal>\r
- method defined by the <literal>opensrf.simple-text</literal> OpenSRF service and returns (via\r
- <literal>gather()</literal>) a reference to an array of results.\r
- </simpara>\r
- </callout>\r
- <callout arearefs="CO4-6">\r
- <simpara>\r
- The <literal>opensrf.simple-text.split()</literal> method is a streaming method that\r
- returns an array of results with one element per <literal>recv()</literal> call on the\r
- result object. We could use the <literal>gather()</literal> method to retrieve all of the\r
- results in a single array reference, but instead we simply iterate over\r
- the result variable until there are no more results to retrieve.\r
- </simpara>\r
- </callout>\r
- <callout arearefs="CO4-7">\r
- <simpara>\r
- While the <literal>gather()</literal> convenience method returns only the content of the\r
- complete set of results for a given request, the <literal>recv()</literal> method returns an\r
- OpenSRF result object with <literal>status</literal>, <literal>statusCode</literal>, and <literal>content</literal> fields as\r
- we saw in <link linkend="OpenSRFOverHTTP">the HTTP results example</link>.\r
- </simpara>\r
- </callout>\r
- <callout arearefs="CO4-8">\r
- <simpara>\r
- This <literal>request()</literal> call passes an array to the\r
- <literal>opensrf.simple-text.statistics</literal> method defined by the <literal>opensrf.simple-text</literal>\r
- OpenSRF service.\r
- </simpara>\r
- </callout>\r
- <callout arearefs="CO4-9">\r
- <simpara>\r
- The result object returns a hash reference via <literal>gather()</literal>. The hash\r
- contains the <literal>length</literal> and <literal>word_count</literal> keys we defined in the method.\r
- </simpara>\r
- </callout>\r
- <callout arearefs="CO4-10">\r
- <simpara>\r
- The <literal>OpenSRF::AppSession->disconnect()</literal> method closes the XMPP client\r
- connection and cleans up resources associated with the session.\r
- </simpara>\r
- </callout>\r
- </calloutlist>\r
- </simplesect>\r
- </simplesect>\r
- <simplesect id="_accepting_and_returning_more_interesting_data_types">\r
- <title>Accepting and returning more interesting data types</title>\r
- <simpara>Of course, the example of accepting a single string and returning a single\r
- string is not very interesting. In real life, our applications tend to pass\r
- around multiple arguments, including arrays and hashes. Fortunately, OpenSRF\r
- makes that easy to deal with; in Perl, for example, returning a reference to\r
- the data type does the right thing. In the following example of a method that\r
- returns a list, we accept two arguments of type string: the string to be split,\r
- and the delimiter that should be used to split the string.</simpara>\r
- <formalpara><title>Basic text splitting method</title><para>\r
-<programlisting language="perl" linenumbering="unnumbered">\r
-sub text_split {\r
- my $self = shift;\r
- my $conn = shift;\r
- my $text = shift;\r
- my $delimiter = shift || ' ';\r
-\r
- my @split_text = split $delimiter, $text;\r
- return \@split_text;\r
-}\r
-\r
-__PACKAGE__->register_method(\r
- method => 'text_split',\r
- api_name => 'opensrf.simple-text.split'\r
-);\r
-</programlisting>\r
- </para></formalpara>\r
- <simpara>We simply return a reference to the list, and OpenSRF does the rest of the work\r
- for us to convert the data into the language-independent format that is then\r
- returned to the caller. As a caller of a given method, you must rely on the\r
- documentation used to register to determine the data structures - if the developer has\r
- added the appropriate documentation.</simpara>\r
- </simplesect>\r
- <simplesect id="_accepting_and_returning_evergreen_objects">\r
- <title>Accepting and returning Evergreen objects</title>\r
- <simpara>OpenSRF is agnostic about objects; its role is to pass JSON back and forth\r
- between OpenSRF clients and services, and it allows the specific clients and\r
- services to define their own semantics for the JSON structures. On top of that\r
- infrastructure, Evergreen offers the fieldmapper: an object-relational mapper\r
- that provides a complete definition of all objects, their properties, their\r
- relationships to other objects, the permissions required to create, read,\r
- update, or delete objects of that type, and the database table or view on which\r
- they are based.</simpara>\r
- <indexterm><primary>Fieldmapper</primary></indexterm>\r
- <simpara>The Evergreen fieldmapper offers a great deal of convenience for working with\r
- complex system objects beyond the basic mapping of classes to database\r
- schemas. Although the result is passed over the wire as a JSON object\r
- containing the indicated fields, fieldmapper-aware clients then turn those\r
- JSON objects into native objects with setter / getter methods for each field.</simpara>\r
- <simpara>All of this metadata about Evergreen objects is defined in the\r
- fieldmapper configuration file (<literal>/openils/conf/fm_IDL.xml</literal>), and access to\r
- these classes is provided by the <literal>open-ils.cstore</literal>, <literal>open-ils.pcrud</literal>, and\r
- <literal>open-ils.reporter-store</literal> OpenSRF services which parse the fieldmapper\r
- configuration file and dynamically register OpenSRF methods for creating,\r
- reading, updating, and deleting all of the defined classes.</simpara>\r
- <formalpara><title>Example fieldmapper class definition for "Open User Summary"</title><para>\r
-<programlisting language="xml" linenumbering="unnumbered">\r
-<class id="mous" controller="open-ils.cstore open-ils.pcrud"\r
- oils_obj:fieldmapper="money::open_user_summary"\r
- oils_persist:tablename="money.open_usr_summary"\r
- reporter:label="Open User Summary"> <co id="CO5-1"/>\r
- <fields oils_persist:primary="usr" oils_persist:sequence=""> <co id="CO5-2"/> \r
- <field name="balance_owed" reporter:datatype="money" /> <co id="CO5-3"/> \r
- <field name="total_owed" reporter:datatype="money" />\r
- <field name="total_paid" reporter:datatype="money" />\r
- <field name="usr" reporter:datatype="link"/>\r
- </fields>\r
- <links>\r
- <link field="usr" reltype="has_a" key="id" map="" class="au"/><co id="CO5-4"/> \r
- </links>\r
- <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1"><co id="CO5-5"/> \r
- <actions>\r
- <retrieve permission="VIEW_USER"><co id="CO5-6"/> \r
- <context link="usr" field="home_ou"/><co id="CO5-7"/>\r
- </retrieve>\r
- </actions>\r
- </permacrud>\r
-</class>\r
-</programlisting>\r
- </para></formalpara>\r
- <calloutlist>\r
- <callout arearefs="CO5-1">\r
- <simpara>\r
- The <literal><class></literal> element defines the class:\r
- </simpara>\r
- <itemizedlist>\r
- <listitem>\r
- <simpara>\r
- The <literal>id</literal> attribute defines the <emphasis>class hint</emphasis> that identifies the class both\r
- elsewhere in the fieldmapper configuration file, such as in the value of the\r
- <literal>field</literal> attribute of the <literal><link></literal> element, and in the JSON object itself when\r
- it is instantiated. For example, an "Open User Summary" JSON object would have\r
- the top level property of <literal>"__c":"mous"</literal>.\r
- </simpara>\r
- </listitem>\r
- <listitem>\r
- <simpara>\r
- The <literal>controller</literal> attribute identifies the services that have direct access\r
- to this class. If <literal>open-ils.pcrud</literal> is not listed, for example, then there is\r
- no means to directly access members of this class through a public service.\r
- </simpara>\r
- </listitem>\r
- <listitem>\r
- <simpara>\r
- The <literal>oils_obj:fieldmapper</literal> attribute defines the name of the Perl\r
- fieldmapper class that will be dynamically generated to provide setter and\r
- getter methods for instances of the class.\r
- </simpara>\r
- </listitem>\r
- <listitem>\r
- <simpara>\r
- The <literal>oils_persist:tablename</literal> attribute identifies the schema name and table\r
- name of the database table that stores the data that represents the instances\r
- of this class. In this case, the schema is <literal>money</literal> and the table is\r
- <literal>open_usr_summary</literal>.\r
- </simpara>\r
- </listitem>\r
- <listitem>\r
- <simpara>\r
- The <literal>reporter:label</literal> attribute defines a human-readable name for the class\r
- used in the reporting interface to identify the class. These names are defined\r
- in English in the fieldmapper configuration file; however, they are extracted\r
- so that they can be translated and served in the user’s language of choice.\r
- </simpara>\r
- </listitem>\r
- </itemizedlist>\r
- </callout>\r
- <callout arearefs="CO5-2">\r
- <simpara>\r
- The <literal><fields></literal> element lists all of the fields that belong to the object.\r
- </simpara>\r
- <itemizedlist>\r
- <listitem>\r
- <simpara>\r
- The <literal>oils_persist:primary</literal> attribute identifies the field that acts as the\r
- primary key for the object; in this case, the field with the name <literal>usr</literal>.\r
- </simpara>\r
- </listitem>\r
- <listitem>\r
- <simpara>\r
- The <literal>oils_persist:sequence</literal> attribute identifies the sequence object\r
- (if any) in this database provides values for new instances of this class. In\r
- this case, the primary key is defined by a field that is linked to a different\r
- table, so no sequence is used to populate these instances.\r
- </simpara>\r
- </listitem>\r
- </itemizedlist>\r
- </callout>\r
- <callout arearefs="CO5-3">\r
- <simpara>\r
- Each <literal><field></literal> element defines a single field with the following attributes:\r
- </simpara>\r
- <itemizedlist>\r
- <listitem>\r
- <simpara>\r
- The <literal>name</literal> attribute identifies the column name of the field in the\r
- underlying database table as well as providing a name for the setter / getter\r
- method that can be invoked in the JSON or native version of the object.\r
- </simpara>\r
- </listitem>\r
- <listitem>\r
- <simpara>\r
- The <literal>reporter:datatype</literal> attribute defines how the reporter should treat\r
- the contents of the field for the purposes of querying and display.\r
- </simpara>\r
- </listitem>\r
- <listitem>\r
- <simpara>\r
- The <literal>reporter:label</literal> attribute can be used to provide a human-readable name\r
- for each field; without it, the reporter falls back to the value of the <literal>name</literal>\r
- attribute.\r
- </simpara>\r
- </listitem>\r
- </itemizedlist>\r
- </callout>\r
- <callout arearefs="CO5-4">\r
- <simpara>\r
- The <literal><links></literal> element contains a set of zero or more <literal><link></literal> elements,\r
- each of which defines a relationship between the class being described and\r
- another class.\r
- </simpara>\r
- <itemizedlist>\r
- <listitem>\r
- <simpara>\r
- The <literal>field</literal> attribute identifies the field named in this class that links\r
- to the external class.\r
- </simpara>\r
- </listitem>\r
- <listitem>\r
- <simpara>\r
- The <literal>reltype</literal> attribute identifies the kind of relationship between the\r
- classes; in the case of <literal>has_a</literal>, each value in the <literal>usr</literal> field is guaranteed\r
- to have a corresponding value in the external class.\r
- </simpara>\r
- </listitem>\r
- <listitem>\r
- <simpara>\r
- The <literal>key</literal> attribute identifies the name of the field in the external\r
- class to which this field links.\r
- </simpara>\r
- </listitem>\r
- <listitem>\r
- <simpara>\r
- The rarely-used <literal>map</literal> attribute identifies a second class to which\r
- the external class links; it enables this field to define a direct\r
- relationship to an external class with one degree of separation, to\r
- avoid having to retrieve all of the linked members of an intermediate\r
- class just to retrieve the instances from the actual desired target class.\r
- </simpara>\r
- </listitem>\r
- <listitem>\r
- <simpara>\r
- The <literal>class</literal> attribute identifies the external class to which this field\r
- links.\r
- </simpara>\r
- </listitem>\r
- </itemizedlist>\r
- </callout>\r
- <callout arearefs="CO5-5">\r
- <simpara>\r
- The <literal><permacrud></literal> element defines the permissions that must have been\r
- granted to a user to operate on instances of this class.\r
- </simpara>\r
- </callout>\r
- <callout arearefs="CO5-6">\r
- <simpara>\r
- The <literal><retrieve></literal> element is one of four possible children of the\r
- <literal><actions></literal> element that define the permissions required for each action:\r
- create, retrieve, update, and delete.\r
- </simpara>\r
- <itemizedlist>\r
- <listitem>\r
- <simpara>\r
- The <literal>permission</literal> attribute identifies the name of the permission that must\r
- have been granted to the user to perform the action.\r
- </simpara>\r
- </listitem>\r
- <listitem>\r
- <simpara>\r
- The <literal>contextfield</literal> attribute, if it exists, defines the field in this class\r
- that identifies the library within the system for which the user must have\r
- prvileges to work. If a user has been granted a given permission, but has not been\r
- granted privileges to work at a given library, they can not perform the action\r
- at that library.\r
- </simpara>\r
- </listitem>\r
- </itemizedlist>\r
- </callout>\r
- <callout arearefs="CO5-7">\r
- <simpara>\r
- The rarely-used <literal><context></literal> element identifies a linked field (<literal>link</literal>\r
- attribute) in this class which links to an external class that holds the field\r
- (<literal>field</literal> attribute) that identifies the library within the system for which the\r
- user must have privileges to work.\r
- </simpara>\r
- </callout>\r
- </calloutlist>\r
- <simpara>When you retrieve an instance of a class, you can ask for the result to\r
- <emphasis>flesh</emphasis> some or all of the linked fields of that class, so that the linked\r
- instances are returned embedded directly in your requested instance. In that\r
- same request you can ask for the fleshed instances to in turn have their linked\r
- fields fleshed. By bundling all of this into a single request and result\r
- sequence, you can avoid the network overhead of requiring the client to request\r
- the base object, then request each linked object in turn.</simpara>\r
- <simpara>You can also iterate over a collection of instances and set the automatically\r
- generated <literal>isdeleted</literal>, <literal>isupdated</literal>, or <literal>isnew</literal> properties to indicate that\r
- the given instance has been deleted, updated, or created respectively.\r
- Evergreen can then act in batch mode over the collection to perform the\r
- requested actions on any of the instances that have been flagged for action.</simpara>\r
- </simplesect>\r
- <simplesect id="_returning_streaming_results">\r
- <title>Returning streaming results</title>\r
- <simpara>In the previous implementation of the <literal>opensrf.simple-text.split</literal> method, we\r
- returned a reference to the complete array of results. For small values being\r
- delivered over the network, this is perfectly acceptable, but for large sets of\r
- values this can pose a number of problems for the requesting client. Consider a\r
- service that returns a set of bibliographic records in response to a query like\r
- "all records edited in the past month"; if the underlying database is\r
- relatively active, that could result in thousands of records being returned as\r
- a single network request. The client would be forced to block until all of the\r
- results are returned, likely resulting in a significant delay, and depending on\r
- the implementation, correspondingly large amounts of memory might be consumed\r
- as all of the results are read from the network in a single block.</simpara>\r
- <simpara>OpenSRF offers a solution to this problem. If the method returns results that\r
- can be divided into separate meaningful units, you can register the OpenSRF\r
- method as a streaming method and enable the client to loop over the results one\r
- unit at a time until the method returns no further results. In addition to\r
- registering the method with the provided name, OpenSRF also registers an additional\r
- method with <literal>.atomic</literal> appended to the method name. The <literal>.atomic</literal> variant gathers\r
- all of the results into a single block to return to the client, giving the caller\r
- the ability to choose either streaming or atomic results from a single method\r
- definition.</simpara>\r
- <simpara>In the following example, the text splitting method has been reimplemented to\r
- support streaming; very few changes are required:</simpara>\r
- <formalpara><title>Text splitting method - streaming mode</title><para>\r
-<programlisting language="perl" linenumbering="unnumbered">\r
-sub text_split {\r
- my $self = shift;\r
- my $conn = shift;\r
- my $text = shift;\r
- my $delimiter = shift || ' ';\r
-\r
- my @split_text = split $delimiter, $text;\r
- foreach my $string (@split_text) { <co id="CO6-1"/>\r
- $conn->respond($string);\r
- }\r
- return undef;\r
-}\r
-\r
-__PACKAGE__->register_method(\r
- method => 'text_split',\r
- api_name => 'opensrf.simple-text.split',\r
- stream => 1<co id="CO6-2"/>\r
-);\r
-</programlisting>\r
- </para></formalpara>\r
- <calloutlist>\r
- <callout arearefs="CO6-1">\r
- <simpara>\r
- Rather than returning a reference to the array, a streaming method loops\r
- over the contents of the array and invokes the <literal>respond()</literal> method of the\r
- connection object on each element of the array.\r
- </simpara>\r
- </callout>\r
- <callout arearefs="CO6-2">\r
- <simpara>\r
- Registering the method as a streaming method instructs OpenSRF to also\r
- register an atomic variant (<literal>opensrf.simple-text.split.atomic</literal>).\r
- </simpara>\r
- </callout>\r
- </calloutlist>\r
- </simplesect>\r
- <simplesect id="_error_warning_info_debug">\r
- <title>Error! Warning! Info! Debug!</title>\r
- <simpara>As hard as it may be to believe, it is true: applications sometimes do not\r
- behave in the expected manner, particularly when they are still under\r
- development. The service language bindings for OpenSRF include integrated\r
- support for logging messages at the levels of ERROR, WARNING, INFO, DEBUG, and\r
- the extremely verbose INTERNAL to either a local file or to a syslogger\r
- service. The destination of the log files, and the level of verbosity to be\r
- logged, is set in the <literal>opensrf_core.xml</literal> configuration file. To add logging to\r
- our Perl example, we just have to add the <literal>OpenSRF::Utils::Logger</literal> package to our\r
- list of used Perl modules, then invoke the logger at the desired logging level.</simpara>\r
- <simpara>You can include many calls to the OpenSRF logger; only those that are higher\r
- than your configured logging level will actually hit the log. The following\r
- example exercises all of the available logging levels in OpenSRF:</simpara>\r
-<programlisting language="perl" linenumbering="unnumbered">\r
-use OpenSRF::Utils::Logger;\r
-my $logger = OpenSRF::Utils::Logger;\r
-# some code in some function\r
-{\r
- $logger->error("Hmm, something bad DEFINITELY happened!");\r
- $logger->warn("Hmm, something bad might have happened.");\r
- $logger->info("Something happened.");\r
- $logger->debug("Something happened; here are some more details.");\r
- $logger->internal("Something happened; here are all the gory details.")\r
-}\r
-</programlisting>\r
- <simpara>If you call the mythical OpenSRF method containing the preceding OpenSRF logger\r
- statements on a system running at the default logging level of INFO, you will\r
- only see the INFO, WARN, and ERR messages, as follows:</simpara>\r
- <formalpara><title>Results of logging calls at the default level of INFO</title><para>\r
-<screen>\r
-[2010-03-17 22:27:30] opensrf.simple-text [ERR :5681:SimpleText.pm:277:] \r
-[2010-03-17 22:27:30] opensrf.simple-text [WARN:5681:SimpleText.pm:278:] \r
-[2010-03-17 22:27:30] opensrf.simple-text [INFO:5681:SimpleText.pm:279:] \r
-</screen>\r
- </para></formalpara>\r
- <simpara>If you then increase the the logging level to INTERNAL (5), the logs will\r
- contain much more information, as follows:</simpara>\r
- <formalpara><title>Results of logging calls at the default level of INTERNAL</title><para>\r
-<screen>\r
-[2010-03-17 22:48:11] opensrf.simple-text [ERR :5934:SimpleText.pm:277:] \r
-[2010-03-17 22:48:11] opensrf.simple-text [WARN:5934:SimpleText.pm:278:] \r
-[2010-03-17 22:48:11] opensrf.simple-text [INFO:5934:SimpleText.pm:279:] \r
-[2010-03-17 22:48:11] opensrf.simple-text [DEBG:5934:SimpleText.pm:280:] \r
-[2010-03-17 22:48:11] opensrf.simple-text [INTL:5934:SimpleText.pm:281:] \r
-[2010-03-17 22:48:11] opensrf.simple-text [ERR :5934:SimpleText.pm:283:] \r
-[2010-03-17 22:48:21] opensrf.simple-text [INTL:5934:Cache.pm:125:] \r
-[2010-03-17 22:48:21] opensrf.simple-text [DEBG:5934:Application.pm:579:] \r
-[2010-03-17 22:48:21] opensrf.simple-text [DEBG:5934:Application.pm:586:] \r
-[2010-03-17 22:48:21] opensrf.simple-text [DEBG:5934:Application.pm:190:] \r
-[2010-03-17 22:48:21] opensrf.simple-text [INTL:5934:AppSession.pm:780:] Calling queue_wait(0)\r
-[2010-03-17 22:48:21] opensrf.simple-text [INTL:5934:AppSession.pm:769:] Resending...0\r
-[2010-03-17 22:48:21] opensrf.simple-text [INTL:5934:AppSession.pm:450:] In send\r
-[2010-03-17 22:48:21] opensrf.simple-text [DEBG:5934:AppSession.pm:506:] \r
-[2010-03-17 22:48:21] opensrf.simple-text [DEBG:5934:AppSession.pm:506:] \r
-...\r
-</screen>\r
- </para></formalpara>\r
- <simpara>To see everything that is happening in OpenSRF, try leaving your logging level\r
- set to INTERNAL for a few minutes - just ensure that you have a lot of free disk\r
- space available if you have a moderately busy system!</simpara>\r
- </simplesect>\r
- <simplesect id="_caching_results_one_secret_of_scalability">\r
- <title>Caching results: one secret of scalability</title>\r
- <indexterm><primary>search results</primary><secondary>caching</secondary></indexterm>\r
- <simpara>If you have ever used an application that depends on a remote Web service\r
- outside of your control — say, if you need to retrieve results from a\r
- microblogging service — you know the pain of latency and dependability (or the\r
- lack thereof). To improve the response time for OpenSRF services, you can take\r
- advantage of the support offered by the <literal>OpenSRF::Utils::Cache</literal> module for\r
- communicating with a local instance or cluster of <literal>memcache</literal> daemons to store\r
- and retrieve persistent values. The following example demonstrates caching\r
- by sleeping for 10 seconds the first time it receives a given cache key and\r
- cannot retrieve a corresponding value from the cache:</simpara>\r
- <formalpara><title>Simple caching OpenSRF service</title><para>\r
-<programlisting language="perl" linenumbering="unnumbered">\r
-use OpenSRF::Utils::Cache;<co id="CO7-1"/>\r
-sub test_cache {\r
- my $self = shift;\r
- my $conn = shift;\r
- my $test_key = shift;\r
- my $cache = OpenSRF::Utils::Cache->new('global'); <co id="CO7-2"/>\r
- my $cache_key = "opensrf.simple-text.test_cache.$test_key"; <co id="CO7-3"/>\r
- my $result = $cache->get_cache($cache_key) || undef; <co id="CO7-4"/>\r
- if ($result) {\r
- $logger->info("Resolver found a cache hit");\r
- return $result;\r
- }\r
- sleep 10; <co id="CO7-5"/>\r
- my $cache_timeout = 300; <co id="CO7-6"/>\r
- $cache->put_cache($cache_key, "here", $cache_timeout); <co id="CO7-7"/>\r
- return "There was no cache hit.";\r
-}\r
-</programlisting>\r
- </para></formalpara>\r
- <calloutlist>\r
- <callout arearefs="CO7-1">\r
- <simpara>\r
- The OpenSRF::Utils::Cache module provides access to the built-in caching\r
- support in OpenSRF.\r
- </simpara>\r
- </callout>\r
- <callout arearefs="CO7-2">\r
- <simpara>\r
- The constructor for the cache object accepts a single argument to define\r
- the cache type for the object. Each cache type can use a separate <literal>memcache</literal>\r
- server to keep the caches separated. Most Evergreen services use the <literal>global</literal>\r
- cache, while the <literal>anon</literal> cache is used for Web sessions.\r
- </simpara>\r
- </callout>\r
- <callout arearefs="CO7-3">\r
- <simpara>\r
- The cache key is simply a string that uniquely identifies the value you\r
- want to store or retrieve. This line creates a cache key based on the OpenSRF\r
- method name and request input value.\r
- </simpara>\r
- </callout>\r
- <callout arearefs="CO7-4">\r
- <simpara>\r
- The <literal>get_cache()</literal> method checks to see if the cache key already exists. If\r
- a matching key is found, the service immediately returns the stored value.\r
- </simpara>\r
- </callout>\r
- <callout arearefs="CO7-5">\r
- <simpara>\r
- If the cache key does not exist, the code sleeps for 10 seconds to\r
- simulate a call to a slow remote Web service or an intensive process.\r
- </simpara>\r
- </callout>\r
- <callout arearefs="CO7-6">\r
- <simpara>\r
- The <literal>$cache_timeout</literal> variable represents a value for the lifetime of the\r
- cache key in seconds.\r
- </simpara>\r
- </callout>\r
- <callout arearefs="CO7-7">\r
- <simpara>\r
- After the code retrieves its value (or, in the case of this example,\r
- finishes sleeping), it creates the cache entry by calling the <literal>put_cache()</literal>\r
- method. The method accepts three arguments: the cache key, the value to be\r
- stored ("here"), and the timeout value in seconds to ensure that we do not\r
- return stale data on subsequent calls.\r
- </simpara>\r
- </callout>\r
- </calloutlist>\r
- </simplesect>\r
- <simplesect id="_initializing_the_service_and_its_children_child_labour">\r
- <title>Initializing the service and its children: child labour</title>\r
- <simpara>When an OpenSRF service is started, it looks for a procedure called\r
- <literal>initialize()</literal> to set up any global variables shared by all of the children of\r
- the service. The <literal>initialize()</literal> procedure is typically used to retrieve\r
- configuration settings from the <literal>opensrf.xml</literal> file.</simpara>\r
- <simpara>An OpenSRF service spawns one or more children to actually do the work\r
- requested by callers of the service. For every child process an OpenSRF service\r
- spawns, the child process clones the parent environment and then each child\r
- process runs the <literal>child_init()</literal> process (if any) defined in the OpenSRF service\r
- to initialize any child-specific settings.</simpara>\r
- <simpara>When the OpenSRF service kills a child process, it invokes the <literal>child_exit()</literal>\r
- procedure (if any) to clean up any resources associated with the child process.\r
- Similarly, when the OpenSRF service is stopped, it calls the <literal>DESTROY()</literal>\r
- procedure to clean up any remaining resources.</simpara>\r
- </simplesect>\r
- <simplesect id="_retrieving_configuration_settings">\r
- <title>Retrieving configuration settings</title>\r
- <simpara>The settings for OpenSRF services are maintained in the <literal>opensrf.xml</literal> XML\r
- configuration file. The structure of the XML document consists of a root\r
- element <literal><opensrf></literal> containing two child elements:</simpara>\r
- <itemizedlist>\r
- <listitem>\r
- <simpara>\r
- The <literal><default></literal> element contains an <literal><apps></literal> element describing all\r
- OpenSRF services running on this system — see <xref linkend="serviceRegistration"/> --, as\r
- well as any other arbitrary XML descriptions required for global configuration\r
- purposes. For example, Evergreen uses this section for email notification and\r
- inter-library patron privacy settings.\r
- </simpara>\r
- </listitem>\r
- <listitem>\r
- <simpara>\r
- The <literal><hosts></literal> element contains one element per host that participates in\r
- this OpenSRF system. Each host element must include an <literal><activeapps></literal> element\r
- that lists all of the services to start on this host when the system starts\r
- up. Each host element can optionally override any of the default settings.\r
- </simpara>\r
- </listitem>\r
- </itemizedlist>\r
- <simpara>OpenSRF includes a service named <literal>opensrf.settings</literal> to provide distributed\r
- cached access to the configuration settings with a simple API:</simpara>\r
- <itemizedlist>\r
- <listitem>\r
- <simpara>\r
- <literal>opensrf.settings.default_config.get</literal> accepts zero arguments and returns\r
- the complete set of default settings as a JSON document.\r
- </simpara>\r
- </listitem>\r
- <listitem>\r
- <simpara>\r
- <literal>opensrf.settings.host_config.get</literal> accepts one argument (hostname) and\r
- returns the complete set of settings, as customized for that hostname, as a\r
- JSON document.\r
- </simpara>\r
- </listitem>\r
- <listitem>\r
- <simpara>\r
- <literal>opensrf.settings.xpath.get</literal> accepts one argument (an\r
- <ulink url="http://www.w3.org/TR/xpath/">XPath</ulink> expression) and returns the portion of\r
- the configuration file that matches the expression as a JSON document.\r
- </simpara>\r
- </listitem>\r
- </itemizedlist>\r
- <simpara>For example, to determine whether an Evergreen system uses the opt-in\r
- support for sharing patron information between libraries, you could either\r
- invoke the <literal>opensrf.settings.default_config.get</literal> method and parse the\r
- JSON document to determine the value, or invoke the <literal>opensrf.settings.xpath.get</literal>\r
- method with the XPath <literal>/opensrf/default/share/user/opt_in</literal> argument to\r
- retrieve the value directly.</simpara>\r
- <simpara>In practice, OpenSRF includes convenience libraries in all of its client\r
- language bindings to simplify access to configuration values. C offers\r
- osrfConfig.c, Perl offers <literal>OpenSRF::Utils::SettingsClient</literal>, Java offers\r
- <literal>org.opensrf.util.SettingsClient</literal>, and Python offers <literal>osrf.set</literal>. These\r
- libraries locally cache the configuration file to avoid network roundtrips for\r
- every request and enable the developer to request specific values without\r
- having to manually construct XPath expressions.</simpara>\r
- </simplesect>\r
- </section>\r
- <section id="_getting_under_the_covers_with_opensrf">\r
- <title>OpenSRF Communication Flows</title>\r
- <indexterm><primary>OpenSRF</primary><secondary>Communication Flows</secondary></indexterm>\r
- <simpara>Now that you have seen that it truly is easy to create an OpenSRF service, we\r
- can take a look at what is going on under the covers to make all of this work\r
- for you.</simpara>\r
- <simplesect id="_get_on_the_messaging_bus_safely">\r
- <title>Get on the messaging bus - safely</title>\r
- <simpara>One of the core innovations of OpenSRF was to use the Extensible Messaging and\r
- Presence Protocol (XMPP, more colloquially known as Jabber) as the messaging\r
- bus that ties OpenSRF services together across servers. XMPP is an "XML\r
- protocol for near-real-time messaging, presence, and request-response services"\r
- (<ulink url="http://www.ietf.org/rfc/rfc3920.txt">http://www.ietf.org/rfc/rfc3920.txt</ulink>) that OpenSRF relies on to handle most of\r
- the complexity of networked communications. OpenSRF requres an XMPP server\r
- that supports multiple domains such as <ulink url="http://www.ejabberd.im/">ejabberd</ulink>.\r
- Multiple domain support means that a single server can support XMPP virtual\r
- hosts with separate sets of users and access privileges per domain. By\r
- routing communications through separate public and private XMPP domains,\r
- OpenSRF services gain an additional layer of security.</simpara>\r
- <simpara>The <ulink url="http://evergreen-ils.org/dokuwiki/doku.php?id=opensrf:1.2:install">OpenSRF\r
- installation documentation</ulink> instructs you to create two separate hostnames\r
- (<literal>private.localhost</literal> and <literal>public.localhost</literal>) to use as XMPP domains. OpenSRF\r
- can control access to its services based on the domain of the client and\r
- whether a given service allows access from clients on the public domain. When\r
- you start OpenSRF, the first XMPP clients that connect to the XMPP server are\r
- the OpenSRF public and private <emphasis>routers</emphasis>. OpenSRF routers maintain a list of\r
- available services and connect clients to available services. When an OpenSRF\r
- service starts, it establishes a connection to the XMPP server and registers\r
- itself with the private router. The OpenSRF configuration contains a list of\r
- public OpenSRF services, each of which must also register with the public\r
- router.</simpara>\r
- </simplesect>\r
- <simplesect id="_opensrf_communication_flows_over_xmpp">\r
- <title>OpenSRF communication flows over XMPP</title>\r
- <indexterm><primary>XMPP</primary></indexterm>\r
- <simpara>In a minimal OpenSRF deployment, two XMPP users named "router" connect to the\r
- XMPP server, with one connected to the private XMPP domain and one connected to\r
- the public XMPP domain. Similarly, two XMPP users named "opensrf" connect to\r
- the XMPP server via the private and public XMPP domains. When an OpenSRF\r
- service is started, it uses the "opensrf" XMPP user to advertise its\r
- availability with the corresponding router on that XMPP domain; the XMPP server\r
- automatically assigns a Jabber ID (<emphasis>JID</emphasis>) based on the client hostname to each\r
- service’s listener process and each connected drone process waiting to carry\r
- out requests. When an OpenSRF router receives a request to invoke a method on a\r
- given service, it connects the requester to the next available listener in the\r
- list of registered listeners for that service.</simpara>\r
- <simpara>Services and clients connect to the XMPP server using a single set of XMPP\r
- client credentials (for example, <literal>opensrf@private.localhost</literal>), but use XMPP\r
- resource identifiers to differentiate themselves in the JID for each\r
- connection. For example, the JID for a copy of the <literal>opensrf.simple-text</literal>\r
- service with process ID <literal>6285</literal> that has connected to the <literal>private.localhost</literal>\r
- domain using the <literal>opensrf</literal> XMPP client credentials could be\r
- <literal>opensrf@private.localhost/opensrf.simple-text_drone_at_localhost_6285</literal>. By\r
- convention, the user name for OpenSRF clients is <literal>opensrf</literal>, and the user name\r
- for OpenSRF routers is <literal>router</literal>, so the XMPP server for OpenSRF will have four\r
- separate users registered:\r
- * <literal>opensrf@private.localhost</literal> is an OpenSRF client that connects with these\r
- credentials and which can access any OpenSRF service.\r
- * <literal>opensrf@public.localhost</literal> is an OpenSRF client that connects with these\r
- credentials and which can only access OpenSRF services that have registered\r
- with the public router.\r
- * <literal>router@private.localhost</literal> is the private OpenSRF router with which all\r
- services register.\r
- * <literal>router@public.localhost</literal> is the public OpenSRF router with which only\r
- services that must be publicly accessible register.</simpara>\r
- <simpara>All OpenSRF services automatically register themselves with the private XMPP\r
- domain, but only those services that register themselves with the public XMPP\r
- domain can be invoked from public OpenSRF clients. The OpenSRF client and\r
- router user names, passwords, and domain names, along with the list of services\r
- that should be public, are contained in the <literal>opensrf_core.xml</literal> configuration\r
- file.</simpara>\r
- </simplesect>\r
- <simplesect id="OpenSRFOverHTTP">\r
- <title>OpenSRF communication flows over HTTP</title>\r
- <indexterm><primary>HTTP</primary><secondary>translator</secondary></indexterm>\r
- <simpara>In some contexts, access to a full XMPP client is not a practical option. For\r
- example, while XMPP clients have been implemented in JavaScript, you might\r
- be concerned about browser compatibility and processing overhead - or you might\r
- want to issue OpenSRF requests from the command line with <literal>curl</literal>. Fortunately,\r
- any OpenSRF service registered with the public router is accessible via the\r
- OpenSRF HTTP Translator. The OpenSRF HTTP Translator implements the\r
- <ulink url="http://www.open-ils.org/dokuwiki/doku.php?id=opensrf_over_http">OpenSRF-over-HTTP\r
- proposed specification</ulink> as an Apache module that translates HTTP requests into\r
- OpenSRF requests and returns OpenSRF results as HTTP results to the initiating\r
- HTTP client.</simpara>\r
- <formalpara><title>Issuing an HTTP POST request to an OpenSRF method via the OpenSRF HTTP Translator</title><para>\r
-<programlisting language="bash" linenumbering="unnumbered">\r
-# curl request broken up over multiple lines for legibility\r
-curl -H "X-OpenSRF-service: opensrf.simple-text"<co id="CO8-1"/>\r
- --data 'osrf-msg=[ \<co id="CO8-2"/>\r
- {"__c":"osrfMessage","__p":{"threadTrace":0,"locale":"en-CA", <co id="CO8-3"/>\r
- "type":"REQUEST","payload": {"__c":"osrfMethod","__p": \r
- {"method":"opensrf.simple-text.reverse","params":["foobar"]} \r
- }} \r
- }]' \r
-http://localhost/osrf-http-translator <co id="CO8-4"/>\r
-</programlisting>\r
- </para></formalpara>\r
- <calloutlist>\r
- <callout arearefs="CO8-1">\r
- <simpara>\r
- The <literal>X-OpenSRF-service</literal> header identifies the OpenSRF service of interest.\r
- </simpara>\r
- </callout>\r
- <callout arearefs="CO8-2">\r
- <simpara>\r
- The POST request consists of a single parameter, the <literal>osrf-msg</literal> value,\r
- which contains a JSON array.\r
- </simpara>\r
- </callout>\r
- <callout arearefs="CO8-3">\r
- <simpara>\r
- The first object is an OpenSRF message (<literal>"__c":"osrfMessage"</literal>) with a set of\r
- parameters (<literal>"__p":{}</literal>).\r
- </simpara>\r
- <itemizedlist>\r
- <listitem>\r
- <simpara>\r
- The identifier for the request (<literal>"threadTrace":0</literal>); this value is echoed\r
- back in the result.\r
- </simpara>\r
- </listitem>\r
- <listitem>\r
- <simpara>\r
- The message type (<literal>"type":"REQUEST"</literal>).\r
- </simpara>\r
- </listitem>\r
- <listitem>\r
- <simpara>\r
- The locale for the message; if the OpenSRF method is locale-sensitive, it\r
- can check the locale for each OpenSRF request and return different information\r
- depending on the locale.\r
- </simpara>\r
- </listitem>\r
- <listitem>\r
- <simpara>\r
- The payload of the message (<literal>"payload":{}</literal>) containing the OpenSRF method\r
- request (<literal>"__c":"osrfMethod"</literal>) and its parameters (<literal>"__p:"{}</literal>).\r
- </simpara>\r
- <itemizedlist>\r
- <listitem>\r
- <simpara>\r
- The method name for the request (<literal>"method":"opensrf.simple-text.reverse"</literal>).\r
- </simpara>\r
- </listitem>\r
- <listitem>\r
- <simpara>\r
- A set of JSON parameters to pass to the method (<literal>"params":["foobar"]</literal>); in\r
- this case, a single string <literal>"foobar"</literal>.\r
- </simpara>\r
- </listitem>\r
- </itemizedlist>\r
- </listitem>\r
- </itemizedlist>\r
- </callout>\r
- <callout arearefs="CO8-4">\r
- <simpara>\r
- The URL on which the OpenSRF HTTP translator is listening,\r
- <literal>/osrf-http-translator</literal> is the default location in the Apache example\r
- configuration files shipped with the OpenSRF source, but this is configurable.\r
- </simpara>\r
- </callout>\r
- </calloutlist>\r
- <formalpara><title>Results from an HTTP POST request to an OpenSRF method via the OpenSRF HTTP Translator</title><para>\r
-<programlisting language="bash" linenumbering="unnumbered">\r
-# HTTP response broken up over multiple lines for legibility\r
-[{"__c":"osrfMessage","__p": <co id="CO9-1"/>\r
- {"threadTrace":0, "payload": <co id="CO9-2"/>\r
- {"__c":"osrfResult","__p": <co id="CO9-3"/>\r
- {"status":"OK","content":"raboof","statusCode":200} <co id="CO9-4"/>\r
- },"type":"RESULT","locale":"en-CA" <co id="CO9-5"/>\r
- }\r
-},\r
-{"__c":"osrfMessage","__p": <co id="CO9-6"/>\r
- {"threadTrace":0,"payload": <co id="CO9-7"/>\r
- {"__c":"osrfConnectStatus","__p": <co id="CO9-8"/>\r
- {"status":"Request Complete","statusCode":205}<co id="CO9-9"/>\r
- },"type":"STATUS","locale":"en-CA" <co id="CO9-10"/>\r
- }\r
-}]\r
-</programlisting>\r
- </para></formalpara>\r
- <calloutlist>\r
- <callout arearefs="CO9-1">\r
- <simpara>\r
- The OpenSRF HTTP Translator returns an array of JSON objects in its\r
- response. Each object in the response is an OpenSRF message\r
- (<literal>"__c":"osrfMessage"</literal>) with a collection of response parameters (<literal>"__p":</literal>).\r
- </simpara>\r
- </callout>\r
- <callout arearefs="CO9-2">\r
- <simpara>\r
- The OpenSRF message identifier (<literal>"threadTrace":0</literal>) confirms that this\r
- message is in response to the request matching the same identifier.\r
- </simpara>\r
- </callout>\r
- <callout arearefs="CO9-3">\r
- <simpara>\r
- The message includes a payload JSON object (<literal>"payload":</literal>) with an OpenSRF\r
- result for the request (<literal>"__c":"osrfResult"</literal>).\r
- </simpara>\r
- </callout>\r
- <callout arearefs="CO9-4">\r
- <simpara>\r
- The result includes a status indicator string (<literal>"status":"OK"</literal>), the content\r
- of the result response - in this case, a single string "raboof"\r
- (<literal>"content":"raboof"</literal>) - and an integer status code for the request\r
- (<literal>"statusCode":200</literal>).\r
- </simpara>\r
- </callout>\r
- <callout arearefs="CO9-5">\r
- <simpara>\r
- The message also includes the message type (<literal>"type":"RESULT"</literal>) and the\r
- message locale (<literal>"locale":"en-CA"</literal>).\r
- </simpara>\r
- </callout>\r
- <callout arearefs="CO9-6">\r
- <simpara>\r
- The second message in the set of results from the response.\r
- </simpara>\r
- </callout>\r
- <callout arearefs="CO9-7">\r
- <simpara>\r
- Again, the message identifier confirms that this message is in response to\r
- a particular request.\r
- </simpara>\r
- </callout>\r
- <callout arearefs="CO9-8">\r
- <simpara>\r
- The payload of the message denotes that this message is an\r
- OpenSRF connection status message (<literal>"__c":"osrfConnectStatus"</literal>), with some\r
- information about the particular OpenSRF connection that was used for this\r
- request.\r
- </simpara>\r
- </callout>\r
- <callout arearefs="CO9-9">\r
- <simpara>\r
- The response parameters for an OpenSRF connection status message include a\r
- verbose status (<literal>"status":"Request Complete"</literal>) and an integer status code for\r
- the connection status (`"statusCode":205).\r
- </simpara>\r
- </callout>\r
- <callout arearefs="CO9-10">\r
- <simpara>\r
- The message also includes the message type (<literal>"type":"RESULT"</literal>) and the\r
- message locale (<literal>"locale":"en-CA"</literal>).\r
- </simpara>\r
- </callout>\r
- </calloutlist>\r
- <tip><simpara>Before adding a new public OpenSRF service, ensure that it does\r
- not introduce privilege escalation or unchecked access to data. For example,\r
- the Evergreen <literal>open-ils.cstore</literal> private service is an object-relational mapper\r
- that provides read and write access to the entire Evergreen database, so it\r
- would be catastrophic to expose that service publicly. In comparison, the\r
- Evergreen <literal>open-ils.pcrud</literal> public service offers the same functionality as\r
- <literal>open-ils.cstore</literal> to any connected HTTP client or OpenSRF client, but the\r
- additional authentication and authorization layer in <literal>open-ils.pcrud</literal> prevents\r
- unchecked access to Evergreen’s data.</simpara></tip>\r
- </simplesect>\r
- <simplesect id="_stateless_and_stateful_connections">\r
- <title>Stateless and stateful connections</title>\r
- <simpara>OpenSRF supports both <emphasis>stateless</emphasis> and <emphasis>stateful</emphasis> connections. When an OpenSRF\r
- client issues a <literal>REQUEST</literal> message in a <emphasis>stateless</emphasis> connection, the router\r
- forwards the request to the next available service and the service returns the\r
- result directly to the client.</simpara>\r
- \r
- <simpara>When an OpenSRF client issues a <literal>CONNECT</literal> message to create a <emphasis>stateful</emphasis> conection, the\r
- router returns the Jabber ID of the next available service to the client so\r
- that the client can issue one or more <literal>REQUEST</literal> message directly to that\r
- particular service and the service will return corresponding <literal>RESULT</literal> messages\r
- directly to the client. Until the client issues a <literal>DISCONNECT</literal> message, that\r
- particular service is only available to the requesting client. Stateful connections\r
- are useful for clients that need to make many requests from a particular service,\r
- as it avoids the intermediary step of contacting the router for each request, as\r
- well as for operations that require a controlled sequence of commands, such as a\r
- set of database INSERT, UPDATE, and DELETE statements within a transaction.</simpara>\r
- \r
- </simplesect>\r
- <simplesect id="_message_body_format">\r
- <title>Message body format</title>\r
- <simpara>OpenSRF was an early adopter of JavaScript Object Notation (JSON). While XMPP\r
- is an XML protocol, the Evergreen developers recognized that the compactness of\r
- the JSON format offered a significant reduction in bandwidth for the volume of\r
- messages that would be generated in an application of that size. In addition,\r
- the ability of languages such as JavaScript, Perl, and Python to generate\r
- native objects with minimal parsing offered an attractive advantage over\r
- invoking an XML parser for every message. Instead, the body of the XMPP message\r
- is a simple JSON structure. For a simple request, like the following example\r
- that simply reverses a string, it looks like a significant overhead: but we get\r
- the advantages of locale support and tracing the request from the requester\r
- through the listener and responder (drone).</simpara>\r
- <formalpara><title>A request for opensrf.simple-text.reverse("foobar"):</title><para>\r
-<programlisting language="xml" linenumbering="unnumbered">\r
-<message from='router@private.localhost/opensrf.simple-text'\r
- to='opensrf@private.localhost/opensrf.simple-text_listener_at_localhost_6275'\r
- router_from='opensrf@private.localhost/_karmic_126678.3719_6288'\r
- router_to='' router_class='' router_command='' osrf_xid=''\r
->\r
- <thread>1266781414.366573.12667814146288</thread>\r
- <body>\r
-[\r
- {"__c":"osrfMessage","__p":\r
- {"threadTrace":"1","locale":"en-US","type":"REQUEST","payload":\r
- {"__c":"osrfMethod","__p":\r
- {"method":"opensrf.simple-text.reverse","params":["foobar"]}\r
- }\r
- }\r
- }\r
-]\r
- </body>\r
-</message>\r
-</programlisting>\r
- </para></formalpara>\r
- <formalpara><title>A response from opensrf.simple-text.reverse("foobar")</title><para>\r
-<programlisting language="xml" linenumbering="unnumbered">\r
-<message from='opensrf@private.localhost/opensrf.simple-text_drone_at_localhost_6285'\r
- to='opensrf@private.localhost/_karmic_126678.3719_6288'\r
- router_command='' router_class='' osrf_xid=''\r
->\r
- <thread>1266781414.366573.12667814146288</thread>\r
- <body>\r
-[\r
- {"__c":"osrfMessage","__p":\r
- {"threadTrace":"1","payload":\r
- {"__c":"osrfResult","__p":\r
- {"status":"OK","content":"raboof","statusCode":200}\r
- } ,"type":"RESULT","locale":"en-US"}\r
- },\r
- {"__c":"osrfMessage","__p":\r
- {"threadTrace":"1","payload":\r
- {"__c":"osrfConnectStatus","__p":\r
- {"status":"Request Complete","statusCode":205}\r
- },"type":"STATUS","locale":"en-US"}\r
- }\r
-]\r
- </body>\r
-</message>\r
-</programlisting>\r
- </para></formalpara>\r
- <simpara>The content of the <literal><body></literal> element of the OpenSRF request and result should\r
- look familiar; they match the structure of the <link linkend="OpenSRFOverHTTP">OpenSRF over HTTP examples</link> that we previously dissected.</simpara>\r
- </simplesect>\r
- <simplesect id="_registering_opensrf_methods_in_depth">\r
- <title>Registering OpenSRF methods in depth</title>\r
- <simpara>Let’s explore the call to <literal>__PACKAGE__->register_method()</literal>; most of the members\r
- of the hash are optional, and for the sake of brevity we omitted them in the\r
- previous example. As we have seen in the results of the <link linkend="opensrfIntrospection">introspection call</link>, a\r
- verbose registration method call is recommended to better enable the internal\r
- documentation. Here is the complete set of members that you should pass to\r
- <literal>__PACKAGE__->register_method()</literal>:</simpara>\r
- <itemizedlist>\r
- <listitem>\r
- <simpara>\r
- The <literal>method</literal> member specifies the name of the procedure in this module that is being registered as an OpenSRF method.\r
- </simpara>\r
- </listitem>\r
- <listitem>\r
- <simpara>\r
- The <literal>api_name</literal> member specifies the invocable name of the OpenSRF method; by convention, the OpenSRF service name is used as the prefix.\r
- </simpara>\r
- </listitem>\r
- <listitem>\r
- <simpara>\r
- The optional <literal>api_level</literal> member can be used for versioning the methods to allow the use of a deprecated API, but in practical use is always 1.\r
- </simpara>\r
- </listitem>\r
- <listitem>\r
- <simpara>\r
- The optional <literal>argc</literal> member specifies the minimal number of arguments that the method expects.\r
- </simpara>\r
- </listitem>\r
- <listitem>\r
- <simpara>\r
- The optional <literal>stream</literal> member, if set to any value, specifies that the method supports returning multiple values from a single call to \r
- subsequent requests. OpenSRF automatically creates a corresponding method with ".atomic" appended to its name that returns the complete set of results in a \r
- single request. Streaming methods are useful if you are returning hundreds of records and want to act on the results as they return.\r
- </simpara>\r
- </listitem>\r
- <listitem>\r
- <simpara>\r
- The optional <literal>signature</literal> member is a hash that describes the method’s purpose, arguments, and return value.\r
- </simpara>\r
- <itemizedlist>\r
- <listitem>\r
- <simpara>\r
- The <literal>desc</literal> member of the <literal>signature</literal> hash describes the method’s purpose.\r
- </simpara>\r
- </listitem>\r
- <listitem>\r
- <simpara>\r
- The <literal>params</literal> member of the <literal>signature</literal> hash is an array of hashes in which each array element describes the corresponding method \r
- argument in order.\r
- </simpara>\r
- <itemizedlist>\r
- <listitem>\r
- <simpara>\r
- The <literal>name</literal> member of the argument hash specifies the name of the argument.\r
- </simpara>\r
- </listitem>\r
- <listitem>\r
- <simpara>\r
- The <literal>desc</literal> member of the argument hash describes the argument’s purpose.\r
- </simpara>\r
- </listitem>\r
- <listitem>\r
- <simpara>\r
- The <literal>type</literal> member of the argument hash specifies the data type of the argument: for example, string, integer, boolean, number, array, or hash.\r
- </simpara>\r
- </listitem>\r
- </itemizedlist>\r
- </listitem>\r
- <listitem>\r
- <simpara>\r
- The <literal>return</literal> member of the <literal>signature</literal> hash is a hash that describes the return value of the method.\r
- </simpara>\r
- <itemizedlist>\r
- <listitem>\r
- <simpara>\r
- The <literal>desc</literal> member of the <literal>return</literal> hash describes the return value.\r
- </simpara>\r
- </listitem>\r
- <listitem>\r
- <simpara>\r
- The <literal>type</literal> member of the <literal>return</literal> hash specifies the data type of the return value: for example, string, integer, boolean, number, \r
- array, or hash.\r
- </simpara>\r
- </listitem>\r
- </itemizedlist>\r
- </listitem>\r
- </itemizedlist>\r
- </listitem>\r
- </itemizedlist>\r
- </simplesect>\r
- </section>\r
- <section id="_evergreen_specific_opensrf_services">\r
- <title>Evergreen-specific OpenSRF services</title>\r
- <simpara>Evergreen is currently the primary showcase for the use of OpenSRF as an\r
- application architecture. Evergreen 1.6.1 includes the following\r
- set of OpenSRF services:</simpara>\r
- <itemizedlist>\r
- <listitem>\r
- <simpara>\r
- The <systemitem class="service">open-ils.actor</systemitem> service supports common tasks for working with user\r
- accounts and libraries.\r
- </simpara>\r
- </listitem>\r
- <listitem>\r
- <simpara>\r
- The <systemitem class="service">open-ils.auth</systemitem> service supports authentication of Evergreen users.\r
- </simpara>\r
- </listitem>\r
- <listitem>\r
- <simpara>\r
- The <systemitem class="service">open-ils.booking</systemitem> service supports the management of reservations\r
- for bookable items.\r
- </simpara>\r
- </listitem>\r
- <listitem>\r
- <simpara>\r
- The <systemitem class="service">open-ils.cat</systemitem> service supports common cataloging tasks, such as\r
- creating, modifying, and merging bibliographic and authority records.\r
- </simpara>\r
- </listitem>\r
- <listitem>\r
- <simpara>\r
- The <systemitem class="service">open-ils.circ</systemitem> service supports circulation tasks such as checking\r
- out items and calculating due dates.\r
- </simpara>\r
- </listitem>\r
- <listitem>\r
- <simpara>\r
- The <systemitem class="service">open-ils.collections</systemitem> service supports tasks that assist collections\r
- agencies in contacting users with outstanding fines above a certain\r
- threshold.\r
- </simpara>\r
- </listitem>\r
- <listitem>\r
- <simpara>\r
- The <systemitem class="service">open-ils.cstore</systemitem> private service supports unrestricted access to\r
- Evergreen fieldmapper objects.\r
- </simpara>\r
- </listitem>\r
- <listitem>\r
- <simpara>\r
- The <systemitem class="service">open-ils.ingest</systemitem> private service supports tasks for importing\r
- data such as bibliographic and authority records.\r
- </simpara>\r
- </listitem>\r
- <listitem>\r
- <simpara>\r
- The <systemitem class="service">open-ils.pcrud</systemitem> service supports permission-based access to Evergreen\r
- fieldmapper objects.\r
- </simpara>\r
- </listitem>\r
- <listitem>\r
- <simpara>\r
- The <systemitem class="service">open-ils.penalty</systemitem> penalty service supports the calculation of\r
- penalties for users, such as being blocked from further borrowing, for\r
- conditions such as having too many items checked out or too many unpaid\r
- fines.\r
- </simpara>\r
- </listitem>\r
- <listitem>\r
- <simpara>\r
- The <systemitem class="service">open-ils.reporter</systemitem> service supports the creation and scheduling of\r
- reports.\r
- </simpara>\r
- </listitem>\r
- <listitem>\r
- <simpara>\r
- The <systemitem class="service">open-ils.reporter-store</systemitem> private service supports access to Evergreen\r
- fieldmapper objects for the reporting service.\r
- </simpara>\r
- </listitem>\r
- <listitem>\r
- <simpara>\r
- The <systemitem class="service">open-ils.search</systemitem> service supports searching across bibliographic\r
- records, authority records, serial records, Z39.50 sources, and ZIP codes.\r
- </simpara>\r
- </listitem>\r
- <listitem>\r
- <simpara>\r
- The <systemitem class="service">open-ils.storage</systemitem> private service supports a deprecated method of\r
- providing access to Evergreen fieldmapper objects. Implemented in Perl,\r
- this service has largely been replaced by the much faster C-based\r
- <literal>open-ils.cstore</literal> service.\r
- </simpara>\r
- </listitem>\r
- <listitem>\r
- <simpara>\r
- The <systemitem class="service">open-ils.supercat</systemitem> service supports transforms of MARC records into\r
- other formats, such as MODS, as well as providing Atom and RSS feeds and\r
- SRU access.\r
- </simpara>\r
- </listitem>\r
- <listitem>\r
- <simpara>\r
- The <systemitem class="service">open-ils.trigger</systemitem> private service supports event-based triggers for\r
- actions such as overdue and holds available notification emails.\r
- </simpara>\r
- </listitem>\r
- <listitem>\r
- <simpara>\r
- The <systemitem class="service">open-ils.vandelay</systemitem> service supports the import and export of batches of\r
- bibliographic and authority records.\r
- </simpara>\r
- </listitem>\r
- </itemizedlist>\r
- <simpara>Of some interest is that the <systemitem class="service">open-ils.reporter-store</systemitem> and <systemitem class="service">open-ils.cstore</systemitem>\r
- services have identical implementations. Surfacing them as separate services\r
- enables a deployer of Evergreen to ensure that the reporting service does not\r
- interfere with the performance-critical <systemitem class="service">open-ils.cstore</systemitem> service. One can also\r
- direct the reporting service to a read-only database replica to, again, avoid\r
- interference with <systemitem class="service">open-ils.cstore</systemitem> which must write to the master database.</simpara>\r
- <simpara>There are only a few significant services that are not built on OpenSRF in\r
- Evergreen 1.6.0, such as the SIP and Z39.50 servers. These services implement\r
- different protocols and build on existing daemon architectures (Simple2ZOOM\r
- for Z39.50), but still rely on the other OpenSRF services to provide access\r
- to the Evergreen data. The non-OpenSRF services are reasonably self-contained\r
- and can be deployed on different servers to deliver the same sort of deployment\r
- flexibility as OpenSRF services, but have the disadvantage of not being\r
- integrated into the same configuration and control infrastructure as the\r
- OpenSRF services.</simpara>\r
- </section>\r
-</chapter>\r