needle 1.2.0 → 1.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/doc/manual-html/chapter-1.html +138 -78
- data/doc/manual-html/chapter-2.html +180 -99
- data/doc/manual-html/chapter-3.html +111 -75
- data/doc/manual-html/chapter-4.html +80 -48
- data/doc/manual-html/chapter-5.html +106 -56
- data/doc/manual-html/chapter-6.html +82 -34
- data/doc/manual-html/chapter-7.html +74 -38
- data/doc/manual-html/chapter-8.html +70 -41
- data/doc/manual-html/chapter-9.html +88 -63
- data/doc/manual-html/index.html +6 -6
- data/doc/manual-html/needle.png +0 -0
- data/doc/manual-html/{manual.css → stylesheets/manual.css} +83 -10
- data/doc/manual-html/stylesheets/ruby.css +17 -0
- data/doc/manual/chapter.erb +20 -0
- data/doc/manual/img/Needle.ai +0 -0
- data/doc/manual/img/needle.png +0 -0
- data/doc/manual/manual.rb +80 -5
- data/doc/manual/manual.yml +3 -3
- data/doc/manual/page.erb +1 -1
- data/doc/manual/parts/01_use_cases.txt +70 -70
- data/doc/manual/parts/02_creating.txt +19 -19
- data/doc/manual/parts/02_namespaces.txt +29 -29
- data/doc/manual/parts/02_services.txt +40 -41
- data/doc/manual/parts/03_conventional.txt +20 -20
- data/doc/manual/parts/03_locator.txt +44 -44
- data/doc/manual/parts/04_overview.txt +1 -1
- data/doc/manual/parts/04_setup.txt +32 -32
- data/doc/manual/parts/customizing_contexts.txt +14 -14
- data/doc/manual/parts/customizing_interceptors.txt +25 -25
- data/doc/manual/parts/customizing_namespaces.txt +12 -12
- data/doc/manual/parts/interceptors_attaching.txt +29 -30
- data/doc/manual/parts/interceptors_custom.txt +16 -16
- data/doc/manual/parts/interceptors_ordering.txt +5 -5
- data/doc/manual/parts/libraries_creating.txt +18 -18
- data/doc/manual/parts/libraries_using.txt +19 -19
- data/doc/manual/parts/logging_configuration.txt +13 -13
- data/doc/manual/parts/logging_logfactory.txt +21 -22
- data/doc/manual/parts/models_models.txt +8 -8
- data/doc/manual/parts/models_overview.txt +1 -1
- data/doc/manual/parts/models_pipelines.txt +22 -22
- data/doc/manual/{manual.css → stylesheets/manual.css} +83 -10
- data/doc/manual/stylesheets/ruby.css +17 -0
- data/lib/needle/definition-context.rb +3 -2
- data/lib/needle/lifecycle/proxy.rb +1 -1
- data/lib/needle/version.rb +1 -1
- metadata +94 -85
@@ -1,29 +1,29 @@
|
|
1
1
|
A conventional architecture will have each component instantiate its own dependencies. For example, the @Application@ would do something like this:
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
end
|
3
|
+
{{{lang=ruby,number=true,caption=A conventional application implementation
|
4
|
+
class Application
|
5
|
+
def initialize
|
6
|
+
@logger = Logger.new
|
7
|
+
@authenticator = Authenticator.new
|
8
|
+
@database = Database.new
|
9
|
+
@view = View.new
|
10
|
+
@session = Session.new
|
12
11
|
end
|
13
|
-
|
12
|
+
end
|
13
|
+
}}}
|
14
14
|
|
15
15
|
However, the above is already flawed, because the @Authenticator@ and the @Session@ both need access to the @Database@, so you really need to make sure you instantiate things in the right order and pass them as parameters to the constructor of each object that needs them, like so:
|
16
16
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
end
|
17
|
+
{{{lang=ruby,number=true,caption=A parameterized application implementation
|
18
|
+
class Application
|
19
|
+
def initialize
|
20
|
+
@view = View.new
|
21
|
+
@logger = Logger.new
|
22
|
+
@database = Database.new( @logger )
|
23
|
+
@authenticator = Authenticator.new( @logger, @database )
|
24
|
+
@session = Session.new( @logger, @database )
|
26
25
|
end
|
27
|
-
|
26
|
+
end
|
27
|
+
}}}
|
28
28
|
|
29
29
|
The problem with this is that if you later decide that @View@ needs to access the database, you need to rearrange the order of how things are instantiated in the @Application@ constructor.
|
@@ -1,40 +1,40 @@
|
|
1
1
|
The _service locator_ pattern makes things a _little_ easier. Instead of instantiating everything in the constructor of the @Application@, you can create a factory method somewhere that returns the new @Application@ instance. Then, inside of this factory method, you assign each new object to collection, and pass that collection to each constructor.
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
3
|
+
{{{lang=ruby,number=true,caption=Service locator example
|
4
|
+
require 'needle'
|
5
|
+
|
6
|
+
def create_application
|
7
|
+
locator = Needle::Registry.new
|
8
|
+
|
9
|
+
locator.register( :view ) { View.new(locator) }
|
10
|
+
locator.register( :logger ) { Logger.new(locator) }
|
11
|
+
locator.register( :database ) { Database.new(locator) }
|
12
|
+
locator.register( :authenticator ) {Authenticator.new(locator) }
|
13
|
+
locator.register( :session ) { Session.new(locator) }
|
14
|
+
locator.register( :app ) { Application.new(locator) }
|
15
|
+
|
16
|
+
locator[:app]
|
17
|
+
end
|
18
|
+
|
19
|
+
class Application
|
20
|
+
def initialize( locator )
|
21
|
+
@view = locator[:view]
|
22
|
+
@logger = locator[:logger]
|
23
|
+
@database = locator[:database]
|
24
|
+
@authenticator = locator[:authenticator]
|
25
|
+
@session = locator[:session]
|
17
26
|
end
|
27
|
+
end
|
18
28
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
@database = locator[:database]
|
24
|
-
@authenticator = locator[:authenticator]
|
25
|
-
@session = locator[:session]
|
26
|
-
end
|
29
|
+
class Session
|
30
|
+
def initialize( locator )
|
31
|
+
@database = locator[:database]
|
32
|
+
@logger = locator[:logger]
|
27
33
|
end
|
34
|
+
end
|
28
35
|
|
29
|
-
|
30
|
-
|
31
|
-
@database = locator[:database]
|
32
|
-
@logger = locator[:logger]
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
...
|
37
|
-
</pre>
|
36
|
+
...
|
37
|
+
}}}
|
38
38
|
|
39
39
|
This has the benefit of allowing each object to construct itself _� la carte_ from the objects in the locator. Also, each object no longer cares what class implements each service--it only cares that each object implements the methods it will attempt to invoke on that object.
|
40
40
|
|
@@ -44,17 +44,17 @@ Thus, when we get the @:app@ service (on the last line), the @Application@ const
|
|
44
44
|
|
45
45
|
In the interest of brevity, the @create_application@ could have been written like this, using a "builder" object (called @b@ in the example below) to help register the services:
|
46
46
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
end
|
57
|
-
|
58
|
-
locator[:app]
|
47
|
+
{{{lang=ruby,number=true,caption=Service locator example using #define
|
48
|
+
def create_application
|
49
|
+
locator = Needle::Registry.define do |b|
|
50
|
+
b.view { View.new(locator) }
|
51
|
+
b.logger { Logger.new(locator) }
|
52
|
+
b.database { Database.new(locator) }
|
53
|
+
b.authenticator {Authenticator.new(locator) }
|
54
|
+
b.session { Session.new(locator) }
|
55
|
+
b.app { Application.new(locator) }
|
59
56
|
end
|
60
|
-
|
57
|
+
|
58
|
+
locator[:app]
|
59
|
+
end
|
60
|
+
}}}
|
@@ -6,4 +6,4 @@ The service locator works well when there are few dependencies, and the dependen
|
|
6
6
|
|
7
7
|
# For deep dependency graphs, it can become cumbersome to have to pass the locator to each constructor.
|
8
8
|
|
9
|
-
This is where _dependency injection_ comes in. It allows you to define how each service is initialized, including setting dependencies (either via constructor parameters or via property accessors). In fact, it can do a lot more than that, even allowing you to specify how the
|
9
|
+
This is where _dependency injection_ comes in. It allows you to define how each service is initialized, including setting dependencies (either via constructor parameters or via property accessors). In fact, it can do a lot more than that, even allowing you to specify how the lifestyle of the service should be managed and hooking "interceptors" onto the service to filter method invocations.
|
@@ -1,43 +1,43 @@
|
|
1
1
|
Setting up for DI is very similar to the setup for a service locator, but instead of passing the locator (we'll call it a _registry_ now), we only pass (or set) the dependencies that the service itself needs.
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
end
|
3
|
+
{{{lang=ruby,number=true,caption=Dependency injection example
|
4
|
+
require 'needle'
|
5
|
+
|
6
|
+
def create_application
|
7
|
+
registry = Needle::Registry.define do |b|
|
8
|
+
b.view { View.new }
|
9
|
+
b.logger { Logger.new }
|
10
|
+
b.database { Database.new( b.logger ) }
|
11
|
+
b.authenticator { Authenticator.new(b.logger, b.database) }
|
12
|
+
b.session { Session.new(b.logger, b.database) }
|
13
|
+
|
14
|
+
b.app do
|
15
|
+
app = Application.new
|
16
|
+
app.logger = b.logger
|
17
|
+
app.view = b.view
|
18
|
+
app.database = b.database
|
19
|
+
app.authenticator = b.authenticator
|
20
|
+
app.session = b.session
|
21
|
+
app
|
23
22
|
end
|
24
|
-
|
25
|
-
registry[:app]
|
26
23
|
end
|
27
24
|
|
28
|
-
|
29
|
-
|
30
|
-
end
|
25
|
+
registry[:app]
|
26
|
+
end
|
31
27
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
28
|
+
class Application
|
29
|
+
attr_writer :view, :logger, :database, :authenticator, :session
|
30
|
+
end
|
31
|
+
|
32
|
+
class Session
|
33
|
+
def initialize( logger, database )
|
34
|
+
@database = database
|
35
|
+
@logger = logger
|
37
36
|
end
|
37
|
+
end
|
38
38
|
|
39
|
-
|
40
|
-
|
39
|
+
...
|
40
|
+
}}}
|
41
41
|
|
42
42
|
The @create_application@ method is now (necessarily) a little more complex, since it now contains all of the initialization logic for each service in the application. However, look how much simpler this made the other classes, especially the @Application@ class.
|
43
43
|
|
@@ -4,21 +4,21 @@ The default implementation used for definition contexts is defined by the @:defi
|
|
4
4
|
|
5
5
|
Consider the following contrived example, where you want to provide a convenient way to register services of type Hash.
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
end
|
7
|
+
{{{lang=ruby,number=true,caption=Custom DefinitionContext example
|
8
|
+
class MyDefinitionContext < Needle::DefinitionContext
|
9
|
+
def register_hash( name, opts={} )
|
10
|
+
this_container.register( name, opts ) { Hash.new }
|
12
11
|
end
|
12
|
+
end
|
13
13
|
|
14
|
-
|
15
|
-
|
14
|
+
reg = Needle::Registry.new
|
15
|
+
reg.register( :definition_context_factory ) { MyDefinitionContext }
|
16
16
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
17
|
+
reg.define do |b|
|
18
|
+
b.register_hash( :test1 )
|
19
|
+
b.register_hash( :test2 )
|
20
|
+
end
|
21
21
|
|
22
|
-
|
23
|
-
|
24
|
-
|
22
|
+
reg.test1[:key] = "value"
|
23
|
+
reg.test2[:foo] = "bar"
|
24
|
+
}}}
|
@@ -2,37 +2,37 @@ When you attach an interceptor to a service, that new interceptor is wrapped in
|
|
2
2
|
|
3
3
|
It is this wrapper object that allows interceptor definitions to be done using method chaining:
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
5
|
+
{{{lang=ruby,caption=Configuring an interceptor
|
6
|
+
reg.intercept( :foo ).with { ... }.with_options(...)
|
7
|
+
}}}
|
8
8
|
|
9
9
|
If you wish to add custom, domain-specific functionality to the interceptor wrapper, you can register your own implementation of the @:interceptor_impl_factory@. Consider the following contrived example, where an "only_if" clause is given to determine when the interceptor should be invoked.
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
11
|
+
{{{lang=ruby,number=true,caption=Advanced configuration of an interceptor
|
12
|
+
class OnlyIfInterceptor < Needle::Interceptor
|
13
|
+
def only_if( &block )
|
14
|
+
@only_if = block
|
15
|
+
self
|
16
|
+
end
|
17
17
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
end
|
18
|
+
def action
|
19
|
+
action_proc = super
|
20
|
+
lambda do |chain,ctx|
|
21
|
+
if @only_if.call( chain, ctx )
|
22
|
+
action_proc.call( chain, ctx )
|
23
|
+
else
|
24
|
+
chain.process_next( ctx )
|
26
25
|
end
|
27
26
|
end
|
28
27
|
end
|
28
|
+
end
|
29
29
|
|
30
|
-
|
31
|
-
|
32
|
-
|
30
|
+
reg = Needle::Registry.new
|
31
|
+
reg.register( :interceptor_impl_factory ) { OnlyIfInterceptor }
|
32
|
+
reg.register( :foo ) { Bar.new }
|
33
33
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
34
|
+
reg.intercept( :foo ).
|
35
|
+
with { |c| c.logging_interceptor }.
|
36
|
+
only_if { |ch,ctx| something_is_true( ch, ctx ) }.
|
37
|
+
with_options(...)
|
38
|
+
}}}
|
@@ -4,21 +4,21 @@ You can specify your own custom implementation for namespaces by registering you
|
|
4
4
|
|
5
5
|
Here's a contrived example. Suppose you want each namespace to keep track of the precise time that it was created.
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
7
|
+
{{{lang=ruby,number=true,caption=Custom namespace implementations
|
8
|
+
class TimeTrackerNamespace < Needle::Container
|
9
|
+
attr_reader :birth_date
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
end
|
11
|
+
def initialize( *args )
|
12
|
+
super
|
13
|
+
@birth_date = Time.now
|
15
14
|
end
|
15
|
+
end
|
16
16
|
|
17
|
-
|
18
|
-
|
17
|
+
reg = Needle::Registry.new
|
18
|
+
reg.register( :namespace_impl_factory ) { TimeTrackerNamespace }
|
19
19
|
|
20
|
-
|
21
|
-
|
22
|
-
|
20
|
+
reg.namespace :hello
|
21
|
+
p reg.hello.birth_date
|
22
|
+
}}}
|
23
23
|
|
24
24
|
In general, you'll be better off having your custom implementation extend @Needle::Container@, although the only _real_ requirement is that your implementation publish the same interface as the default namespace implementation.
|
@@ -8,37 +8,36 @@ An example is the LoggingInterceptor that ships with Needle. Because it is funct
|
|
8
8
|
|
9
9
|
You can attach interceptor factories to your service using the @#interceptor(...).with {...}@ syntax:
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
11
|
+
{{{lang=ruby,number=true,caption=Attaching an interceptor to a service
|
12
|
+
reg.register( :foo ) {...}
|
13
|
+
reg.intercept( :foo ).with { MyInterceptorFactory }
|
14
|
+
}}}
|
15
15
|
|
16
16
|
Note that you could also make the interceptor factory a service:
|
17
17
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
18
|
+
{{{lang=ruby,number=true,caption=Attaching an service as an interceptor
|
19
|
+
reg.register( :foo ) {...}
|
20
|
+
reg.register( :my_interceptor ) { MyInterceptorFactory }
|
21
|
+
reg.intercept( :foo ).with { |c| c.my_interceptor }
|
22
|
+
}}}
|
23
23
|
|
24
24
|
And, to make accessing interceptor services even more convenient, you can use the @#with!@ method (which executes its block within the context of the calling container):
|
25
25
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
26
|
+
{{{lang=ruby,number=true,caption=Attaching an service as an interceptor via #with!
|
27
|
+
reg.register( :foo ) {...}
|
28
|
+
reg.register( :my_interceptor ) { MyInterceptorFactory }
|
29
|
+
reg.intercept( :foo ).with! { my_interceptor }
|
30
|
+
}}}
|
32
31
|
|
33
32
|
h3. Blocks
|
34
33
|
|
35
34
|
Sometimes creating an entire class to implement an interceptor is overkill. This is particularly the case during debugging or testing, when you might want to attach an interceptor to class to verify that a parameter passed is correct, or a return value is what you expect. To satisfy these conditions, you can using the
|
36
35
|
@#doing@ method. Just give it a block that accepts two parameters (the chain, and context) and you're good to go!
|
37
36
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
37
|
+
{{{lang=ruby,number=true,caption=Defining interceptors on the fly
|
38
|
+
reg.register( :foo ) {...}
|
39
|
+
reg.intercept( :foo ).doing { |chain,ctx| ...; chain.process_next( ctx ) }
|
40
|
+
}}}
|
42
41
|
|
43
42
|
Note that this approach is about 40% slower than using an interceptor factory, so it should not be used if performance is an issue.
|
44
43
|
|
@@ -46,19 +45,19 @@ h3. Options
|
|
46
45
|
|
47
46
|
Some interceptors can accept configuration options. For example, the LoggingInterceptor allows clients to specify methods that should and shouldn't be intercepted. Options are specified via the @#with_options@ method.
|
48
47
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
48
|
+
{{{lang=ruby,number=true,caption=Configuring interceptors
|
49
|
+
reg.register( :foo ) {...}
|
50
|
+
reg.intercept( :foo ).
|
51
|
+
with { |c| c.logging_interceptor }.
|
52
|
+
with_options( :exclude => [ "method1", "method2" ] )
|
53
|
+
}}}
|
55
54
|
|
56
55
|
Options can apply to the blocks given to the @#doing@ method, too. The block may access the options via the @#data[:options]@ member of the context:
|
57
56
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
57
|
+
{{{lang=ruby,number=true,caption=Configuring #doing interceptors
|
58
|
+
reg.intercept( :foo ).
|
59
|
+
doing { |ch,ctx| ...; p ctx.data[:options][:value]; ... }.
|
60
|
+
with_options( :value => "hello" )
|
61
|
+
}}}
|
63
62
|
|
64
63
|
With blocks, of course, the value of such an approach is limited.
|