This post originated from an RSS feed registered with Ruby Buzz
by Jamis Buck.
Original Post: Refactoring Net::SSH: Part 2
Feed Title: the buckblogs here
Feed URL: http://weblog.jamisbuck.org/blog.cgi/programming/index.rss
Feed Description: Jamis Buck's corner of the blogging universe. Mostly about ruby, but includes ramblings on a variety of topics.
This is the second in a series of articles that deal with refactoring the Net::SSH library to take advantage of dependency injection. The first was Net::SSH Refactoring Adventure.
Because I am using Syringe, and because the Syringe API has not yet solidified, refactoring Net::SSH to use Syringe is something of an iterative process. For example, I defined the services using one version of the Syringe API. Then, I implemented a suggestion from Eivind Eklund, adding a few new API’s to Syringe. This caused me to go back and change how the services were defined in Net::SSH, and so forth.
Original Interface
The original interface (which is still supported) was to call #register on the container every time one wanted to install a new service definition:
This works great. It is actually the same syntax that Jim Weirich proposed in his article on dependency injection in Ruby, which inspired me to write Syringe in the first place.
However, Eivind pointed out that if you are registering many services in one place (which will often be the case), the “container.register” part starts detracting from the readability of your code. You wind up with something like this:
So, Eivind proposed using an instance_eval‘d block, within which the service names are functions to which their construction blocks are attached directly. This seems to follow the DRY rule, as well.
This new interface is introduced via the register! method (note the bang at the end of the name).
container.register! do
something {...}
another {...}
yet_another {...}
foo {...}
bar {...}
baz {...}
...
end
Likewise, there are corresponding methods for registering libraries:
And also for declaring a namespace on a container:
Container#namespace! {...}
Note the convention: bang methods in Syringe will denote the instance_eval‘d version of the met
1000
hod.
I should also note that the instance_eval is done on a proxy object, to prevent pollution of the Container namespace. The proxy object also defines convenience methods for service interception, requiring libraries, and namespace declaration:
container.register! do
something { ... }
namespace :foo do
something {...}
...
end
intercept( :something ).with { ... }
require "some/syringe-ified/library'
end
Application of the New Interface
So, in Net::SSH, there is a namespace called “net.ssh.transport.ossl”. This namespace (and its subnamespaces) includes all of the OpenSSL-specific functionality in Net::SSH. As such, it declares a lot of services—various factories, some Hashes for storing configuration information, etc. It was functional and fairly readable in the old syntax:
Syringe.register_library_namespace( "net/ssh/transport/ossl/package", :ossl ) do |c|
c.register( :hmac_algorithm_sources ) { Array.new }
c.require( "/net/ssh/transport/ossl/hmac/package" )
c.register( :cipher_names ) do
Hash.new "3des-cbc" => "des-ede3-cbc",
"blowfish-cbc" => "bf-cbc",
...
end
...
c.register( :cipher_factory, :model => :singleton_deferred ) do |c|
require 'net/ssh/transport/ossl/cipher-factory'
svc = Net::SSH::Transport::OSSL::CipherFactory.new( c.cipher_names )
svc.identity_cipher = c.identity_cipher
svc
end
c.register( :hmac_factory, :model => :singleton_deferred ) do |c|
require 'net/ssh/transport/ossl/hmac-factory'
Net::SSH::Transport::OSSL::HMACFactory.new( c.hmac_algorithm_sources )
end
...
end
However, converting it to the new syntax made it even better:
Syringe.register_library_namespace!( 'net/ssh/transport/ossl/package', :ossl ) do
hmac_algorithm_sources { Array.new }
require 'net/ssh/transport/ossl/hmac/package'
cipher_names do
Hash.new "3des-cbc" => "des-ede3-cbc",
"blowfish-cbc" => "bf-cbc",
...
end
...
cipher_factory( :model => :singleton_deferred ) do |c|
require 'net/ssh/transport/ossl/cipher-factory'
svc = Net::SSH::Transport::OSSL::CipherFactory.new( c.cipher_names )
svc.identity_cipher = c.identity_cipher
svc
end
hmac_factory( :model => :singleton_deferred ) do |c|
require 'net/ssh/transport/ossl/hmac-factory'
Net::SSH::Transport::OSSL::HMACFactory.new( c.hmac_algorithm_sources )
end
...
end