dci 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|