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