dci 0.1.0 → 0.2.0
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/.ruby +4 -4
- data/demo/account_example.md +3 -3
- data/lib/dci/context.rb +92 -76
- data/lib/dci/role.rb +50 -47
- metadata +7 -7
data/.ruby
CHANGED
@@ -26,18 +26,18 @@ repositories:
|
|
26
26
|
scm: git
|
27
27
|
name: upstream
|
28
28
|
resources:
|
29
|
-
home:
|
30
|
-
code:
|
29
|
+
home: http://rubyworks.github.com/dci
|
30
|
+
code: http://github.com/rubyworks/dci
|
31
31
|
mail: http://groups.google.com/groups/rubyworks-mailinglist
|
32
32
|
extra: {}
|
33
33
|
load_path:
|
34
34
|
- lib
|
35
35
|
revision: 0
|
36
|
-
version: 0.
|
36
|
+
version: 0.2.0
|
37
37
|
name: dci
|
38
38
|
title: DCI
|
39
39
|
summary: DCI for Ruby
|
40
40
|
created: '2011-03-04'
|
41
41
|
description: Faithful DCI framework for Ruby application development.
|
42
42
|
organization: Rubyworks
|
43
|
-
date: '2012-02-
|
43
|
+
date: '2012-02-08'
|
data/demo/account_example.md
CHANGED
@@ -28,14 +28,14 @@ a balance of $100. (We're generous like that.)
|
|
28
28
|
We set up two Roles, one role for withdrawing money from an account,
|
29
29
|
and one for depositing money into an account.
|
30
30
|
|
31
|
-
class Account::TransferWithdraw < Role
|
31
|
+
class Account::TransferWithdraw < DCI::Role
|
32
32
|
def transfer(amount)
|
33
33
|
decrease_balance(amount)
|
34
34
|
#log "Tranfered $#{amount} from account ##{account_id}."
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
38
|
-
class Account::TransferDeposit < Role
|
38
|
+
class Account::TransferDeposit < DCI::Role
|
39
39
|
def transfer(amount)
|
40
40
|
increase_balance(amount)
|
41
41
|
#log "Tranfered $#{amount} into account ##{account_id}."
|
@@ -46,7 +46,7 @@ Now we create a Context which will assign accounts to the roles
|
|
46
46
|
and used to perfomr the transfer.
|
47
47
|
|
48
48
|
# We can think of a context as setting a scene.
|
49
|
-
class Account::Transfer < Context
|
49
|
+
class Account::Transfer < DCI::Context
|
50
50
|
role :source_account => Account::TransferWithdraw
|
51
51
|
role :destination_account => Account::TransferDeposit
|
52
52
|
|
data/lib/dci/context.rb
CHANGED
@@ -1,89 +1,105 @@
|
|
1
|
-
|
2
|
-
# for a given algorithm, scenario, or use case, as well as the code to map these
|
3
|
-
# roles into objects at run time and to enact the use case. Each role is bound
|
4
|
-
# to exactly one object during any given use case enactment; however, a single
|
5
|
-
# object may simultaneously play several roles. A context is instantiated at the
|
6
|
-
# beginning of the enactment of an algorithm, scenario, or use case. In summary,
|
7
|
-
# a Context comprises use cases and algorithms in which data objects are used
|
8
|
-
# through specific Roles.
|
9
|
-
#
|
10
|
-
# Each context represents one or more use cases. A context object is instantiated
|
11
|
-
# for each enactment of a use case for which it is responsible. Its main job
|
12
|
-
# is to identify the objects that will participate in the use case and to
|
13
|
-
# assign them to play the Roles which carry out the use case through their
|
14
|
-
# responsibilities. A role may comprise methods, and each method is some small
|
15
|
-
# part of the logic of an algorithm implementing a use case. Role methods run
|
16
|
-
# in the context of an object that is selected by the context to play that role
|
17
|
-
# for the current use case enactment. The role-to-object bindings that take place
|
18
|
-
# in a context can be contrasted with the polymorphism of vernacular object-oriented
|
19
|
-
# programming. The overall business functionality is the sum of complex, dynamic
|
20
|
-
# networks of methods decentralized in multiple contexts and their roles.
|
21
|
-
#
|
22
|
-
# Each context is a scope that includes identifiers that correspond to its roles.
|
23
|
-
# Any role executing within that context can refer to the other roles in that
|
24
|
-
# context through these identifiers. These identifiers have come to be called
|
25
|
-
# methodless roles. At use case enactment time, each and every one of these
|
26
|
-
# identifiers becomes bound to an object playing the corresponding Role for
|
27
|
-
# this Context.
|
28
|
-
#
|
29
|
-
# An example of a context could be a wire transfer between two accounts,
|
30
|
-
# where data models (the banking accounts) are used through roles named
|
31
|
-
# SourceAccount and # DestinationAccount.
|
32
|
-
#
|
33
|
-
# class Account::Transfer < Context
|
34
|
-
# role :source_account => Account::TransferWithdraw
|
35
|
-
# role :destination_account => Account::TransferDeposit
|
36
|
-
#
|
37
|
-
# def initialize(source_account, destination_account)
|
38
|
-
# self.source_account = source_account
|
39
|
-
# self.destination_account = destination_account
|
40
|
-
# end
|
41
|
-
#
|
42
|
-
# def transfer(amount)
|
43
|
-
# roles.each{ |role| role.transfer(amount) }
|
44
|
-
# end
|
45
|
-
# end
|
46
|
-
#
|
47
|
-
class Context
|
1
|
+
module DCI
|
48
2
|
|
49
|
-
#
|
50
|
-
#
|
3
|
+
# Context - The Context is the class (or its instance) whose code includes the roles
|
4
|
+
# for a given algorithm, scenario, or use case, as well as the code to map these
|
5
|
+
# roles into objects at run time and to enact the use case. Each role is bound
|
6
|
+
# to exactly one object during any given use case enactment; however, a single
|
7
|
+
# object may simultaneously play several roles. A context is instantiated at the
|
8
|
+
# beginning of the enactment of an algorithm, scenario, or use case. In summary,
|
9
|
+
# a Context comprises use cases and algorithms in which data objects are used
|
10
|
+
# through specific Roles.
|
51
11
|
#
|
52
|
-
|
53
|
-
|
54
|
-
|
12
|
+
# Each context represents one or more use cases. A context object is instantiated
|
13
|
+
# for each enactment of a use case for which it is responsible. Its main job
|
14
|
+
# is to identify the objects that will participate in the use case and to
|
15
|
+
# assign them to play the Roles which carry out the use case through their
|
16
|
+
# responsibilities. A role may comprise methods, and each method is some small
|
17
|
+
# part of the logic of an algorithm implementing a use case. Role methods run
|
18
|
+
# in the context of an object that is selected by the context to play that role
|
19
|
+
# for the current use case enactment. The role-to-object bindings that take place
|
20
|
+
# in a context can be contrasted with the polymorphism of vernacular object-oriented
|
21
|
+
# programming. The overall business functionality is the sum of complex, dynamic
|
22
|
+
# networks of methods decentralized in multiple contexts and their roles.
|
23
|
+
#
|
24
|
+
# Each context is a scope that includes identifiers that correspond to its roles.
|
25
|
+
# Any role executing within that context can refer to the other roles in that
|
26
|
+
# context through these identifiers. These identifiers have come to be called
|
27
|
+
# methodless roles. At use case enactment time, each and every one of these
|
28
|
+
# identifiers becomes bound to an object playing the corresponding Role for
|
29
|
+
# this Context.
|
30
|
+
#
|
31
|
+
# An example of a context could be a wire transfer between two accounts,
|
32
|
+
# where data models (the banking accounts) are used through roles named
|
33
|
+
# SourceAccount and # DestinationAccount.
|
34
|
+
#
|
35
|
+
# class Account::Transfer < Context
|
36
|
+
# role :source_account => Account::TransferWithdraw
|
37
|
+
# role :destination_account => Account::TransferDeposit
|
38
|
+
#
|
39
|
+
# def initialize(source_account, destination_account)
|
40
|
+
# self.source_account = source_account
|
41
|
+
# self.destination_account = destination_account
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# def transfer(amount)
|
45
|
+
# roles.each{ |role| role.transfer(amount) }
|
46
|
+
# end
|
47
|
+
# end
|
48
|
+
#
|
49
|
+
class Context
|
55
50
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
51
|
+
# Define a role given the name the role will use in this context,
|
52
|
+
# and the role class that is to be played.
|
53
|
+
#
|
54
|
+
def self.role(name_to_role)
|
55
|
+
@roles = nil # reset
|
56
|
+
|
57
|
+
name_to_role.each do |name, role|
|
58
|
+
define_method("role_#{name}"){ role }
|
60
59
|
|
61
|
-
|
62
|
-
|
60
|
+
module_eval %{
|
61
|
+
def #{name}=(data)
|
62
|
+
@#{name} = role_#{name}.new(data)
|
63
|
+
end
|
64
|
+
|
65
|
+
def #{name}
|
66
|
+
@#{name}
|
67
|
+
end
|
68
|
+
}
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Return a list of the names of defined roles.
|
73
|
+
#
|
74
|
+
# TODO: Consider private vs public roles.
|
75
|
+
def self.roles
|
76
|
+
@roles ||= (
|
77
|
+
list = []
|
78
|
+
instance_methods.each do |name|
|
79
|
+
next unless name.to_s.start_with?('role_')
|
80
|
+
list << name.to_s[5..-1].to_sym
|
63
81
|
end
|
64
|
-
|
82
|
+
list
|
83
|
+
)
|
65
84
|
end
|
66
|
-
end
|
67
85
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
86
|
+
# The default contructor can be used to assign roles via
|
87
|
+
# a settings hash.
|
88
|
+
#
|
89
|
+
def initialize(settings={})
|
90
|
+
settings.each do |k,v|
|
91
|
+
__send__("#{k}=", v)
|
92
|
+
end
|
74
93
|
end
|
75
|
-
end
|
76
94
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
next unless name.to_s.start_with?('role_')
|
84
|
-
list << __send__(name.to_s.sub('role_',''))
|
95
|
+
# Return Array of all role instances in the context.
|
96
|
+
#
|
97
|
+
def roles
|
98
|
+
self.class.roles.map do |name|
|
99
|
+
__send__(name)
|
100
|
+
end
|
85
101
|
end
|
86
|
-
|
102
|
+
|
87
103
|
end
|
88
104
|
|
89
105
|
end
|
data/lib/dci/role.rb
CHANGED
@@ -1,53 +1,56 @@
|
|
1
|
-
|
2
|
-
# is implemented as Roles which are played by objects at run time. These objects
|
3
|
-
# combine the state and methods of a Data (domain) object with methods (but no
|
4
|
-
# state, as Roles are stateless) from one or more Roles. In good DCI style,
|
5
|
-
# a Role addresses another object only in terms of its (methodless) Role. There
|
6
|
-
# is a special Role called `@self` which binds to the object playing the current
|
7
|
-
# Role. Code within a Role method may invoke a method on `@self` and thereby
|
8
|
-
# invoke a method of the Data part of the current object. One curious aspect
|
9
|
-
# of DCI is that these bindings are guaranteed to be in place only at run time
|
10
|
-
# (using a variety of approaches and conventions; C++ templates can be used to
|
11
|
-
# guarantee that the bindings will succeed). This means that Interactions—the
|
12
|
-
# Role methods—are generic. In fact, some DCI implementations use generics or
|
13
|
-
# templates for Roles.
|
14
|
-
#
|
15
|
-
# A Role is a stateless programming construct that corresponds to the end user's
|
16
|
-
# mental model of some entity in the system. A Role represents a collection of
|
17
|
-
# responsibilities. Whereas vernacular object-oriented programming speaks of
|
18
|
-
# objects or classes as the loci of responsibilities, DCI ascribes them to Roles.
|
19
|
-
# An object participating in a use case has responsibilities: those that it takes
|
20
|
-
# on as a result of playing a particular Role.
|
21
|
-
#
|
22
|
-
# In the money transfer use case, for example, the role methods in the
|
23
|
-
# SourceAccount and DestinationAccount enact the actual transfer.
|
24
|
-
#
|
25
|
-
# class Account::TransferWithdraw < Role
|
26
|
-
# def transfer(amount)
|
27
|
-
# decrease_balance(amount)
|
28
|
-
# log "Tranfered from account #{account_id} $#{amount}"
|
29
|
-
# end
|
30
|
-
# end
|
31
|
-
#
|
32
|
-
# class Account::TransferDepoit < Role
|
33
|
-
# def transfer(amount)
|
34
|
-
# increase_balance(amount)
|
35
|
-
# log "Tranfered into account #{account_id} $#{amount}"
|
36
|
-
# end
|
37
|
-
# end
|
38
|
-
#
|
39
|
-
class Role
|
1
|
+
module DCI
|
40
2
|
|
3
|
+
# Interaction - The Interaction is "what the system does." The Interaction
|
4
|
+
# is implemented as Roles which are played by objects at run time. These objects
|
5
|
+
# combine the state and methods of a Data (domain) object with methods (but no
|
6
|
+
# state, as Roles are stateless) from one or more Roles. In good DCI style,
|
7
|
+
# a Role addresses another object only in terms of its (methodless) Role. There
|
8
|
+
# is a special Role called `@self` which binds to the object playing the current
|
9
|
+
# Role. Code within a Role method may invoke a method on `@self` and thereby
|
10
|
+
# invoke a method of the Data part of the current object. One curious aspect
|
11
|
+
# of DCI is that these bindings are guaranteed to be in place only at run time
|
12
|
+
# (using a variety of approaches and conventions; C++ templates can be used to
|
13
|
+
# guarantee that the bindings will succeed). This means that Interactions—the
|
14
|
+
# Role methods—are generic. In fact, some DCI implementations use generics or
|
15
|
+
# templates for Roles.
|
41
16
|
#
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
17
|
+
# A Role is a stateless programming construct that corresponds to the end user's
|
18
|
+
# mental model of some entity in the system. A Role represents a collection of
|
19
|
+
# responsibilities. Whereas vernacular object-oriented programming speaks of
|
20
|
+
# objects or classes as the loci of responsibilities, DCI ascribes them to Roles.
|
21
|
+
# An object participating in a use case has responsibilities: those that it takes
|
22
|
+
# on as a result of playing a particular Role.
|
23
|
+
#
|
24
|
+
# In the money transfer use case, for example, the role methods in the
|
25
|
+
# SourceAccount and DestinationAccount enact the actual transfer.
|
26
|
+
#
|
27
|
+
# class Account::TransferWithdraw < Role
|
28
|
+
# def transfer(amount)
|
29
|
+
# decrease_balance(amount)
|
30
|
+
# log "Tranfered from account #{account_id} $#{amount}"
|
31
|
+
# end
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# class Account::TransferDepoit < Role
|
35
|
+
# def transfer(amount)
|
36
|
+
# increase_balance(amount)
|
37
|
+
# log "Tranfered into account #{account_id} $#{amount}"
|
38
|
+
# end
|
39
|
+
# end
|
46
40
|
#
|
47
|
-
|
48
|
-
|
49
|
-
|
41
|
+
class Role
|
42
|
+
|
43
|
+
#
|
44
|
+
def initialize(player)
|
45
|
+
@self = player
|
46
|
+
end
|
47
|
+
|
48
|
+
#
|
49
|
+
# @todo Should use #public_send?
|
50
|
+
def method_missing(s, *a, &b)
|
51
|
+
@self.__send__(s, *a, &b)
|
52
|
+
end
|
53
|
+
|
50
54
|
end
|
51
55
|
|
52
56
|
end
|
53
|
-
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dci
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-02-
|
12
|
+
date: 2012-02-09 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: detroit
|
16
|
-
requirement: &
|
16
|
+
requirement: &12937320 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *12937320
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: qed
|
27
|
-
requirement: &
|
27
|
+
requirement: &12935480 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,7 +32,7 @@ dependencies:
|
|
32
32
|
version: '0'
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *12935480
|
36
36
|
description: Faithful DCI framework for Ruby application development.
|
37
37
|
email:
|
38
38
|
- transfire@gmail.com
|
@@ -54,7 +54,7 @@ files:
|
|
54
54
|
- COPYING.md
|
55
55
|
- HISTORY.md
|
56
56
|
- README.md
|
57
|
-
homepage:
|
57
|
+
homepage: http://rubyworks.github.com/dci
|
58
58
|
licenses:
|
59
59
|
- BSD-2-Clause
|
60
60
|
post_install_message:
|