copland 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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.