rddd 0.2.1 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -4,6 +4,7 @@ source 'https://rubygems.org'
4
4
  gemspec
5
5
 
6
6
  gem 'rake'
7
+ gem 'curb'
7
8
 
8
9
  group :testing do
9
10
  gem 'rspec'
data/Gemfile.lock CHANGED
@@ -1,29 +1,31 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rddd (0.2.0)
4
+ rddd (0.2.1)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
8
  specs:
9
+ curb (0.8.3)
9
10
  diff-lcs (1.1.3)
10
11
  metaclass (0.0.1)
11
- mocha (0.12.7)
12
+ mocha (0.13.1)
12
13
  metaclass (~> 0.0.1)
13
- rake (0.9.2.2)
14
- rspec (2.11.0)
15
- rspec-core (~> 2.11.0)
16
- rspec-expectations (~> 2.11.0)
17
- rspec-mocks (~> 2.11.0)
18
- rspec-core (2.11.1)
19
- rspec-expectations (2.11.3)
14
+ rake (10.0.3)
15
+ rspec (2.12.0)
16
+ rspec-core (~> 2.12.0)
17
+ rspec-expectations (~> 2.12.0)
18
+ rspec-mocks (~> 2.12.0)
19
+ rspec-core (2.12.2)
20
+ rspec-expectations (2.12.1)
20
21
  diff-lcs (~> 1.1.3)
21
- rspec-mocks (2.11.3)
22
+ rspec-mocks (2.12.1)
22
23
 
23
24
  PLATFORMS
24
25
  ruby
25
26
 
26
27
  DEPENDENCIES
28
+ curb
27
29
  mocha
28
30
  rake
29
31
  rddd!
data/README.md CHANGED
@@ -14,69 +14,11 @@ Intention of rddd is to provide basic skeleton for DDD ruby application. Its fra
14
14
  * Provide basic DDD elements
15
15
  * Support separation of domain from delivery mechanism (mostly web framework such Rails or Sinatra)
16
16
  * Support separation of domain from persistancy mechanism
17
+ * Make it easy to postpone decisions about background jobs processing or spliting the application into pieces
17
18
 
18
- ## Basic architecture
19
+ ## Elements
19
20
 
20
- ![Architecture overview](https://github.com/petrjanda/rddd/blob/master/documentation/rddd.png?raw=true)
21
-
22
- There are two major entry points to the domain layer from the delivery mechanism. Service bus, for a command execution and
23
- Presenter factory, to build up the presented business data (views / reports).
24
-
25
- ### Service bus
26
-
27
- To further enhance the separation of delivery mechanism and domain from each other, every call to the domain service is done through the ```Service bus```. The key design goal is to decouple concrete service implementation from the non-domain code, so they never have to be directly instantiated outside the domain itself. Instead, service bus defines simple protocol for any object, which can call its ```execute``` method with service name and parameters. The instantiation of the service object then becomes implementation detail of domain code, thus leaves the flexibility for alternative solutions within domain.
28
-
29
- In Rails application, it might look like:
30
-
31
- class ProjectsController < ApplicationController
32
- include ServiceBus
33
-
34
- def create
35
- execute(:create_project, params) do |errors|
36
- render :new, :errors => errors
37
- return
38
- end
39
-
40
- redirect_to projects_path, :notice => 'Project was successfully created!'
41
- end
42
- end
43
-
44
- ### Presenter factory
45
-
46
- We dont wanna DM (Delivery mechanism) know too much about domain implementation internals. Neither about specific presenter classes. We apply same approach as for Service bus and define simple interface to load a specific view.
47
-
48
- In Rails application, it might be used as:
49
-
50
- class ProjectsController < ApplicationController
51
- def index
52
- PresenterFactory.build(:projects_by_account, :account_id => params[:account_id])
53
- end
54
- end
55
-
56
- ### Services
57
-
58
- Service is the object to represent a single use case. Its responsibility is to orchestrate the cooperation of multiple aggregate roots (and entities), in a given order defined by use case.
59
-
60
- Service takes a list of attributes, which are necessary to execute the given use case. At the end nothing is returned, so service represent the command to the domain and doesn't return any data back. Therefore, client code should construct and pass all the data for the given use case and keep them, so it doesn't have to use presenters to load the result of the operation back.
61
-
62
- ### Presenters
63
-
64
- Presenter is the view to the domain data, which is typically aggregation over multiple aggregate roots and entities. In other
65
- language presenter is report. In Rddd, views output plain Ruby hashes, thus no domain object is leaking outside the domain
66
- itself. The key design goal was to dont let framework later call any additional adhoc methods. Thats why framework doesn't interact with view object directly and pure Hash is returned back.
67
-
68
- ### Aggregate root
69
-
70
- ### Entity
71
-
72
- ### Repository
73
-
74
- ### Factory
75
-
76
- ## Planned features
77
-
78
- * Asynchronous notifications from services - Services might be executed synchronous but also asynchronous way. Simple extension of Service bus would do the trick (add ```execute_async``` method). Its a common case that client code might
79
- be waiting for the result from background job execution so it can talk back to user. There is a plan to add ```Notifier``` interface, which would be available during service execution. This interface should get concrete implementation within delivery mechanism (websockets, ...).
21
+ ![Elements](https://github.com/petrjanda/rddd/blob/master/documentation/rddd-elements.png?raw=true)
80
22
 
81
23
  ## Project file structure
82
24
 
Binary file
@@ -8,6 +8,9 @@ module Rddd
8
8
  class Configuration
9
9
  include Singleton
10
10
 
11
- attr_accessor :service_creator, :repository_creator
11
+ attr_accessor :service_creator,
12
+ :repository_creator,
13
+ :caching_strategy,
14
+ :remote_services
12
15
  end
13
16
  end
@@ -0,0 +1,28 @@
1
+ module Rddd
2
+ module Presenters
3
+ #
4
+ # Cache object providing several caching methods on top of the chosen
5
+ # strategy.
6
+ #
7
+ class CacheEntry
8
+ def initialize(key, strategy)
9
+ @key = key
10
+ @strategy = strategy
11
+ end
12
+
13
+ def delete
14
+ write(nil)
15
+ end
16
+
17
+ def read
18
+ @strategy.get(@key)
19
+ end
20
+
21
+ def write(data, timeout = nil)
22
+ expire_at = timeout ? Time.now + timeout : nil
23
+
24
+ @strategy.set(@key, data, expire_at)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,148 @@
1
+ module Rddd
2
+ module Presenters
3
+ #
4
+ # View extension, which would make it cacheable. In case you wanna use it
5
+ # be sure to have a CacheStrategy set in the configuration. As soon as you
6
+ # include the module, View is gonna start caching without any timeout.
7
+ #
8
+ # Here is how you configure your caching with simple strategy:
9
+ #
10
+ # class InMemoryStrategy
11
+ # def get(key)
12
+ # @entries[key]
13
+ # end
14
+ #
15
+ # def set(key, value, options)
16
+ # @entries ||= {}
17
+ # @entries[key] = value
18
+ # end
19
+ # end
20
+ #
21
+ # Rddd.configure do |config|
22
+ # config.cache_strategy = InMemoryStrategy.new
23
+ # end
24
+ #
25
+ #
26
+ # Each time you hit data method, cache is first checked for presence of the
27
+ # data and if none is there build method is called to compose the view,
28
+ # result is updated to the cache and returned.
29
+ #
30
+ # Usage:
31
+ #
32
+ # class ProjectsView < Rddd::Presenters::Presenter
33
+ # extend Rddd::Presenters::Cacheable
34
+ #
35
+ # def build
36
+ # Projects.find_by_account_id(@id).map { |project| format(project) }
37
+ # end
38
+ #
39
+ # def format(project)
40
+ # {
41
+ # :name => project.name,
42
+ # :deathline => project.deathline.strftime("%d %B %Y")
43
+ # }
44
+ # end
45
+ # end
46
+ #
47
+ # ProjectsView.new(account_id).data #= [{:name => 'Rddd', :deathline => '01 January 2013'}]
48
+ #
49
+ # ## Timeout
50
+ #
51
+ # By default, there is no expiration to the values you store in the cache.
52
+ # This means, value is theoretically stored for infinite time or until some
53
+ # purging mechanism clean the code from cache as the less queried one.
54
+ #
55
+ # If you want to have more control of when the certain key should be removed
56
+ # you can use :timeout option. Its configurable per view.
57
+ #
58
+ # class ProjectsView < Rddd::Presenters::Presenter
59
+ # extend Rddd::Presenters::Cacheable
60
+ #
61
+ # cache :timeout => 60 * 24
62
+ # end
63
+ #
64
+ # Above call to ```cache``` with timeout changed its behavior. Each key for a given
65
+ # view now expires in 24 * 60 minutes, therefore in a day from its creation.
66
+ #
67
+ # ## Serialization
68
+ #
69
+ # By default cache does no serialization so in general String values are expected
70
+ # as caches almost exclusively work with Strings. You can inject serializor with
71
+ # cache configuration.
72
+ #
73
+ # class ProjectsView < Rddd::Presenters::Presenter
74
+ # extend Rddd::Presenters::Cacheable
75
+ #
76
+ # cache :serializer => Marshal
77
+ # end
78
+ #
79
+ # Above would call ```Marshal::dump``` each time before write to cache and
80
+ # ```Marshal::load``` each time the value is retrieved from it. Yo can inject
81
+ # your custom serializor class. Make sure it implements the same interface as
82
+ # Ruby standard ```Marshal::dump``` and ```Marshal::load```.
83
+ #
84
+ module Cacheable
85
+ def self.included(base)
86
+ base.extend CacheableConfig
87
+ end
88
+
89
+ def self.extended(base)
90
+ base.class.extend CacheableConfig
91
+ end
92
+
93
+ #
94
+ # Invalidate the given view so next time its requested no cached value
95
+ # would be given.
96
+ #
97
+ def invalidate
98
+ __cache__.delete
99
+ end
100
+
101
+ #
102
+ # Trigger the warming up of the cache for a given view.
103
+ #
104
+ def warm_up
105
+ __update__(build)
106
+ end
107
+
108
+ def data
109
+ __read__ || __update__(build)
110
+ end
111
+
112
+ private
113
+
114
+ def __read__
115
+ data = __cache__.read
116
+
117
+ return nil unless data
118
+
119
+ self.class.serializer ? self.class.serializer::load(data) : data
120
+ end
121
+
122
+ def __update__(data)
123
+ data = self.class.serializer::dump(data) if self.class.serializer
124
+
125
+ __cache__.write(data, self.class.timeout)
126
+
127
+ data
128
+ end
129
+
130
+ def __cache__
131
+ CacheEntry.new(
132
+ "#{self.class.name.downcase.to_sym}#{id}",
133
+ @strategy ||= Configuration.instance.caching_strategy
134
+ )
135
+ end
136
+ end
137
+
138
+ module CacheableConfig
139
+ attr_reader :timeout, :serializer
140
+
141
+ def cache(attributes)
142
+ @timeout = attributes.fetch(:timeout, nil)
143
+ @timeout = @timeout && 60 * @timeout
144
+ @serializer = attributes.fetch(:serializer, nil)
145
+ end
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,64 @@
1
+ require 'rddd/presenters/cacheable'
2
+ require 'rddd/presenters/cache_entry'
3
+
4
+ module Rddd
5
+ #
6
+ # Presenter is simply a view on the domain data. Presenters should be used
7
+ # each time, you are about to get some data out of the domain. The result
8
+ # returned from presenter should be either very dumb DTO (data transfer object)
9
+ # or one of the base data types like Array, Hash or even some scalar value.
10
+ #
11
+ # Key goal is to make a clear boundary to the domain, so none full fledged
12
+ # business object is leaked outside of it.
13
+ #
14
+ # Presenter output is composed inside ```build``` method, which is called each
15
+ # time someone tries to access view ```data```. Lets take a look at simple example:
16
+ #
17
+ # class EventsByCityAndDatePresenter < Rddd::Presenters::View
18
+ # def initialize(city_id, date)
19
+ # @city_id = city_id
20
+ # @date = date
21
+ #
22
+ # super("#{city_id}-#{date}")
23
+ # end
24
+ #
25
+ # private
26
+ #
27
+ # def build
28
+ # format(City.find(@city_id).events_by_date(@date))
29
+ # end
30
+ #
31
+ # def format(events)
32
+ # events.map { |event| {:name => event.name, :time => event.time} }
33
+ # end
34
+ # end
35
+ #
36
+ # EventsByCityAndDatePresenter.new(city_id, Date.today).data #= [{:name => 'Happy new year!', :time => '00:00'}]
37
+ #
38
+ # ## Caching
39
+ #
40
+ # So far we didn't got too many benefits from use of the cache, although
41
+ # because the View made a clear boundary, there are some benefits which
42
+ # we can easily build on top of it. First is the caching. In order to plug
43
+ # it in simply extend Rddd::Presenters::Cacheable. See its documentation.
44
+ #
45
+ module Presenters
46
+ class View
47
+ attr_reader :id
48
+
49
+ def initialize(id)
50
+ @id = id
51
+ end
52
+
53
+ def data
54
+ build
55
+ end
56
+
57
+ private
58
+
59
+ def build
60
+ raise NotImplementedError
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,59 @@
1
+ require 'json'
2
+ require 'curb'
3
+ require 'rddd/services/service'
4
+
5
+ module Rddd
6
+ module Services
7
+ #
8
+ # Remote service is the way to execute logic on different application.
9
+ # Currently JSON encoded, HTTP based transport is supported. In future
10
+ # there is a plan for more generic RPC support.
11
+ #
12
+ # Usage:
13
+ #
14
+ # # Configure the remote endpoint.
15
+ # Rddd.configure do |config|
16
+ # config.remote_services = [
17
+ # {:namespace => 'projects', :endpoint => 'http://products.dev/'}
18
+ # ]
19
+ # end
20
+ #
21
+ # # Execute service
22
+ # class Test < Sinatra:Base
23
+ # get '/:account_id/projects' do
24
+ # execute_service(:projects__list, params[:account_id])
25
+ # end
26
+ # end
27
+ #
28
+ # # Finally here is the remote app
29
+ # class Projects < Sinatra:Base
30
+ # post ':service_name' do
31
+ # execute_service(params[:service_name], request.body)
32
+ # end
33
+ # end
34
+ #
35
+ # As you can see remote app uses the service bus as well. This architecture
36
+ # would allow you pull part of your application to separate box without need
37
+ # for any change in your client or services code base.
38
+ #
39
+ class RemoteService < Service
40
+ def initialize(url, attributes = {})
41
+ super(attributes)
42
+
43
+ @url = url
44
+ end
45
+
46
+ def execute
47
+ JSON.parse(Curl.post(@url, @attributes).body_str)
48
+ end
49
+
50
+ def self.build(namespace, service_name, attributes)
51
+ remote = Configuration.instance.remote_services.find do |item|
52
+ item[:namespace] == namespace
53
+ end
54
+
55
+ RemoteService.new("#{remote[:endpoint]}#{service_name}", attributes)
56
+ end
57
+ end
58
+ end
59
+ end
@@ -1,4 +1,5 @@
1
1
  require 'rddd/services/service_factory'
2
+ require 'rddd/services/remote_service'
2
3
 
3
4
  module Rddd
4
5
  #
@@ -41,7 +42,11 @@ module Rddd
41
42
  # @param [Block] optional error callback block.
42
43
  #
43
44
  def execute_service(service_name, attributes = {})
44
- service = build_service(service_name, attributes)
45
+ namespace, service_name = service_name.to_s.split('__') if service_name.to_s.include?('__')
46
+
47
+ service = namespace ? \
48
+ build_remote_service(namespace.to_sym, service_name.to_sym, attributes) : \
49
+ build_service(service_name, attributes)
45
50
 
46
51
  unless service.valid?
47
52
  yield(service.errors) if block_given?
@@ -56,6 +61,10 @@ module Rddd
56
61
  def build_service(service_name, attributes)
57
62
  ServiceFactory.build(service_name, attributes)
58
63
  end
64
+
65
+ def build_remote_service(namespace, service_name, attributes)
66
+ RemoteService.build(namespace, service_name, attributes)
67
+ end
59
68
  end
60
69
  end
61
70
  end
data/lib/rddd/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Rddd
2
- VERSION = "0.2.1"
2
+ VERSION = "0.2.2"
3
3
  end
@@ -0,0 +1,188 @@
1
+ require 'spec_helper'
2
+ require 'rddd/presenters/presenter'
3
+ require 'rddd/aggregates/repositories/base'
4
+
5
+ class NilStrategy
6
+ def get(id)
7
+ nil
8
+ end
9
+
10
+ def set(id, value, timeout)
11
+ value
12
+ end
13
+ end
14
+
15
+ describe Rddd::Presenters::View do
16
+ before do
17
+ Rddd.configure do |config|
18
+ config.caching_strategy = NilStrategy.new
19
+ end
20
+ end
21
+
22
+ let(:id) { :id }
23
+
24
+ let(:view) { Rddd::Presenters::View.new(id).tap {|view| view.extend Rddd::Presenters::Cacheable } }
25
+
26
+ let(:nil_strategy) { stub('nil_strategy') }
27
+
28
+ let(:data) { stub('data') }
29
+
30
+ subject { view }
31
+
32
+ describe '#initialize' do
33
+ its(:id) { should == id}
34
+ end
35
+
36
+ describe '#data' do
37
+ subject { view.data }
38
+
39
+ it { lambda {subject}.should raise_exception NotImplementedError }
40
+ end
41
+
42
+ describe '#warm_up' do
43
+ subject { view.warm_up }
44
+
45
+ before do
46
+ view.expects(:build).returns(data)
47
+
48
+ NilStrategy.any_instance.expects(:set).with("rddd::presenters::#{:view}#{:id}", data, anything)
49
+ end
50
+
51
+ it { subject }
52
+ end
53
+
54
+ describe '#invalidate' do
55
+ subject { view.invalidate }
56
+
57
+ before do
58
+ NilStrategy.any_instance.expects(:set).with("rddd::presenters::#{:view}#{:id}", nil, nil)
59
+ end
60
+
61
+ it { subject }
62
+ end
63
+
64
+ describe '#data' do
65
+ subject { view.data }
66
+
67
+ context 'without view repository' do
68
+ before do
69
+ view.expects(:build).returns(data)
70
+ end
71
+
72
+ it { subject.should == data }
73
+ end
74
+
75
+ context 'not cached' do
76
+ before do
77
+ NilStrategy.any_instance.expects(:get).with("rddd::presenters::#{:view}#{:id}").returns(nil)
78
+
79
+ view.expects(:build).returns(data)
80
+
81
+ NilStrategy.any_instance.expects(:set).with("rddd::presenters::#{:view}#{:id}", data, nil)
82
+ end
83
+
84
+ it { subject.should == data }
85
+ end
86
+
87
+ context 'cached' do
88
+ before do
89
+ NilStrategy.any_instance.expects(:get).with("rddd::presenters::#{:view}#{:id}").returns(data)
90
+ end
91
+
92
+ it { subject.should == data }
93
+ end
94
+ end
95
+
96
+ describe 'view without cache' do
97
+ class CacheLessView < Rddd::Presenters::View
98
+ def build
99
+ end
100
+ end
101
+
102
+ let(:id) { stub('id') }
103
+
104
+ let(:cache_less_view) do
105
+ CacheLessView.new(id)
106
+ end
107
+
108
+ it 'should not try to load data from cache' do
109
+ NilStrategy.expects(:new).never
110
+
111
+ cache_less_view.expects(:build).returns(data)
112
+
113
+ cache_less_view.data.should == data
114
+ end
115
+ end
116
+
117
+ describe 'timeout enabling' do
118
+ class TimeoutingView < Rddd::Presenters::View
119
+ include Rddd::Presenters::Cacheable
120
+
121
+ cache :timeout => 24 * 60
122
+
123
+ def build
124
+ end
125
+ end
126
+
127
+ let(:id) { :id }
128
+
129
+ let(:timeouting_view) do
130
+ TimeoutingView.new(id)
131
+ end
132
+
133
+ it 'should set timeout to cache data' do
134
+ NilStrategy.any_instance.expects(:get).with("#{:timeoutingview}#{:id}").returns(nil)
135
+
136
+ timeouting_view.expects(:build).returns(data)
137
+
138
+ NilStrategy.any_instance.expects(:set).with do |got_id, got_data, got_time|
139
+ got_id == "#{:timeoutingview}#{:id}" &&
140
+ got_data == data &&
141
+ got_time.to_i == (Time.now + 24 * 60 * 60).to_i
142
+ end
143
+
144
+ timeouting_view.data
145
+ end
146
+ end
147
+
148
+ describe 'with serializer' do
149
+ class SerializingView < Rddd::Presenters::View
150
+ include Rddd::Presenters::Cacheable
151
+
152
+ cache :serializer => Marshal
153
+
154
+ def build
155
+ end
156
+ end
157
+
158
+ let(:id) { :id }
159
+
160
+ let(:data) { SerializingView.new(1) }
161
+
162
+ let(:serializing_view) do
163
+ SerializingView.new(id)
164
+ end
165
+
166
+ it 'should marshal data on write' do
167
+ NilStrategy.any_instance.expects(:get).with("#{:serializingview}#{:id}").returns(nil)
168
+
169
+ serializing_view.expects(:build).returns(data)
170
+
171
+ NilStrategy.any_instance.expects(:set).with do |got_id, got_data, got_time|
172
+ got_id == "#{:serializingview}#{:id}" &&
173
+ got_data == Marshal::dump(data) &&
174
+ got_time == nil
175
+ end
176
+
177
+ serializing_view.data
178
+ end
179
+
180
+ it 'should marshal data on read' do
181
+ NilStrategy.any_instance.expects(:get).twice.with("#{:serializingview}#{:id}") \
182
+ .returns(Marshal::dump(data))
183
+
184
+ serializing_view.data.should be_instance_of SerializingView
185
+ serializing_view.data.id.should be 1
186
+ end
187
+ end
188
+ end
@@ -0,0 +1,38 @@
1
+ require 'spec_helper'
2
+ require 'rddd/services/remote_service'
3
+
4
+ module Rddd
5
+ module Services
6
+ describe RemoteService do
7
+ let(:attributes) { stub('attributes') }
8
+
9
+ let(:url) { 'http://remote.dev/' }
10
+
11
+ describe '#initialize' do
12
+ subject { RemoteService.new(url, attributes) }
13
+
14
+ it 'should store attributes' do
15
+ subject.instance_variable_get(:@attributes).should == attributes
16
+ end
17
+ end
18
+
19
+ describe '#valid?' do
20
+ subject { RemoteService.new(url).valid? }
21
+
22
+ it { should be_true }
23
+ end
24
+
25
+ describe '#execute' do
26
+ subject { RemoteService.new(url, attributes).execute }
27
+
28
+ let(:curl) { stub('curl', :body_str => '{"foo": "bar"}')}
29
+
30
+ it 'should raise not implemented' do
31
+ Curl.expects(:post).with(url, attributes).returns(curl)
32
+
33
+ subject.should == {'foo' => 'bar'}
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -22,6 +22,17 @@ describe Rddd::Services::ServiceBus do
22
22
  end
23
23
  end
24
24
 
25
+ context 'namespaced service with remote http transport' do
26
+ let(:service) { stub('service', :valid? => true, :execute => nil) }
27
+
28
+ it do
29
+ Rddd::Services::RemoteService.expects(:build).with(:remote, :foo, attributes).returns(service)
30
+ service.expects(:execute)
31
+
32
+ controller.execute_service(:remote__foo, attributes)
33
+ end
34
+ end
35
+
25
36
  context 'not valid call to service' do
26
37
  context 'without block' do
27
38
  before { service.stubs(:valid?).returns(false) }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rddd
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.2.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: 2012-12-27 00:00:00.000000000 Z
12
+ date: 2013-01-19 00:00:00.000000000 Z
13
13
  dependencies: []
14
14
  description: Ruby DDD framework
15
15
  email:
@@ -27,6 +27,7 @@ files:
27
27
  - LICENSE.txt
28
28
  - README.md
29
29
  - Rakefile
30
+ - documentation/rddd-elements.png
30
31
  - documentation/rddd.png
31
32
  - lib/core_ext/string.rb
32
33
  - lib/rddd.rb
@@ -39,13 +40,14 @@ files:
39
40
  - lib/rddd/authorization/rule.rb
40
41
  - lib/rddd/authorization/rules_builder.rb
41
42
  - lib/rddd/configuration.rb
43
+ - lib/rddd/presenters/cache_entry.rb
44
+ - lib/rddd/presenters/cacheable.rb
45
+ - lib/rddd/presenters/presenter.rb
46
+ - lib/rddd/services/remote_service.rb
42
47
  - lib/rddd/services/service.rb
43
48
  - lib/rddd/services/service_bus.rb
44
49
  - lib/rddd/services/service_factory.rb
45
50
  - lib/rddd/version.rb
46
- - lib/rddd/views/cache.rb
47
- - lib/rddd/views/cacheable.rb
48
- - lib/rddd/views/view.rb
49
51
  - rddd.gemspec
50
52
  - spec/integration_spec.rb
51
53
  - spec/lib/aggregates/entity_spec.rb
@@ -54,12 +56,13 @@ files:
54
56
  - spec/lib/authorization/integration_spec.rb
55
57
  - spec/lib/authorization/rule_spec.rb
56
58
  - spec/lib/authorization/rules_builder_spec.rb
59
+ - spec/lib/presenters/integration_spec.rb
57
60
  - spec/lib/repositories/repository_factory_spec.rb
58
61
  - spec/lib/repositories/repository_spec.rb
62
+ - spec/lib/services/remote_service_spec.rb
59
63
  - spec/lib/services/service_bus_spec.rb
60
64
  - spec/lib/services/service_factory_spec.rb
61
65
  - spec/lib/services/service_spec.rb
62
- - spec/lib/views/integration_spec.rb
63
66
  - spec/spec_helper.rb
64
67
  homepage: http://blog.ngneers.com
65
68
  licenses: []
@@ -95,11 +98,12 @@ test_files:
95
98
  - spec/lib/authorization/integration_spec.rb
96
99
  - spec/lib/authorization/rule_spec.rb
97
100
  - spec/lib/authorization/rules_builder_spec.rb
101
+ - spec/lib/presenters/integration_spec.rb
98
102
  - spec/lib/repositories/repository_factory_spec.rb
99
103
  - spec/lib/repositories/repository_spec.rb
104
+ - spec/lib/services/remote_service_spec.rb
100
105
  - spec/lib/services/service_bus_spec.rb
101
106
  - spec/lib/services/service_factory_spec.rb
102
107
  - spec/lib/services/service_spec.rb
103
- - spec/lib/views/integration_spec.rb
104
108
  - spec/spec_helper.rb
105
109
  has_rdoc:
@@ -1,24 +0,0 @@
1
- module Rddd
2
- module Views
3
- class Cache
4
- def initialize(id, repository)
5
- @id = id
6
- @repository = repository
7
- end
8
-
9
- def read
10
- @repository.get(@id) if @repository
11
- end
12
-
13
- def invalidate
14
- @repository.set(@id, nil)
15
- end
16
-
17
- def write(data, timeout)
18
- expire_at = timeout ? Time.now + timeout : nil
19
-
20
- @repository.set(@id, data, expire_at)
21
- end
22
- end
23
- end
24
- end
@@ -1,50 +0,0 @@
1
- module Rddd
2
- module Views
3
- module Caching
4
- attr_reader :cache_disabled, :timeout
5
-
6
- def cache(attributes)
7
- @cache_disabled = attributes.has_key?(:enabled) ? !attributes[:enabled] : false
8
- @timeout = attributes.has_key?(:timeout) ? 60 * attributes[:timeout] : nil
9
- end
10
- end
11
-
12
- module Cacheable
13
- def invalidate
14
- cache.invalidate if cache
15
- end
16
-
17
- def warm_cache
18
- update_cache(build)
19
- end
20
-
21
- private
22
-
23
- def read_cache
24
- cache.read if cache
25
- end
26
-
27
- def update_cache(data)
28
- cache.write(data, self.class.timeout) if cache
29
- end
30
-
31
- def cache
32
- nil unless repository
33
-
34
- @cache ||= Cache.new(cache_id, repository)
35
- end
36
-
37
- def cache_id
38
- "#{name}#{id}"
39
- end
40
-
41
- def repository
42
- begin
43
- @repository ||= ViewRepository.new
44
- rescue
45
- nil
46
- end
47
- end
48
- end
49
- end
50
- end
@@ -1,38 +0,0 @@
1
- require 'rddd/views/cache'
2
- require 'rddd/views/cacheable'
3
-
4
- module Rddd
5
- module Views
6
- class View
7
- include Cacheable
8
- extend Caching
9
-
10
- attr_reader :id
11
-
12
- def initialize(id)
13
- @id = id
14
- end
15
-
16
- def name
17
- self.class.name.downcase.to_sym
18
- end
19
-
20
- def build
21
- raise NotImplementedError
22
- end
23
-
24
- def data
25
- return build if self.class.cache_disabled
26
-
27
- cached_data = read_cache
28
-
29
- return cached_data if cached_data
30
-
31
- data = build
32
- update_cache(data)
33
-
34
- data
35
- end
36
- end
37
- end
38
- end
@@ -1,153 +0,0 @@
1
- require 'spec_helper'
2
- require 'rddd/views/view'
3
- require 'rddd/aggregates/repositories/base'
4
-
5
- class ViewRepository < Rddd::Repositories::Base
6
- def get(id)
7
- end
8
-
9
- def set(id, value, timeout)
10
- end
11
- end
12
-
13
- describe Rddd::Views::View do
14
- let(:id) { :id }
15
-
16
- let(:view) { Rddd::Views::View.new(id) }
17
-
18
- let(:view_repository) { stub('view_repository') }
19
-
20
- let(:data) { stub('data') }
21
-
22
- subject { view }
23
-
24
- describe '#initialize' do
25
- its(:id) { should == id}
26
- end
27
-
28
- describe '#name' do
29
- its(:name) { should == :'rddd::views::view' }
30
- end
31
-
32
- describe '#build' do
33
- subject { view.build }
34
-
35
- it { lambda {subject}.should raise_exception NotImplementedError }
36
- end
37
-
38
- describe '#warm_cache' do
39
- subject { view.warm_cache }
40
-
41
- before do
42
- ViewRepository.expects(:new).returns(view_repository)
43
-
44
- view.expects(:build).returns(data)
45
-
46
- view_repository.expects(:set).with("rddd::views::#{:view}#{:id}", data, anything)
47
- end
48
-
49
- it { subject }
50
- end
51
-
52
- describe '#invalidate' do
53
- subject { view.invalidate }
54
-
55
- before do
56
- ViewRepository.expects(:new).returns(view_repository)
57
-
58
- view_repository.expects(:set).with("rddd::views::#{:view}#{:id}", nil)
59
- end
60
-
61
- it { subject }
62
- end
63
-
64
- describe '#data' do
65
- subject { view.data }
66
-
67
- context 'without view repository' do
68
- before do
69
- view.expects(:build).returns(data)
70
- end
71
-
72
- it { subject.should == data }
73
- end
74
-
75
- context 'not cached' do
76
- before do
77
- ViewRepository.expects(:new).returns(view_repository)
78
- end
79
-
80
- before do
81
- view_repository.expects(:get).with("rddd::views::#{:view}#{:id}").returns(nil)
82
-
83
- view.expects(:build).returns(data)
84
-
85
- view_repository.expects(:set).with("rddd::views::#{:view}#{:id}", data, nil)
86
- end
87
-
88
- it { subject.should == data }
89
- end
90
-
91
- context 'cached' do
92
- before do
93
- ViewRepository.expects(:new).returns(view_repository)
94
- view_repository.expects(:get).with("rddd::views::#{:view}#{:id}").returns(data)
95
- end
96
-
97
- it { subject.should == data }
98
- end
99
- end
100
-
101
- describe 'cache disabling' do
102
- class CacheLessView < Rddd::Views::View
103
- cache :enabled => false
104
-
105
- def build
106
- end
107
- end
108
-
109
- let(:id) { stub('id') }
110
-
111
- let(:cache_less_view) do
112
- CacheLessView.new(id)
113
- end
114
-
115
- it 'should not try to load data from cache' do
116
- ViewRepository.expects(:new).never
117
-
118
- cache_less_view.expects(:build).returns(data)
119
-
120
- cache_less_view.data.should == data
121
- end
122
- end
123
-
124
- describe 'timeout enabling' do
125
- class TimeoutingView < Rddd::Views::View
126
- cache :timeout => 24 * 60
127
-
128
- def build
129
- end
130
- end
131
-
132
- let(:id) { :id }
133
-
134
- let(:timeouting_view) do
135
- TimeoutingView.new(id)
136
- end
137
-
138
- it 'should set timeout to cache data' do
139
- ViewRepository.expects(:new).returns(view_repository)
140
- view_repository.expects(:get).with("#{:timeoutingview}#{:id}").returns(nil)
141
-
142
- timeouting_view.expects(:build).returns(data)
143
-
144
- view_repository.expects(:set).with do |got_id, got_data, got_time|
145
- got_id == "#{:timeoutingview}#{:id}" &&
146
- got_data == data &&
147
- got_time.to_i == (Time.now + 24 * 60 * 60).to_i
148
- end
149
-
150
- timeouting_view.data
151
- end
152
- end
153
- end