]> git.evergreen-ils.org Git - Evergreen.git/blob - docs/modules/development/pages/intro_opensrf.adoc
dd79863eb295030f31db9ed58961723b4c88f5fa
[Evergreen.git] / docs / modules / development / pages / intro_opensrf.adoc
1 = Easing gently into OpenSRF =
2 :toc:
3
4 == Abstract ==
5 The Evergreen open-source library system serves library consortia composed of
6 hundreds of branches with millions of patrons - for example,
7 http://www.georgialibraries.org/statelibrarian/bythenumbers.pdf[the Georgia
8 Public Library Service PINES system]. One of the claimed advantages of
9 Evergreen over alternative integrated library systems is the underlying Open
10 Service Request Framework (OpenSRF, pronounced "open surf") architecture. This
11 article introduces OpenSRF, demonstrates how to build OpenSRF services through
12 simple code examples, and explains the technical foundations on which OpenSRF
13 is built.
14
15 == Introducing OpenSRF ==
16 OpenSRF is a message routing network that offers scalability and failover
17 support for individual services and entire servers with minimal development and
18 deployment overhead. You can use OpenSRF to build loosely-coupled applications
19 that can be deployed on a single server or on clusters of geographically
20 distributed servers using the same code and minimal configuration changes.
21 Although copyright statements on some of the OpenSRF code date back to Mike
22 Rylander's original explorations in 2000, Evergreen was the first major
23 application to be developed with, and to take full advantage of, the OpenSRF
24 architecture starting in 2004. The first official release of OpenSRF was 0.1 in
25 February 2005 (http://evergreen-ils.org/blog/?p=21), but OpenSRF's development
26 continues a steady pace of enhancement and refinement, with the release of
27 1.0.0 in October 2008 and the most recent release of 1.2.2 in February 2010.
28
29 OpenSRF is a distinct break from the architectural approach used by previous
30 library systems and has more in common with modern Web applications. The
31 traditional "scale-up" approach to serve more transactions is to purchase a
32 server with more CPUs and more RAM, possibly splitting the load between a Web
33 server, a database server, and a business logic server. Evergreen, however, is
34 built on the Open Service Request Framework (OpenSRF) architecture, which
35 firmly embraces the "scale-out" approach of spreading transaction load over
36 cheap commodity servers. The http://evergreen-ils.org/blog/?p=56[initial GPLS
37 PINES hardware cluster], while certainly impressive, may have offered the
38 misleading impression that Evergreen is complex and requires a lot of hardware
39 to run. 
40
41 This article hopes to correct any such lingering impression by demonstrating
42 that OpenSRF itself is an extremely simple architecture on which one can easily
43 build applications of many kinds – not just library applications – and that you
44 can use a number of different languages to call and implement OpenSRF methods
45 with a minimal learning curve. With an application built on OpenSRF, when you
46 identify a bottleneck in your application's business logic layer, you can
47 adjust the number of the processes serving that particular bottleneck on each
48 of your servers; or if the problem is that your service is resource-hungry, you
49 could add an inexpensive server to your cluster and dedicate it to running that
50 resource-hungry service.
51
52 === Programming language support ===
53
54 If you need to develop an entirely new OpenSRF service, you can choose from a
55 number of different languages in which to implement that service. OpenSRF
56 client language bindings have been written for C, Java, JavaScript, Perl, and
57 Python, and server language bindings have been written for C, Perl, and Python.
58 This article uses Perl examples as a lowest common denominator programming
59 language. Writing an OpenSRF binding for another language is a relatively small
60 task if that language offers libraries that support the core technologies on
61 which OpenSRF depends:
62
63   * http://tools.ietf.org/html/rfc3920[Extensible Messaging and Presence
64 Protocol] (XMPP, sometimes referred to as Jabber) - provides the base messaging
65 infrastructure between OpenSRF clients and servers
66   * http://json.org[JavaScript Object Notation] (JSON) - serializes the content
67 of each XMPP message in a standardized and concise format
68   * http://memcached.org[memcached] - provides the caching service
69   * http://tools.ietf.org/html/rfc5424[syslog] - the standard UNIX logging
70 service
71
72 Unfortunately, the
73 http://evergreen-ils.org/dokuwiki/doku.php?id=osrf-devel:primer[OpenSRF
74 reference documentation], although augmented by the
75 http://evergreen-ils.org/dokuwiki/doku.php?id=osrf-devel:primer[OpenSRF
76 glossary], blog posts like http://evergreen-ils.org/blog/?p=36[the description
77 of OpenSRF and Jabber], and even this article, is not a sufficient substitute
78 for a complete specification on which one could implement a language binding.
79 The recommended option for would-be developers of another language binding is
80 to use the Python implementation as the cleanest basis for a port to another
81 language.
82
83 === OpenSRF communication flows over XMPP ===
84
85 The XMPP messaging service underpins OpenSRF, requiring an XMPP server such
86 as http://www.ejabberd.im/[ejabberd].  When you start OpenSRF, the first XMPP
87 clients that connect to the XMPP server are the OpenSRF public and private
88 _routers_. OpenSRF routers maintain a list of available services and connect
89 clients to available services. When an OpenSRF service starts, it establishes a
90 connection to the XMPP server and registers itself with the private router. The
91 OpenSRF configuration contains a list of public OpenSRF services, each of which
92 must also register with the public router. Services and clients connect to the
93 XMPP server using a single set of XMPP client credentials (for example,
94 `opensrf@private.localhost`), but use XMPP resource identifiers to
95 differentiate themselves in the Jabber ID (JID) for each connection. For
96 example, the JID for a copy of the `opensrf.simple-text` service with process
97 ID `6285` that has connected to the `private.localhost` domain using the
98 `opensrf` XMPP client credentials could be
99 `opensrf@private.localhost/opensrf.simple-text_drone_at_localhost_6285`.
100
101 [#OpenSRFOverHTTP]
102 === OpenSRF communication flows over HTTP ===
103 Any OpenSRF service registered with the public router is accessible via the
104 OpenSRF HTTP Translator. The OpenSRF HTTP Translator implements the
105 http://www.open-ils.org/dokuwiki/doku.php?id=opensrf_over_http[OpenSRF-over-HTTP
106 proposed specification] as an Apache module that translates HTTP requests into
107 OpenSRF requests and returns OpenSRF results as HTTP results to the initiating
108 HTTP client. 
109
110 .Issuing an HTTP POST request to an OpenSRF method via the OpenSRF HTTP Translator
111 [source,bash]
112 --------------------------------------------------------------------------------
113 # curl request broken up over multiple lines for legibility
114 curl -H "X-OpenSRF-service: opensrf.simple-text"                                \ # <1>
115     --data 'osrf-msg=[                                                          \ # <2>
116         {"__c":"osrfMessage","__p":{"threadTrace":0,"locale":"en-CA",           \ # <3>
117             "type":"REQUEST","payload": {"__c":"osrfMethod","__p":              \
118                 {"method":"opensrf.simple-text.reverse","params":["foobar"]}    \
119             }}                                                                  \
120         }]'                                                                     \
121 http://localhost/osrf-http-translator                                           \ # <4>
122 --------------------------------------------------------------------------------
123
124 <1> The `X-OpenSRF-service` header identifies the OpenSRF service of interest.
125
126 <2> The POST request consists of a single parameter, the `osrf-msg` value,
127 which contains a JSON array.
128
129 <3> The first object is an OpenSRF message (`"__c":"osrfMessage"`) with a set of
130 parameters (`"__p":{}`) containing:
131
132   * the identifier for the request (`"threadTrace":0`); this value is echoed
133 back in the result
134
135   * the message type (`"type":"REQUEST"`) 
136
137   * the locale for the message; if the OpenSRF method is locale-sensitive, it
138 can check the locale for each OpenSRF request and return different information
139 depending on the locale
140
141   * the payload of the message (`"payload":{}`) containing the OpenSRF method
142 request (`"__c":"osrfMethod"`) and its parameters (`"__p:"{}`), which in turn
143 contains:
144
145   ** the method name for the request (`"method":"opensrf.simple-text.reverse"`)
146
147   ** a set of JSON parameters to pass to the method (`"params":["foobar"]`); in
148 this case, a single string `"foobar"`
149
150 <4> The URL on which the OpenSRF HTTP translator is listening,
151 `/osrf-http-translator` is the default location in the Apache example
152 configuration files shipped with the OpenSRF source, but this is configurable.
153
154 [#httpResults]
155 .Results from an HTTP POST request to an OpenSRF method via the OpenSRF HTTP Translator
156 [source,bash]
157 --------------------------------------------------------------------------------
158 # HTTP response broken up over multiple lines for legibility
159 [{"__c":"osrfMessage","__p":                                                    \ # <1>
160     {"threadTrace":0, "payload":                                                \ # <2> 
161         {"__c":"osrfResult","__p":                                              \ # <3>
162             {"status":"OK","content":"raboof","statusCode":200}                 \ # <4>
163         },"type":"RESULT","locale":"en-CA"                                      \ # <5>
164     }
165 },
166 {"__c":"osrfMessage","__p":                                                     \ # <6>
167     {"threadTrace":0,"payload":                                                 \ # <7>
168         {"__c":"osrfConnectStatus","__p":                                       \ # <8>
169             {"status":"Request Complete","statusCode":205}                      \ # <9>
170         },"type":"STATUS","locale":"en-CA"                                      \ # <10>
171     }
172 }]
173 --------------------------------------------------------------------------------
174
175 <1> The OpenSRF HTTP Translator returns an array of JSON objects in its
176 response. Each object in the response is an OpenSRF message
177 (`"__c":"osrfMessage"`) with a collection of response parameters (`"__p":`).
178
179 <2> The OpenSRF message identifier (`"threadTrace":0`) confirms that this
180 message is in response to the request matching the same identifier.
181
182 <3> The message includes a payload JSON object (`"payload":`) with an OpenSRF
183 result for the request (`"__c":"osrfResult"`).
184
185 <4> The result includes a status indicator string (`"status":"OK"`), the content
186 of the result response - in this case, a single string "raboof"
187 (`"content":"raboof"`) - and an integer status code for the request
188 (`"statusCode":200`).
189
190 <5> The message also includes the message type (`"type":"RESULT"`) and the
191 message locale (`"locale":"en-CA"`).
192
193 <6> The second message in the set of results from the response.
194
195 <7> Again, the message identifier confirms that this message is in response to
196 a particular request.
197
198 <8> The payload of the message denotes that this message is an
199 OpenSRF connection status message (`"__c":"osrfConnectStatus"`), with some
200 information about the particular OpenSRF connection that was used for this
201 request.
202
203 <9> The response parameters for an OpenSRF connection status message include a
204 verbose status (`"status":"Request Complete"`) and an integer status code for
205 the connection status (`"statusCode":205).
206
207 <10> The message also includes the message type (`"type":"RESULT"`) and the
208 message locale (`"locale":"en-CA"`).
209
210
211 [TIP]
212 Before adding a new public OpenSRF service, ensure that it does
213 not introduce privilege escalation or unchecked access to data. For example,
214 the Evergreen `open-ils.cstore` private service is an object-relational mapper
215 that provides read and write access to the entire Evergreen database, so it
216 would be catastrophic to expose that service publicly. In comparison, the
217 Evergreen `open-ils.pcrud` public service offers the same functionality as
218 `open-ils.cstore` to any connected HTTP client or OpenSRF client, but the
219 additional authentication and authorization layer in `open-ils.pcrud` prevents
220 unchecked access to Evergreen's data.
221
222 === Stateless and stateful connections ===
223
224 OpenSRF supports both _stateless_ and _stateful_ connections.  When an OpenSRF
225 client issues a `REQUEST` message in a _stateless_ connection, the router
226 forwards the request to the next available service and the service returns the
227 result directly to the client. 
228
229 .REQUEST flow in a stateless connection
230 image:media/REQUEST.png[REQUEST flow in a stateless connection]
231
232 When an OpenSRF client issues a `CONNECT` message to create a _stateful_ connection, the
233 router returns the Jabber ID of the next available service to the client so
234 that the client can issue one or more `REQUEST` message directly to that
235 particular service and the service will return corresponding `RESULT` messages
236 directly to the client. Until the client issues a `DISCONNECT` message, that
237 particular service is only available to the requesting client. Stateful connections
238 are useful for clients that need to make many requests from a particular service,
239 as it avoids the intermediary step of contacting the router for each request, as
240 well as for operations that require a controlled sequence of commands, such as a
241 set of database INSERT, UPDATE, and DELETE statements within a transaction.
242
243 .CONNECT, REQUEST, and DISCONNECT flow in a stateful connection
244 image:media/CONNECT.png[CONNECT, REQUEST, and DISCONNECT flow in a stateful connection]
245
246 == Enough jibber-jabber: writing an OpenSRF service ==
247 Imagine an application architecture in which 10 lines of Perl or Python, using
248 the data types native to each language, are enough to implement a method that
249 can then be deployed and invoked seamlessly across hundreds of servers.  You
250 have just imagined developing with OpenSRF – it is truly that simple. Under the
251 covers, of course, the OpenSRF language bindings do an incredible amount of
252 work on behalf of the developer. An OpenSRF application consists of one or more
253 OpenSRF services that expose methods: for example, the `opensrf.simple-text`
254 http://git.evergreen-ils.org/?p=OpenSRF.git;a=blob_plain;f=src/perl/lib/OpenSRF/Application/Demo/SimpleText.pm[demonstration
255 service] exposes the `opensrf.simple-text.split()` and
256 `opensrf.simple-text.reverse()` methods. Each method accepts zero or more
257 arguments and returns zero or one results. The data types supported by OpenSRF
258 arguments and results are typical core language data types: strings, numbers,
259 booleans, arrays, and hashes.
260
261 To implement a new OpenSRF service, perform the following steps:
262
263   1. Include the base OpenSRF support libraries
264   2. Write the code for each of your OpenSRF methods as separate procedures 
265   3. Register each method
266   4. Add the service definition to the OpenSRF configuration files
267
268 For example, the following code implements an OpenSRF service. The service
269 includes one method named `opensrf.simple-text.reverse()` that accepts one
270 string as input and returns the reversed version of that string:
271
272 [source,perl]
273 --------------------------------------------------------------------------------
274 #!/usr/bin/perl
275
276 package OpenSRF::Application::Demo::SimpleText;
277
278 use strict;
279
280 use OpenSRF::Application;
281 use parent qw/OpenSRF::Application/;
282
283 sub text_reverse {
284     my ($self , $conn, $text) = @_;
285     my $reversed_text = scalar reverse($text);
286     return $reversed_text;
287 }
288
289 __PACKAGE__->register_method(
290     method    => 'text_reverse',
291     api_name  => 'opensrf.simple-text.reverse'
292 );
293 --------------------------------------------------------------------------------
294
295 Ten lines of code, and we have a complete OpenSRF service that exposes a single
296 method and could be deployed quickly on a cluster of servers to meet your
297 application's ravenous demand for reversed strings! If you're unfamiliar with
298 Perl, the `use OpenSRF::Application; use parent qw/OpenSRF::Application/;`
299 lines tell this package to inherit methods and properties from the
300 `OpenSRF::Application` module. For example, the call to
301 `__PACKAGE__->register_method()` is defined in `OpenSRF::Application` but due to
302 inheritance is available in this package (named by the special Perl symbol
303 `__PACKAGE__` that contains the current package name). The `register_method()`
304 procedure is how we introduce a method to the rest of the OpenSRF world.
305
306 [#serviceRegistration]
307 === Registering a service with the OpenSRF configuration files ===
308
309 Two files control most of the configuration for OpenSRF:
310
311   * `opensrf.xml` contains the configuration for the service itself as well as
312 a list of which application servers in your OpenSRF cluster should start
313 the service
314   * `opensrf_core.xml` (often referred to as the "bootstrap configuration"
315 file) contains the OpenSRF networking information, including the XMPP server
316 connection credentials for the public and private routers; you only need to touch
317 this for a new service if the new service needs to be accessible via the
318 public router
319
320 Begin by defining the service itself in `opensrf.xml`. To register the
321 `opensrf.simple-text` service, add the following section to the `<apps>`
322 element (corresponding to the XPath `/opensrf/default/apps/`):
323
324 [source,xml]
325 --------------------------------------------------------------------------------
326 <apps>
327   <opensrf.simple-text>                                                      <!-- <1> -->
328     <keepalive>3</keepalive>                                                 <!-- <2> -->
329     <stateless>1</stateless>                                                 <!-- <3> -->        
330     <language>perl</language>                                                <!-- <4> -->
331     <implementation>OpenSRF::Application::Demo::SimpleText</implementation>  <!-- <5> -->
332     <max_requests>100</max_requests>                                         <!-- <6> -->
333     <unix_config>
334       <max_requests>1000</max_requests>                                      <!-- <7> -->
335       <unix_log>opensrf.simple-text_unix.log</unix_log>                      <!-- <8> -->
336       <unix_sock>opensrf.simple-text_unix.sock</unix_sock>                   <!-- <9> -->
337       <unix_pid>opensrf.simple-text_unix.pid</unix_pid>                     <!-- <10> -->
338       <min_children>5</min_children>                                        <!-- <11> -->
339       <max_children>15</max_children>                                       <!-- <12> -->
340       <min_spare_children>2</min_spare_children>                            <!-- <13> -->
341       <max_spare_children>5</max_spare_children>                            <!-- <14> -->
342     </unix_config>
343   </opensrf.simple-text>
344
345   <!-- other OpenSRF services registered here... -->
346 </apps>
347 --------------------------------------------------------------------------------
348
349 <1> The element name is the name that the OpenSRF control scripts use to refer
350 to the service.
351
352 <2> Specifies the interval (in seconds) between checks to determine if the
353 service is still running.
354
355 <3> Specifies whether OpenSRF clients can call methods from this service
356 without first having to create a connection to a specific service backend
357 process for that service. If the value is `1`, then the client can simply
358 issue a request and the router will forward the request to an available
359 service and the result will be returned directly to the client.
360
361 <4> Specifies the programming language in which the service is implemented
362
363 <5> Specifies the name of the library or module in which the service is implemented
364
365 <6> (C implementations): Specifies the maximum number of requests a process
366 serves before it is killed and replaced by a new process.
367
368 <7> (Perl implementations): Specifies the maximum number of requests a process
369 serves before it is killed and replaced by a new process.
370
371 <8> The name of the log file for language-specific log messages such as syntax
372 warnings.
373
374 <9> The name of the UNIX socket used for inter-process communications.
375
376 <10> The name of the PID file for the master process for the service.
377
378 <11> The minimum number of child processes that should be running at any given
379 time.
380
381 <12> The maximum number of child processes that should be running at any given
382 time.
383
384 <13> The minimum number of child processes that should be available to handle
385 incoming requests. If there are fewer than this number of spare child
386 processes, new processes will be spawned.
387
388 <14>  The maximum number of child processes that should be available to handle
389 incoming requests. If there are more than this number of spare child processes,
390 the extra processes will be killed.
391
392 To make the service accessible via the public router, you must also
393 edit the `opensrf_core.xml` configuration file to add the service to the list
394 of publicly accessible services:
395
396 .Making a service publicly accessible in `opensrf_core.xml`
397 [source,xml]
398 --------------------------------------------------------------------------------
399 <router>                                                                     <!-- <1> --> 
400     <!-- This is the public router. On this router, we only register applications
401      which should be accessible to everyone on the opensrf network -->
402     <name>router</name>
403     <domain>public.localhost</domain>                                        <!-- <2> -->
404     <services>
405         <service>opensrf.math</service> 
406         <service>opensrf.simple-text</service>                               <!-- <3> -->
407     </services>
408 </router>
409 --------------------------------------------------------------------------------
410
411 <1> This section of the `opensrf_core.xml` file is located at XPath 
412 `/config/opensrf/routers/`.
413
414 <2> `public.localhost` is the canonical public router domain in the OpenSRF
415 installation instructions.
416
417 <3> Each `<service>` element contained in the `<services>` element 
418 offers their services via the public router as well as the private router.
419
420 Once you have defined the new service, you must restart the OpenSRF Router
421 to retrieve the new configuration and start or restart the service itself.
422
423 === Calling an OpenSRF method ===
424 OpenSRF clients in any supported language can invoke OpenSRF services in any
425 supported language. So let's see a few examples of how we can call our fancy
426 new `opensrf.simple-text.reverse()` method:
427
428 ==== Calling OpenSRF methods from the srfsh client ====
429 `srfsh` is a command-line tool installed with OpenSRF that you can use to call
430 OpenSRF methods. To call an OpenSRF method, issue the `request` command and pass
431 the OpenSRF service and method name as the first two arguments; then pass a list
432 of JSON objects as the arguments to the method being invoked.
433
434 The following example calls the `opensrf.simple-text.reverse` method of the
435 `opensrf.simple-text` OpenSRF service, passing the string `"foobar"` as the
436 only method argument:
437
438 [source,sh]
439 --------------------------------------------------------------------------------
440 $ srfsh
441 srfsh # request opensrf.simple-text opensrf.simple-text.reverse "foobar"
442
443 Received Data: "raboof"
444
445 =------------------------------------
446 Request Completed Successfully
447 Request Time in seconds: 0.016718
448 =------------------------------------
449 --------------------------------------------------------------------------------
450
451 [#opensrfIntrospection]
452 ==== Getting documentation for OpenSRF methods from the srfsh client ====
453
454 The `srfsh` client also gives you command-line access to retrieving metadata
455 about OpenSRF services and methods. For a given OpenSRF method, for example,
456 you can retrieve information such as the minimum number of required arguments,
457 the data type and a description of each argument, the package or library in
458 which the method is implemented, and a description of the method. To retrieve
459 the documentation for an opensrf method from `srfsh`, issue the `introspect`
460 command, followed by the name of the OpenSRF service and (optionally) the
461 name of the OpenSRF method. If you do not pass a method name to the `introspect`
462 command, `srfsh` lists all of the methods offered by the service. If you pass
463 a partial method name, `srfsh` lists all of the methods that match that portion
464 of the method name.
465
466 [NOTE]
467 The quality and availability of the descriptive information for each
468 method depends on the developer to register the method with complete and
469 accurate information. The quality varies across the set of OpenSRF and
470 Evergreen APIs, although some effort is being put towards improving the
471 state of the internal documentation.
472
473 [source,sh]
474 --------------------------------------------------------------------------------
475 srfsh# introspect opensrf.simple-text "opensrf.simple-text.reverse"
476 --> opensrf.simple-text
477
478 Received Data: {
479   "__c":"opensrf.simple-text",
480   "__p":{
481     "api_level":1,
482     "stream":0,                                                      \ # <1>
483     "object_hint":"OpenSRF_Application_Demo_SimpleText",
484     "remote":0,
485     "package":"OpenSRF::Application::Demo::SimpleText",              \ # <2>
486     "api_name":"opensrf.simple-text.reverse",                        \ # <3>
487     "server_class":"opensrf.simple-text",
488     "signature":{                                                    \ # <4>
489       "params":[                                                     \ # <5>
490         {
491           "desc":"The string to reverse",
492           "name":"text",
493           "type":"string"
494         }
495       ],
496       "desc":"Returns the input string in reverse order\n",          \ # <6>
497       "return":{                                                     \ # <7>
498         "desc":"Returns the input string in reverse order",
499         "type":"string"
500       }
501     },
502     "method":"text_reverse",                                         \ # <8>
503     "argc":1                                                         \ # <9>
504   }
505 }
506 --------------------------------------------------------------------------------
507
508 <1> `stream` denotes whether the method supports streaming responses or not.
509
510 <2> `package` identifies which package or library implements the method.
511
512 <3> `api_name` identifies the name of the OpenSRF method.
513
514 <4> `signature` is a hash that describes the parameters for the method.
515
516 <5> `params` is an array of hashes describing each parameter in the method;
517 each parameter has a description (`desc`), name (`name`), and type (`type`).
518
519 <6> `desc` is a string that describes the method itself.
520
521 <7> `return` is a hash that describes the return value for the method; it
522 contains a description of the return value (`desc`) and the type of the
523 returned value (`type`).
524
525 <8> `method` identifies the name of the function or method in the source
526 implementation.
527
528 <9> `argc` is an integer describing the minimum number of arguments that
529 must be passed to this method.
530
531 ==== Calling OpenSRF methods from Perl applications ====
532
533 To call an OpenSRF method from Perl, you must connect to the OpenSRF service,
534 issue the request to the method, and then retrieve the results.
535
536 [source,perl]
537 --------------------------------------------------------------------------------
538 #/usr/bin/perl
539 use strict;
540 use OpenSRF::AppSession;
541 use OpenSRF::System;
542
543 OpenSRF::System->bootstrap_client(config_file => '/openils/conf/opensrf_core.xml'); # <1>
544
545 my $session = OpenSRF::AppSession->create("opensrf.simple-text");                   # <2>
546
547 print "substring: Accepts a string and a number as input, returns a string\n";
548 my $result = $session->request("opensrf.simple-text.substring", "foobar", 3);       # <3>
549 my $request = $result->gather();                                                    # <4>
550 print "Substring: $request\n\n";
551
552 print "split: Accepts two strings as input, returns an array of strings\n";
553 $request = $session->request("opensrf.simple-text.split", "This is a test", " ");   # <5>
554 my $output = "Split: [";
555 my $element;
556 while ($element = $request->recv()) {                                               # <6>
557     $output .= $element->content . ", ";                                            # <7>
558 }
559 $output =~ s/, $/]/;
560 print $output . "\n\n";
561
562 print "statistics: Accepts an array of strings as input, returns a hash\n";
563 my @many_strings = [
564     "First I think I'll have breakfast",
565     "Then I think that lunch would be nice",
566     "And then seventy desserts to finish off the day"
567 ];
568
569 $result = $session->request("opensrf.simple-text.statistics", @many_strings);       # <8>
570 $request = $result->gather();                                                       # <9>
571 print "Length: " . $result->{'length'} . "\n";
572 print "Word count: " . $result->{'word_count'} . "\n";
573
574 $session->disconnect();                                                             # <10>
575 --------------------------------------------------------------------------------
576
577 <1> The `OpenSRF::System->bootstrap_client()` method reads the OpenSRF
578 configuration information from the indicated file and creates an XMPP client
579 connection based on that information.
580
581 <2> The `OpenSRF::AppSession->create()` method accepts one argument - the name
582 of the OpenSRF service to which you want to want to make one or more requests -
583 and returns an object prepared to use the client connection to make those
584 requests.
585
586 <3> The `OpenSRF::AppSession->request()` method accepts a minimum of one
587 argument - the name of the OpenSRF method to which you want to make a request -
588 followed by zero or more arguments to pass to the OpenSRF method as input
589 values. This example passes a string and an integer to the
590 `opensrf.simple-text.substring` method defined by the `opensrf.simple-text`
591 OpenSRF service.
592
593 <4> The `gather()` method, called on the result object returned by the
594 `request()` method, iterates over all of the possible results from the result
595 object and returns a single variable.
596
597 <5> This `request()` call passes two strings to the `opensrf.simple-text.split`
598 method defined by the `opensrf.simple-text` OpenSRF service and returns (via
599 `gather()`) a reference to an array of results.
600
601 <6> The `opensrf.simple-text.split()` method is a streaming method that
602 returns an array of results with one element per `recv()` call on the
603 result object. We could use the `gather()` method to retrieve all of the
604 results in a single array reference, but instead we simply iterate over
605 the result variable until there are no more results to retrieve.
606
607 <7> While the `gather()` convenience method returns only the content of the
608 complete set of results for a given request, the `recv()` method returns an
609 OpenSRF result object with `status`, `statusCode`, and `content` fields as
610 we saw in <<httpResults,the HTTP results example>>.
611
612 <8> This `request()` call passes an array to the
613 `opensrf.simple-text.statistics` method defined by the `opensrf.simple-text`
614 OpenSRF service.
615
616 <9> The result object returns a hash reference via `gather()`. The hash 
617 contains the `length` and `word_count` keys we defined in the method.
618
619 <10> The `OpenSRF::AppSession->disconnect()` method closes the XMPP client
620 connection and cleans up resources associated with the session.
621
622 === Accepting and returning more interesting data types ===
623
624 Of course, the example of accepting a single string and returning a single
625 string is not very interesting. In real life, our applications tend to pass
626 around multiple arguments, including arrays and hashes. Fortunately, OpenSRF
627 makes that easy to deal with; in Perl, for example, returning a reference to
628 the data type does the right thing. In the following example of a method that
629 returns a list, we accept two arguments of type string: the string to be split,
630 and the delimiter that should be used to split the string.
631
632 .Text splitting method - streaming mode
633 [source,perl]
634 --------------------------------------------------------------------------------
635 sub text_split {
636     my $self = shift;
637     my $conn = shift;
638     my $text = shift;
639     my $delimiter = shift || ' ';
640
641     my @split_text = split $delimiter, $text;
642     return \@split_text;
643 }
644
645 __PACKAGE__->register_method(
646     method    => 'text_split',
647     api_name  => 'opensrf.simple-text.split'
648 );
649 --------------------------------------------------------------------------------
650
651 We simply return a reference to the list, and OpenSRF does the rest of the work
652 for us to convert the data into the language-independent format that is then
653 returned to the caller. As a caller of a given method, you must rely on the
654 documentation used to register to determine the data structures - if the developer has
655 added the appropriate documentation.
656
657 === Accepting and returning Evergreen objects ===
658
659 OpenSRF is agnostic about objects; its role is to pass JSON back and forth
660 between OpenSRF clients and services, and it allows the specific clients and
661 services to define their own semantics for the JSON structures. On top of that
662 infrastructure, Evergreen offers the fieldmapper: an object-relational mapper
663 that provides a complete definition of all objects, their properties, their
664 relationships to other objects, the permissions required to create, read,
665 update, or delete objects of that type, and the database table or view on which
666 they are based.
667
668 The Evergreen fieldmapper offers a great deal of convenience for working with
669 complex system objects beyond the basic mapping of classes to database
670 schemas. Although the result is passed over the wire as a JSON object
671 containing the indicated fields, fieldmapper-aware clients then turn those
672 JSON objects into native objects with setter / getter methods for each field.
673
674 All of this metadata about Evergreen objects is defined in the
675 fieldmapper configuration file (`/openils/conf/fm_IDL.xml`), and access to
676 these classes is provided by the `open-ils.cstore`, `open-ils.pcrud`, and
677 `open-ils.reporter-store` OpenSRF services which parse the fieldmapper
678 configuration file and dynamically register OpenSRF methods for creating,
679 reading, updating, and deleting all of the defined classes.
680
681 .Example fieldmapper class definition for "Open User Summary"
682 [source,xml]
683 --------------------------------------------------------------------------------
684 <class id="mous" controller="open-ils.cstore open-ils.pcrud"
685  oils_obj:fieldmapper="money::open_user_summary"
686  oils_persist:tablename="money.open_usr_summary"
687  reporter:label="Open User Summary">                                       <!-- <1> -->
688     <fields oils_persist:primary="usr" oils_persist:sequence="">           <!-- <2> -->
689         <field name="balance_owed" reporter:datatype="money" />            <!-- <3> -->
690         <field name="total_owed" reporter:datatype="money" />
691         <field name="total_paid" reporter:datatype="money" />
692         <field name="usr" reporter:datatype="link"/>
693     </fields>
694     <links>
695         <link field="usr" reltype="has_a" key="id" map="" class="au"/>     <!-- <4> -->
696     </links>
697     <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">  <!-- <5> -->
698         <actions>
699             <retrieve permission="VIEW_USER">                              <!-- <6> -->
700                 <context link="usr" field="home_ou"/>                      <!-- <7> -->
701             </retrieve>
702         </actions>
703     </permacrud>
704 </class>
705 --------------------------------------------------------------------------------
706
707 <1> The `<class>` element defines the class:
708
709   * The `id` attribute defines the _class hint_ that identifies the class both
710 elsewhere in the fieldmapper configuration file, such as in the value of the
711 `field` attribute of the `<link>` element, and in the JSON object itself when
712 it is instantiated. For example, an "Open User Summary" JSON object would have
713 the top level property of `"__c":"mous"`.
714
715   * The `controller` attribute identifies the services that have direct access
716 to this class. If `open-ils.pcrud` is not listed, for example, then there is
717 no means to directly access members of this class through a public service.
718
719   * The `oils_obj:fieldmapper` attribute defines the name of the Perl
720 fieldmapper class that will be dynamically generated to provide setter and
721 getter methods for instances of the class.
722
723   * The `oils_persist:tablename` attribute identifies the schema name and table
724 name of the database table that stores the data that represents the instances
725 of this class. In this case, the schema is `money` and the table is
726 `open_usr_summary`.
727
728   * The `reporter:label` attribute defines a human-readable name for the class
729 used in the reporting interface to identify the class. These names are defined
730 in English in the fieldmapper configuration file; however, they are extracted
731 so that they can be translated and served in the user's language of choice.
732
733 <2> The `<fields>` element lists all of the fields that belong to the object.
734
735   * The `oils_persist:primary` attribute identifies the field that acts as the
736 primary key for the object; in this case, the field with the name `usr`.
737
738   * The `oils_persist:sequence` attribute identifies the sequence object
739 (if any) in this database provides values for new instances of this class. In
740 this case, the primary key is defined by a field that is linked to a different
741 table, so no sequence is used to populate these instances.
742
743 <3> Each `<field>` element defines a single field with the following attributes:
744
745   * The `name` attribute identifies the column name of the field in the
746 underlying database table as well as providing a name for the setter / getter
747 method that can be invoked in the JSON or native version of the object.
748
749   * The `reporter:datatype` attribute defines how the reporter should treat
750 the contents of the field for the purposes of querying and display.
751
752   * The `reporter:label` attribute can be used to provide a human-readable name
753 for each field; without it, the reporter falls back to the value of the `name`
754 attribute.
755
756 <4> The `<links>` element contains a set of zero or more `<link>` elements,
757 each of which defines a relationship between the class being described and
758 another class.
759
760   * The `field` attribute identifies the field named in this class that links
761 to the external class.
762
763   * The `reltype` attribute identifies the kind of relationship between the
764 classes; in the case of `has_a`, each value in the `usr` field is guaranteed
765 to have a corresponding value in the external class.
766
767   * The `key` attribute identifies the name of the field in the external
768 class to which this field links.
769
770   * The rarely-used `map` attribute identifies a second class to which
771 the external class links; it enables this field to define a direct
772 relationship to an external class with one degree of separation, to
773 avoid having to retrieve all of the linked members of an intermediate
774 class just to retrieve the instances from the actual desired target class.
775
776   * The `class` attribute identifies the external class to which this field
777 links.
778
779 <5> The `<permacrud>` element defines the permissions that must have been
780 granted to a user to operate on instances of this class.
781
782 <6> The `<retrieve>` element is one of four possible children of the
783 `<actions>` element that define the permissions required for each action:
784 create, retrieve, update, and delete.
785
786   * The `permission` attribute identifies the name of the permission that must
787 have been granted to the user to perform the action.
788
789   * The `contextfield` attribute, if it exists, defines the field in this class
790 that identifies the library within the system for which the user must have
791 privileges to work. If a user has been granted a given permission, but has not been
792 granted privileges to work at a given library, they can not perform the action
793 at that library.
794
795 <7> The rarely-used `<context>` element identifies a linked field (`link`
796 attribute) in this class which links to an external class that holds the field
797 (`field` attribute) that identifies the library within the system for which the
798 user must have privileges to work.
799
800 When you retrieve an instance of a class, you can ask for the result to
801 _flesh_ some or all of the linked fields of that class, so that the linked
802 instances are returned embedded directly in your requested instance. In that
803 same request you can ask for the fleshed instances to in turn have their linked
804 fields fleshed. By bundling all of this into a single request and result
805 sequence, you can avoid the network overhead of requiring the client to request
806 the base object, then request each linked object in turn.
807
808 You can also iterate over a collection of instances and set the automatically
809 generated `isdeleted`, `isupdated`, or `isnew` properties to indicate that
810 the given instance has been deleted, updated, or created respectively.
811 Evergreen can then act in batch mode over the collection to perform the
812 requested actions on any of the instances that have been flagged for action.
813
814 === Returning streaming results ===
815
816 In the previous implementation of the `opensrf.simple-text.split` method, we
817 returned a reference to the complete array of results. For small values being
818 delivered over the network, this is perfectly acceptable, but for large sets of
819 values this can pose a number of problems for the requesting client. Consider a
820 service that returns a set of bibliographic records in response to a query like
821 "all records edited in the past month"; if the underlying database is
822 relatively active, that could result in thousands of records being returned as
823 a single network request. The client would be forced to block until all of the
824 results are returned, likely resulting in a significant delay, and depending on
825 the implementation, correspondingly large amounts of memory might be consumed
826 as all of the results are read from the network in a single block.
827
828 OpenSRF offers a solution to this problem. If the method returns results that
829 can be divided into separate meaningful units, you can register the OpenSRF
830 method as a streaming method and enable the client to loop over the results one
831 unit at a time until the method returns no further results. In addition to
832 registering the method with the provided name, OpenSRF also registers an additional
833 method with `.atomic` appended to the method name. The `.atomic` variant gathers
834 all of the results into a single block to return to the client, giving the caller
835 the ability to choose either streaming or atomic results from a single method
836 definition.
837
838 In the following example, the text splitting method has been reimplemented to
839 support streaming; very few changes are required:
840
841 .Text splitting method - streaming mode
842 [source,perl]
843 --------------------------------------------------------------------------------
844 sub text_split {
845     my $self = shift;
846     my $conn = shift;
847     my $text = shift;
848     my $delimiter = shift || ' ';
849
850     my @split_text = split $delimiter, $text;
851     foreach my $string (@split_text) {           # <1>
852         $conn->respond($string);
853     }
854     return undef;
855 }
856
857 __PACKAGE__->register_method(
858     method    => 'text_split',
859     api_name  => 'opensrf.simple-text.split',
860     stream    => 1                              # <2>
861 );
862 --------------------------------------------------------------------------------
863
864 <1> Rather than returning a reference to the array, a streaming method loops
865 over the contents of the array and invokes the `respond()` method of the
866 connection object on each element of the array.
867
868 <2> Registering the method as a streaming method instructs OpenSRF to also
869 register an atomic variant (`opensrf.simple-text.split.atomic`).
870
871 === Error! Warning! Info! Debug! ===
872 As hard as it may be to believe, it is true: applications sometimes do not
873 behave in the expected manner, particularly when they are still under
874 development. The server language bindings for OpenSRF include integrated
875 support for logging messages at the levels of ERROR, WARNING, INFO, DEBUG, and
876 the extremely verbose INTERNAL to either a local file or to a syslogger
877 service. The destination of the log files, and the level of verbosity to be
878 logged, is set in the `opensrf_core.xml` configuration file. To add logging to
879 our Perl example, we just have to add the `OpenSRF::Utils::Logger` package to our
880 list of used Perl modules, then invoke the logger at the desired logging level.
881
882 You can include many calls to the OpenSRF logger; only those that are higher
883 than your configured logging level will actually hit the log. The following
884 example exercises all of the available logging levels in OpenSRF:
885
886 [source,perl]
887 --------------------------------------------------------------------------------
888 use OpenSRF::Utils::Logger;
889 my $logger = OpenSRF::Utils::Logger;
890 # some code in some function
891 {
892     $logger->error("Hmm, something bad DEFINITELY happened!");
893     $logger->warn("Hmm, something bad might have happened.");
894     $logger->info("Something happened.");
895     $logger->debug("Something happened; here are some more details.");
896     $logger->internal("Something happened; here are all the gory details.")
897 }
898 --------------------------------------------------------------------------------
899
900 If you call the mythical OpenSRF method containing the preceding OpenSRF logger
901 statements on a system running at the default logging level of INFO, you will
902 only see the INFO, WARN, and ERR messages, as follows: 
903
904 .Results of logging calls at the default level of INFO
905 --------------------------------------------------------------------------------
906 [2010-03-17 22:27:30] opensrf.simple-text [ERR :5681:SimpleText.pm:277:] Hmm, something bad DEFINITELY happened!
907 [2010-03-17 22:27:30] opensrf.simple-text [WARN:5681:SimpleText.pm:278:] Hmm, something bad might have happened.
908 [2010-03-17 22:27:30] opensrf.simple-text [INFO:5681:SimpleText.pm:279:] Something happened.
909 --------------------------------------------------------------------------------
910
911 If you then increase the the logging level to INTERNAL (5), the logs will
912 contain much more information, as follows:
913
914 .Results of logging calls at the default level of INTERNAL
915 --------------------------------------------------------------------------------
916 [2010-03-17 22:48:11] opensrf.simple-text [ERR :5934:SimpleText.pm:277:] Hmm, something bad DEFINITELY happened!
917 [2010-03-17 22:48:11] opensrf.simple-text [WARN:5934:SimpleText.pm:278:] Hmm, something bad might have happened.
918 [2010-03-17 22:48:11] opensrf.simple-text [INFO:5934:SimpleText.pm:279:] Something happened.
919 [2010-03-17 22:48:11] opensrf.simple-text [DEBG:5934:SimpleText.pm:280:] Something happened; here are some more details.
920 [2010-03-17 22:48:11] opensrf.simple-text [INTL:5934:SimpleText.pm:281:] Something happened; here are all the gory details.
921 [2010-03-17 22:48:11] opensrf.simple-text [ERR :5934:SimpleText.pm:283:] Resolver did not find a cache hit
922 [2010-03-17 22:48:21] opensrf.simple-text [INTL:5934:Cache.pm:125:] Stored opensrf.simple-text.test_cache.masaa => "here" in memcached server
923 [2010-03-17 22:48:21] opensrf.simple-text [DEBG:5934:Application.pm:579:] Coderef for [OpenSRF::Application::Demo::SimpleText::test_cache] has been run
924 [2010-03-17 22:48:21] opensrf.simple-text [DEBG:5934:Application.pm:586:] A top level Request object is responding de nada
925 [2010-03-17 22:48:21] opensrf.simple-text [DEBG:5934:Application.pm:190:] Method duration for [opensrf.simple-text.test_cache]:  10.005
926 [2010-03-17 22:48:21] opensrf.simple-text [INTL:5934:AppSession.pm:780:] Calling queue_wait(0)
927 [2010-03-17 22:48:21] opensrf.simple-text [INTL:5934:AppSession.pm:769:] Resending...0
928 [2010-03-17 22:48:21] opensrf.simple-text [INTL:5934:AppSession.pm:450:] In send
929 [2010-03-17 22:48:21] opensrf.simple-text [DEBG:5934:AppSession.pm:506:] AppSession sending RESULT to opensrf@private.localhost/_dan-karmic-liblap_1268880489.752154_5943 with threadTrace [1]
930 [2010-03-17 22:48:21] opensrf.simple-text [DEBG:5934:AppSession.pm:506:] AppSession sending STATUS to opensrf@private.localhost/_dan-karmic-liblap_1268880489.752154_5943 with threadTrace [1]
931 ...
932 --------------------------------------------------------------------------------
933
934 To see everything that is happening in OpenSRF, try leaving your logging level
935 set to INTERNAL for a few minutes - just ensure that you have a lot of free disk
936 space available if you have a moderately busy system!
937
938 === Caching results: one secret of scalability ===
939 If you have ever used an application that depends on a remote Web service
940 outside of your control-say, if you need to retrieve results from a
941 microblogging service-you know the pain of latency and dependability (or the
942 lack thereof). To improve response time in OpenSRF applications, you can take
943 advantage of the support offered by the `OpenSRF::Utils::Cache` module for
944 communicating with a local instance or cluster of memcache daemons to store
945 and retrieve persistent values.
946
947 [source,perl]
948 --------------------------------------------------------------------------------
949 use OpenSRF::Utils::Cache;                                       # <1>
950 sub test_cache {
951     my $self = shift;
952     my $conn = shift;
953     my $test_key = shift;
954     my $cache = OpenSRF::Utils::Cache->new('global');            # <2>
955     my $cache_key = "opensrf.simple-text.test_cache.$test_key";  # <3>
956     my $result = $cache->get_cache($cache_key) || undef;         # <4>
957     if ($result) {
958         $logger->info("Resolver found a cache hit");
959         return $result;
960     }
961     sleep 10;                                                    # <5>
962     my $cache_timeout = 300;                                     # <6>
963     $cache->put_cache($cache_key, "here", $cache_timeout);       # <7>
964     return "There was no cache hit.";
965 }
966 --------------------------------------------------------------------------------
967
968 This example:
969
970 <1> Imports the OpenSRF::Utils::Cache module
971
972 <2> Creates a cache object
973
974 <3> Creates a unique cache key based on the OpenSRF method name and
975 request input value
976
977 <4> Checks to see if the cache key already exists; if so, it immediately
978 returns that value
979
980 <5> If the cache key does not exist, the code sleeps for 10 seconds to
981 simulate a call to a slow remote Web service, or an intensive process
982
983 <6> Sets a value for the lifetime of the cache key in seconds
984
985 <7> When the code has retrieved its value, then it can create the cache
986 entry, with the cache key, value to be stored ("here"), and the timeout
987 value in seconds to ensure that we do not return stale data on subsequent
988 calls
989
990 === Initializing the service and its children: child labour ===
991 When an OpenSRF service is started, it looks for a procedure called
992 `initialize()` to set up any global variables shared by all of the children of
993 the service. The `initialize()` procedure is typically used to retrieve
994 configuration settings from the `opensrf.xml` file.
995
996 An OpenSRF service spawns one or more children to actually do the work
997 requested by callers of the service. For every child process an OpenSRF service
998 spawns, the child process clones the parent environment and then each child
999 process runs the `child_init()` process (if any) defined in the OpenSRF service
1000 to initialize any child-specific settings.
1001
1002 When the OpenSRF service kills a child process, it invokes the `child_exit()`
1003 procedure (if any) to clean up any resources associated with the child process.
1004 Similarly, when the OpenSRF service is stopped, it calls the `DESTROY()`
1005 procedure to clean up any remaining resources.
1006
1007 === Retrieving configuration settings ===
1008 The settings for OpenSRF services are maintained in the `opensrf.xml` XML 
1009 configuration file. The structure of the XML document consists of a root
1010 element `<opensrf>` containing two child elements:
1011
1012   * `<default>` contains an `<apps>` element describing all
1013 OpenSRF services running on this system -- see <<serviceRegistration>> --, as
1014 well as any other arbitrary XML descriptions required for global configuration
1015 purposes. For example, Evergreen uses this section for email notification and
1016 inter-library patron privacy settings.
1017   * `<hosts>` contains one element per host that participates in
1018 this OpenSRF system. Each host element must include an `<activeapps>` element
1019 that lists all of the services to start on this host when the system starts
1020 up. Each host element can optionally override any of the default settings.
1021
1022 OpenSRF includes a service named `opensrf.settings` to provide distributed
1023 cached access to the configuration settings with a simple API:
1024
1025   * `opensrf.settings.default_config.get`: accepts zero arguments and returns
1026 the complete set of default settings as a JSON document
1027   * `opensrf.settings.host_config.get`: accepts one argument (hostname) and
1028 returns the complete set of settings, as customized for that hostname, as a
1029 JSON document
1030   * `opensrf.settings.xpath.get`: accepts one argument (an
1031 http://www.w3.org/TR/xpath/[XPath] expression) and returns the portion of
1032 the configuration file that matches the expression as a JSON document
1033
1034 For example, to determine whether an Evergreen system uses the opt-in
1035 support for sharing patron information between libraries, you could either
1036 invoke the `opensrf.settings.default_config.get` method and parse the
1037 JSON document to determine the value, or invoke the `opensrf.settings.xpath.get`
1038 method with the XPath `/opensrf/default/share/user/opt_in` argument to
1039 retrieve the value directly.
1040
1041 In practice, OpenSRF includes convenience libraries in all of its client
1042 language bindings to simplify access to configuration values. C offers
1043 osrfConfig.c, Perl offers `OpenSRF::Utils::SettingsClient`, Java offers
1044 `org.opensrf.util.SettingsClient`, and Python offers `osrf.set`. These
1045 libraries locally cache the configuration file to avoid network roundtrips for
1046 every request and enable the developer to request specific values without
1047 having to manually construct XPath expressions.
1048
1049 == Getting under the covers with OpenSRF ==
1050 Now that you have seen that it truly is easy to create an OpenSRF service, we
1051 can take a look at what is going on under the covers to make all of this work
1052 for you.
1053
1054 === Get on the messaging bus - safely ===
1055 One of the core innovations of OpenSRF was to use the Extensible Messaging and
1056 Presence Protocol (XMPP, more colloquially known as Jabber) as the messaging
1057 bus that ties OpenSRF services together across servers. XMPP is an "XML
1058 protocol for near-real-time messaging, presence, and request-response services"
1059 (http://www.ietf.org/rfc/rfc3920.txt) that OpenSRF relies on to handle most of
1060 the complexity of networked communications. OpenSRF achieves a measure of
1061 security for its services through the use of public and private XMPP domains;
1062 all OpenSRF services automatically register themselves with the private XMPP
1063 domain, but only those services that register themselves with the public XMPP
1064 domain can be invoked from public OpenSRF clients.
1065
1066 In a minimal OpenSRF deployment, two XMPP users named "router" connect to the
1067 XMPP server, with one connected to the private XMPP domain and one connected to
1068 the public XMPP domain. Similarly, two XMPP users named "opensrf" connect to
1069 the XMPP server via the private and public XMPP domains. When an OpenSRF
1070 service is started, it uses the "opensrf" XMPP user to advertise its
1071 availability with the corresponding router on that XMPP domain; the XMPP server
1072 automatically assigns a Jabber ID (JID) based on the client hostname to each
1073 service's listener process and each connected drone process waiting to carry
1074 out requests. When an OpenSRF router receives a request to invoke a method on a
1075 given service, it connects the requester to the next available listener in the
1076 list of registered listeners for  that service.
1077
1078 The opensrf and router user names, passwords, and domain names, along with the
1079 list of services that should be public, are contained in the `opensrf_core.xml`
1080 configuration file.
1081
1082 === Message body format ===
1083 OpenSRF was an early adopter of JavaScript Object Notation (JSON). While XMPP
1084 is an XML protocol, the Evergreen developers recognized that the compactness of
1085 the JSON format offered a significant reduction in bandwidth for the volume of
1086 messages that would be generated in an application of that size. In addition,
1087 the ability of languages such as JavaScript, Perl, and Python to generate
1088 native objects with minimal parsing offered an attractive advantage over
1089 invoking an XML parser for every message. Instead, the body of the XMPP message
1090 is a simple JSON structure. For a simple request, like the following example
1091 that simply reverses a string, it looks like a significant overhead: but we get
1092 the advantages of locale support and tracing the request from the requester
1093 through the listener and responder (drone).
1094
1095 .A request for opensrf.simple-text.reverse("foobar"):
1096 [source,xml]
1097 --------------------------------------------------------------------------------
1098 <message from='router@private.localhost/opensrf.simple-text'
1099   to='opensrf@private.localhost/opensrf.simple-text_listener_at_localhost_6275'
1100   router_from='opensrf@private.localhost/_karmic_126678.3719_6288'
1101   router_to='' router_class='' router_command='' osrf_xid=''
1102 >
1103   <thread>1266781414.366573.12667814146288</thread>
1104   <body>
1105 [
1106   {"__c":"osrfMessage","__p":
1107     {"threadTrace":"1","locale":"en-US","type":"REQUEST","payload":
1108       {"__c":"osrfMethod","__p":
1109         {"method":"opensrf.simple-text.reverse","params":["foobar"]}
1110       }
1111     }
1112   }
1113 ]
1114   </body>
1115 </message>
1116 --------------------------------------------------------------------------------
1117
1118 .A response from opensrf.simple-text.reverse("foobar")
1119 [source,xml]
1120 --------------------------------------------------------------------------------
1121 <message from='opensrf@private.localhost/opensrf.simple-text_drone_at_localhost_6285'
1122   to='opensrf@private.localhost/_karmic_126678.3719_6288'
1123   router_command='' router_class='' osrf_xid=''
1124 >
1125   <thread>1266781414.366573.12667814146288</thread>
1126   <body>
1127 [
1128   {"__c":"osrfMessage","__p":
1129     {"threadTrace":"1","payload":
1130       {"__c":"osrfResult","__p":
1131         {"status":"OK","content":"raboof","statusCode":200}
1132       } ,"type":"RESULT","locale":"en-US"}
1133   },
1134   {"__c":"osrfMessage","__p":
1135     {"threadTrace":"1","payload":
1136       {"__c":"osrfConnectStatus","__p":
1137         {"status":"Request Complete","statusCode":205}
1138       },"type":"STATUS","locale":"en-US"}
1139   }
1140 ]
1141   </body>
1142 </message>
1143 --------------------------------------------------------------------------------
1144
1145 The content of the `<body>` element of the OpenSRF request and result should
1146 look familiar; they match the structure of the <<OpenSRFOverHTTP,OpenSRF over
1147 HTTP examples>> that we previously dissected.
1148
1149 === Registering OpenSRF methods in depth ===
1150 Let's explore the call to `__PACKAGE__->register_method()`; most of the elements
1151 of the hash are optional, and for the sake of brevity we omitted them in the
1152 previous example. As we have seen in the results of the <<opensrfIntrospection,introspection call>>, a
1153 verbose registration method call is recommended to better enable the internal
1154 documentation. So, for the sake of completeness here, is the set of elements
1155 that you should pass to `__PACKAGE__->register_method()`:
1156
1157   * `method`: the name of the procedure in this module that is being registered as an OpenSRF method
1158   * `api_name`: the invocable name of the OpenSRF method; by convention, the OpenSRF service name is used as the prefix
1159   * `api_level`: (optional) can be used for versioning the methods to allow the use of a deprecated API, but in practical use is always 1
1160   * `argc`: (optional) the minimal number of arguments that the method expects
1161   * `stream`: (optional) if this argument is set to any value, then the method supports returning multiple values from a single call to subsequent requests, and OpenSRF automatically creates a corresponding method with ".atomic" appended to its name that returns the complete set of results in a single request; streaming methods are useful if you are returning hundreds of records and want to act on the results as they return
1162   * `signature`: (optional) a hash describing the method's purpose, arguments, and return value
1163     ** `desc`: a description of the method's purpose
1164     ** `params`: an array of hashes, each of which describes one of the method arguments
1165       *** `name`: the name of the argument
1166       *** `desc`: a description of the argument's purpose
1167       *** `type`: the data type of the argument: for example, string, integer, boolean, number, array, or hash
1168     ** `return`: a hash describing the return value of the method
1169       *** `desc`: a description of the return value
1170       *** `type`: the data type of the return value: for example, string, integer, boolean, number, array, or hash
1171
1172 == Evergreen-specific OpenSRF services ==
1173
1174 Evergreen is currently the primary showcase for the use of OpenSRF as an
1175 application architecture. Evergreen 2.6.0 includes the following
1176 set of OpenSRF services:
1177
1178   * `open-ils.acq` Supports tasks for managing the acquisitions process
1179   * `open-ils.actor`: Supports common tasks for working with user accounts
1180      and libraries.
1181   * `open-ils.auth`: Supports authentication of Evergreen users.
1182   * `open-ils.auth_proxy`: Support using external services such as LDAP
1183   directories to authenticate Evergreen users
1184   * `open-ils.cat`: Supports common cataloging tasks, such as creating,
1185      modifying, and merging bibliographic and authority records.
1186   * `open-ils.circ`: Supports circulation tasks such as checking out items and
1187     calculating due dates.
1188   * `open-ils.collections`: Supports tasks to assist collections services for
1189     contacting users with outstanding fines above a certain threshold.
1190   * `open-ils.cstore`: Supports unrestricted access to Evergreen fieldmapper
1191     objects. This is a private service.
1192   * `open-ils.fielder`
1193   * `open-ils.justintime`: Support tasks for determining if an action/trigger
1194   event is still valid
1195   * `open-ils.pcrud`: Supports access to Evergreen fieldmapper objects,
1196   restricted by staff user permissions. This is a private service.
1197     objects.
1198   * `open-ils.permacrud`: Supports access to Evergreen fieldmapper objects,
1199   restricted by staff user permissions. This is a private service.
1200   * `open-ils.reporter`: Supports the creation and scheduling of reports.
1201   * `open-ils.reporter-store`: Supports access to Evergreen fieldmapper objects
1202     for the reporting service. This is a private service.
1203   * `open-ils.resolver` Support tasks for integrating with an OpenURL resolver.
1204   * `open-ils.search`: Supports searching across bibliographic records,
1205     authority records, serial records, Z39.50 sources, and ZIP codes.
1206   * `open-ils.serial`: Support tasks for serials management
1207   * `open-ils.storage`: A deprecated method of providing access to Evergreen
1208     fieldmapper objects. Implemented in Perl, this service has largely been
1209     replaced by the much faster C-based `open-ils.cstore` service.
1210   * `open-ils.supercat`: Supports transforms of MARC records into other formats,
1211     such as MODS, as well as providing Atom and RSS feeds and SRU access.
1212   * `open-ils.trigger`: Supports event-based triggers for actions such as
1213   overdue and holds available notification emails.
1214   * `open-ils.url_verify`: Support tasks for validating URLs
1215   * `open-ils.vandelay`: Supports the import and export of batches of
1216     bibliographic and authority records.
1217   * `opensrf.settings`: Supports communicating opensrf.xml settings to other services.
1218
1219 Of some interest is that the `open-ils.reporter-store` and `open-ils.cstore`
1220 services have identical implementations. Surfacing them as separate services
1221 enables a deployer of Evergreen to ensure that the reporting service does not
1222 interfere with the performance-critical `open-ils.cstore` service. One can also
1223 direct the reporting service to a read-only database replica to, again, avoid
1224 interference with `open-ils.cstore` which must write to the master database.
1225
1226 There are only a few significant services that are not built on OpenSRF, such
1227 as the SIP and Z39.50 servers. These services implement
1228 different protocols and build on existing daemon architectures (Simple2ZOOM
1229 for Z39.50), but still rely on the other OpenSRF services to provide access
1230 to the Evergreen data. The non-OpenSRF services are reasonably self-contained
1231 and can be deployed on different servers to deliver the same sort of deployment
1232 flexibility as OpenSRF services, but have the disadvantage of not being
1233 integrated into the same configuration and control infrastructure as the
1234 OpenSRF services.
1235
1236 == Evergreen after one year: reflections on OpenSRF ==
1237
1238 http://projectconifer.ca[Project Conifer] has been live on Evergreen for just
1239 over a year now, and as one of the primary technologists I have had to work
1240 closely with the OpenSRF infrastructure during that time. As such, I am in
1241 a position to identify some of the strengths and weaknesses of OpenSRF based
1242 on our experiences.
1243
1244 === Strengths of OpenSRF ===
1245
1246 As a service infrastructure, OpenSRF has been remarkably reliable. We initially
1247 deployed Evergreen on an unreleased version of both OpenSRF and Evergreen due
1248 to our requirements for some functionality that had not been delivered in a
1249 stable release at that point in time, and despite this risky move we suffered
1250 very little unplanned downtime in the opening months. On July 27, 2009 we
1251 moved to a newer (but still unreleased) version of the OpenSRF and Evergreen
1252 code, and began formally tracking our downtime. Since then, we have achieved
1253 more than 99.9% availability - including scheduled downtime for maintenance.
1254 This compares quite favourably to the maximum of 75% availability that we were
1255 capable of achieving on our previous library system due to the nightly downtime
1256 that was required for our backup process. The OpenSRF "maximum request"
1257 configuration parameter for each service that kills off drone processes after
1258 they have served a given number of requests provides a nice failsafe for
1259 processes that might otherwise suffer from a memory leak or hung process. It
1260 also helps that when we need to apply an update to a Perl service that is
1261 running on multiple servers, we can apply the updated code, then restart the
1262 service on one server at a time to avoid any downtime.
1263
1264 As promised by the OpenSRF infrastructure, we have also been able to tune our
1265 cluster of servers to provide better performance. For example, we were able to
1266 change the number of maximum concurrent processes for our database services
1267 when we noticed that we seeing a performance bottleneck with database access.
1268 Making a configuration change go live simply requires you to restart the
1269 `opensrf.setting` service to pick up the configuration change, then restart the
1270 affected service on each of your servers. We were also able to turn off some of
1271 the less-used OpenSRF services, such as `open-ils.collections`, on one of our
1272 servers to devote more resources on that server to the more frequently used
1273 services and other performance-critical processes such as Apache.
1274
1275 The support for logging and caching that is built into OpenSRF has been
1276 particularly helpful with the development of a custom service for SFX holdings
1277 integration into our catalog. Once I understood how OpenSRF works, most of
1278 the effort required to build that SFX integration service was spent on figuring
1279 out how to properly invoke the SFX API to display human-readable holdings.
1280 Adding a new OpenSRF service and registering several new methods for the
1281 service was relatively easy. The support for directing log messages to syslog
1282 in OpenSRF has also been a boon for both development and debugging when
1283 problems arise in a cluster of five servers; we direct all of our log messages
1284 to a single server where we can inspect the complete set of messages for the
1285 entire cluster in context, rather than trying to piece them together across
1286 servers.
1287
1288 === Weaknesses ===
1289
1290 The primary weakness of OpenSRF is the lack of either formal or informal
1291 documentation for OpenSRF. There are many frequently asked questions on the
1292 Evergreen mailing lists and IRC channel that indicate that some of the people
1293 running Evergreen or trying to run Evergreen have not been able to find
1294 documentation to help them understand, even at a high level, how the OpenSRF
1295 Router and services work with XMPP and the Apache Web server to provide a
1296 working Evergreen system. Also, over the past few years several developers
1297 have indicated an interest in developing Ruby and PHP bindings for OpenSRF, but
1298 the efforts so far have resulted in no working code. Without a formal
1299 specification, clearly annotated examples, and unit tests for the major OpenSRF
1300 communication use cases that could be ported to the new language as a base set
1301 of expectations for a working binding, the hurdles for a developer new to
1302 OpenSRF are significant. As a result, Evergreen integration efforts with
1303 popular frameworks like Drupal, Blacklight, and VuFind result in the best
1304 practical option for a developer with limited time -- database-level
1305 integration -- which has the unfortunate side effect of being much more likely
1306 to break after an upgrade.
1307
1308 In conjunction with the lack of documentation that makes it hard to get started
1309 with the framework, a disincentive for new developers to contribute to OpenSRF
1310 itself is the lack of integrated unit tests. For a developer to contribute a
1311 significant, non-obvious patch to OpenSRF, they need to manually run through
1312 various (undocumented, again) use cases to try and ensure that the patch
1313 introduced no unanticipated side effects. The same problems hold for Evergreen
1314 itself, although the
1315 http://git.evergreen-ils.org/?p=working/random.git;a=shortlog;h=refs/heads/collab/berick/constrictor[Constrictor] stress-testing
1316 framework offers a way of performing some automated system testing and
1317 performance testing.
1318
1319 These weaknesses could be relatively easily overcome with the effort through
1320 contributions from people with the right skill sets. This article arguably
1321 offers a small set of clear examples at both the networking and application
1322 layer of OpenSRF. A technical writer who understands OpenSRF could contribute a
1323 formal specification to the project.  With a formal specification at their
1324 disposal, a quality assurance expert could create an automated test harness and
1325 a basic set of unit tests that could be incrementally extended to provide more
1326 coverage over time. If one or more continual integration environments are set
1327 up to track the various OpenSRF branches of interest, then the OpenSRF
1328 community would have immediate feedback on build quality. Once a unit testing
1329 framework is in place, more developers might be willing to develop and
1330 contribute patches as they could sanity check their own code without an intense
1331 effort before exposing it to their peers.
1332
1333 == Summary ==
1334 In this article, I attempted to provide both a high-level and detailed overview
1335 of how OpenSRF works, how to build and deploy new OpenSRF services, how to make
1336 requests to OpenSRF method from OpenSRF clients or over HTTP, and why you
1337 should consider it a possible infrastructure for building your next
1338 high-performance system that requires the capability to scale out. In addition,
1339 I surveyed the Evergreen services built on OpenSRF and reflected on the
1340 strengths and weaknesses of the platform based on the experiences of Project
1341 Conifer after a year in production, with some thoughts about areas where the
1342 right application of skills could make a significant difference to the Evergreen
1343 and OpenSRF projects.
1344
1345 == Appendix: Python client ==
1346
1347 Following is a Python client that makes the same OpenSRF calls as the Perl
1348 client:
1349
1350 [source, python] 
1351 --------------------------------------------------------------------------------
1352 include::example$python_client.py[]
1353 --------------------------------------------------------------------------------
1354
1355 NOTE: Python's `dnspython` module refuses to read `/etc/resolv.conf`, so to
1356 access hostnames that are not served up via DNS, such as the extremely common
1357 case of `localhost`, you may need to install a package like `dnsmasq` to act
1358 as a local DNS server for those hostnames.
1359
1360 // vim: set syntax=asciidoc: