copland 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- data/doc/README +88 -0
- data/doc/manual-html/chapter-1.html +454 -0
- data/doc/manual-html/chapter-10.html +399 -0
- data/doc/manual-html/chapter-11.html +600 -0
- data/doc/manual-html/chapter-12.html +406 -0
- data/doc/manual-html/chapter-2.html +382 -0
- data/doc/manual-html/chapter-3.html +424 -0
- data/doc/manual-html/chapter-4.html +432 -0
- data/doc/manual-html/chapter-5.html +381 -0
- data/doc/manual-html/chapter-6.html +364 -0
- data/doc/manual-html/chapter-7.html +434 -0
- data/doc/manual-html/chapter-8.html +373 -0
- data/doc/manual-html/chapter-9.html +324 -0
- data/doc/manual-html/copland.png +0 -0
- data/doc/manual-html/index.html +331 -0
- data/doc/manual-html/manual.css +179 -0
- data/doc/manual-html/tutorial-1.html +407 -0
- data/doc/manual-html/tutorial-2.html +451 -0
- data/doc/manual-html/tutorial-3.html +484 -0
- data/doc/manual-html/tutorial-4.html +446 -0
- data/doc/manual-html/tutorial-5.html +520 -0
- data/doc/manual/chapter.erb +18 -0
- data/doc/manual/example.erb +18 -0
- data/doc/manual/img/copland.png +0 -0
- data/doc/manual/index.erb +30 -0
- data/doc/manual/manual.css +179 -0
- data/doc/manual/manual.rb +239 -0
- data/doc/manual/manual.yml +2643 -0
- data/doc/manual/page.erb +102 -0
- data/doc/manual/tutorial.erb +30 -0
- data/doc/packages/copland.html +764 -0
- data/doc/packages/copland.lib.html +439 -0
- data/doc/packages/copland.remote.html +2096 -0
- data/doc/packages/copland.webrick.html +925 -0
- data/doc/packages/index.html +49 -0
- data/doc/packages/packrat.css +125 -0
- data/examples/calc/calc.rb +47 -0
- data/examples/calc/package.yml +35 -0
- data/examples/calc/services.rb +74 -0
- data/examples/solitaire-cipher/README +11 -0
- data/examples/solitaire-cipher/Rakefile +57 -0
- data/examples/solitaire-cipher/bin/main.rb +14 -0
- data/examples/solitaire-cipher/lib/cipher.rb +230 -0
- data/examples/solitaire-cipher/lib/cli.rb +24 -0
- data/examples/solitaire-cipher/lib/package.yml +106 -0
- data/examples/solitaire-cipher/test/tc_deck.rb +30 -0
- data/examples/solitaire-cipher/test/tc_key-stream.rb +19 -0
- data/examples/solitaire-cipher/test/tc_keying-algorithms.rb +31 -0
- data/examples/solitaire-cipher/test/tc_solitaire-cipher.rb +66 -0
- data/examples/solitaire-cipher/test/tc_unkeyed-algorithm.rb +17 -0
- data/examples/solitaire-cipher/test/tests.rb +2 -0
- data/lib/copland.rb +56 -0
- data/lib/copland/class-factory.rb +95 -0
- data/lib/copland/configuration-point.rb +38 -0
- data/lib/copland/configuration-point/common.rb +203 -0
- data/lib/copland/configuration-point/errors.rb +44 -0
- data/lib/copland/configuration-point/list.rb +59 -0
- data/lib/copland/configuration-point/map.rb +59 -0
- data/lib/copland/configuration/errors.rb +43 -0
- data/lib/copland/configuration/loader.rb +113 -0
- data/lib/copland/configuration/yaml/configuration-point.rb +87 -0
- data/lib/copland/configuration/yaml/implementor.rb +134 -0
- data/lib/copland/configuration/yaml/interceptor.rb +63 -0
- data/lib/copland/configuration/yaml/listener.rb +56 -0
- data/lib/copland/configuration/yaml/loader.rb +122 -0
- data/lib/copland/configuration/yaml/package.rb +125 -0
- data/lib/copland/configuration/yaml/parser.rb +71 -0
- data/lib/copland/configuration/yaml/schema.rb +165 -0
- data/lib/copland/configuration/yaml/service-point.rb +116 -0
- data/lib/copland/configuration/yaml/utils.rb +82 -0
- data/lib/copland/default-schema-processor.rb +144 -0
- data/lib/copland/errors.rb +82 -0
- data/lib/copland/event-producer.rb +95 -0
- data/lib/copland/impl/builder-factory.rb +112 -0
- data/lib/copland/impl/copland-config.yml +1 -0
- data/lib/copland/impl/include-exclude.rb +140 -0
- data/lib/copland/impl/logging-interceptor.rb +106 -0
- data/lib/copland/impl/package.yml +217 -0
- data/lib/copland/impl/startup.rb +116 -0
- data/lib/copland/impl/symbol-source-manager.rb +131 -0
- data/lib/copland/impl/symbol-source.rb +63 -0
- data/lib/copland/instantiator.rb +38 -0
- data/lib/copland/instantiator/abstract.rb +91 -0
- data/lib/copland/instantiator/complex.rb +96 -0
- data/lib/copland/instantiator/identity.rb +58 -0
- data/lib/copland/instantiator/simple.rb +68 -0
- data/lib/copland/interceptor-chain.rb +166 -0
- data/lib/copland/interceptor.rb +139 -0
- data/lib/copland/log-factory.rb +206 -0
- data/lib/copland/models.rb +39 -0
- data/lib/copland/models/abstract.rb +78 -0
- data/lib/copland/models/prototype-deferred.rb +58 -0
- data/lib/copland/models/prototype.rb +58 -0
- data/lib/copland/models/proxy.rb +100 -0
- data/lib/copland/models/singleton-deferred.rb +59 -0
- data/lib/copland/models/singleton.rb +77 -0
- data/lib/copland/models/threaded.rb +65 -0
- data/lib/copland/ordering.rb +123 -0
- data/lib/copland/package.rb +246 -0
- data/lib/copland/registry.rb +368 -0
- data/lib/copland/schema.rb +206 -0
- data/lib/copland/service-point.rb +282 -0
- data/lib/copland/utils.rb +221 -0
- data/lib/copland/version.rb +47 -0
- data/test/conf-test/list-bad-key.yml +30 -0
- data/test/conf-test/list-bad-missing.yml +28 -0
- data/test/conf-test/list-bad-type.yml +28 -0
- data/test/conf-test/list-good.yml +29 -0
- data/test/conf-test/map-bad-key.yml +25 -0
- data/test/conf-test/map-bad-missing.yml +24 -0
- data/test/conf-test/map-bad-type.yml +23 -0
- data/test/conf-test/map-good.yml +25 -0
- data/test/configuration-point/package.yml +52 -0
- data/test/configuration/yaml/config/copland-config.yml +2 -0
- data/test/configuration/yaml/config/module.yml +2 -0
- data/test/configuration/yaml/config/subdir/copland-config.yml +2 -0
- data/test/configuration/yaml/config/subdir/package.yml +4 -0
- data/test/configuration/yaml/defaults/package.yml +5 -0
- data/test/configuration/yaml/defaults/subdir/package.yml +4 -0
- data/test/configuration/yaml/tc_config-loader.rb +86 -0
- data/test/configuration/yaml/tc_configuration-point-processor.rb +134 -0
- data/test/configuration/yaml/tc_implementor-processor.rb +104 -0
- data/test/configuration/yaml/tc_interceptor-processor.rb +85 -0
- data/test/configuration/yaml/tc_listener-processor.rb +69 -0
- data/test/configuration/yaml/tc_loader.rb +74 -0
- data/test/configuration/yaml/tc_package-processor.rb +120 -0
- data/test/configuration/yaml/tc_parser.rb +94 -0
- data/test/configuration/yaml/tc_schema-parser.rb +160 -0
- data/test/configuration/yaml/tc_service-point-processor.rb +104 -0
- data/test/configuration/yaml/tc_type-validator.rb +90 -0
- data/test/custom-logger.yml +3 -0
- data/test/impl/logging/package.yml +44 -0
- data/test/impl/logging/services.rb +84 -0
- data/test/impl/startup/package.yml +46 -0
- data/test/impl/startup/services.rb +47 -0
- data/test/impl/symbols/package.yml +24 -0
- data/test/impl/symbols/services.rb +38 -0
- data/test/impl/tc_builder-factory.rb +173 -0
- data/test/impl/tc_logging-interceptor.rb +148 -0
- data/test/impl/tc_startup.rb +59 -0
- data/test/impl/tc_symbol-sources.rb +61 -0
- data/test/logger.yml +6 -0
- data/test/mock.rb +201 -0
- data/test/schema/bad-package.yml +65 -0
- data/test/schema/package.yml +102 -0
- data/test/schema/services.rb +5 -0
- data/test/services/package.yml +79 -0
- data/test/services/simple.rb +87 -0
- data/test/tc_class-factory.rb +93 -0
- data/test/tc_complex-instantiator.rb +107 -0
- data/test/tc_configuration-point-contrib.rb +74 -0
- data/test/tc_configuration-point-schema.rb +122 -0
- data/test/tc_configuration-point.rb +91 -0
- data/test/tc_default-schema-processor.rb +297 -0
- data/test/tc_identity-instantiator.rb +61 -0
- data/test/tc_interceptors.rb +84 -0
- data/test/tc_logger.rb +131 -0
- data/test/tc_models.rb +176 -0
- data/test/tc_package.rb +165 -0
- data/test/tc_proxy.rb +65 -0
- data/test/tc_registry.rb +141 -0
- data/test/tc_schema.rb +78 -0
- data/test/tc_service-point.rb +178 -0
- data/test/tc_service.rb +70 -0
- data/test/tc_simple-instantiator.rb +61 -0
- data/test/tests.rb +93 -0
- data/tutorial/01/main.rb +7 -0
- data/tutorial/01/package.yml +8 -0
- data/tutorial/01/tutorial.rb +7 -0
- data/tutorial/02/main.rb +10 -0
- data/tutorial/02/package.yml +27 -0
- data/tutorial/02/tutorial.rb +46 -0
- data/tutorial/03/main.rb +24 -0
- data/tutorial/03/package.yml +29 -0
- data/tutorial/03/tutorial.rb +48 -0
- data/tutorial/04/main.rb +24 -0
- data/tutorial/04/package.yml +35 -0
- data/tutorial/04/tutorial.rb +48 -0
- data/tutorial/05/functions/package.yml +16 -0
- data/tutorial/05/functions/services.rb +15 -0
- data/tutorial/05/main.rb +10 -0
- data/tutorial/05/package.yml +35 -0
- data/tutorial/05/tutorial.rb +53 -0
- 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.
|