copland 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (184) hide show
  1. data/doc/README +88 -0
  2. data/doc/manual-html/chapter-1.html +454 -0
  3. data/doc/manual-html/chapter-10.html +399 -0
  4. data/doc/manual-html/chapter-11.html +600 -0
  5. data/doc/manual-html/chapter-12.html +406 -0
  6. data/doc/manual-html/chapter-2.html +382 -0
  7. data/doc/manual-html/chapter-3.html +424 -0
  8. data/doc/manual-html/chapter-4.html +432 -0
  9. data/doc/manual-html/chapter-5.html +381 -0
  10. data/doc/manual-html/chapter-6.html +364 -0
  11. data/doc/manual-html/chapter-7.html +434 -0
  12. data/doc/manual-html/chapter-8.html +373 -0
  13. data/doc/manual-html/chapter-9.html +324 -0
  14. data/doc/manual-html/copland.png +0 -0
  15. data/doc/manual-html/index.html +331 -0
  16. data/doc/manual-html/manual.css +179 -0
  17. data/doc/manual-html/tutorial-1.html +407 -0
  18. data/doc/manual-html/tutorial-2.html +451 -0
  19. data/doc/manual-html/tutorial-3.html +484 -0
  20. data/doc/manual-html/tutorial-4.html +446 -0
  21. data/doc/manual-html/tutorial-5.html +520 -0
  22. data/doc/manual/chapter.erb +18 -0
  23. data/doc/manual/example.erb +18 -0
  24. data/doc/manual/img/copland.png +0 -0
  25. data/doc/manual/index.erb +30 -0
  26. data/doc/manual/manual.css +179 -0
  27. data/doc/manual/manual.rb +239 -0
  28. data/doc/manual/manual.yml +2643 -0
  29. data/doc/manual/page.erb +102 -0
  30. data/doc/manual/tutorial.erb +30 -0
  31. data/doc/packages/copland.html +764 -0
  32. data/doc/packages/copland.lib.html +439 -0
  33. data/doc/packages/copland.remote.html +2096 -0
  34. data/doc/packages/copland.webrick.html +925 -0
  35. data/doc/packages/index.html +49 -0
  36. data/doc/packages/packrat.css +125 -0
  37. data/examples/calc/calc.rb +47 -0
  38. data/examples/calc/package.yml +35 -0
  39. data/examples/calc/services.rb +74 -0
  40. data/examples/solitaire-cipher/README +11 -0
  41. data/examples/solitaire-cipher/Rakefile +57 -0
  42. data/examples/solitaire-cipher/bin/main.rb +14 -0
  43. data/examples/solitaire-cipher/lib/cipher.rb +230 -0
  44. data/examples/solitaire-cipher/lib/cli.rb +24 -0
  45. data/examples/solitaire-cipher/lib/package.yml +106 -0
  46. data/examples/solitaire-cipher/test/tc_deck.rb +30 -0
  47. data/examples/solitaire-cipher/test/tc_key-stream.rb +19 -0
  48. data/examples/solitaire-cipher/test/tc_keying-algorithms.rb +31 -0
  49. data/examples/solitaire-cipher/test/tc_solitaire-cipher.rb +66 -0
  50. data/examples/solitaire-cipher/test/tc_unkeyed-algorithm.rb +17 -0
  51. data/examples/solitaire-cipher/test/tests.rb +2 -0
  52. data/lib/copland.rb +56 -0
  53. data/lib/copland/class-factory.rb +95 -0
  54. data/lib/copland/configuration-point.rb +38 -0
  55. data/lib/copland/configuration-point/common.rb +203 -0
  56. data/lib/copland/configuration-point/errors.rb +44 -0
  57. data/lib/copland/configuration-point/list.rb +59 -0
  58. data/lib/copland/configuration-point/map.rb +59 -0
  59. data/lib/copland/configuration/errors.rb +43 -0
  60. data/lib/copland/configuration/loader.rb +113 -0
  61. data/lib/copland/configuration/yaml/configuration-point.rb +87 -0
  62. data/lib/copland/configuration/yaml/implementor.rb +134 -0
  63. data/lib/copland/configuration/yaml/interceptor.rb +63 -0
  64. data/lib/copland/configuration/yaml/listener.rb +56 -0
  65. data/lib/copland/configuration/yaml/loader.rb +122 -0
  66. data/lib/copland/configuration/yaml/package.rb +125 -0
  67. data/lib/copland/configuration/yaml/parser.rb +71 -0
  68. data/lib/copland/configuration/yaml/schema.rb +165 -0
  69. data/lib/copland/configuration/yaml/service-point.rb +116 -0
  70. data/lib/copland/configuration/yaml/utils.rb +82 -0
  71. data/lib/copland/default-schema-processor.rb +144 -0
  72. data/lib/copland/errors.rb +82 -0
  73. data/lib/copland/event-producer.rb +95 -0
  74. data/lib/copland/impl/builder-factory.rb +112 -0
  75. data/lib/copland/impl/copland-config.yml +1 -0
  76. data/lib/copland/impl/include-exclude.rb +140 -0
  77. data/lib/copland/impl/logging-interceptor.rb +106 -0
  78. data/lib/copland/impl/package.yml +217 -0
  79. data/lib/copland/impl/startup.rb +116 -0
  80. data/lib/copland/impl/symbol-source-manager.rb +131 -0
  81. data/lib/copland/impl/symbol-source.rb +63 -0
  82. data/lib/copland/instantiator.rb +38 -0
  83. data/lib/copland/instantiator/abstract.rb +91 -0
  84. data/lib/copland/instantiator/complex.rb +96 -0
  85. data/lib/copland/instantiator/identity.rb +58 -0
  86. data/lib/copland/instantiator/simple.rb +68 -0
  87. data/lib/copland/interceptor-chain.rb +166 -0
  88. data/lib/copland/interceptor.rb +139 -0
  89. data/lib/copland/log-factory.rb +206 -0
  90. data/lib/copland/models.rb +39 -0
  91. data/lib/copland/models/abstract.rb +78 -0
  92. data/lib/copland/models/prototype-deferred.rb +58 -0
  93. data/lib/copland/models/prototype.rb +58 -0
  94. data/lib/copland/models/proxy.rb +100 -0
  95. data/lib/copland/models/singleton-deferred.rb +59 -0
  96. data/lib/copland/models/singleton.rb +77 -0
  97. data/lib/copland/models/threaded.rb +65 -0
  98. data/lib/copland/ordering.rb +123 -0
  99. data/lib/copland/package.rb +246 -0
  100. data/lib/copland/registry.rb +368 -0
  101. data/lib/copland/schema.rb +206 -0
  102. data/lib/copland/service-point.rb +282 -0
  103. data/lib/copland/utils.rb +221 -0
  104. data/lib/copland/version.rb +47 -0
  105. data/test/conf-test/list-bad-key.yml +30 -0
  106. data/test/conf-test/list-bad-missing.yml +28 -0
  107. data/test/conf-test/list-bad-type.yml +28 -0
  108. data/test/conf-test/list-good.yml +29 -0
  109. data/test/conf-test/map-bad-key.yml +25 -0
  110. data/test/conf-test/map-bad-missing.yml +24 -0
  111. data/test/conf-test/map-bad-type.yml +23 -0
  112. data/test/conf-test/map-good.yml +25 -0
  113. data/test/configuration-point/package.yml +52 -0
  114. data/test/configuration/yaml/config/copland-config.yml +2 -0
  115. data/test/configuration/yaml/config/module.yml +2 -0
  116. data/test/configuration/yaml/config/subdir/copland-config.yml +2 -0
  117. data/test/configuration/yaml/config/subdir/package.yml +4 -0
  118. data/test/configuration/yaml/defaults/package.yml +5 -0
  119. data/test/configuration/yaml/defaults/subdir/package.yml +4 -0
  120. data/test/configuration/yaml/tc_config-loader.rb +86 -0
  121. data/test/configuration/yaml/tc_configuration-point-processor.rb +134 -0
  122. data/test/configuration/yaml/tc_implementor-processor.rb +104 -0
  123. data/test/configuration/yaml/tc_interceptor-processor.rb +85 -0
  124. data/test/configuration/yaml/tc_listener-processor.rb +69 -0
  125. data/test/configuration/yaml/tc_loader.rb +74 -0
  126. data/test/configuration/yaml/tc_package-processor.rb +120 -0
  127. data/test/configuration/yaml/tc_parser.rb +94 -0
  128. data/test/configuration/yaml/tc_schema-parser.rb +160 -0
  129. data/test/configuration/yaml/tc_service-point-processor.rb +104 -0
  130. data/test/configuration/yaml/tc_type-validator.rb +90 -0
  131. data/test/custom-logger.yml +3 -0
  132. data/test/impl/logging/package.yml +44 -0
  133. data/test/impl/logging/services.rb +84 -0
  134. data/test/impl/startup/package.yml +46 -0
  135. data/test/impl/startup/services.rb +47 -0
  136. data/test/impl/symbols/package.yml +24 -0
  137. data/test/impl/symbols/services.rb +38 -0
  138. data/test/impl/tc_builder-factory.rb +173 -0
  139. data/test/impl/tc_logging-interceptor.rb +148 -0
  140. data/test/impl/tc_startup.rb +59 -0
  141. data/test/impl/tc_symbol-sources.rb +61 -0
  142. data/test/logger.yml +6 -0
  143. data/test/mock.rb +201 -0
  144. data/test/schema/bad-package.yml +65 -0
  145. data/test/schema/package.yml +102 -0
  146. data/test/schema/services.rb +5 -0
  147. data/test/services/package.yml +79 -0
  148. data/test/services/simple.rb +87 -0
  149. data/test/tc_class-factory.rb +93 -0
  150. data/test/tc_complex-instantiator.rb +107 -0
  151. data/test/tc_configuration-point-contrib.rb +74 -0
  152. data/test/tc_configuration-point-schema.rb +122 -0
  153. data/test/tc_configuration-point.rb +91 -0
  154. data/test/tc_default-schema-processor.rb +297 -0
  155. data/test/tc_identity-instantiator.rb +61 -0
  156. data/test/tc_interceptors.rb +84 -0
  157. data/test/tc_logger.rb +131 -0
  158. data/test/tc_models.rb +176 -0
  159. data/test/tc_package.rb +165 -0
  160. data/test/tc_proxy.rb +65 -0
  161. data/test/tc_registry.rb +141 -0
  162. data/test/tc_schema.rb +78 -0
  163. data/test/tc_service-point.rb +178 -0
  164. data/test/tc_service.rb +70 -0
  165. data/test/tc_simple-instantiator.rb +61 -0
  166. data/test/tests.rb +93 -0
  167. data/tutorial/01/main.rb +7 -0
  168. data/tutorial/01/package.yml +8 -0
  169. data/tutorial/01/tutorial.rb +7 -0
  170. data/tutorial/02/main.rb +10 -0
  171. data/tutorial/02/package.yml +27 -0
  172. data/tutorial/02/tutorial.rb +46 -0
  173. data/tutorial/03/main.rb +24 -0
  174. data/tutorial/03/package.yml +29 -0
  175. data/tutorial/03/tutorial.rb +48 -0
  176. data/tutorial/04/main.rb +24 -0
  177. data/tutorial/04/package.yml +35 -0
  178. data/tutorial/04/tutorial.rb +48 -0
  179. data/tutorial/05/functions/package.yml +16 -0
  180. data/tutorial/05/functions/services.rb +15 -0
  181. data/tutorial/05/main.rb +10 -0
  182. data/tutorial/05/package.yml +35 -0
  183. data/tutorial/05/tutorial.rb +53 -0
  184. metadata +260 -0
@@ -0,0 +1,2643 @@
1
+ --- !jamisbuck.org,2004/^manual
2
+
3
+ # This content is made available under the Attribution-ShareAlike 2.0
4
+ # license from the Create Commons:
5
+ #
6
+ # http://creativecommons.org/licenses/by-sa/2.0/
7
+
8
+ meta: !^meta
9
+ copyright: 2004
10
+ author: Jamis Buck
11
+ email: jgb3@email.byu.edu
12
+
13
+ product: !^product
14
+ name: Copland
15
+ tagline: compose yourself...
16
+ version: !!version ../../lib/copland/version/Copland::Version::STRING
17
+ logo: copland.png
18
+ web: http://copland.rubyforge.org
19
+ project: http://rubyforge.org/projects/copland
20
+
21
+ recent_updates:
22
+ - described the "initializer block" feature of Registry#service.
23
+
24
+ chapters:
25
+
26
+ - Introduction:
27
+ - What is Copland?: >
28
+ Copland is an _Inversion of Control_ (IoC) container, written in
29
+ "Ruby":http://www.ruby-lang.org, for use in Ruby programs.
30
+ Depending on whether or not the term "IoC" means anything at all to you,
31
+ you can read either the next section ("Copland _sans_ Buzzwords"), or the
32
+ one after it ("Copland _avec_ Buzzwords").
33
+
34
+
35
+ h3. Copland _sans_ Buzzwords
36
+
37
+
38
+ Imagine being able to take all the various pieces of your program and implement
39
+ them in a vacuum. You just assume that all dependencies will be satisfied at runtime,
40
+ by properties being set, or parameters being passed to each object's constructor.
41
+ Nowhere in your code do you specify the instantiation of another application
42
+ component.
43
+
44
+
45
+ However, those dependencies and relationships between objects _still exist_; you've just
46
+ written your code without any explicit knowledge of them. Somehow, you still need to be
47
+ able to wire the pieces all together so they work as required in tandem. To put it another
48
+ way, you need to be able to take your classes and _compose_ them into something greater.
49
+
50
+
51
+ Enter Copland.
52
+
53
+
54
+ Copland helps you tie the pieces of your application together. Instead of inter-object
55
+ relationships and dependencies being specified in the code, you feed them into Copland.
56
+ This makes those relationships easier to modify, since you never have to modify the
57
+ source code to change them.
58
+
59
+
60
+ Copland is named after the American composer
61
+ "Aaron Copland":http://www.lucidcafe.com/library/95nov/copland.html
62
+ for a reason. You may or may not have any musical talent, but Copland will give you
63
+ the ability to compose great "symphonies of software" by allowing you to work easily
64
+ at both extremes of software architecture:
65
+
66
+
67
+ # It helps you to focus on the implementation of a single class by allowing you to
68
+ ignore the implementation details of the dependencies of the current class.
69
+
70
+ # It helps you to focus on the relationships between classes by allowing you to
71
+ specify those relationships and dependencies as run-time configuration options.
72
+
73
+
74
+ h3. Copland _avec_ Buzzwords
75
+
76
+
77
+ If you are already familiar with IoC, then you either love it or hate it. In the first
78
+ case, you probably only want to know what features Copland has compared to other IoC
79
+ containers you've used. In the second case, there's probably not much I can say to
80
+ change your opinion, so I won't try.
81
+
82
+
83
+ I'll save all the meaty discussions for the chapter on Copland's design, but here's
84
+ the rundown:
85
+
86
+
87
+ * Copland is based heavily on the "HiveMind":http://jakarta.apache.org/hivemind
88
+ IoC container for Java. If you've ever used that one, much of Copland will probably feel very
89
+ familiar to you.
90
+
91
+ * Copland allows you to define _service points_, _configuration points_, contribute to
92
+ _configuration points_, add _interceptors_ to services, add _listeners_ to services, define
93
+ _multicast_ services, and even make your services web-enabled by using the built-in
94
+ SOAP or dRuby interfaces.
95
+
96
+ * Copland uses "YAML":http://www.yaml.org for its configuration files, instead of XML. If
97
+ you've never used YAML, you'll find it surprisingly easy to pick up.
98
+
99
+
100
+ h3. The Buzzwords Themselves
101
+
102
+
103
+ As "Martin Fowler":http://www.martinfowler.com
104
+ "points out":http://martinfowler.com/articles/injection.html, the term "Inversion of Control"
105
+ is a poorly-chosen one. Still, it is the one that most people know the concept by, so I'll
106
+ use it throughout this document.
107
+
108
+
109
+ For more info about IoC, you might try reading the following articles:
110
+
111
+
112
+ * "Inversion of Control Containers and the Dependency Injection pattern":http://martinfowler.com/articles/injection.html (Martin Fowler)
113
+
114
+ * "Inversion of Control":http://docs.codehaus.org/display/PICO/Inversion+of+Control
115
+ (from the PicoContainer site, another Java-based IoC container)
116
+
117
+ * "Inversion of Control":http://jakarta.apache.org/hivemind/ioc.html
118
+ (HiveMind's take on the subject)
119
+
120
+ - Features: >
121
+ Currently, Copland features the following buzzwords:
122
+
123
+
124
+ * Type 2 IoC (_setter injection_)
125
+
126
+ * Type 3 IoC (_constructor injection_)
127
+
128
+ * YAML-based descriptor configuration
129
+
130
+ * Object-, Class-, and Singleton-backed services
131
+
132
+ * Service factories, with extendable rule-processing engine for defining new factories.
133
+
134
+ * Interceptors (for adding basic AOP-like "pre" and "post" hooks to every method of a service)
135
+
136
+ * Multicast services, which do nothing themselves except delegate recieved messages to a set of
137
+ other interested services.
138
+
139
+ * Event/Listener infrastructure, for notifying services at various points during another
140
+ service's lifecycle.
141
+
142
+ * dRuby and SOAP support, in the form of two core services that applications may use to export
143
+ their services to remote clients.
144
+
145
+ - Getting Copland: >
146
+
147
+ h3. Using "RubyGems":http://rubygems.rubyforge.org
148
+
149
+
150
+ Installing via the "RubyGems":http://rubygems.rubyforge.org system is trivial--you just need to
151
+ make sure that you have RubyGems properly installed. Once RubyGems is installed, make sure
152
+ you are logged in as a user with permissions to update your local gem repository, and then type:
153
+
154
+
155
+ <pre>
156
+ gem install copland
157
+ </pre>
158
+
159
+
160
+ Alternatively, you can download the gem file itself (if, for instance, the RubyGems gem
161
+ repository is down, or hasn't been updated with the latest version of Copland). Just go to
162
+ "Copland's RubyForge project page":http://rubyforge.org/projects/copland and download the
163
+ gem for the latest version. Then, from the command-line, type:
164
+
165
+
166
+ <pre>
167
+ gem install path/to/the/file/you/downloaded.gem --local
168
+ </pre>
169
+
170
+
171
+ h3. Manual Installation
172
+
173
+
174
+ If, for whatever reason, RubyGems is not an option for you, you can go to
175
+ "Copland's RubyForge project page":http://rubyforge.org/projects/copland and download the
176
+ @tar.gz@ file for the latest version of Copland. Then:
177
+
178
+
179
+ # Unpack the archive. It will extract all files to a new directory under the current
180
+ directory.
181
+
182
+ # @cd@ to the new directory.
183
+
184
+ # Make sure you are logged in as a user with permissions to install Ruby libraries. If
185
+ you are, just type:
186
+
187
+
188
+ <pre>
189
+ ruby setup.rb config
190
+ ruby setup.rb setup
191
+ ruby setup.rb install
192
+ </pre>
193
+
194
+ - License Information: >
195
+ Copland is currently distrubuted under
196
+ "the BSD license":http://www.opensource.org/licenses/bsd-license.php. Versions
197
+ of Copland prior to 0.6 were released under
198
+ "the same license as Ruby":http://www.ruby-lang.org/en/LICENSE.txt.
199
+
200
+
201
+ This manual (both in YAML and HTML formats), as well as the minimal Ruby scripts used to
202
+ generate it, are distributed under the "Creative Commons":http://creativecommons.org
203
+ "Attribution-ShareAlike":http://creativecommons.org/licenses/by-sa/2.0 license.
204
+
205
+ - Support: >
206
+ Mailing lists, bug trackers, feature requests, and public forums are available (courtesy
207
+ of "RubyForge":http://rubyforge.org) at Copland's RubyForge project page. Just direct
208
+ your browser to "http://rubyforge.org/projects/copland":http://rubyforge.org/projects/copland.
209
+
210
+ - Justification:
211
+ - IoC in One Paragraph: >
212
+ In one paragraph, "Inversion of Control" is a design pattern that describes the practice of
213
+ delegating some aspect of control to another part of the program (usually called the
214
+ "container"). For example, you might invert control over instantiating the dependencies of
215
+ an object, so that instead of the object instantiating its own dependencies, the container
216
+ instantiates them for it. This particular inversion of control is called the Dependency
217
+ Injection pattern by some, because the container is "injecting" the dependencies into
218
+ the system. "Inversion of Control" is a more general pattern than "Dependency Injection",
219
+ although you'll sometimes hear them used interchangeably.
220
+
221
+ - Why IoC?: >
222
+ The fact is, most IoC containers provide a few more bells and whistles than just
223
+ dependency injection. (Other kinds of control that might be inverted (besides dependencies)
224
+ include logging, configuration, concurrency, localization, and lifecycle management.) Because
225
+ these containers provide a meta-programming interface to your objects, they can do a lot of
226
+ fancy stuff behind the scenes, such as adding AOP-like "before" and "after" hooks, or
227
+ multicasting. These meta-programming tools make it easier to extend, debug, and unit-test your
228
+ sub-systems, and with the dependency injection pattern that is at the core of all IoC
229
+ containers, you can easily wire your sub-systems together in a non-intrusive, easily-scalable
230
+ manner.
231
+
232
+
233
+ IoC also makes unit testing easier, because every service is _dependency injected_. This
234
+ means that instead of the object instantiating all of its dependencies manually, it accepts
235
+ them as either constructor parameters or properties, so when unit testing you can easily
236
+ assign mock objects to those dependencies. This simplifies the test cases considerably.
237
+
238
+
239
+ To be sure, IoC is not a panacea. It will not solve every problem that has ever plagued you
240
+ as a software engineer. Nor will it make every task easier. However, for large and complex
241
+ systems (and the larger and more complex, the better), the features provided by an IoC
242
+ container can make designing, implementing, and maintaining that system much easier, since
243
+ a large part of the complexity of such a system is maintaining the dependencies inside the
244
+ system.
245
+
246
+
247
+ h3. Why Copland?
248
+
249
+
250
+ Ruby (and scripting languages in general) are not widely viewed by most decision makers as
251
+ acceptible solutions for enterprise-level applications. In perfect honesty, it must be
252
+ admitted that there is no single programming language that is the perfect solution to every
253
+ problem. However, there are niches where scripting languages (and Ruby in particular) are
254
+ becoming more and more accepted for large-scale systems. Unfortunately, tools are sadly
255
+ lacking in Ruby for building large, scalable, and maintainable systems.
256
+
257
+
258
+ As of this writing, there is only one other IoC container for Ruby,
259
+ "Rico":http://docs.codehaus.org/display/PICO/Rico. Unfortunately, it is still fairly
260
+ early in its development, and although it is usable for basic dependency injection
261
+ needs, even the developers admit that "it is primarily a proof of concept at this stage."
262
+
263
+
264
+ Copland is fairly new, too, but is making great strides forward. It has already gone
265
+ through one significant rewrite and as a result is much more robust and internally
266
+ consistant than it was before. It already supports at least
267
+ as much functionality as most other mature IoC systems, and sports some features that I haven't
268
+ seen anywhere else. At this point, I have to admit that Copland is perhaps not yet ready for the
269
+ enterprise, but if enough people play with it and
270
+ "submit suggestions":http://rubyforge.org/tracker/?func=add&group_id=201&atid=846 and
271
+ "bug reports":http://rubyforge.org/tracker/?func=add&group_id=201&atid=843, it may
272
+ not be long before it is.
273
+
274
+ - A Case Study: >
275
+ The following case study has been shamelessly borrowed from the one used in the
276
+ "HiveMind":http://jakarta.apache.org/hivemind documentation. It is a beautiful
277
+ and compelling example of the power of IoC. I've paraphrased (and rephrased) portions of it, and
278
+ removed the lengthy Java and XML sample snippets. The original author of this case study is
279
+ "Howard Lewis Ship":http://howardlewisship.com, the creator of HiveMind, and the original
280
+ version of the case study may be viewed in the
281
+ "HiveMind documentation":http://jakarta.apache.org/hivemind/case1.html.
282
+
283
+
284
+ Consider a hypothetical product called "Panorama." It is a large web application deployed
285
+ via WEBrick, and consists of several hundred classes, divided into a large number of tools
286
+ and services.
287
+
288
+
289
+ Even though Panorama is an enterprise application, it is still logically a number of subsystems.
290
+ Many of these subsystems require some form of startup and shutdown logic. As an example, the
291
+ Help service caches help data, which is stored in a database, and the Mail service sets up
292
+ database cleanup jobs to run at specified intervals. _In toto_, there are over 40 startup tasks,
293
+ and a few shutdown tasks.
294
+
295
+
296
+ One way to architect this without an IoC container (because it certainly can be done), is to
297
+ create a controller object that manages all of the startup and shutdown activity. This controller
298
+ would be invoked at application start-up, it would run the necessary startup tasks in the
299
+ required order (since order is important, for some of them), and then
300
+ when the application shuts down the controller invokes all of the necessary shutdown tasks.
301
+
302
+
303
+ This works, of course. However, you've had to hard-code the dependencies between the controller
304
+ and the various services; the controller must be aware of every service, and anytime you add a
305
+ new service, you have to edit the controller. This can be mitigated to a large degree by having
306
+ each service register itself with the controller, but there you've got the same problem, to a
307
+ smaller degree: each service now has an explicit dependency on the controller object, and if
308
+ you ever refactor that controller class into a different location or a different name, that's
309
+ 40+ services you have to modify!
310
+
311
+
312
+ h3. IoC to the Rescue
313
+
314
+
315
+ IoC moves all of those dependencies out of the code and into configuration files, called
316
+ (in Copland) _package descriptors_. These descriptors contain service definitions, configuration
317
+ information, and so forth.
318
+
319
+
320
+ We do more-or-less what we described in the non-IoC solution, above: we create a service
321
+ called "Startup", and we have services register themselves with that service. However, the
322
+ registration is done indirectly, via what is called a "configuration point." Services
323
+ are added (along with the order in which they want to be started) to that configuration point,
324
+ and then the Startup service reads from that configuration point to determine who gets started.
325
+
326
+
327
+ Similarly, we define a "Shutdown" service that reads from yet another configuration point.
328
+ Services that wish to be notified of system shutdown are added to that configuration point,
329
+ and the Shutdown service takes care of the rest.
330
+
331
+
332
+ Finally, we have the Startup service add itself as a listener to the registry, so that when
333
+ the registry is initialized, the Startup service will be notified. Then, we add Shutdown as
334
+ a listener to the registry as well, so that when the registry is shutdown, the Shutdown
335
+ service can take any necessary actions.
336
+
337
+
338
+ h3. Summary
339
+
340
+
341
+ Certainly, you could implement all of this yourself. It's nothing magical or new. However, you'd
342
+ just be implementing a special case of an IoC container, and if you ever wanted to add logging
343
+ or security to your "services", you'd find yourself diving back into your code to add the
344
+ necessary calls at various points. Before you know it, you'd find that you'd have implemented
345
+ an IoC container after all.
346
+
347
+
348
+ Copland offers these features "out-of-the-box". Applications can easily take advantage of them.
349
+ Developers don't need to spend time reinventing the wheel (even though that is often most of the
350
+ fun of some projects).
351
+
352
+ - Getting Started:
353
+ - Terminology: >
354
+ In the tutorials and examples that follow, you'll see a lot of (potentially) new terminology.
355
+ Here is a brief glossary of some of the more prominant terms, to help you get started. Don't
356
+ feel like you need to read and understand each term before proceeding through this manual;
357
+ just know that you can refer back to these if you need to.
358
+
359
+
360
+ * *configuration point*: This is either a list or a map (configurable) which packages may
361
+ contribute to, and which services may read from to initialize themselves. Packages may
362
+ define multiple configuration points.
363
+
364
+ * *contribution*: This is how a package contributes to a _configuration point_. A package may
365
+ have any number of contributions, to any number of configuration points. Contributions may
366
+ be made to configuration points both inside and outside of the current package.
367
+
368
+ * *dependency injection*: A design pattern in which the dependencies of an object are
369
+ managed independently of the object, usually by some "container" framework. See "inversion
370
+ of control".
371
+
372
+ * *event producer*: A service that other services may listen to in order to obtain
373
+ notifications of various service-specific events.
374
+
375
+ * *interceptor*: An interceptor is a special object that is attached to a service. A _chain_
376
+ of interceptors may wrap the methods of a service and act as filters for any invocations
377
+ of those methods. This is the means by which AOP-like "pre" and "post" hooks are
378
+ implemented in Copland.
379
+
380
+ * *inversion of control*: A general design pattern in which various types of control are
381
+ delegated ("inverted") to some enclosing framework, called a "container", "Dependency
382
+ injection" is one form of control inversion, though the two terms are sometimes used
383
+ interchangeably.
384
+
385
+ * *listener*: A service that has been registered with another service point in order to
386
+ receive events produced by the latter service.
387
+
388
+ * *package*: The unit of organization in Copland. All _service points_, _configuration points_,
389
+ and _contributions_ are grouped into _packages_. Each _package_ is defined in its own
390
+ _package descriptor_.
391
+
392
+ * *package descriptor*: The file that contains the definition of single package. This always called
393
+ @package.yml@ and contains YAML.
394
+
395
+ * *multicast service*: A special kind of service that, by itself does nothing, but which other
396
+ services may register themselves with. Then, when a method on the multicast service is invoked,
397
+ the corresponding methods of all registered services will be invoked.
398
+
399
+ * *registry*: This is the primary interface into Copland, used by clients. It is the broker
400
+ that returns _services_ requested by clients.
401
+
402
+ * *service*: This is the basic unit of activity in Copland. The _service model_ determines how
403
+ the service is instantiated, such as whether it is a singleton or not. Some services are actually
404
+ _service factories_ which may in turn be used to help create more complex services. Another
405
+ special type of service is the _interceptor factory_, which is used to create interceptor
406
+ instances. An application's components will typically be implemented as services.
407
+
408
+ * *service factory*: A service factory is a special kind of service, which may be used to
409
+ bootstrap the creation of more complex services. Copland comes with several predefined service
410
+ factories, most notably the "BuilderFactory" (which may be used to create the vast majority of
411
+ the services you will need).
412
+
413
+ * *service model*: A service model defines how and when a service is instantiated. There are
414
+ six predefined service models, allowing for simple instantiation (one instance per request,
415
+ created at the time of the request), singleton instantiation (only one instance of the service
416
+ is ever created, and it will be returned every time the service is requested), deferred
417
+ instantiation (the service will be created the first time a method is invoked on it), and even
418
+ combinations of these. The default model is "singleton deferred".
419
+
420
+ * *service point*: A definition of a service. A service point is to a service as a class is to
421
+ an object. The service point contains the configuration of interceptors, event producers, service
422
+ model, service factory, and so forth that will be used to construct its corresponding service.
423
+
424
+ - Quickstart: >
425
+ This "Quickstart" is designed to show you the "look" of a simple Copland application. It is
426
+ not intended to teach you how to use Copland, nor to be an example of application design in
427
+ Copland. Very little time will be spent on explanation. If you wish to actually learn how to
428
+ use Copland, refer instead to the Tutorials section, and the Examples section.
429
+
430
+
431
+ h3. Service Implementation
432
+
433
+
434
+ A service is typically backed by a simple, ordinary Ruby object, instantiated from a class.
435
+ You don't have to do anything special to your definition of the class (unless you want it
436
+ to interact with other services--but that's beyond the scope of this "quickstart").
437
+
438
+
439
+ <pre>
440
+ class ASimpleService
441
+ def do_something
442
+ ...
443
+ end
444
+
445
+ def do_something_complicated( a, b, opts={}, &block )
446
+ ...
447
+ end
448
+ end
449
+ </pre>
450
+
451
+
452
+ For the rest of this example, we'll assume the above implementation is contained in a file
453
+ called @simple-service.rb@.
454
+
455
+
456
+ h3. Service Definition
457
+
458
+
459
+ The services are always defined in a package descriptor file. This file is always named
460
+ @package.yml@, which means you can only have one per directory. It is a YAML-formatted
461
+ description of a package, which will include all of the service points (service definitions)
462
+ for that package.
463
+
464
+
465
+ <pre>
466
+ ---
467
+ id: quickstart
468
+
469
+ service-points:
470
+
471
+ SimpleService:
472
+ implementor: simple-service/ASimpleService
473
+ </pre>
474
+
475
+
476
+ h3. Registry Initialization
477
+
478
+
479
+ Creating a registry is simple: just invoke @build@ on the Registry class.
480
+
481
+
482
+ <pre>
483
+ require 'copland'
484
+
485
+ registry = Copland::Registry.build
486
+ </pre>
487
+
488
+
489
+ The @build@ method is a convenience method. It takes care of searching
490
+ subdirectories for package descriptors and adding them to the new registry.
491
+
492
+
493
+ h3. Getting Services
494
+
495
+
496
+ Services are queried through the registry. The name of a service is the name of it's
497
+ package (the 'id' in the package descriptor), followed by a dot '.', followed by the
498
+ id of the service.
499
+
500
+
501
+ Once you have a reference to the service, you can invoke its methods just as if it were
502
+ a plain-old Ruby object.
503
+
504
+
505
+ <pre>
506
+ service = registry.service( "quickstart.SimpleService" )
507
+ service.do_something
508
+ service.do_something_complex( 1, 2, :name=>"Jim" ) { |a| puts "#{a}: here" }
509
+ </pre>
510
+
511
+
512
+ h3. Service Initialization
513
+
514
+
515
+ Sometimes (especially for "prototype" services--see the prototype service model for
516
+ more info) you want to be able to pass some kind of initialization data to the new
517
+ service the first time it is initialized. You _could_ do this with a method that you
518
+ explicitly call after obtaining a reference to the service--but then the service needs
519
+ to keep track of whether the method has already been invoked or not, to prevent the
520
+ initialization from occurring more than once per service instance.
521
+
522
+
523
+ To make this simpler, Copland 0.8 introduced the concept of an _initialization block_.
524
+ When you call @Registry#service@, you can attach a block to it, and that block will be
525
+ passed to @instance_eval@ on the new service every time that service is instantiated.
526
+
527
+
528
+ Something like this:
529
+
530
+
531
+ <pre>
532
+ service = registry.service( "quickstart.SimpleService" ) do
533
+ ...
534
+ # put your initialization code here, to be executed every
535
+ # time this service is instantiated.
536
+ ...
537
+ end
538
+ </pre>
539
+
540
+
541
+ If the service was instantiated previously (i.e., it is a singleton service and
542
+ was requested earlier), then the initialization block will be ignored.
543
+
544
+ - Copland's Design:
545
+ - >
546
+ Here you'll read about various issues regarding Copland's design. These will probably not be
547
+ interesting to anyone except those who want to dig into Copland's internals.
548
+
549
+ - HiveMind: >
550
+ Copland borrows much of its design from the "HiveMind":http://jakarta.apache.org/hivemind
551
+ IoC container for Java. However, Copland was written without significant experience with HiveMind's
552
+ internals, so although they may look similar on the outside, their implementations will differ
553
+ significantly.
554
+
555
+
556
+ h3. Similarities
557
+
558
+
559
+ Copland uses HiveMind's concepts of "modules" (but calls them "packages"), "service points",
560
+ "configuration points", and "contributions". Also, a "service models" are borrowed from
561
+ HiveMmind, although Copland implements them in a more flexible (and extensible) manner.
562
+
563
+
564
+ The idea of a _service factory_ was also borrowed from Copland.
565
+ HiveMind supports digester-like "rules" for processing descriptor files, but because of
566
+ Ruby's highly dynamic nature, such complexity was not necessary.
567
+
568
+
569
+ Interceptors, and the way they get created (via special "interceptor factories") are identical
570
+ to HiveMind's interface.
571
+
572
+
573
+ h3. Differences
574
+
575
+
576
+ HiveMind uses XML for it's module descriptors. Copland uses YAML.
577
+
578
+
579
+ HiveMind's configuration points revolve around XML--you define a configuration point and
580
+ a schema to go with it, and all contributions to that configuration point must match that
581
+ schema. Copland's approach is simpler: simply declare whether a configuration point is a
582
+ list or a map, and then all contributions must be either in the form of list elements to
583
+ append to the configuration point, or map elements to merge into it.
584
+
585
+
586
+ Copland names the service models differently:
587
+
588
+
589
+ | *HiveMind* | *Copland* |
590
+
591
+ |=. --- |<. prototype |
592
+
593
+ |=. --- |<. prototype-deferred |
594
+
595
+ | primitive | singleton |
596
+
597
+ | singleton | singleton-deferred |
598
+
599
+ | threaded | threaded |
600
+
601
+ | pooled |=. --- |
602
+
603
+
604
+ Events and listeners exist in HiveMind, too, but because of Ruby's dynamic nature they
605
+ can be implemented much more simply in Copland. Therefore, the way they are declared and
606
+ used is different in Copland.
607
+
608
+
609
+ Multicast services do not exist in HiveMind, nor do services that are backed by anything
610
+ other than objects. Copland allows services to be backed by classes and singletons, as well
611
+ as objects.
612
+
613
+ - Registry Initialization: >
614
+ The initialization process for the registry consists of two phases. The first phase is where the
615
+ package descriptors are loaded and parsed. Once all of the packages have been loaded the
616
+ the second phase is started, which causes all of the packages to process those parts of
617
+ themselves that refer to other packages. This includes contributions to configuration points
618
+ and processing any referenced parameter schema definitions.
619
+
620
+
621
+ One the initialization has been completed, the registry (which is itself exported as a service)
622
+ loads the copland.Startup service and instructs it to start any services that have registered
623
+ themselves with it (via its configuration point).
624
+
625
+ - Service Instantiation: >
626
+ The way a service is instantiated depends on two things: it's _service model_, and its
627
+ _instantiator_ (or _implementor_).
628
+
629
+
630
+ The _instantiator_ defines _how_ the service is instantiated. It may
631
+ either be a string (in which case the service is created simply by calling 'new'
632
+ on the class described by that string), or a map, in which case the creation of the
633
+ class is delegated to a service factory. These two approaches are defined inside Copland
634
+ as "instantiation factories", with the first being @Copland::Instantiator::Simple@,
635
+ and the second being @Copland::Instantiator::Complex@.
636
+
637
+
638
+ The _service models_ define _when_ the service is instantiated. For example, the "singleton"
639
+ model says that a service is only instantiated the first time it is requested, with
640
+ subsequent requests returning the cached instance. The "singleton-deferred" model
641
+ indicates that a service should only be instantiated the first time any of its methods
642
+ are invoked (this level of granularity is achieved via a proxy object that defers instantiation
643
+ of its wrapped service).
644
+
645
+
646
+ Thus, when a user requests a service, the registry calls the @#instance@ method of the
647
+ corresponding service point. This calls the @#instance@ method for the service point's model,
648
+ and the service model may then either call the service point's @#instantiate@ method (to actually
649
+ instantiate the service point) or return a cached instance.
650
+
651
+
652
+ The service point's @#instantiate@ method will call the @#instantiate@ method for the instantiation
653
+ factory for this service point. The @Simple@ factory simply calls @#new@ on the service point's
654
+ backing class, but the @Complex@ factory will basically "execute" the parameter scheme for the
655
+ service factory that was specified, and then invoke the @#create_instance@ method of the service
656
+ factory. Either way, the backing object for the service is returned.
657
+
658
+
659
+ Then, any interceptors that were specified are added to the new service, the new service is
660
+ added as a listener to any requested event producers, and the new service is returned.
661
+
662
+ - Interceptor Chains: >
663
+ Interceptor chains are implemented as chains of proxy objects, each of which wraps an
664
+ interceptor instance. Each method of the service that is being intercepted is then
665
+ renamed and replaced with a new method, which just executes the @#process_next@ method on
666
+ the first element in the chain. When the last element of the chain invokes the @#process_next@
667
+ element on what it believes to be the next link, the renamed method of the service is
668
+ invoked.
669
+
670
+
671
+ This makes it look to each interceptor as if they are a filter for the message invocation,
672
+ which indeed they are. Each interceptor may invoke the @#process_next@ method of the next
673
+ link in the chain, if they desire, or not, if they desire.
674
+
675
+ - Packages:
676
+ - >
677
+ Packages are the unit of organization in Copland. All service and configuration points are
678
+ grouped in packages.
679
+
680
+ - Descriptor Syntax: >
681
+ Each package is described in a single file, named "package.yml". (You can actually change
682
+ what the descriptor file name ought to be--the default, though, is "package.yml"). It will
683
+ typically contain all of the services defined in the same directory. In keeping with standard
684
+ YAML syntax, this file should begin with a single line consisting solely of three dashes: @---@.
685
+
686
+
687
+ The file may contain the following attributes:
688
+
689
+
690
+ table(list).
691
+
692
+ |^. @id@|This is the name of the package. It must be a string, and it must be
693
+ unique. If the registry encounters a package named identically to some other package it has
694
+ already parsed, it will raise an exception.|
695
+
696
+ |^. @require@|This is a list of other (non-core) packages that this package depends on.
697
+ For example, to take advantage of any of the services in the "copland.lib" namespace, you
698
+ would need to add "copland/lib" to the require list for the package that needs those
699
+ services.|
700
+
701
+ |^. @description@|This is an (optional) description of the current package. It is useful for
702
+ documentation purposes, but is not used at all by Copland itself.|
703
+
704
+ |^. @configuration-points@|This must contain a hash of values, each key of which names and
705
+ references the definition of a _configuration point_ (q.v.).|
706
+
707
+ |^. @contributions@|This must contain a hash of values, each key of names a contribution to
708
+ some configuration point. (See the chapter on contributions for more information.)|
709
+
710
+ |^. @service-points@|This must contain a hash of values, each key of which names and references
711
+ the definition of a _service point_ (q.v.).|
712
+
713
+
714
+ Of the attributes descibed above, only the @id@ attribute is required, though it makes little
715
+ sense to define a package that does not include at least one configuration point, contribution,
716
+ or service point.
717
+
718
+
719
+ Also, if your package references any non-core packages (like "copland.lib" or "copland.remote"),
720
+ you will need to add the requisite libraries to your package's @require@ clause.
721
+
722
+ - Tips: >
723
+ Name your packages consistently. Here is a recommended naming convention:
724
+
725
+
726
+ * Use dashes instead of underscores in your package names.
727
+
728
+ * Make package names all lowercase.
729
+
730
+ * Name your primary package after your application.
731
+
732
+ * If your application uses more than one package (and large applications very well may),
733
+ prefix each package name with the name of your application, followed by a dot. In other
734
+ words, if your application was named "Astroscopus", and you had a subpackage named
735
+ "notewise", you would name your primary package "astroscopus", and the subpackage
736
+ "astroscopus.notewise". This gives your packages a hierarchical feel, though there is
737
+ nothing saying they need to be laid out in a hierarchical directory structure.
738
+
739
+ * If you are distributing a package that you created for others to use, name
740
+ the package with your name or company (or website) as the primary package name, and your
741
+ library as the secondary name. For instance, if your company was "Interrelated Autorotation, Inc.",
742
+ and you created a library called "papillomatosis", you might name your package
743
+ "interrelated-autorotation.papillomatosis".
744
+
745
+
746
+ Try to group the service points and configuration points in your packages by common functionality.
747
+ Don't be afraid to create new packages where necessary to separate functional groups. Although having
748
+ many packages may make your application's start-up time a little slower (and it will only be a little),
749
+ it is worth the benefits to maintainance it will add.
750
+
751
+ - Service Points:
752
+ - >
753
+ A _service point_ is a definition of a service. It describes how and when the service should be
754
+ instantiated, what parameters should be passed to it when it is created and what properties assigned,
755
+ what other services it should listen to, and so forth. You could say that a service point is to a
756
+ service, as a class is to an object. That is to say, a service point is simply a template for how
757
+ a service should be created.
758
+
759
+ - Descriptor Syntax: >
760
+ Every service point must be defined as part of the "service-points" section of a package
761
+ descriptor. The id (or name) of the service point will be the key used in that section to
762
+ reference it, and it may contain any character you wish except for a ".". Also, if you use
763
+ any character that YAML treats specially (like commas, or colons), you'll need to put the
764
+ service name in quotes.
765
+
766
+
767
+ Whenever the name of a class is required (for a service, for instance), it must be in a
768
+ particular format. The name of the class must be prefixed with the path that must be
769
+ required (ie, the file that contains the class), followed by a forward slash ("/"),
770
+ followed by the complete name of the class (including any modules that it is defined
771
+ inside of). For example:
772
+
773
+
774
+ <pre>
775
+ some/special/service/MyServices::MyService
776
+ </pre>
777
+
778
+
779
+ The above example indicates a class named @MyService@, defined inside of a module
780
+ called @MyServices@, and which is contained in a file called @some/special/service.rb@.
781
+
782
+
783
+ The allowed attributes of a service point are:
784
+
785
+
786
+ table(list).
787
+
788
+ |^. @description@|this (optional) attribute is useful for documentation purposes, but has no
789
+ use at all inside Copland.|
790
+
791
+ |^. @model@|This describes how (and when) the service will actually be instantiated. It must
792
+ be either @simple@, @simple-deferred@, @singleton@, @singleton-deferred@, @threaded@, or @pooled@
793
+ (or, if you've defined a custom service model, the name of that custom service model). If this
794
+ attribute is not specified, it defaults to @singleton-deferred@. (See the chapter on "Service
795
+ Models" for more information.)|
796
+
797
+ |^. @implementor@| This describes how the service should be _implemented_. If the value of
798
+ this attribute is a string, then it must be the name of the class to instantiate. (If the
799
+ class includes the Singleton mixin, then the singleton instance will be returned; otherwise
800
+ a new instance of the class will be created.) If this value is a map, then it may contain
801
+ at the key @factory@. This must identify a _factory service_ which will be used to
802
+ create the new service. (If omitted, it defaults to @copland.BuilderFactory@.) In addition
803
+ to the @factory@ attribute, the hash should contain any other attributes required by the
804
+ service factory that was specified. See the documentation for the corresponding service factory
805
+ for a description of which attributes are available.|
806
+
807
+ |^. @interceptors@|This must be an array of interceptor definitions, each of which is a hash.
808
+ The only required key in each hash is @service@, which names the interceptor factory service
809
+ to use when instantiating the corresponding interceptor. The other allowed attributes are @before@
810
+ and @after@, which may be used to define the order in which the interceptors are applied.
811
+ Besides those two attributes, some interceptors may allow additional attributes to be specified
812
+ in order to configure them. See the documentation for those interceptors for more information.|
813
+
814
+ |^. @schema@|This must be either a string or a hash. If it is a string, then it references
815
+ another schema (either in this package or a different one). Otherwise, it's format is described
816
+ in more detail in the chapter "Schemas". This is only needed by services that
817
+ define service or interceptor factories.|
818
+
819
+ |^. @listen-to@|This must be an array of service point names. If the referrenced service points are
820
+ not defined in the same package as the current service point, their full names (including package)
821
+ must be given, otherwise the package name may be omitted. Every time the current service point is
822
+ instantiated, the new instance will be added as a listener to each of the service points listed
823
+ here, to be notified when some service-specific event is triggered. See the chapter on
824
+ "Listeners and Event Producers" for more information.|
825
+
826
+
827
+ Of these attributes, only @implementor@ is required.
828
+
829
+ - Service Models:
830
+ - >
831
+ Service models are used to specify when a service point should be instantiated. There are
832
+ five pre-defined service models, and you can easily define more yourself should you need them.
833
+
834
+
835
+ The service model for a service point is specified using the @model@ attribute in the service
836
+ point definition. This attribute is optional, defaulting to @singleton-deferred@, but if given it
837
+ must refer to either one of the five standard service models, or to a custom service model you have
838
+ defined.
839
+
840
+
841
+ - Standard Models: >
842
+ The five standard service models are:
843
+
844
+
845
+ table(list).
846
+
847
+ |^. @prototype@ | A service point using this model will be reinstantiated every time it is requested.
848
+ This may be useful, for instance, when the requested service is stateful, and you do not want instances
849
+ of the service to be reused.|
850
+
851
+ |^. @prototype-deferred@ | This is the same as the @prototype@ service model, except that the
852
+ service will not be instantiated until the first time any of its methods are invoked. Thus, if
853
+ you _might_ need the service, but there are some paths through your program that won't use it,
854
+ you can use this model so that the overhead of initializing the service only occurs when you
855
+ actually invoke a method on it.|
856
+
857
+ |^. @singleton@ | Only one instance of the service is ever created. Any subsequent request for the
858
+ service will return the instance that was created the first time. This is the most common scenario
859
+ for most services, since in general a service is either stateless, or its state is effectively
860
+ global throughout an application.|
861
+
862
+ |^. @singleton-deferred@ | This is identical to the @singleton@ model, above, but also has the
863
+ deferring functionality of the @prototype-deferred@ model.|
864
+
865
+ |^. @threaded@ | This model results in every thread obtaining its own instance of a service. It is
866
+ otherwise identical to @singleton-deferred@.|
867
+
868
+ - Deferred Instantiation: >
869
+ Using one of the "deferred" service models (@prototype-deferred@, @singleton-deferred@, or @threaded@)
870
+ can be very useful, but there are side-effects that you ought to be aware of. Otherwise,
871
+ you may spend a lot of time debugging some difficult-to-track-down problems.
872
+
873
+
874
+ In particular, it is important to keep in mind that the service is not actually instantiated until
875
+ a method is invoked on it. This means that if the service's initialization routine raises an error,
876
+ the exception will not occur where the reference to the service was obtained, but will occur where
877
+ the method call was made. The service will also not have been properly instantiated, so if you
878
+ try to inoke any of its methods again, you'll get @nil@ back every time.
879
+
880
+
881
+ For example, consider the following scenario.
882
+
883
+
884
+ Suppose you are writing a web-application framework, using Copland to modularize the various
885
+ components. One of the services you create is a "page manager", which will broker all requests
886
+ for page objects. When an application needs a page, it queries the page manager, which will
887
+ look up the page and return it.
888
+
889
+
890
+ You make the page manager service @singleton-deferred@.
891
+
892
+
893
+ Now, inside the controller for your architecture, you grab a reference to the page manager.
894
+ (Note, however, that the object you are given is _not_ the page manager--instead, it is a proxy
895
+ object that will instantiate the page manager the first time a method is invoked on it.) Some time
896
+ later you invoke @#get_page@ on the page manager, which you expect to go grab a page object and
897
+ return it.
898
+
899
+
900
+ _However_. What actually happens is this: the proxy object intercepts the method call and tries
901
+ to instantiate the page manager service, since it hasn't been instantiated yet. During the
902
+ page manager initialization, however, something goes wrong. Perhaps one of the page classes
903
+ doesn't exist, or there is a syntax error in one of the files that the page manager needs to
904
+ require. Whatever it is, the page manager cannot be initialized and an exception is thrown.
905
+
906
+
907
+ The controller code catches the exception.
908
+
909
+
910
+ _Problem_. Inside the rescue clause for the exception, _the controller queries the page manager
911
+ for the error page_, so that it can display a coherent error to the client. However, since there
912
+ was an error instantiating the page manager, future queries to the proxy object always return
913
+ @nil@. This may result in an "undefined method `x' for nil:NilClass" error down the road.
914
+
915
+
916
+ To avoid problems like this, you can do one of two things. One, you can use the @singleton@ model,
917
+ instead of @singleton-deferred@. This way, Copland will never deliver you a reference to a proxy.
918
+ The other solution is to create a method in your class that always returns non-@nil@. Then, if
919
+ it ever _does_ return @nil@, you can be pretty sure that the service was not instantiated correctly.
920
+ This, unfortunately, requires your code to know that the service was deferred.
921
+
922
+ - Custom Service Models: >
923
+ Creating your own service models is very straight-forward. In general, all you need to do is
924
+ create a class. The class should extend the @Copland::Instantiator::Abstract@ class, and must
925
+ override the @instantiate@ method. The @instantiate@ method should accept a no parameters.
926
+ The method should then return an object that represents the instantiated service.
927
+
928
+
929
+ After creating the class, you need to register it with the @Copland::ClassFactory@
930
+ class by calling it's @register@ method, passing the name to want for this new model, and
931
+ a reference to the class that implements it.
932
+
933
+
934
+ To return a deferred version of the service, return a new instance of @Copland::Instantiator::Proxy@.
935
+ The proxy's constructor accepts a single parameter: the service point to instantiate.
936
+
937
+
938
+ To create a singleton service, the @instantiate@ method should cache the new service, returning
939
+ it (if it exists) instead of instantiating a new service.
940
+
941
+
942
+ Lastly, be aware that you may need to do some synchronization via mutexes to make your new
943
+ service model thread-safe. Multiple threads could call it concurrently, which may cause
944
+ problems if your model stores any state (as is the case with models that enforce a singleton
945
+ model of service instantiation).
946
+
947
+
948
+ Here's a brief example of a custom service model, pulled off the top of my head:
949
+
950
+
951
+ <pre>
952
+ class FiveMinuteServiceModel < Copland::Instantiator::Abstract
953
+ CachedService = Struct.new( :service, :time )
954
+
955
+ def initialize( point, definition )
956
+ super
957
+ @mutex = Mutex.new
958
+ @service_cache = Hash.new
959
+ end
960
+
961
+ def instantiate
962
+ @mutex.synchronize do
963
+ svc = @service_cache[ point ]
964
+
965
+ if svc.nil? || Time.now - svc.time > 300
966
+ svc = CachedService.new( point.instantiate, Time.now )
967
+ @service_cache[ point ] = svc
968
+ end
969
+
970
+ return svc.service
971
+ end
972
+ end
973
+
974
+ register_as "custom.five-minutes"
975
+ end
976
+ </pre>
977
+
978
+
979
+ This (contrived) example will result in a singleton-style model (non-deferred),
980
+ which will instantiate the requested service point if it has been more than five
981
+ minutes (300 seconds) since the point was last instantiated. It can be referenced
982
+ in a service point definition under the @model@ attribute, as "custom.five-minutes".
983
+
984
+ - Configuration Points:
985
+ - >
986
+ A _configuration point_ is either a list or map that other packages may contribute
987
+ values to. You can then have Copland automatically wire that configuration point into
988
+ a service.
989
+
990
+
991
+ Why would you want to do this? Various reasons. One is so that you can allow other
992
+ packages to configure the behavior of a particular service. The @copland.remote.DRbServer@
993
+ service, for example, does this, to allow applications to configure the host and port
994
+ that should be listened to, and whether or not the service should be automatically started.
995
+
996
+
997
+ Values are added to a configuration point via the "contributions" section of a package
998
+ descriptor.
999
+
1000
+ - Descriptor Syntax: >
1001
+ Every configuration point must be defined as part of the "configuration-points" section
1002
+ of a package descriptor. The id (or name) of the configuration point will be the key used
1003
+ in that section to reference it, and it may contain any character you wish except for a ".".
1004
+ Also, if you use any character that YAML treats specially (like commas, or colons), you'll
1005
+ need to put the configuration name in quotes.
1006
+
1007
+
1008
+ The allowed attributes of a configuration point are:
1009
+
1010
+
1011
+ table(list).
1012
+
1013
+ |^. @description@ | This is an (optional) description of what the configuration
1014
+ point describes. Where a schema (see below) is insufficient, the description may also
1015
+ be used for describing what kinds of values the configuration point expects.|
1016
+
1017
+ |^. @type@ | This is the type of the configuration point. By default, only @list@
1018
+ and @map@ are supported, but you can add your own configuration point types.|
1019
+
1020
+ |^. @schema@|This must be either a string or a hash. If it is a string, then it references
1021
+ another schema (either in this package or a different one). Otherwise, it's format is described
1022
+ in more detail in the chapter "Schemas". This is used to restrict the values
1023
+ that can be contributed to the configuration point. If the configuration point is of type
1024
+ @list@, then the schema applies to each element of the configuration point. Otherwise, the
1025
+ schema describes the format of the map.|
1026
+
1027
+
1028
+ Of these attributes, only @type@ is required.
1029
+
1030
+ - DefaultSymbolSource: >
1031
+ One of the core pre-defined configuration points is the map @copland.DefaultSymbolSource@.
1032
+ Although you will rarely access this configuration point directly, it is integrated
1033
+ tightly with Copland. Any value you add to it becomes available as a _substitution
1034
+ symbol_.
1035
+
1036
+
1037
+ Substitution symbols will look familiar to those of you coming from a Java background;
1038
+ utilities like "ant":http://ant.apache.org and "maven":http://maven.apache.org make
1039
+ heavy use of them. What they are, is this: any time Copland encounters a value that
1040
+ contains a certain pattern, it replaces that pattern with the value of the same name
1041
+ from the DefaultSymbolSource. The pattern is a dollar sign, followed by curly braces
1042
+ that contain the name of the symbol.
1043
+
1044
+
1045
+ For example, suppose you want to allow the application to configure which class gets
1046
+ used when a particular service point is instantiated. You can do this by pulling the
1047
+ name of the class from the DefaultSymbolSource as a substitution symbol:
1048
+
1049
+
1050
+ <pre>
1051
+ SomeServicePoint:
1052
+ implementor: ${the.class.to.use}
1053
+ </pre>
1054
+
1055
+
1056
+ Then, the application would contribute a value named @the.class.to.use@ to
1057
+ @copland.DefaultSymbolSource@, and when @SomeServicePoint@ is instantiated, it would
1058
+ use the value thus provided.
1059
+
1060
+ - Contributions:
1061
+ - >
1062
+ _Contributions_ are the means by which packages may add values to configuration points.
1063
+ In general, the value of a contribution must match both the _type_ of the configuration
1064
+ point, and the requirements of any _schema_ that the configuration point may possess.
1065
+
1066
+
1067
+ Adding contributions is simple. All contributions must go in the _contributions_ section
1068
+ of the package descriptor. Then, specify a map key with the name of the configuration
1069
+ point you want to contribute to, and then specify (as the value of the key) the value(s)
1070
+ you want to contribute.
1071
+
1072
+
1073
+ For example, to contribute a value to the DefaultSymbolSource configuration point:
1074
+
1075
+
1076
+ <pre>
1077
+ contributions:
1078
+
1079
+ copland.DefaultSymbolSource:
1080
+ user.name: fritz
1081
+ </pre>
1082
+
1083
+
1084
+ That little snippet just added a value called @user.name@ to the @copland.DefaultSymbolSource@
1085
+ configuration point!
1086
+
1087
+ - Service Factories:
1088
+ - >
1089
+ Sometimes it requires a little more effort (or a lot more effort) to instantiate and
1090
+ initialize a service than simply calling @#new@ on it's associated class. In such cases,
1091
+ you need to rely on a _service factory_ to instantiate the service.
1092
+
1093
+
1094
+ A service factory is just a regular service as far as Copland is concerned. It is declared
1095
+ in the usual way. However, a service that will be used as a service factory must implement
1096
+ at least one method: @#create_instance@. This method should accept two parameters, the
1097
+ _service point_ to instantiate, and the _parameters_ associated with this instantiation.
1098
+ (The @parameters@ parameter will always be a hash.)
1099
+
1100
+
1101
+ Here is an example that implements a trivial service factory:
1102
+
1103
+
1104
+ <pre>
1105
+ class ExampleServiceFactory
1106
+
1107
+ def create_instance( point, parms )
1108
+ return { :point => point,
1109
+ :parms => parms }
1110
+ end
1111
+
1112
+ end
1113
+ </pre>
1114
+
1115
+
1116
+ This service would be introduced into Copland via the following package
1117
+ descriptor:
1118
+
1119
+
1120
+ <pre>
1121
+ ---
1122
+ id: example
1123
+
1124
+ service-points:
1125
+
1126
+ ExampleServiceFactory:
1127
+ implementor: some/file/ExampleServiceFactory
1128
+ </pre>
1129
+
1130
+
1131
+ And it would be employed like this:
1132
+
1133
+
1134
+ <pre>
1135
+ ---
1136
+ id: demo
1137
+
1138
+ service-points:
1139
+
1140
+ ExampleService:
1141
+ implementor:
1142
+ factory: example.ExampleServiceFactory
1143
+ </pre>
1144
+
1145
+
1146
+ This service factory, when used to instantiate a service, would always return a new
1147
+ Hash object consisting of the service point and its parameters. Thus, the @ExampleService@
1148
+ service point would, when instantiated, always consist of a hash containing its own
1149
+ service point, and any parameters that were given when it was instantiated (none, in this
1150
+ case). Not a very useful service factory, but it demonstrates what it ought to do.
1151
+
1152
+ - Schemas: >
1153
+ As mentioned in the chapter on service points, a service point may be associated
1154
+ with a _schema_. More will be said on schemas (and specifically, on their formats)
1155
+ in the next chapter, but suffice it to say here that the schema allows a service
1156
+ point to specify what parameters it accepts when invoked as a factory service.
1157
+
1158
+ - How do they work?: >
1159
+ When you specify a factory service as the implementor of another service, Copland
1160
+ automatically marks that service point as needing a _complex instantiator_. Thus, when
1161
+ it comes time to instantiate the service point, the parameters are collected, and
1162
+ if the factory has a schema, the parameters are validated against that schema. Then,
1163
+ the parameters are preprocessed (to translate values to their appropriate and expected
1164
+ types), and the factory's @#create_instance@ method is called. The result of that call
1165
+ is then treated as the new service.
1166
+
1167
+
1168
+ Contrast this with the _simple instantiator_. When the simple instantiator is used,
1169
+ all it does (more or less) is invoke @#new@ on the named class and return the result.
1170
+ The existence of factory services allows for much more complex (and powerful)
1171
+ behavior.
1172
+
1173
+ - BuilderFactory: >
1174
+ Copland comes with one predefined factory service: @copland.BuilderFactory@. With
1175
+ this factory you can implement most of the more common types of services. (There are
1176
+ always special cases, though--the @copland.lib@, @copland.remote@ and @copland.webrick@
1177
+ libraries all define additional factory services for specialized uses.)
1178
+
1179
+
1180
+ The BuilderFactory allows you to not only instantiate a class, but to specify
1181
+ constructor parameters and set properties on the new object. It also allows you to
1182
+ specify methods that should be invoked in order to initialize a service. It is by this
1183
+ means that the "dependency injection" aspect of Copland comes into play.
1184
+
1185
+
1186
+ - Schemas:
1187
+ - >
1188
+ "Schemas" are the mechanism by which you can restrict what values are contributed to
1189
+ configuration points, or which parameters are acceptable to a service constructed via
1190
+ a factory service.
1191
+
1192
+
1193
+ - Basic Format: >
1194
+ In YAML terminology, a schema is simply a map that follows a special format. Consider
1195
+ the following configuration point:
1196
+
1197
+
1198
+ <pre>
1199
+ MyConfigurationPoint:
1200
+ type: map
1201
+ schema:
1202
+ definition:
1203
+ user.name:
1204
+ type: string
1205
+ required: true
1206
+ home.directory:
1207
+ type: string
1208
+ user.groups:
1209
+ type: array
1210
+ </pre>
1211
+
1212
+
1213
+ This configuration point is a map that only allows three keys: @user.name@,
1214
+ @home.directory@, and @user.groups@. Any contribution made to this configuration point
1215
+ _must not_ contain any keys other than these values. And since the @user.name@ key
1216
+ is marked as required, any contribution to this configuration point _must_ contain
1217
+ at least that key.
1218
+
1219
+
1220
+ Note the type definitions as well. The recognized types are:
1221
+
1222
+
1223
+ * @any@
1224
+
1225
+ * @array@
1226
+
1227
+ * @configuration@
1228
+
1229
+ * @integer@
1230
+
1231
+ * @log@
1232
+
1233
+ * @hash@
1234
+
1235
+ * @real@
1236
+
1237
+ * @service@
1238
+
1239
+ * @string@
1240
+
1241
+
1242
+ If the type is left out, it defaults to @any@. If a value is contributed to that
1243
+ key that is not of the required type, a validation error results.
1244
+
1245
+
1246
+ Note that schemas may be applied to service points as well, in identical fashion
1247
+ to that of configuration points. (That is to say, they are introduced by the @schema@
1248
+ descriptor element.) In that case, the schema applies to the parameters of any
1249
+ service point that uses this service point as its factory service.
1250
+
1251
+ - Subschemas: >-
1252
+ Schemas may be nested, to allow for arbitrarily deep schema "trees". Consider the
1253
+ following example:
1254
+
1255
+
1256
+ <pre>
1257
+ MyConfigurationPoint:
1258
+ type: map
1259
+ schema:
1260
+ definition:
1261
+ user.name:
1262
+ type: string
1263
+ required: true
1264
+ home.directory:
1265
+ type: string
1266
+ user.groups:
1267
+ type: array
1268
+ user.address:
1269
+ definition:
1270
+ line1:
1271
+ type: string
1272
+ line2:
1273
+ type: string
1274
+ city:
1275
+ type: string
1276
+ state:
1277
+ type: string
1278
+ zip:
1279
+ type: string
1280
+ </pre>
1281
+
1282
+
1283
+ In this case, the @user.address@ element of the schema is itself _another schema_.
1284
+ That is to say, any element that it matches must be a hash, which may be empty,
1285
+ but which may contain no keys other than those specified (@line1@, @line2@, @city@,
1286
+ @state@, and @zip@).
1287
+
1288
+
1289
+ If you specify a subschema definintion (via the @definition@ keyword), and the same
1290
+ element is of any type other than @hash@ or @array@, you'll get an error. You cannot
1291
+ used schema's to validate any other type of data. (Arrays are a special case--see
1292
+ the next section.)
1293
+
1294
+ - Arrays: >-
1295
+ When you specify a schema for an array (or for a configuration point of type @list@),
1296
+ the schema will be applied to every element of any array that is contributed. For
1297
+ example, consider this:
1298
+
1299
+
1300
+ <pre>
1301
+ MoviesILike:
1302
+ type: list
1303
+ schema:
1304
+ definition:
1305
+ name:
1306
+ type: string
1307
+ required: true
1308
+ genre:
1309
+ type: string
1310
+ actors:
1311
+ type: array
1312
+ definition:
1313
+ name:
1314
+ type: string
1315
+ required: true
1316
+ gender:
1317
+ type: string
1318
+ birthdate:
1319
+ type: string
1320
+ </pre>
1321
+
1322
+
1323
+ This configuration point is a @list@. Every element that is contributed to it
1324
+ must conform to the given schema. Note, too, that the @actors@ element of the
1325
+ schema expects an array, and that each element of _that_ array must conform to
1326
+ the given subschema.
1327
+
1328
+
1329
+ Given that schema, the following contribution would be valid:
1330
+
1331
+
1332
+ <pre>
1333
+ contributions:
1334
+
1335
+ MoviesILike:
1336
+ - name: Twelve Angry Men
1337
+ genre: Drama
1338
+ actors:
1339
+ - name: Henry Fonda
1340
+ gender: male
1341
+ birthdate: 16 May 1905
1342
+ - name: Jack Klugman
1343
+ birthdate: 27 Apr 1922
1344
+ - name: Ed Binns
1345
+ - name: John Fiedler
1346
+ - name: Lawrence of Arabia
1347
+ actors:
1348
+ - name: Peter O'Toole
1349
+ - name: Alec Guinness
1350
+ birthdate: 2 Apr 1914
1351
+ - name: Remains of the Day
1352
+ </pre>
1353
+
1354
+ - Named vs. Anonymous Schemas: >-
1355
+ The schemas that have been shown so far have been _anonymous_. This is fine for schemas
1356
+ that are very specific and have a very special purpose. However, sometimes, you want
1357
+ several configuration points of service points to have the same schema. To accomplish
1358
+ this, you need to give your schemas names.
1359
+
1360
+
1361
+ Named schemas are owned by the package in which they are defined, but once named they
1362
+ may be used in any package (just like service points and configuration points).
1363
+
1364
+
1365
+ To name a schema, just add a @name@ element at the same level as the @definition@
1366
+ element:
1367
+
1368
+
1369
+ <pre>
1370
+ MoviesILike:
1371
+ type: list
1372
+ schema:
1373
+ name: MovieDefinition
1374
+ definition:
1375
+ ...
1376
+ </pre>
1377
+
1378
+
1379
+ Then, you can reuse the schema by putting the schema name after the @schema@ element,
1380
+ instead of a hash:
1381
+
1382
+
1383
+ <pre>
1384
+ MoviesIHate:
1385
+ type: list
1386
+ schema: MovieDefinition
1387
+ </pre>
1388
+
1389
+
1390
+ This second configuration point will reuse the @MovieDefinition@ schema. Note
1391
+ that you can specify an existing schema (or name a schema) at any level of a
1392
+ schema definition:
1393
+
1394
+
1395
+ <pre>
1396
+ MoviesILike:
1397
+ type: list
1398
+ schema:
1399
+ name: MovieDefinition
1400
+ definition:
1401
+ name:
1402
+ type: string
1403
+ required: true
1404
+ genre:
1405
+ type: string
1406
+ actors:
1407
+ name: ActorDefinition
1408
+ type: array
1409
+ definition:
1410
+ name:
1411
+ type: string
1412
+ required: true
1413
+ gender:
1414
+ type: string
1415
+ birthdate:
1416
+ type: string
1417
+
1418
+ MoviesIHate:
1419
+ type: list
1420
+ schema: MovieDefinition
1421
+
1422
+ FavoriteActors:
1423
+ type: list
1424
+ schema: ActorDefinition
1425
+
1426
+ PartiesAttended:
1427
+ type: list
1428
+ schema:
1429
+ definition:
1430
+ place:
1431
+ type: string
1432
+ host:
1433
+ type: string
1434
+ attendees: ActorDefinition
1435
+ </pre>
1436
+
1437
+ - Extending Schemas: >-
1438
+ Sometimes it happens that you want to create a schema that is _almost_ like an
1439
+ existing schema, but adds one or two new elements. You can do this in Copland
1440
+ by _extending_ the existing schema:
1441
+
1442
+
1443
+ <pre>
1444
+ People:
1445
+ type: list
1446
+ schema:
1447
+ name: PersonSchema
1448
+ definition:
1449
+ name:
1450
+ required: true
1451
+ type: string
1452
+ gender:
1453
+ required: true
1454
+ type: string
1455
+
1456
+ Employees:
1457
+ type: list
1458
+ schema:
1459
+ name: EmployeeSchema
1460
+ extend: PersonSchema
1461
+ definition:
1462
+ department:
1463
+ required: true
1464
+ type: string
1465
+ </pre>
1466
+
1467
+
1468
+ In the above instance, the "EmployeeSchema" is exactly like the PersonSchema,
1469
+ except it adds a "department" key.
1470
+
1471
+ - Limitations: >-
1472
+ The existing schema implementation is sufficient for most purposes, but it has
1473
+ some limitations:
1474
+
1475
+
1476
+ * You cannot specify a format for a non-hash value.
1477
+
1478
+ * You cannot specify whether a subschema that reuses an existing schema is
1479
+ required or not.
1480
+
1481
+ * You cannot enforce the constraint "any one of a set of keys is required." The
1482
+ schema subsystem only understands a single key being required or not.
1483
+
1484
+
1485
+ For most purposes, however, it is sufficient.
1486
+
1487
+ - Listeners and Event Producers:
1488
+ - >
1489
+ Sometimes, you may have a service that produces events. In terms of a GUI, you might
1490
+ have a button that emits events liked "clicked", "mouse over", "mouse out", "mouse down",
1491
+ and so forth. Copland provides a framework for indicating that instances of a particular
1492
+ service point should listen for events from instances of another service point.
1493
+
1494
+ - Event Producers: >
1495
+ At one end of this functionality is the _event producer_. Using the above example, this
1496
+ would be the button. It is the service that generates and emits the events.
1497
+
1498
+
1499
+ Any service can be an event producer--all it requires is that the service implement an
1500
+ @#add_listener@ method by which other services may be added as interested parties. Also,
1501
+ although there is nothing that prevents you from using non-singleton services as event
1502
+ producers, it really only works correctly with services using the @singleton@ and
1503
+ @singleton-deferred@ model.
1504
+
1505
+
1506
+ When an event occurs, the producer should notify those registered listeners by
1507
+ invoking a method on them. The method should be named "on_#{event}" (where event
1508
+ is the name of the event), and it should accept the producer as the first argument,
1509
+ followed by any number of other arguments (as needed for the given event). The listener
1510
+ method should only be invoked if the listener responds to that method; otherwise, it should
1511
+ be silently skipped.
1512
+
1513
+
1514
+ Optionally, if a listener does not respond to a specific event, the producer may look for
1515
+ a method called "on_any_event"; if the listener responds to that, that method may be invoked
1516
+ instead, with the producer as the first parameter and the event as the second, followed by
1517
+ any additional arguments.
1518
+
1519
+
1520
+ To make it easier to create event producers, you can mixin the Copland::EventProducer module
1521
+ into your existing services. It provides several methods, including @#add_listener@ and
1522
+ @#fire_event@.
1523
+
1524
+
1525
+ <pre>
1526
+ require 'copland/event-producer'
1527
+
1528
+ class MyProducerService
1529
+ include Copland::EventProducer
1530
+
1531
+ def click
1532
+ do_something_about_it
1533
+ fire_event :clicked
1534
+ end
1535
+
1536
+ def mouse_over
1537
+ do_something_else_about_it
1538
+ fire_event :mouse_over
1539
+ end
1540
+ end
1541
+ </pre>
1542
+
1543
+ - Listeners: >
1544
+ Listeners are at the other end of the producer/listener link. As with the event producer,
1545
+ any service can be a listener. It just needs to implement a method for each event that it is
1546
+ interested in, and then indicate (in its service point) that it wants to be registered as a
1547
+ listener of a particular service. Unlike event producers, the listeners can use any service
1548
+ model they like.
1549
+
1550
+
1551
+ As described above, the listener methods are named "on_#{event}", where "event" is the name
1552
+ of the event to listen to. Alternatively, a listener may declare a method called
1553
+ "on_any_event", which will be called for any event that does not have an explicit listener
1554
+ method declared for.
1555
+
1556
+
1557
+ <pre>
1558
+ class MyListenerService
1559
+ def on_click( producer, *args )
1560
+ puts "Argh! It was clicked!"
1561
+ end
1562
+
1563
+ def on_any_event( producer, event, *args )
1564
+ puts "Something happened: #{event}"
1565
+ end
1566
+ end
1567
+ </pre>
1568
+
1569
+
1570
+ To register interest in the producer, use the @listen-to@ element when defining the
1571
+ service point. This element expects an array of service point names. Every time
1572
+ the service point is instantiated, the list of @listen-to@ elements will be
1573
+ processed and the new service will be added to each of those service points in
1574
+ turn, as a listener:
1575
+
1576
+
1577
+ <pre>
1578
+ service-points:
1579
+
1580
+ Producer:
1581
+ implementor: MyProducerService
1582
+
1583
+ Listener:
1584
+ implementor: MyListenerService
1585
+ listen-to:
1586
+ - Producer
1587
+ </pre>
1588
+
1589
+ - The Registry as an Event Producer: >-
1590
+ Currently, Copland only comes with one event producer built in--the registry itself.
1591
+ Every time you instantiate a registry, it adds itself to itself as the
1592
+ "copland.Registry" service. When the registry is shutdown, it emits a
1593
+ @:registry_shutdown@ event to all of its listeners.
1594
+
1595
+
1596
+ Thus, if you have a service that you want to be able to do some cleanup when the
1597
+ registry is being shutdown, you can have that service listen to "copland.Registry",
1598
+ and implement a listener method called @on_registry_shutdown@ (or @on_any_event@).
1599
+
1600
+ tutorials:
1601
+
1602
+ - Creating Services:
1603
+ brief: The basics of creating new services in Copland.
1604
+
1605
+ intro: >-
1606
+ A "service" is just an object. A plain, old, vanilla-flavored Ruby
1607
+ object. It's as simple as that. You create a Ruby object, define methods
1608
+ and attributes on it, put it in Copland, and voila! Instant service.
1609
+
1610
+
1611
+ I'm assuming you already know how to create Ruby objects. (If not, you may
1612
+ want to check out a more basic tutorial.) This tutorial will show you how
1613
+ to plug those objects into Copland, and how to access them in your program.
1614
+
1615
+ steps:
1616
+ - >
1617
+ h3. Implement the Service
1618
+
1619
+
1620
+ For this tutorial, we'll create a service that adds two numbers and returns
1621
+ the result. Pretty basic, but easy to understand.
1622
+
1623
+
1624
+ So, first things first: let's write a Ruby class that performs this operation:
1625
+
1626
+
1627
+ <pre>
1628
+ class Adder
1629
+
1630
+ def add( a, b )
1631
+ a.to_f + b.to_f
1632
+ end
1633
+
1634
+ end
1635
+ </pre>
1636
+
1637
+
1638
+ (Just to give this example a little more value, we'll have it explicitly convert its
1639
+ operands to floats--it makes things a little more impressive.)
1640
+
1641
+
1642
+ Save your file as "tutorial.rb" and move on to the next step.
1643
+
1644
+ - >
1645
+ h3. Tell Copland About It
1646
+
1647
+
1648
+ Next, we need to tell Copland about our service. We'll create a package descriptor file
1649
+ (called "package.yml") in the same directory as our "tutorial.rb" file, and in it we'll
1650
+ tell Copland the name of our service and what Ruby class implements it (among other
1651
+ things). This file is YAML-formatted, so if you've used YAML before, the format (at
1652
+ least) should look very familiar.
1653
+
1654
+
1655
+ <pre>
1656
+ ---
1657
+ id: tutorial
1658
+ description: This is the package for the Copland tutorials.
1659
+
1660
+ service-points:
1661
+
1662
+ Adder:
1663
+ description: A service for adding two numbers.
1664
+ implementor: tutorial/Adder
1665
+ </pre>
1666
+
1667
+
1668
+ (The 'description' elements are always optional... we've shown them here just to
1669
+ demonstrate how they are used, but in later tutorials we'll leave them out, for
1670
+ clarity.)
1671
+
1672
+
1673
+ This descriptor creates a new _package_, called "tutorial". In Copland, all services
1674
+ are defined within _packages_.
1675
+
1676
+ The descriptor then defines a single _service point_, called Adder. (Service points
1677
+ are the definitions of services. You can think of it like this: just as an object is
1678
+ the instantiation of a class, so is a service the instantiation of a service point.)
1679
+ This service point is implemented by "tutorial/Adder", which means that "tutorial"
1680
+ will be @require@d, and then "Adder" instantiated.
1681
+
1682
+ - >
1683
+ h3. Use the Service
1684
+
1685
+
1686
+ Lastly, we create a "main.rb" driver file which we'll use to test our new service point.
1687
+ This driver will simply instantiate a registry, grab a reference to the new service, and
1688
+ then invoke our @#add@ method:
1689
+
1690
+
1691
+ <pre>
1692
+ require 'copland'
1693
+
1694
+ registry = Copland::Registry.build
1695
+
1696
+ adder = registry.service( "tutorial.Adder" )
1697
+
1698
+ puts adder.add( "5", 7 )
1699
+ </pre>
1700
+
1701
+
1702
+ The @#build@ method of Copland::Registry will recursively search for package descriptor
1703
+ files. By default, it will search from the current directory (but you can specify a
1704
+ different directory by giving it as a parameter to @#build@).
1705
+
1706
+
1707
+ Once the registry has been initialized, you can query it for services. In this case, we
1708
+ ask the registry for the @tutorial.Adder@ service (i.e., the @Adder@ service that exists
1709
+ in the @tutorial@ package). Copland then returns this service.
1710
+
1711
+
1712
+ Lastly, we invoke the @#add@ method and print the result. And it just works!
1713
+
1714
+ summary: >-
1715
+ This tutorial demonstrated a few things:
1716
+
1717
+
1718
+ # Copland services are just plain-ol' Ruby objects.
1719
+
1720
+ # Package descriptors are just YAML files
1721
+
1722
+ # Registry instantiation and querying services
1723
+
1724
+
1725
+ Also, this tutorial used the _simple_ implementor; all it did was accept a class name
1726
+ (and @require@ path) and instantiate it. Although this is useful for simple services,
1727
+ it doesn't give us the _dependency injection_ that IoC containers are famous for. The
1728
+ next tutorial will show how to wire several services together automatically, using the
1729
+ _complex_ implementor.
1730
+
1731
+ - Service Factories:
1732
+ brief: >-
1733
+ Introduces the concept of a "service factory", and shows how to use them to
1734
+ create complex services.
1735
+
1736
+ intro: >-
1737
+ As you saw in the last tutorial, you can create services by mapping them directly to
1738
+ Ruby classes. The last tutorial used the _simple_ implementor to do this, which has
1739
+ its limitations. For one, the class must have a no-argument constructor, which restricts
1740
+ what classes you can use with it.
1741
+
1742
+
1743
+ This tutorial will demonstrate the use of _service factories_ to perform more complex
1744
+ styles of instantiation and initialization.
1745
+
1746
+
1747
+ A _service factory_ is just a special kind of service that is used to create other
1748
+ services. There are several different service factories that Copland provides by default;
1749
+ this tutorial will only use the BuilderFactory.
1750
+
1751
+
1752
+ This tutorial will continue the application started in the first tutorial. We'll extend
1753
+ the "adder" example into a simple calculator object that delegates its operations to
1754
+ different services.
1755
+
1756
+ steps:
1757
+ - >-
1758
+ h3. Implement the Services
1759
+
1760
+
1761
+ Most calculators support at least four operations: addition, multiplication, division,
1762
+ and subtraction. We've already got a service that does addition: let's write three
1763
+ more that do the multiplication, division, and subtraction:
1764
+
1765
+
1766
+ <pre>
1767
+ class Subtractor
1768
+ def subtract( a, b )
1769
+ a.to_f - b.to_f
1770
+ end
1771
+ end
1772
+
1773
+ class Multiplier
1774
+ def multiply( a, b )
1775
+ a.to_f * b.to_f
1776
+ end
1777
+ end
1778
+
1779
+ class Divider
1780
+ def divide( a, b )
1781
+ a.to_f / b.to_f
1782
+ end
1783
+ end
1784
+ </pre>
1785
+
1786
+
1787
+ Lastly, we'll create our calculator class:
1788
+
1789
+
1790
+ <pre>
1791
+ class Calculator
1792
+ attr_writer :adder
1793
+ attr_writer :subtractor
1794
+ attr_writer :multiplier
1795
+ attr_writer :divider
1796
+
1797
+ def add( a, b )
1798
+ @adder.add( a, b )
1799
+ end
1800
+
1801
+ def subtract( a, b )
1802
+ @subtractor.subtract( a, b )
1803
+ end
1804
+
1805
+ def multiply( a, b )
1806
+ @multiplier.multiply( a, b )
1807
+ end
1808
+
1809
+ def divide( a, b )
1810
+ @divider.divide( a, b )
1811
+ end
1812
+ end
1813
+ </pre>
1814
+
1815
+
1816
+ Notice that we never instantiate any of the delegate classes: we're going to
1817
+ leave that up to Copland. Instead, we simply provide some setters, which
1818
+ Copland will use to set those dependencies. (Note that these setters can also
1819
+ aid in unit testing, since you can easily set each of those properties to
1820
+ some mock object. We won't demonstrate that, though, since unit testing is
1821
+ beyond the scope of this tutorial.)
1822
+
1823
+ - >-
1824
+ h3. Package Descriptor
1825
+
1826
+
1827
+ We now add to our package descriptor. Just as we defined a service point for
1828
+ the Adder class, we now have to add service points for each of the other
1829
+ classes we created.
1830
+
1831
+
1832
+ The Subtractor, Multiplier, and Divider service points will look very much
1833
+ like the Adder service point. Each uses the _simple_ implementor.
1834
+
1835
+
1836
+ <pre>
1837
+ Subtractor:
1838
+ implementor: tutorial/Subtractor
1839
+
1840
+ Multiplier:
1841
+ implementor: tutorial/Multiplier
1842
+
1843
+ Divider:
1844
+ implementor: tutorial/Divider
1845
+ </pre>
1846
+
1847
+
1848
+ The Calculator service point, however, is a bit more complicated. Not only
1849
+ do we need to say what class implements the service, but we also need to
1850
+ say what services get wired into it for each of its properties. To do this,
1851
+ we'll use the @copland.BuilderFactory@ service.
1852
+
1853
+
1854
+ <pre>
1855
+ Calculator:
1856
+ implementor:
1857
+ factory: copland.BuilderFactory
1858
+ class: tutorial/Calculator
1859
+ properties:
1860
+ adder: !!service tutorial.Adder
1861
+ subtractor: !!service tutorial.Subtractor
1862
+ divider: !!service tutorial.Divider
1863
+ multiplier: !!service tutorial.Multiplier
1864
+ </pre>
1865
+
1866
+
1867
+ The @implementor@ section, in this case, is a map, instead of a string.
1868
+ The map specifies the service _factory_ to use (@copland.BuilderFactory@,
1869
+ in this case), the _class_ that will implement the service, and the
1870
+ properties that will be set. (Note that if you are using @copland.BuilderFactory@
1871
+ as your service factory, you can omit the @factory@ element. It is retained
1872
+ explicitly for this tutorial to demonstrate the use of the @factory@ element.)
1873
+
1874
+
1875
+ Note the special syntax of the properties: that @!!service@ bit says that
1876
+ the following name is the name of a service point that should be instantiated
1877
+ and passed as the value of the corresponding property. In other words, when the
1878
+ BuilderFactory instantiates the Calculator class, it will also obtain references
1879
+ to the Adder, Subtractor, Divider, and Multiplier services, and wire them into
1880
+ the appropriate properties of the new Calculator object.
1881
+
1882
+
1883
+ Lastly, note that we used the fully-qualified service names for the dependencies
1884
+ (i.e., "tutorial.Adder"). Because the Calculator service point is in the same
1885
+ package as the dependencies, we could have simply given the names of the service
1886
+ points, unqualified (without the package names).
1887
+
1888
+ - >
1889
+ h3. Putting it all Together
1890
+
1891
+
1892
+ We'll modify the "main.rb" driver file slightly, to test our new Calculator
1893
+ service:
1894
+
1895
+
1896
+ <pre>
1897
+ require 'copland'
1898
+
1899
+ registry = Copland::Registry.build
1900
+
1901
+ calc = registry.service( "tutorial.Calculator" )
1902
+
1903
+ puts calc.add( "5", 7 )
1904
+ puts calc.subtract( "5", 7 )
1905
+ puts calc.multiply( "5", 7 )
1906
+ puts calc.divide( "5", 7 )
1907
+ </pre>
1908
+
1909
+
1910
+ Once run, you should see the program spit out the answers, as you would expect.
1911
+
1912
+
1913
+ summary: >-
1914
+ This tutorial showed you how to use more complicated implementors. In particular, it
1915
+ showed you how to have Copland automatically wire dependencies together, instantiating
1916
+ services as needed. However, you only saw how to use properties to wire services
1917
+ together; you can also specify constructor parameters that will be passed to the new
1918
+ service when it is instantiated.
1919
+
1920
+
1921
+ You also saw how to use the @!!service@ directive, to instruct Copland to interpret a
1922
+ value as a service point name, instead of simply as a string. There are several such
1923
+ directives; see the Copland documentation for a comprehensive list.
1924
+
1925
+ - Service Models:
1926
+ brief: >-
1927
+ Introduces the concept of the "service model", and shows the difference between
1928
+ "singleton" and "prototype".
1929
+
1930
+ intro: >-
1931
+ Let's take our calculator example one step further and add a "memory" function.
1932
+ By storing a value into the memory, you can then call the various operators
1933
+ with only one parameter, and have them operate on the remembered value.
1934
+
1935
+
1936
+ Sound cool? It's also pretty easy! Here goes...
1937
+
1938
+ steps:
1939
+ - >-
1940
+ h3. Add the Memory
1941
+
1942
+
1943
+ First, we need to modify the calculator to hack in support for memory. This
1944
+ involves adding an instance variable and two methods, getters and setters
1945
+ for that variable:
1946
+
1947
+
1948
+ <pre>
1949
+ attr_accessor :memory
1950
+ </pre>
1951
+
1952
+
1953
+ We don't worry about initializing the corresponding instance variable in
1954
+ the code--that's more properly a task for the IoC container. We just edit
1955
+ the "package.yml" file and add another property initializer to the
1956
+ Calculator service:
1957
+
1958
+
1959
+ <pre>
1960
+ Calculator:
1961
+ implementor:
1962
+ factory: copland.BuilderFactory
1963
+ class: tutorial/Calculator
1964
+ properties:
1965
+ adder: !!service Adder
1966
+ subtractor: !!service Subtractor
1967
+ divider: !!service Divider
1968
+ multiplier: !!service Multiplier
1969
+ memory: 0
1970
+ </pre>
1971
+
1972
+
1973
+ This then says then when constructing a Calculator service, always set
1974
+ the memory property to 0.
1975
+
1976
+ - >-
1977
+ h3. Modify the Operators
1978
+
1979
+
1980
+ Next, we need to modify the operators so that they each treat the second
1981
+ parameter as optional, defaulting to our memory variable:
1982
+
1983
+
1984
+ <pre>
1985
+ def add( a, b=memory )
1986
+ @adder.add( a, b )
1987
+ end
1988
+
1989
+ def subtract( a, b=memory )
1990
+ @subtractor.subtract( a, b )
1991
+ end
1992
+
1993
+ def multiply( a, b=memory )
1994
+ @multiplier.multiply( a, b )
1995
+ end
1996
+
1997
+ def divide( a, b=memory )
1998
+ @divider.divide( a, b )
1999
+ end
2000
+ </pre>
2001
+
2002
+ - >-
2003
+ h3. Play
2004
+
2005
+
2006
+ We can now try it by doing multiples and powers of 5. Edit our "main.rb" driver file
2007
+ to look something like this:
2008
+
2009
+
2010
+ <pre>
2011
+ require 'copland'
2012
+
2013
+ registry = Copland::Registry.build
2014
+
2015
+ calc = registry.service( "tutorial.Calculator" )
2016
+
2017
+ puts "Multiples of 5:"
2018
+ 10.times do
2019
+ puts calc.memory
2020
+ calc.memory = calc.add( 5 )
2021
+ end
2022
+
2023
+ calc.memory = 1.0
2024
+
2025
+ puts
2026
+ puts "Powers of 5:"
2027
+ 10.times do
2028
+ puts calc.memory
2029
+ calc.memory = calc.multiply( 5 )
2030
+ end
2031
+ </pre>
2032
+
2033
+
2034
+ When run, it should spit out a list of the multiples of five, and the powers of five!
2035
+ Slick, huh?
2036
+
2037
+
2038
+ But wait! Let's try another trick--let's get TWO calculators going at once and have
2039
+ them each dealing with a different base. Should be pretty straightforward to do;
2040
+ perhaps something like this:
2041
+
2042
+
2043
+ <pre>
2044
+ require 'copland'
2045
+
2046
+ registry = Copland::Registry.build
2047
+
2048
+ calc1 = registry.service( "tutorial.Calculator" )
2049
+ calc2 = registry.service( "tutorial.Calculator" )
2050
+
2051
+ puts "Multiples:"
2052
+ 10.times do
2053
+ puts "#{calc1.memory}\t#{calc2.memory}"
2054
+ calc1.memory = calc1.add( 5 )
2055
+ calc2.memory = calc2.add( 2 )
2056
+ end
2057
+
2058
+ calc1.memory = 1.0
2059
+ calc2.memory = 1.0
2060
+
2061
+ puts
2062
+ puts "Powers:"
2063
+ 10.times do
2064
+ puts "#{calc1.memory}\t#{calc2.memory}"
2065
+ calc1.memory = calc1.multiply( 5 )
2066
+ calc2.memory = calc2.multiply( 2 )
2067
+ end
2068
+ </pre>
2069
+
2070
+
2071
+ We save it, run it, and--_what?!?_ We're getting multiples of 7 and powers of 10,
2072
+ for each column... Huh?!?
2073
+
2074
+ - >-
2075
+ h3. Diagnosing the Problem
2076
+
2077
+
2078
+ What's happening is this: because you haven't specified a _service model_ for the Calculator
2079
+ service point, the default model (@singleton-deferred@) is being used. Both the
2080
+ @singleton@ and @singleton-deferred@ models are alike in that anytime you request
2081
+ an instance of a service point that uses one of those models, you get _the exact same
2082
+ instance back_. Just like "instantiating" a singleton class.
2083
+
2084
+
2085
+ That's right. When we requested two instances of the Calculator service, we were actually
2086
+ getting two references to the _same instance_, instead. Thus, each iteration through
2087
+ the "multiples" loop was adding 5+2=7 to the memory, and each iteration through the
2088
+ "powers" loop was multiply 5*2=10 by the memory.
2089
+
2090
+
2091
+ Huh. Okay, so we know the problem. How do we get the behavior we wanted?
2092
+
2093
+ - >-
2094
+ h3. Fixing the Problem
2095
+
2096
+
2097
+ So we understand _why_ the problem is happening. But how do we fix it?
2098
+
2099
+
2100
+ Well, first let's understand about service models. The service model is what controls
2101
+ _when_ a service point is instantiated. For the singleton models, the service point
2102
+ is only instantiated once--the first time it is requested. What we want is a service
2103
+ model that will instantiate the service point _every_ time we request it.
2104
+
2105
+
2106
+ We're in luck. There is just such a service model: _prototype_. By setting the
2107
+ service model to @prototype@, we'll cause each request for the @calc.Calculator@
2108
+ service to return a new instance of the Calculator class. Just set the model
2109
+ by specifying the @model@ element in your service point descriptor (in the
2110
+ @package.yml@ file):
2111
+
2112
+
2113
+ <pre>
2114
+ Calculator:
2115
+ model: prototype
2116
+ implementor:
2117
+ ...
2118
+ </pre>
2119
+
2120
+
2121
+ Save your file, and run your "main.rb" script again--the output should look much
2122
+ better!
2123
+
2124
+ summary: >-
2125
+ In this tutorial, you learned:
2126
+
2127
+
2128
+ # The default service model for a service is @singleton-deferred@. This model (and it's
2129
+ cousin, the @singleton@ service model) both cause a service point to be instantiated
2130
+ only the first time the service is requested.
2131
+
2132
+ # To get a service point that is instantiated _every_ time it is requested, use the
2133
+ @prototype@ service model.
2134
+
2135
+
2136
+ In general, use the @prototype@ model (or possibly the @threaded@ model) when you have
2137
+ a service that remembers some kind of state. For stateless services, the @singleton@
2138
+ models are appropriate.
2139
+
2140
+
2141
+ Some models are _deferring_ models. This means that the service is not actually constructed
2142
+ until the first time a method is invoked on the new service. The difference is subtle, but
2143
+ important: @singleton@ and @singleton-deferred@ are identical, except with @singleton@
2144
+ the service is constructed as soon as it is requested, whereas with @singleton-deferred@,
2145
+ the service won't actually be constructed until the last possible moment, when you need to
2146
+ invoke a method of the service.
2147
+
2148
+
2149
+ Likewise, there is a @prototype-deferred@ model, to compliment the @prototype@ model.
2150
+
2151
+
2152
+ The only other standard service model is the @threaded@ model, which constructs a new
2153
+ instance of the service for each thread that requests it. In that way, it is like a cross
2154
+ between the @singleton@ and @prototype@ models.
2155
+
2156
+ - Logging Interceptor:
2157
+ brief: >-
2158
+ Shows how to use the logging interceptor to add logging for method invocations
2159
+ on any service.
2160
+
2161
+ intro: >-
2162
+ Sometimes (particularly when debugging) you want to be able to trace your program's
2163
+ execution. You want to see when certain methods are being invoked, and with what parameters,
2164
+ and what the return values are. If an exception is being raised, you'd like to see that,
2165
+ too.
2166
+
2167
+
2168
+ Although the calculator example we've been using up to this point is trivial enough that
2169
+ there's really no need for such tracing, it is an easy one to demonstrate it on. So,
2170
+ let's take the code we wrote for the last tutorial and add logging, just for the halibut.
2171
+
2172
+
2173
+ What we'll do is modify the program so that it logs everytime a method of
2174
+ the Calculator or Adder services is invoked from outside the service.
2175
+
2176
+ steps:
2177
+ - >-
2178
+ h3. copland.LoggingInterceptor
2179
+
2180
+
2181
+ _Interceptors_ are another kind of service, similar to factory services, that Copland
2182
+ uses to _intercept_ method calls. An instance of the interceptor service sits
2183
+ (conceptually) between the caller and the method, and acts as a filter for everything
2184
+ going into or out of the method.
2185
+
2186
+
2187
+ There is currently only one interceptor that ships with Copland--copland.LoggingInterceptor.
2188
+ It sits between the caller and the method and happily logs away, noting the parameters,
2189
+ return values, and exceptions that are raised.
2190
+
2191
+ - >-
2192
+ h3. Adding the Interceptor
2193
+
2194
+
2195
+ Here we get to demonstrate one of (in my opinion) the "coolest" aspects of Copland.
2196
+ We can add the logging solely by editing our package descriptor--_nothing else needs
2197
+ to change!_ Our Ruby objects remain blissfully ignorant that the logging has been
2198
+ added. Slick, huh?
2199
+
2200
+
2201
+ So, let's open our package descriptor and add the interceptor to the Adder service
2202
+ first:
2203
+
2204
+
2205
+ <pre>
2206
+ Adder:
2207
+ implementor: tutorial/Adder
2208
+ interceptors:
2209
+ - service: copland.LoggingInterceptor
2210
+ </pre>
2211
+
2212
+
2213
+ The @interceptors@ element is always a list of interceptors that should be added
2214
+ to the service. (By default, the interceptors will be invoked in the order they
2215
+ were added. You can change this, but it's a bit beyond the scope of this tutorial.)
2216
+ Each element of the list _must_ be a hash, each with the _service_ element (at
2217
+ least) defined. That element must name a valid interceptor service. In this case,
2218
+ the interceptor service is copland.LoggingInterceptor.
2219
+
2220
+
2221
+ Now, let's add the logging interceptor to the Calculator service:
2222
+
2223
+
2224
+ <pre>
2225
+ Calculator:
2226
+ interceptors:
2227
+ - service: copland.LoggingInterceptor
2228
+ ...
2229
+ </pre>
2230
+
2231
+
2232
+ Note that it is not important _where_ the @interceptors@ element appears in the
2233
+ service point--just as long as it appears at the "top level" of the service point
2234
+ definition.
2235
+
2236
+ - >-
2237
+ h3. Running the Program
2238
+
2239
+
2240
+ So, we've got the interceptors in place now. Let's try running the program and
2241
+ seeing what happens!
2242
+
2243
+
2244
+ You should see the exact same output from the last tutorial--the list of multiples
2245
+ and powers. However, there should be a file named "copland.log" in the same
2246
+ directory. This file has been appearing every time you've run your programs during
2247
+ these tutorials, but it has been largely empty because nothing has been logged. This
2248
+ time, however, there will be quite a bit of text in the log, looking something
2249
+ like this:
2250
+
2251
+
2252
+ <pre>
2253
+ # Logfile created on Sat Sep 04 09:29:58 MDT 2004 by logger.rb/1.5.2.4
2254
+ D, [2004-09-04T09:29:58.963327 #23288] DEBUG -- tutorial.Calculator: memory( )
2255
+ D, [2004-09-04T09:29:58.963471 #23288] DEBUG -- tutorial.Calculator: memory => 0
2256
+ D, [2004-09-04T09:29:58.963522 #23288] DEBUG -- tutorial.Calculator: memory( )
2257
+ D, [2004-09-04T09:29:58.963564 #23288] DEBUG -- tutorial.Calculator: memory => 0
2258
+ D, [2004-09-04T09:29:58.963651 #23288] DEBUG -- tutorial.Calculator: add( 5 )
2259
+ D, [2004-09-04T09:29:58.999262 #23288] DEBUG -- tutorial.Adder: add( 5, 0 )
2260
+ D, [2004-09-04T09:29:58.999486 #23288] DEBUG -- tutorial.Adder: add => 5.0
2261
+ D, [2004-09-04T09:29:58.999547 #23288] DEBUG -- tutorial.Calculator: add => 5.0
2262
+ D, [2004-09-04T09:29:58.999617 #23288] DEBUG -- tutorial.Calculator: memory=( 5.0 )
2263
+ D, [2004-09-04T09:29:58.999663 #23288] DEBUG -- tutorial.Calculator: memory= => 5.0
2264
+ ...
2265
+ </pre>
2266
+
2267
+
2268
+ Let's analyze the output a bit. First, it's saying that the @#memory@ method
2269
+ was invoked, with no parameters, and that it returned 0. Then, it was invoked
2270
+ again, and returned 0 again. (This is because we've got two calculators going
2271
+ at once, both of them logging their method invocations.) Next, @#add@ was
2272
+ invoked, with a parameter of 5. Then, the Adder's @#add@ is invoked, with two
2273
+ parameters (5, and 0). That method then returns a value of 5.0, followed by the
2274
+ Calculator's @#add@ method returning 5.0. Then, the @#memory=@ method is invoked,
2275
+ with a value of 5.0, which it also returns.
2276
+
2277
+
2278
+ That's a bit more verbose than we intended--for example, we didn't really care to
2279
+ see all the @memory@ method invocations. We'd like to be able to _exclude_ those
2280
+ methods from the logger.
2281
+
2282
+ - >-
2283
+ h3. Excluding Methods
2284
+
2285
+
2286
+ The LoggingInterceptor supports (among others) two parameters, @exclude@ and
2287
+ @include@. Each of these elements is an array of special regular expressions
2288
+ that should match method names.
2289
+
2290
+
2291
+ By default, all methods are included. If you specify a set of @exclude@ patterns,
2292
+ any method that matches any one of those patterns will be excluded from
2293
+ consideration. If you further specify a set of @include@ patterns, then any
2294
+ method that has been excluded from consideration, and which matches any one of
2295
+ the @include@ patterns, will be reincluded for consideration.
2296
+
2297
+
2298
+ So, let's exclude the @#memory@ and @#memory=@ methods from the Calculator's
2299
+ logging interceptor. We can do it by name each method explicitly, like this:
2300
+
2301
+
2302
+ <pre>
2303
+ Calculator:
2304
+ interceptors:
2305
+ - service: copland.LoggingInterceptor
2306
+ exclude:
2307
+ - memory
2308
+ - memory=
2309
+ ...
2310
+ </pre>
2311
+
2312
+
2313
+ Or, we can be a little more concise and use regular expressions:
2314
+
2315
+ <pre>
2316
+ Calculator:
2317
+ interceptors:
2318
+ - service: copland.LoggingInterceptor
2319
+ exclude:
2320
+ - memory=?
2321
+ ...
2322
+ </pre>
2323
+
2324
+
2325
+ Delete the original copland.log (otherwise subsequent runs will just append to
2326
+ it) and then rerun your script. None of the @memory@ methods are being logged!
2327
+ Much better.
2328
+
2329
+ summary: >-
2330
+ In this tutorial you learned how to apply a logging interceptor to a service.
2331
+ You also learned how to exclude certain methods from being logged.
2332
+
2333
+
2334
+ Note that you can easily specify that only certain methods should be logged (instead
2335
+ of the reverse, where only certain methods should _not_ be logged). To
2336
+ do this, first exclude all methods (with a ".*" regular expression), and then
2337
+ explicitly include the methods you want to log.
2338
+
2339
+
2340
+ Also, it should be noted that the regular expressions for the include and exclude
2341
+ patterns are special: you can also specify the _arity_ of the methods that should
2342
+ be included or excluded. For example, if you want to exclude all methods that are
2343
+ invoked with 3 parameters, you could do:
2344
+
2345
+
2346
+ <pre>
2347
+ exclude:
2348
+ - .*(3)
2349
+ </pre>
2350
+
2351
+
2352
+ If you prefix the arity number with a comparison operator (less-than or greater-than),
2353
+ you can exclude (or include) methods that are invoked with less than or greater than that
2354
+ number of parameters:
2355
+
2356
+
2357
+ <pre>
2358
+ exclude:
2359
+ - .*(>3)
2360
+ include:
2361
+ - .*(5)
2362
+ </pre>
2363
+
2364
+
2365
+ The above would exclude any method invoked with more than three parameters, unless it
2366
+ was invoked with exactly five parameters.
2367
+
2368
+ - Configuration Points:
2369
+ brief: >-
2370
+ Demonstrates the use of configuration points for decentralizing service configuration.
2371
+
2372
+ intro: >-
2373
+ Suppose, next, we want to add the ability to register new functions with the calculator,
2374
+ so that third parties that wish to reuse our snappy little number cruncher can add their
2375
+ own custom operations.
2376
+
2377
+
2378
+ Think for a minute how you would solve this. There are several approaches that could
2379
+ be taken, none of them necessarily any better or worse than the other. The drawback
2380
+ for each of them is that _you would have to implement the infrastructure yourself_.
2381
+
2382
+
2383
+ Copland provides a ready-made package-centric configuration infrastructure, which
2384
+ (as you will see) allows any package to contribute configuration data to
2385
+ configuration points in any other package.
2386
+
2387
+ steps:
2388
+ - >-
2389
+ h3. Modify Calculator
2390
+
2391
+
2392
+ First, let's modify our Calculator class again. We'll add _another_ writer
2393
+ attribute, called @functions@, which we'll assume will always be a Hash (or
2394
+ Hash-like) object. Each pair in the hash will be a named object that implements
2395
+ the @:compute@ message.
2396
+
2397
+
2398
+ <pre>
2399
+ class Calculator
2400
+ attr_writer :adder
2401
+ attr_writer :subtractor
2402
+ ...
2403
+ attr_writer :functions
2404
+ </pre>
2405
+
2406
+
2407
+ Then, we'll implement a new method on Calculator, called @function@, which allows
2408
+ clients to specify a function to execute and the arguments to give it. We won't
2409
+ bother with error checking:
2410
+
2411
+
2412
+ <pre>
2413
+ def function( name, *arguments )
2414
+ @functions[ name ].compute( *arguments )
2415
+ end
2416
+ </pre>
2417
+
2418
+ - >-
2419
+ h3. Create a Configuration Point
2420
+
2421
+
2422
+ Next, we'll create a configuration point. A configuration point may be either
2423
+ a _list_, or a _map_. We want a map, so that it will work nicely as a lookup for
2424
+ function services.
2425
+
2426
+
2427
+ Configuration points are defined in their own section of the package descriptor,
2428
+ under the @configuration-points@ key. Thus:
2429
+
2430
+
2431
+ <pre>
2432
+ id: tutorial
2433
+
2434
+ service-points:
2435
+ ...
2436
+
2437
+ configuration-points:
2438
+
2439
+ CalculatorFunctions:
2440
+ type: map
2441
+ </pre>
2442
+
2443
+
2444
+ This creates a new (and empty) configuration point, called CalculatorFunctions.
2445
+ It is of type "map", which means it quacks like a Hash. Since it belongs to the
2446
+ tutorial package, its fully qualified name is "tutorial.CalculatorFunctions".
2447
+
2448
+
2449
+ For now, we'll leave it empty. We'll contribute values to it in a little bit.
2450
+
2451
+ - >-
2452
+ h3. Edit the Calculator Service Point
2453
+
2454
+
2455
+ Now, we edit the service point that defines our Calculator service. Specifically,
2456
+ we add another property initializer:
2457
+
2458
+
2459
+ <pre>
2460
+ Calculator:
2461
+ model: prototype
2462
+ implementor:
2463
+ factory: copland.BuilderFactory
2464
+ class: tutorial/Calculator
2465
+ properties:
2466
+ adder: !!service Adder
2467
+ ...
2468
+ functions: !!configuration CalculatorFunctions
2469
+ </pre>
2470
+
2471
+
2472
+ Here, we're telling Copland to associate the CalculatorFunctions configuration point
2473
+ with the @functions@ property of our Calculator.
2474
+
2475
+
2476
+ This means that anything any package adds to that configuration point is going to
2477
+ be visible to our Calculator, via its @functions@ property. The only part we're
2478
+ missing, then, is what goes into that configuration point.
2479
+
2480
+
2481
+ - >-
2482
+ h3. Prepare to Create the @tutorial.functions@ Package
2483
+
2484
+
2485
+ Just to keep things nice and neat, we'll create another package in which we'll
2486
+ define our "custom" calculator functions.
2487
+
2488
+
2489
+ Create a new subdirectory in the same directory as your calculator implementation.
2490
+ Call it @functions@. Then change to that directory.
2491
+
2492
+
2493
+ <pre>
2494
+ $ mkdir functions
2495
+ $ cd functions
2496
+ </pre>
2497
+
2498
+ - >-
2499
+ h3. Implement Some Functions
2500
+
2501
+
2502
+ We need to decide which custom functions to implement. I'll arbitrarily recommend
2503
+ the trigonometric "sin" function, and the natural logarithm, mostly because they're
2504
+ fun to say, but also because they're two of many available functions that Ruby
2505
+ already provides us implementations for.
2506
+
2507
+
2508
+ So, let's create a "services.rb" file in our new "functions" subdirectory. We'll
2509
+ implement two classes, one for each new function. Each class only has to respond
2510
+ to the "compute" message, accepting the expected parameters and returning the
2511
+ computed result. Thus:
2512
+
2513
+
2514
+ <pre>
2515
+ class Sine
2516
+
2517
+ def compute( a )
2518
+ Math.sin( a )
2519
+ end
2520
+
2521
+ end
2522
+
2523
+ class NaturalLogarithm
2524
+
2525
+ def compute( n )
2526
+ Math.log( n )
2527
+ end
2528
+
2529
+ end
2530
+ </pre>
2531
+
2532
+ - >-
2533
+ h3. Define the Service Points
2534
+
2535
+
2536
+ Now, we create the package descriptor for our new package. Create a new file called
2537
+ "package.yml" in the "functions" subdirectory:
2538
+
2539
+
2540
+ <pre>
2541
+ ---
2542
+ id: tutorial.functions
2543
+
2544
+ service-points:
2545
+
2546
+ Sine:
2547
+ implementor: functions/services/Sine
2548
+
2549
+ NaturalLogarithm:
2550
+ implementor: functions/services/NaturalLogarithm
2551
+ </pre>
2552
+
2553
+
2554
+ Note the name of the package: @tutorial.functions@. This is the recommended way of
2555
+ naming your packages, with names of subpackages including the names of their
2556
+ ancestor packages. However, you are in no way constrained to name them this way--Copland
2557
+ does not enforce it.
2558
+
2559
+ - >-
2560
+ h3. Contribute the Services to the Configuration Point
2561
+
2562
+
2563
+ Next, we tie it all together. Our @tutorial.functions@ package needs to contribute
2564
+ the two services it defines to the @tutorial.CalculatorFunctions@ configuration point.
2565
+ This way, when the Calculator service is instantiated, it will come ready-made with
2566
+ a map of all functions that other packages want to be made available.
2567
+
2568
+
2569
+ Contributes are made in the @contributions@ section of the package descriptor. Just
2570
+ specify the name of the configuration point to contribute to, and then the values
2571
+ you want to contribute. For configuration points of type list, the values should be
2572
+ formatted as a list. For maps, the values should be formatted as a map.
2573
+
2574
+
2575
+ <pre>
2576
+ id: tutorial.functions
2577
+
2578
+ service-points:
2579
+ ...
2580
+
2581
+ contributions:
2582
+
2583
+ tutorial.CalculatorFunctions:
2584
+ sin: !!service Sine
2585
+ ln: !!service NaturalLogarithm
2586
+ </pre>
2587
+
2588
+
2589
+ Here we've registered the Sine service under the name "sin", and the NaturalLogarithm
2590
+ service under the name "ln".
2591
+
2592
+ - >-
2593
+ h3. Put it Together
2594
+
2595
+
2596
+ Now, in our "main.rb" driver file, we just need to call our new @function@ interface
2597
+ and see if it really works:
2598
+
2599
+
2600
+ <pre>
2601
+ require 'copland'
2602
+
2603
+ registry = Copland::Registry.build
2604
+
2605
+ calc = registry.service( "tutorial.Calculator" )
2606
+
2607
+ puts "sin(pi/4) = #{calc.function( "sin", Math::PI/4 )}"
2608
+ puts "ln(e^3) = #{calc.function( "ln", Math::E ** 3 )}"
2609
+
2610
+ registry.shutdown
2611
+ </pre>
2612
+
2613
+
2614
+ If all was done correctly, you should see the following output:
2615
+
2616
+
2617
+ <pre>
2618
+ $ ruby main.rb
2619
+ sin(pi/4) = 0.707106781186547
2620
+ ln(e^3) = 3.0
2621
+ </pre>
2622
+
2623
+ summary: >-
2624
+ The following techniques were demonstrated in this tutorial:
2625
+
2626
+
2627
+ # Applications with multiple Copland packages
2628
+
2629
+ # Creating a configuration point
2630
+
2631
+ # Contributing to a configuration point
2632
+
2633
+ # Associating a configuration point with a property of a service
2634
+
2635
+
2636
+ Additionally, you saw that subdirectories would (by default) be recursively
2637
+ searched for package descriptors.
2638
+
2639
+
2640
+ Play with this tutorial some more. Try adding some more functions. Try adding
2641
+ some more packages that contribute to the @tutorial.CalculatorFunctions@
2642
+ configuration point. The more you use these features, the better you'll understand
2643
+ them.