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