rddd 0.2.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Build Status](https://travis-ci.org/petrjanda/rddd.png?branch=master)](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
|