[ANN] MinDI: Minimalist Dependency Injection

J

Joel VanderWerf

MinDI is a DI (dependency injection) or IoC (invesion of control)
framework for ruby inspired by Jamis Buck's Needle
(http://needle.rubyforge.org)
and Jim Weirich's article on DI in Ruby
(http://onestepback.org/index.cgi/Tech/Ruby/DependencyInjectionInRuby.rdoc).

DOWNLOAD

MinDI is available at:

http://redshift.sourceforge.net/mindi/

Includes examples, tests, and basic docs. It's a proof of concept,
but has enough features for production use.

DESCRIPTION

MinDI is minimalist in that it attempts to map concepts of DI into
basic ruby constructs, rather than into a layer of specialized
constructs.

In particular, classes and modules function as containers and
registries, and methods and method definitions function as service
points and services.

There are some inherent advantages and disadvantages to this approach.

Advantages:

- Compact implementation.

- Compact syntax.

- Familiar constructs and idioms, like subclassing, module inclusion,
nested classes, protected and private, all apply.

- Use of classes and methods as containers and services means you can
apply a standard AOP or debugging lib.

- Services can take arguments, and this permits multiton services.

Disadvantages:

- A container's services live in the same namespace as the methods
inherited from Kernel and Object, so a service called "dup", for
example, will prevent calling Object#dup on the container (except in
the implementation of the dup service, which can use super to invoke
Object#dup). The MinDI framework itself adds a few methods that
could conflict with services (#singleton, #generic, etc.).

- No built-in AOP, debugging, or reflection interface.

Notes:

- Supports threaded, deferred, singleton, and multiton service models
(though these are not yet independent choices). Additional service
models can be easily added in modules which include Container. The
"generic" model can be used like "prototype" in Needle, or for
manual service management.

- Use mixins to build apps out of groups of services that need to
coexist in one name space.

- Use a nested class for a group of services when you want them to
live in their own namespace.

EXAMPLES

An example with parametric services:

class Point < Struct.new:)x, :y); end

class MyContainer
extend MinDI::Container

# A multiton service: a unique value is generated for
# each requested parameter list.
point_for_xy { |x,y| Point.new(x,y) }

rect_with_width { |w| [point_for(0,0), point_for(w,200)] }
end

The example from Jim Weirich's article would look like this in MinDI:

class JWApplicationContainer
extend MinDI::Container

logfilename { "logfile.log" }
db_user { "jim" }
db_password { "secret" }
dbi_string { "DBI:pg:example_data" }

app {
app = WebApp.new(quotes, authenticator, database)
app.logger = logger
app.set_error_handler error_handler
app
}

quotes { StockQuotes.new(error_handler, logger) }
authenticator { Authenticator.new(database, logger,
error_handler) }
database { DBI.connect(dbi_string, db_user, db_password) }

logger { Logger.new(logfilename) }
error_handler {
errh = ErrorHandler.new
errh.logger = logger
errh
}
end

def create_application
JWApplicationContainer.new.app
end
 
F

Florian Gross

Joel said:
Disadvantages:

- A container's services live in the same namespace as the methods
inherited from Kernel and Object, so a service called "dup", for
example, will prevent calling Object#dup on the container (except in
the implementation of the dup service, which can use super to invoke
Object#dup). The MinDI framework itself adds a few methods that
could conflict with services (#singleton, #generic, etc.).

They can still be called in a wordy way:
Object.instance_method:)dup).bind(container).call

Maybe this could be wrapped into a small helper method?
 
J

Joel VanderWerf

Florian said:
They can still be called in a wordy way:
Object.instance_method:)dup).bind(container).call

Maybe this could be wrapped into a small helper method?

Sure, thanks for the suggestion.

I don't know if it the namespace problem is really so bad though. It
happens in the following case:

class MyContainer
extend MinDI::Container

singleton :dup do # or threaded or some other service model
#...
end
end

cont = MyContainer.new
cont.dup

But that looks like a design problem to me. Maybe I will feel
differently for methods like #hash, though.

The only other problem I can think of is when using the shortcut (via
method missing) to define services:

class MyContainer
extend MinDI::Container

foo { Foo.new } # ok
dup { Dup.new } # not ok - definition not applied
hash { Hash.new } # not ok
end

This won't work, of course, but it shouldn't be expected to.

My current feeling is that it's just better to be aware of what already
exists in the namespace of MyContainer, just as one does when defining
normal classes.
 
J

Jamis Buck

Joel said:
MinDI is a DI (dependency injection) or IoC (invesion of control)
framework for ruby inspired by Jamis Buck's Needle
(http://needle.rubyforge.org)
and Jim Weirich's article on DI in Ruby
(http://onestepback.org/index.cgi/Tech/Ruby/DependencyInjectionInRuby.rdoc).

Nice work, Joel! It's nice to have a little competition. Drives the
quality up, I hear. :)

Our recent discussion regarding multitons and parameterized services got
me looking harder at Needle's internals, and I discovered that it is
almost ridiculously easy to add them.

So, Needle (in CVS) supports parameterized services and a multiton
service model, working just like you would expect:

registry.define.printer( :model => :multiton ) do |c,p,name|
Printer.new( name )
end

p1 = registry.printer( :monochrome )
p2 = registry.printer( :monochrome )
assert_same p1, p2

p3 = registry.printer( :color )
assert_not_same p1, p3

I'll be pushing this change out on Thursday as version 1.2, in keeping
with Needle's weekly release schedule. Thanks to Joel and Christian
(Neukirchen) for the impetus to add the feature. :)

- Jamis
 
S

Shashank Date

Jamis said:
Joel VanderWerf wrote:
Nice work, Joel! It's nice to have a little competition. Drives the
quality up, I hear. :)

Hear, Hear !!
Our recent discussion regarding multitons and parameterized services got
me looking harder at Needle's internals, and I discovered that it is
almost ridiculously easy to add them.

I'll be pushing this change out on Thursday as version 1.2, in keeping
with Needle's weekly release schedule.

You guys, you know, should slow done a little. Even before I can finish
groking the previous release, out comes a new one. You give me a huge
inferiority complex ;-)

-- shanko
PS> That really goes for all the smart people on this ML.
 
A

Avi Bryant

Joel VanderWerf said:
The example from Jim Weirich's article would look like this in MinDI:

class JWApplicationContainer
extend MinDI::Container

logfilename { "logfile.log" }
quotes { StockQuotes.new(error_handler, logger) }

Forgive me for being dense, but once you've gone this far, is there
any particular reason not to simply write:

class JWApplicationContainer
def logfilename() "logfile.log"; end

...

def quotes
@quotes ||= StockQuotes.new(error_handler, logger)
end

...
end

and so on? I think the win in simplicity probably outweighs the extra
few characters you have to type...

Avi
 
J

Joel VanderWerf

Avi said:
Forgive me for being dense, but once you've gone this far, is there
any particular reason not to simply write:

class JWApplicationContainer
def logfilename() "logfile.log"; end

...

def quotes
@quotes ||= StockQuotes.new(error_handler, logger)
end

...
end

and so on? I think the win in simplicity probably outweighs the extra
few characters you have to type...

I forgive you :) That reaction was exactly the initial reaction that DI
provoked in me and which I wrote up in [ruby-talk:120214]. The style
that you suggest is what I've used in my own code, and it took Jim's
article to see that it was really a form of DI.

I'm not yet sure whether a more explicit DI (Arrow, or MinDI) will
replace that style for me--I haven't actually used it in an application.
It probably depends on how much I use the other service models besides
singleton (e.g., #quotes, above) and prototype (#logfilename, in the way
you defined it). In that case, DI may be a useful abstraction layer that
saves more than typing.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Similar Threads

[ANN] Needle 1.2.0 0
[ANN] Needle 0.5.0 0
ANN main-4.4.0 0
[ANN] Needle 0.6.0 0
[ANN] Needle 0.9.0 0
[ANN] Copland 0.7.0 1
[ANN] Needle-Extras 1.0.0 4
[ANN] Copland 0.6.0 0

Members online

Forum statistics

Threads
473,764
Messages
2,569,566
Members
45,041
Latest member
RomeoFarnh

Latest Threads

Top