rddd 0.2.0 → 0.2.1
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/.DS_Store +0 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +3 -1
- data/README.md +8 -2
- data/Rakefile +5 -0
- data/lib/rddd.rb +5 -5
- data/lib/rddd/aggregates/entity.rb +75 -0
- data/lib/rddd/aggregates/finders.rb +23 -0
- data/lib/rddd/aggregates/repositories/base.rb +20 -0
- data/lib/rddd/aggregates/repositories/factory.rb +33 -0
- data/lib/rddd/aggregates/root.rb +25 -0
- data/lib/rddd/configuration.rb +1 -9
- data/lib/rddd/{service.rb → services/service.rb} +11 -9
- data/lib/rddd/{service_bus.rb → services/service_bus.rb} +23 -20
- data/lib/rddd/services/service_factory.rb +23 -0
- data/lib/rddd/version.rb +1 -1
- data/lib/rddd/views/cache.rb +24 -0
- data/lib/rddd/views/cacheable.rb +50 -0
- data/lib/rddd/views/view.rb +38 -0
- data/spec/integration_spec.rb +9 -9
- data/spec/lib/aggregates/entity_spec.rb +17 -0
- data/spec/lib/{aggregate_root_spec.rb → aggregates/root_spec.rb} +7 -7
- data/spec/lib/repositories/repository_factory_spec.rb +49 -0
- data/spec/lib/repositories/repository_spec.rb +30 -0
- data/spec/lib/{service_bus_spec.rb → services/service_bus_spec.rb} +6 -6
- data/spec/lib/services/service_factory_spec.rb +61 -0
- data/spec/lib/services/service_spec.rb +31 -0
- data/spec/lib/views/integration_spec.rb +153 -0
- metadata +29 -24
- data/lib/rddd/aggregate_root.rb +0 -17
- data/lib/rddd/aggregate_root_finders.rb +0 -21
- data/lib/rddd/entity.rb +0 -11
- data/lib/rddd/repository.rb +0 -16
- data/lib/rddd/repository_factory.rb +0 -25
- data/lib/rddd/service_factory.rb +0 -21
- data/spec/lib/entity_spec.rb +0 -17
- data/spec/lib/repository_factory_spec.rb +0 -47
- data/spec/lib/repository_spec.rb +0 -26
- data/spec/lib/service_factory_spec.rb +0 -59
- data/spec/lib/service_spec.rb +0 -27
data/.DS_Store
CHANGED
Binary file
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
rddd (0.
|
4
|
+
rddd (0.2.0)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: https://rubygems.org/
|
@@ -10,6 +10,7 @@ GEM
|
|
10
10
|
metaclass (0.0.1)
|
11
11
|
mocha (0.12.7)
|
12
12
|
metaclass (~> 0.0.1)
|
13
|
+
rake (0.9.2.2)
|
13
14
|
rspec (2.11.0)
|
14
15
|
rspec-core (~> 2.11.0)
|
15
16
|
rspec-expectations (~> 2.11.0)
|
@@ -24,5 +25,6 @@ PLATFORMS
|
|
24
25
|
|
25
26
|
DEPENDENCIES
|
26
27
|
mocha
|
28
|
+
rake
|
27
29
|
rddd!
|
28
30
|
rspec
|
data/README.md
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
[rDDD@sniffer](https://sniffer.io/info/public-project-135230969156499600) - see the code quality on Sniffer.io
|
2
2
|
|
3
|
+
[](https://travis-ci.org/petrjanda/rddd)
|
4
|
+
|
3
5
|
rddd
|
4
6
|
====
|
5
7
|
|
@@ -63,9 +65,13 @@ Presenter is the view to the domain data, which is typically aggregation over mu
|
|
63
65
|
language presenter is report. In Rddd, views output plain Ruby hashes, thus no domain object is leaking outside the domain
|
64
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.
|
65
67
|
|
66
|
-
### Aggregate root
|
68
|
+
### Aggregate root
|
69
|
+
|
70
|
+
### Entity
|
71
|
+
|
72
|
+
### Repository
|
67
73
|
|
68
|
-
|
74
|
+
### Factory
|
69
75
|
|
70
76
|
## Planned features
|
71
77
|
|
data/Rakefile
CHANGED
data/lib/rddd.rb
CHANGED
@@ -8,13 +8,13 @@ module Rddd
|
|
8
8
|
#
|
9
9
|
# ## Usage
|
10
10
|
#
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
11
|
+
# Rddd.configure do |config|
|
12
|
+
# config.service_creator = lambda do |name|
|
13
|
+
# class_name = "#{name.to_s.camel_case}Service"
|
14
14
|
#
|
15
|
-
#
|
15
|
+
# Rddd::Services.const_get(class_name.to_sym)
|
16
|
+
# end
|
16
17
|
# end
|
17
|
-
# end
|
18
18
|
#
|
19
19
|
def self.configure
|
20
20
|
yield(Rddd::Configuration.instance)
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module Rddd
|
2
|
+
module Aggregates
|
3
|
+
#
|
4
|
+
# Entity is the object with identity, which is unique across the system. Entities
|
5
|
+
# are considered equal only in case their identity is equal discregarding rest
|
6
|
+
# of the similarities.
|
7
|
+
#
|
8
|
+
# rDDD works agnostic to identifier type. String is used by default.
|
9
|
+
#
|
10
|
+
# Lets create a model of flat and rooms with some basic calculations:
|
11
|
+
#
|
12
|
+
# class Room < Entity
|
13
|
+
# def initialize(id, rooms)
|
14
|
+
# super(id)
|
15
|
+
#
|
16
|
+
# @rooms = rooms
|
17
|
+
# end
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# # (see AggregateRoot)
|
21
|
+
# class Flat < AggregateRoot
|
22
|
+
# attr_reader :size
|
23
|
+
#
|
24
|
+
# def initialize(id, size)
|
25
|
+
# super(id)
|
26
|
+
#
|
27
|
+
# @size = size
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# def size
|
31
|
+
# @rooms.reduce(0) {|total, room| total += room.size }
|
32
|
+
# end
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# rooms = []
|
36
|
+
# rooms << Room.new('kitchen', 15)
|
37
|
+
# rooms << Room.new('living room', 35)
|
38
|
+
# rooms << Room.new('bed room', 20)
|
39
|
+
#
|
40
|
+
# flat = Flat.new('A12TY83', rooms)
|
41
|
+
#
|
42
|
+
# flat.size #= 70
|
43
|
+
#
|
44
|
+
# As you can see Room entity has its identity (room name) which although isn't
|
45
|
+
# global, but local to the given flat instead. Its natural way how we seem rooms.
|
46
|
+
# We always identify flat/house first and then room within it. Maintaining global
|
47
|
+
# identity is sometimes overkill. Local identity can do just fine.
|
48
|
+
#
|
49
|
+
class Entity
|
50
|
+
|
51
|
+
#
|
52
|
+
# Entity unique identifier.
|
53
|
+
#
|
54
|
+
attr_reader :id
|
55
|
+
|
56
|
+
#
|
57
|
+
# Create entity with given identity.
|
58
|
+
#
|
59
|
+
# @param [String] Entity identity.
|
60
|
+
#
|
61
|
+
def initialize(id)
|
62
|
+
@id = id
|
63
|
+
end
|
64
|
+
|
65
|
+
#
|
66
|
+
# Compare two entities for equality.
|
67
|
+
#
|
68
|
+
# @param [Entity] Entity to compare with.
|
69
|
+
#
|
70
|
+
def ==(other)
|
71
|
+
@id == other.id
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Rddd
|
2
|
+
module Aggregates
|
3
|
+
module Finders
|
4
|
+
def finder(name)
|
5
|
+
craete_static_method(name) do |*args|
|
6
|
+
repository.send(name, *args)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def repository
|
11
|
+
Repositories::Factory.build(self)
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def craete_static_method(name, &block)
|
17
|
+
(class << self; self; end).module_eval do
|
18
|
+
define_method name, &block
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
#
|
2
|
+
# Repository base class.
|
3
|
+
#
|
4
|
+
module Rddd
|
5
|
+
module Repositories
|
6
|
+
class Base
|
7
|
+
def create(subject)
|
8
|
+
raise NotImplementedError
|
9
|
+
end
|
10
|
+
|
11
|
+
def update(subject)
|
12
|
+
raise NotImplementedError
|
13
|
+
end
|
14
|
+
|
15
|
+
def delete(subject)
|
16
|
+
raise NotImplementedError
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'rddd/configuration'
|
2
|
+
require 'rddd/aggregates/repositories/base'
|
3
|
+
|
4
|
+
module Rddd
|
5
|
+
module Repositories
|
6
|
+
class NotExistingRepository < RuntimeError
|
7
|
+
end
|
8
|
+
|
9
|
+
#
|
10
|
+
# @private
|
11
|
+
#
|
12
|
+
# Create instance of Repository using the Configration#repository_creator
|
13
|
+
# and passing the class along.
|
14
|
+
#
|
15
|
+
class Factory
|
16
|
+
CreatorNotGiven = Class.new(RuntimeError)
|
17
|
+
|
18
|
+
def self.build(clazz)
|
19
|
+
creator = Configuration.instance.repository_creator
|
20
|
+
|
21
|
+
raise CreatorNotGiven unless creator
|
22
|
+
|
23
|
+
begin
|
24
|
+
repository = creator.call(clazz)
|
25
|
+
rescue
|
26
|
+
raise NotExistingRepository unless repository
|
27
|
+
end
|
28
|
+
|
29
|
+
repository.new
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'rddd/aggregates/entity'
|
2
|
+
require 'rddd/aggregates/finders'
|
3
|
+
require 'rddd/aggregates/repositories/factory'
|
4
|
+
|
5
|
+
module Rddd
|
6
|
+
module Aggregates
|
7
|
+
#
|
8
|
+
# Domain model Entities (see Entity#initialize) are organized to clusters
|
9
|
+
# called Aggregates. Every Aggregate should have its Root. Aggregate Root
|
10
|
+
# should be only entity from Aggregate visible from outside to guarantee
|
11
|
+
# consistency of all operations performed on the given Aggregate.
|
12
|
+
#
|
13
|
+
class Root < Entity
|
14
|
+
extend Finders
|
15
|
+
|
16
|
+
finder :find
|
17
|
+
|
18
|
+
[:create, :update, :delete].each do |action|
|
19
|
+
define_method action do
|
20
|
+
self.class.repository.send(action, self)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/rddd/configuration.rb
CHANGED
@@ -8,14 +8,6 @@ module Rddd
|
|
8
8
|
class Configuration
|
9
9
|
include Singleton
|
10
10
|
|
11
|
-
|
12
|
-
|
13
|
-
def service_creator
|
14
|
-
@service_creator
|
15
|
-
end
|
16
|
-
|
17
|
-
def repository_creator
|
18
|
-
@repository_creator
|
19
|
-
end
|
11
|
+
attr_accessor :service_creator, :repository_creator
|
20
12
|
end
|
21
13
|
end
|
@@ -14,17 +14,19 @@
|
|
14
14
|
# Remember dummy data goes in, dummy data goes out.
|
15
15
|
#
|
16
16
|
module Rddd
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
17
|
+
module Services
|
18
|
+
class Service
|
19
|
+
def initialize(attributes = {})
|
20
|
+
@attributes = attributes
|
21
|
+
end
|
21
22
|
|
22
|
-
|
23
|
-
|
24
|
-
|
23
|
+
def valid?
|
24
|
+
true
|
25
|
+
end
|
25
26
|
|
26
|
-
|
27
|
-
|
27
|
+
def execute
|
28
|
+
raise NotImplementedError
|
29
|
+
end
|
28
30
|
end
|
29
31
|
end
|
30
32
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require 'rddd/service_factory'
|
1
|
+
require 'rddd/services/service_factory'
|
2
2
|
|
3
3
|
module Rddd
|
4
4
|
#
|
@@ -30,29 +30,32 @@ module Rddd
|
|
30
30
|
# end
|
31
31
|
#
|
32
32
|
#
|
33
|
-
module ServiceBus
|
34
|
-
#
|
35
|
-
# Execute the given service.
|
36
|
-
#
|
37
|
-
# @param [Symbol] service to be executed.
|
38
|
-
# @param [Hash] attributes to be passed to the service call.
|
39
|
-
# @param [Block] optional error callback block.
|
40
|
-
#
|
41
|
-
def execute_service(service_name, attributes = {})
|
42
|
-
service = build_service(service_name, attributes)
|
43
33
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
34
|
+
module Services
|
35
|
+
module ServiceBus
|
36
|
+
#
|
37
|
+
# Execute the given service.
|
38
|
+
#
|
39
|
+
# @param [Symbol] service to be executed.
|
40
|
+
# @param [Hash] attributes to be passed to the service call.
|
41
|
+
# @param [Block] optional error callback block.
|
42
|
+
#
|
43
|
+
def execute_service(service_name, attributes = {})
|
44
|
+
service = build_service(service_name, attributes)
|
48
45
|
|
49
|
-
|
50
|
-
|
46
|
+
unless service.valid?
|
47
|
+
yield(service.errors) if block_given?
|
48
|
+
return
|
49
|
+
end
|
51
50
|
|
52
|
-
|
51
|
+
service.execute
|
52
|
+
end
|
53
53
|
|
54
|
-
|
55
|
-
|
54
|
+
private
|
55
|
+
|
56
|
+
def build_service(service_name, attributes)
|
57
|
+
ServiceFactory.build(service_name, attributes)
|
58
|
+
end
|
56
59
|
end
|
57
60
|
end
|
58
61
|
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'rddd/configuration'
|
2
|
+
|
3
|
+
module Rddd
|
4
|
+
module Services
|
5
|
+
class ServiceFactory
|
6
|
+
CreatorNotGiven = Class.new(RuntimeError)
|
7
|
+
|
8
|
+
InvalidService = Class.new(RuntimeError)
|
9
|
+
|
10
|
+
def self.build(name, attributes)
|
11
|
+
creator = Configuration.instance.service_creator
|
12
|
+
|
13
|
+
raise CreatorNotGiven unless creator
|
14
|
+
|
15
|
+
begin
|
16
|
+
creator.call(name).new(attributes)
|
17
|
+
rescue
|
18
|
+
raise Rddd::Services::ServiceFactory::InvalidService
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/rddd/version.rb
CHANGED
@@ -0,0 +1,24 @@
|
|
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
|
@@ -0,0 +1,50 @@
|
|
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
|