manana 0.0.1 → 0.0.2

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/.yardopts CHANGED
@@ -5,4 +5,4 @@
5
5
  --readme README.md
6
6
  -
7
7
  LICENSE.txt
8
- CHANGELOG.md
8
+ CHANGELOG.md
data/Gemfile CHANGED
@@ -5,10 +5,26 @@ gemspec
5
5
 
6
6
  # these aren't strictly needed for development but are nice to have
7
7
  unless ENV["TRAVIS"] == "1"
8
- gem 'yard'
9
- gem 'redcarpet'
10
- gem 'simplecov'
11
- gem 'pry'
8
+
9
+ group :development do
10
+ gem 'yard'
11
+ gem 'redcarpet'
12
+
13
+ gem 'pry'
14
+ gem 'pry-debugger'
15
+ gem 'pry-rescue'
16
+ gem 'pry-stack_explorer'
17
+ end
18
+
19
+ group :samples do
20
+ gem 'savon', '~> 2.3'
21
+ gem 'webmock'
22
+ end
23
+
24
+ group :test do
25
+ gem 'simplecov'
26
+ end
27
+
12
28
  end
13
29
 
14
30
  # for travis.cl
data/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2013 coldnebo
1
+ Copyright (c) 2013 Larry Kyrala
2
2
 
3
3
  MIT License
4
4
 
data/README.md CHANGED
@@ -1,12 +1,42 @@
1
1
  # Manana
2
2
 
3
- *Manana* lets you defer the initialization of any class to when an instance method is called.
3
+ *Manana* lets you defer the initialization of an object until its methods are called.
4
4
 
5
- This can be useful in cases where class initialization may take a long time or fail due to errors (i.e. a network service).
6
- In these situations, you don't want to tie the initialization of service adapters to the initialization of your application,
7
- because if the service init fails, then your app fails to init and start (i.e. you want your app to be self-healing and
8
- retry initialization when the method is called later)... also it can unncessarily increase the startup
9
- time of your app for testcases, etc.
5
+ This can be useful in cases where initialization may take a long time or fail due to errors. For example, in rails, if
6
+ the database specified in `database.yml` doesn't exist at start time, the rails application will fail initialization. Likewise
7
+ if you tie configuration of SOAP services (e.g. using [Savon](http://savonrb.com/version2/)) to your application initialization,
8
+ and the service wsdl isn't up, your application will fail to start.
9
+
10
+ Instead, it is an established best practice in service-oriented architectures to make your application or service *startup order independent*:
11
+ i.e. your app starts fast and initializes other dependencies afterwards, providing fault detection and *self-healing* properties for the app.
12
+
13
+ *Manana* is a simple approach that allows you to keep your configuration and initialization code in the same place, while deferring it to method calls.
14
+
15
+ ### Pros
16
+
17
+ * Reduces the startup time for your app. (i.e. moves the startup cost from initialization to first use)
18
+
19
+ * Once initialization is succesful, it stores the object instance for reuse.
20
+
21
+ * Until the initialization is successful, it will retry every time a method is called.
22
+
23
+ * You can layer more complex retry semantics such as [exponential backoff](http://en.wikipedia.org/wiki/Exponential_backoff) using this wrapper. See [samples/exponential_backoff.rb](https://github.com/coldnebo/manana/blob/master/samples/exponential_backoff.rb)
24
+
25
+ ### Cons
26
+
27
+ * If your initialization takes a very long time, (i.e. a cache) you may want to pre-warm it instead of taking the hit on the first use of the object.
28
+
29
+ * Very simple approach. You may want more complex retry semantics, or pooling.
30
+
31
+ ### Similar ideas:
32
+
33
+ * [Connection pool](http://en.wikipedia.org/wiki/Connection_pool) of one?
34
+
35
+ * [Avoid Start Order Dependencies](http://wiki.osgi.org/wiki/Avoid_Start_Order_Dependencies)
36
+
37
+ * [Data Centers need shutdown/startup order](http://www.boche.net/blog/index.php/2009/01/01/datacenters-need-shutdownstartup-order/)
38
+
39
+ * 'self-healing' initialization faults from the practice of [Autonomic computing](http://en.wikipedia.org/wiki/Autonomic_computing)
10
40
 
11
41
  ## Installation
12
42
 
@@ -24,7 +54,7 @@ Or install it yourself as:
24
54
 
25
55
  ## Usage
26
56
 
27
- TODO: Write usage instructions here
57
+ See the [samples](https://github.com/coldnebo/manana/blob/master/samples) for examples of use.
28
58
 
29
59
  ## Contributing
30
60
 
data/Rakefile CHANGED
@@ -6,3 +6,10 @@ Rake::TestTask.new(:test) do |t|
6
6
  end
7
7
 
8
8
  task :default => :test
9
+
10
+ desc "install gems for running samples"
11
+ task :samples do
12
+ Bundler.with_clean_env do
13
+ sh "bundle install --gemfile=samples/common/samples.gemfile"
14
+ end
15
+ end
@@ -1,3 +1,3 @@
1
1
  class Manana
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
data/lib/manana.rb CHANGED
@@ -1,12 +1,42 @@
1
1
  require "manana/version"
2
2
 
3
+
4
+ # *Manana* lets you defer the initialization of an object until its methods are called.
5
+ # @example basic usage - see {https://github.com/coldnebo/manana/blob/master/samples/self_healing.rb samples/self_healing.rb}
6
+ # # initialization...
7
+ # client = Manana.wrap {
8
+ # Weather.setup
9
+ # Weather
10
+ # }
11
+ #
12
+ # runtime_loop {
13
+ # # wait for next interval
14
+ # weather = client.city_weather("02201") # deferred initialization happens here once
15
+ # puts "At %s the temperature is currently %s F and the humidity is %s." % [weather.city, weather.temperature, weather.relative_humidity]
16
+ # }
17
+ #
3
18
  class Manana
4
- attr_reader :deferred_initialization, :instance
5
19
 
6
- def self.wrap(&block)
7
- Manana.new(&block)
20
+ # wraps an object initialization block so that it can be deferred to a later time when object methods are called.
21
+ # @example wrap an object - see {https://github.com/coldnebo/manana/blob/master/samples/self_healing.rb samples/self_healing.rb}
22
+ # client = Manana.wrap {
23
+ # Weather.setup # initialize the class
24
+ # Weather # return the Weather class
25
+ # }
26
+ #
27
+ # @param initialization_block [Proc] object initialization. the block must return the object to be wrapped.
28
+ # @return [Manana] a wrapped version of the object.
29
+ def self.wrap(&initialization_block)
30
+ Manana.new(&initialization_block)
8
31
  end
9
32
 
33
+ # passes any method call through to the wrapped object after ensuring that the initialization block has
34
+ # successfully completed once (setting a valid instance of the object).
35
+ # @note Once the initialization block succeeds, it keeps the resulting object instance for subsequent method calls.
36
+ #
37
+ # @example calling a wrapped object - see {https://github.com/coldnebo/manana/blob/master/samples/self_healing.rb samples/self_healing.rb}
38
+ # weather = client.city_weather("02201")
39
+ #
10
40
  def method_missing(method, *args, &block)
11
41
  instance = get_instance
12
42
  instance.send(method, *args, &block);
@@ -14,8 +44,8 @@ class Manana
14
44
 
15
45
  private
16
46
 
17
- def initialize(&block)
18
- @deferred_initialization = block
47
+ def initialize(&initialization_block)
48
+ @deferred_initialization = initialization_block
19
49
  end
20
50
 
21
51
  def get_instance
data/manana.gemspec CHANGED
@@ -8,9 +8,9 @@ Gem::Specification.new do |spec|
8
8
  spec.version = Manana::VERSION
9
9
  spec.authors = ["coldnebo"]
10
10
  spec.email = ["larry.kyrala@gmail.com"]
11
- spec.description = %q{provides a simple way to defer initialization of an object to action on an object}
12
- spec.summary = %q{I'll get to your initialization tomorrow...}
13
- spec.homepage = ""
11
+ spec.description = %q{provides a simple way to defer initialization of an object until its methods are called}
12
+ spec.summary = %q{provides a simple way to defer initialization of an object until its methods are called}
13
+ spec.homepage = "https://github.com/coldnebo/manana"
14
14
  spec.license = "MIT"
15
15
 
16
16
  spec.files = `git ls-files`.split($/)
@@ -0,0 +1,10 @@
1
+
2
+ source 'https://rubygems.org'
3
+
4
+ # these are gems used in the samples.
5
+ # install with:
6
+ # $ rake samples
7
+
8
+ gem 'rack'
9
+ gem 'savon', '~> 2.3'
10
+ gem 'webmock'
@@ -0,0 +1,42 @@
1
+ GEM
2
+ remote: https://rubygems.org/
3
+ specs:
4
+ addressable (2.3.5)
5
+ akami (1.2.0)
6
+ gyoku (>= 0.4.0)
7
+ nokogiri (>= 1.4.0)
8
+ builder (3.2.2)
9
+ crack (0.4.1)
10
+ safe_yaml (~> 0.9.0)
11
+ gyoku (1.1.0)
12
+ builder (>= 2.1.2)
13
+ httpi (2.1.0)
14
+ rack
15
+ rubyntlm (~> 0.3.2)
16
+ nokogiri (1.5.10)
17
+ nori (2.3.0)
18
+ rack (1.5.2)
19
+ rubyntlm (0.3.4)
20
+ safe_yaml (0.9.7)
21
+ savon (2.3.0)
22
+ akami (~> 1.2.0)
23
+ builder (>= 2.1.2)
24
+ gyoku (~> 1.1.0)
25
+ httpi (~> 2.1.0)
26
+ nokogiri (>= 1.4.0, < 1.6)
27
+ nori (~> 2.3.0)
28
+ wasabi (~> 3.2.0)
29
+ wasabi (3.2.0)
30
+ httpi (~> 2.0)
31
+ nokogiri (>= 1.4.0, < 1.6)
32
+ webmock (1.15.2)
33
+ addressable (>= 2.2.7)
34
+ crack (>= 0.3.2)
35
+
36
+ PLATFORMS
37
+ ruby
38
+
39
+ DEPENDENCIES
40
+ rack
41
+ savon (~> 2.3)
42
+ webmock
@@ -0,0 +1,7 @@
1
+ # sample helper takes care of loading the gem from source without requiring it to be rebuilt and installed.
2
+ # this is useful in allowing the samples in this directory to evolve the behavior of the actual gem.
3
+
4
+ lp = File.expand_path(File.join(*%w[.. .. lib]), File.dirname(__FILE__))
5
+ unless $LOAD_PATH.include?(lp)
6
+ $LOAD_PATH.unshift(lp)
7
+ end
@@ -0,0 +1,65 @@
1
+ # This sample demonstrates how exponential backoff can be layered on top of Manana
2
+ #
3
+ # run from the gem dir with:
4
+ # $ rake samples
5
+ # $ ruby samples/exponential_backoff.rb
6
+
7
+ require_relative 'common/samples_helper.rb'
8
+
9
+ # ------------------------- sample code starts here -------------------------
10
+
11
+ require 'manana'
12
+ require 'benchmark'
13
+
14
+
15
+ class PoorService
16
+ @@instance = 0
17
+
18
+ def initialize
19
+ @@instance += 1
20
+ if @@instance <= 3
21
+ raise "failed to init"
22
+ end
23
+ puts "successfully inited service."
24
+ end
25
+
26
+ def process
27
+ puts "successfully called service!"
28
+ end
29
+
30
+ end
31
+
32
+ # ------------ initialization code
33
+
34
+ client = Manana.wrap {
35
+ # exponential backkoff logic in the initialization block
36
+ retries = 0
37
+ obj = nil
38
+
39
+ puts "trying to create service object..."
40
+ begin
41
+ obj = PoorService.new
42
+ rescue RuntimeError => e
43
+ puts "couldn't create object, sleeping for #{2**retries} seconds before next try..."
44
+ sleep(2**retries)
45
+ retries += 1
46
+
47
+ if retries <= 3
48
+ retry
49
+ else
50
+ puts "giving up."
51
+ raise e
52
+ end
53
+ end
54
+ obj
55
+ }
56
+
57
+
58
+ # ------------ runtime code
59
+
60
+ client.process
61
+ client.process
62
+ client.process
63
+ client.process
64
+
65
+
@@ -0,0 +1,83 @@
1
+ # This sample demonstrates how Manana may be used to defer a long initialization
2
+ # to the first method call.
3
+ #
4
+ # run from the gem dir with:
5
+ # $ rake samples
6
+ # $ ruby samples/fast_startup.rb
7
+
8
+ require_relative 'common/samples_helper.rb'
9
+
10
+ # ------------------------- sample code starts here -------------------------
11
+
12
+ require 'manana'
13
+ require 'benchmark'
14
+
15
+
16
+ class DeathStar
17
+
18
+ def initialize
19
+ puts " DeathStar initialization sequence commencing..."
20
+ super_complex_startup_sequence
21
+ puts " ...sequence complete."
22
+ end
23
+
24
+ def navigate_to(planet)
25
+ puts "setting course for the planet '#{planet}'"
26
+ end
27
+
28
+ private
29
+
30
+ def super_complex_startup_sequence
31
+ puts " 1. press the 'start' button."
32
+ puts " 2. snooze while Vader isn't looking."
33
+ sleep(5)
34
+ puts " 3. ???"
35
+ puts " 4. profit!"
36
+ end
37
+
38
+ end
39
+
40
+ # ------------ initialization code
41
+
42
+ mini_death_star = nil
43
+
44
+ puts "[Vader]: requsition a new DeathStar and make it snappy!"
45
+ # boss said requisitions have to be faster*! Manana to the rescue!
46
+ puts Benchmark.measure {
47
+ mini_death_star = Manana.wrap {
48
+ DeathStar.new
49
+ }
50
+ }
51
+ puts "[Expendable Commander]: completed, sir!"
52
+
53
+
54
+ puts "-------------------------------------------"
55
+
56
+
57
+ # ------------ runtime code
58
+
59
+ puts "[Vader]: ok, send it towards some rebels..."
60
+
61
+ # * this first method eats the hidden startup cost...
62
+ puts Benchmark.measure {
63
+ mini_death_star.navigate_to("Yavin")
64
+ }
65
+ puts "[Vader raises an eyebrow and starts to reach towards the commander] \n# first method call was long, but subsequent calls are faster..."
66
+
67
+ # * subsequent method calls are fast though... maybe tradeoff is good enough for Vader?
68
+ puts Benchmark.measure {
69
+ mini_death_star.navigate_to("Bespin")
70
+ }
71
+
72
+ puts Benchmark.measure {
73
+ mini_death_star.navigate_to("Tatooine")
74
+ }
75
+
76
+ puts "[Vader shrugs]: Well done Expendable Commander!"
77
+ puts "[Expendable Commander sweatly profusely]: Thank you Lord Vader!"
78
+
79
+
80
+
81
+
82
+
83
+
@@ -0,0 +1,104 @@
1
+ # This sample demonstrates how Manana may be used to create a self-healing soap adapter
2
+ # by wrapping an existing Savon::Model client.
3
+ #
4
+ # run from the gem dir with:
5
+ # $ rake samples
6
+ # $ ruby samples/self_healing.rb
7
+
8
+ require_relative 'common/samples_helper.rb'
9
+
10
+ # ------------------------- sample code starts here -------------------------
11
+
12
+ require 'manana'
13
+ require 'savon'
14
+ require 'ostruct'
15
+
16
+ # used to simulate conn up/down
17
+ require 'webmock'
18
+ include WebMock::API
19
+ # fake a failure to get the wsdl (service down temporarily)
20
+ stub_request(:any, /wsf.cdyne.com\/.*/).
21
+ to_return(:status => 500, :body => "Internal Server Error", :headers => {})
22
+
23
+
24
+ LOGGING_ENABLED = false
25
+ HTTPI.log = false unless LOGGING_ENABLED
26
+
27
+ class Weather < OpenStruct
28
+ extend Savon::Model
29
+
30
+ client wsdl: "http://wsf.cdyne.com/WeatherWS/Weather.asmx?WSDL", convert_request_keys_to: :none, log: LOGGING_ENABLED
31
+
32
+ def self.setup
33
+ # if this were outside of setup() the class would fail to load if the service was down.
34
+ # instead we dynamically initialize the available operations from withing a class method to be used by the Manana wrapper.
35
+ operations *client.operations
36
+ end
37
+
38
+ def self.city_weather(zip)
39
+ resp = get_city_weather_by_zip(message: {"ZIP" => zip})
40
+ weather = resp.body[:get_city_weather_by_zip_response][:get_city_weather_by_zip_result]
41
+ Weather.new(weather)
42
+ end
43
+ end
44
+
45
+
46
+ # ------------ initialization code
47
+
48
+ # Before we start, we use Manana to wrap the client setup so that method calls on the client can be self-healing in case of failure.
49
+ # NOTE: that the caller doesn't have to deal with whether or not this initialization succeeded, they can just call the client methods repeatedly.
50
+ client = Manana.wrap {
51
+ Weather.setup
52
+ Weather
53
+ }
54
+
55
+
56
+ # ------------ runtime code
57
+
58
+ # first call fails...
59
+ # a) Weather.setup() called, but raises because the server is 500.
60
+ begin
61
+ weather = client.city_weather("02201")
62
+ rescue Wasabi::Resolver::HTTPError => e
63
+ puts "# 1. first call failed as expected, because Weather.setup() couldn't connect to provide the operations for the client."
64
+ end
65
+
66
+ # server restored
67
+ WebMock.disable!
68
+
69
+ # second call ok...
70
+ # a) Weather.setup() succeeds, caches instance for future use.
71
+ # b) city_weather() succeeds.
72
+ weather = client.city_weather("02201")
73
+
74
+ puts "# 2. second call should succeed; Weather.setup() runs successfully and returns a successfully intialized Weather class."
75
+ puts " > At %s the temperature is currently %s F and the humidity is %s." % [weather.city, weather.temperature, weather.relative_humidity]
76
+
77
+ # server down again
78
+ WebMock.enable!
79
+
80
+ # third call; service down again...
81
+ # remember, we already have a valid instance, so Weather.setup() is not called this time.
82
+ # a) city_weather() fails because the server is 500.
83
+ begin
84
+ weather = client.city_weather("02201")
85
+ rescue Savon::HTTPError => e
86
+ puts "# 3. third call fails; the connection is down again, but this time the failure is in the API call since Weather has successfully intialized opertaions from call #2."
87
+ end
88
+
89
+
90
+ # server restored
91
+ WebMock.disable!
92
+
93
+ # fourth call ok...
94
+ # a) city_weather() succeeds.
95
+ weather = client.city_weather("02201")
96
+
97
+ puts "# 4. fourth call should succeed; connection is up again, still using cached operations from the first time service was up (call #2)."
98
+ puts " > At %s the temperature is currently %s F and the humidity is %s." % [weather.city, weather.temperature, weather.relative_humidity]
99
+
100
+
101
+ puts "\nThis concludes the demonstration of how Manana provides self-healing capability to a web-service adapter."
102
+
103
+
104
+
data/test/test_manana.rb CHANGED
@@ -22,11 +22,7 @@ class TestManana < MiniTest::Unit::TestCase
22
22
  end
23
23
 
24
24
  def raise_something
25
- raise "kablewey!" # simulate a call that raises an exception
26
- end
27
-
28
- def self.do_another_thing
29
- "I did something else" # simulate a class method call
25
+ raise "kablooey!" # simulate a call that raises an exception
30
26
  end
31
27
  }
32
28
  end
@@ -43,6 +39,14 @@ class TestManana < MiniTest::Unit::TestCase
43
39
  assert_raises RuntimeError do
44
40
  obj.raise_something
45
41
  end
42
+
43
+ begin
44
+ obj.raise_something
45
+ rescue Exception => e
46
+ # make sure we get an appropriate stack trace at the point of raise.
47
+ assert_match(/test_manana.rb:25:in .raise_something./, e.backtrace.first)
48
+ end
49
+
46
50
  end
47
51
 
48
52
  def test_deferred_init
@@ -68,6 +72,14 @@ class TestManana < MiniTest::Unit::TestCase
68
72
  assert_instance_of(String, result)
69
73
 
70
74
 
75
+ begin
76
+ handle.raise_something
77
+ rescue Exception => e
78
+ # make sure we get an appropriate stack trace at the point of raise.
79
+ assert_match(/test_manana.rb:25:in .raise_something./, e.backtrace.first)
80
+ end
81
+
82
+
71
83
  end
72
84
 
73
85
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: manana
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-11-08 00:00:00.000000000 Z
12
+ date: 2013-11-15 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: bundler
@@ -59,8 +59,8 @@ dependencies:
59
59
  - - ! '>='
60
60
  - !ruby/object:Gem::Version
61
61
  version: '0'
62
- description: provides a simple way to defer initialization of an object to action
63
- on an object
62
+ description: provides a simple way to defer initialization of an object until its
63
+ methods are called
64
64
  email:
65
65
  - larry.kyrala@gmail.com
66
66
  executables: []
@@ -77,9 +77,15 @@ files:
77
77
  - lib/manana.rb
78
78
  - lib/manana/version.rb
79
79
  - manana.gemspec
80
+ - samples/common/samples.gemfile
81
+ - samples/common/samples.gemfile.lock
82
+ - samples/common/samples_helper.rb
83
+ - samples/exponential_backoff.rb
84
+ - samples/fast_startup.rb
85
+ - samples/self_healing.rb
80
86
  - test/minitest_helper.rb
81
87
  - test/test_manana.rb
82
- homepage: ''
88
+ homepage: https://github.com/coldnebo/manana
83
89
  licenses:
84
90
  - MIT
85
91
  post_install_message:
@@ -94,7 +100,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
94
100
  version: '0'
95
101
  segments:
96
102
  - 0
97
- hash: -3461867159210393723
103
+ hash: -1234298163661349463
98
104
  required_rubygems_version: !ruby/object:Gem::Requirement
99
105
  none: false
100
106
  requirements:
@@ -103,13 +109,14 @@ required_rubygems_version: !ruby/object:Gem::Requirement
103
109
  version: '0'
104
110
  segments:
105
111
  - 0
106
- hash: -3461867159210393723
112
+ hash: -1234298163661349463
107
113
  requirements: []
108
114
  rubyforge_project:
109
115
  rubygems_version: 1.8.25
110
116
  signing_key:
111
117
  specification_version: 3
112
- summary: I'll get to your initialization tomorrow...
118
+ summary: provides a simple way to defer initialization of an object until its methods
119
+ are called
113
120
  test_files:
114
121
  - test/minitest_helper.rb
115
122
  - test/test_manana.rb