ellington 0.0.1 → 0.0.2
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/Gemfile +14 -2
- data/Gemfile.lock +40 -0
- data/README.md +66 -17
- data/lib/ellington.rb +6 -4
- data/lib/ellington/attendant.rb +21 -0
- data/lib/ellington/conductor.rb +59 -0
- data/lib/ellington/connection.rb +11 -0
- data/lib/ellington/connection_list.rb +29 -0
- data/lib/ellington/errros.rb +22 -0
- data/lib/ellington/has_targets.rb +29 -0
- data/lib/ellington/line.rb +95 -0
- data/lib/ellington/line_info.rb +34 -0
- data/lib/ellington/line_list.rb +24 -0
- data/lib/ellington/logger.rb +11 -0
- data/lib/ellington/passenger.rb +67 -0
- data/lib/ellington/route.rb +114 -0
- data/lib/ellington/route_info.rb +25 -0
- data/lib/ellington/station.rb +88 -0
- data/lib/ellington/station_info.rb +11 -0
- data/lib/ellington/station_list.rb +24 -0
- data/lib/ellington/target.rb +25 -0
- data/lib/ellington/ticket.rb +22 -0
- data/lib/ellington/transition_info.rb +4 -0
- data/lib/ellington/unique_type_array.rb +39 -0
- data/lib/ellington/version.rb +1 -1
- data/test/attendant_test.rb +42 -0
- data/test/conductor_test.rb +32 -0
- data/test/example.rb +212 -0
- data/test/line_test.rb +95 -0
- data/test/passenger_test.rb +73 -0
- data/test/route_test.rb +51 -0
- data/test/station_test.rb +115 -0
- data/test/target_test.rb +54 -0
- data/test/test_helper.rb +6 -0
- data/test/ticket_test.rb +39 -0
- metadata +92 -14
- data/.gitignore +0 -17
- data/ellington.gemspec +0 -19
data/Gemfile
CHANGED
@@ -1,4 +1,16 @@
|
|
1
1
|
source 'https://rubygems.org'
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
gem "hero", "~> 0.1.7"
|
4
|
+
gem "state_jacket", "~> 0.0.7"
|
5
|
+
|
6
|
+
group :development, :test do
|
7
|
+
gem "yell"
|
8
|
+
gem "awesome_print"
|
9
|
+
gem "pry"
|
10
|
+
#gem "pry-nav"
|
11
|
+
gem "pry-stack_explorer"
|
12
|
+
#gem "pry-rescue"
|
13
|
+
gem "pry-exception_explorer"
|
14
|
+
gem "micro_test"
|
15
|
+
gem "micro_mock"
|
16
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
GEM
|
2
|
+
remote: https://rubygems.org/
|
3
|
+
specs:
|
4
|
+
awesome_print (1.1.0)
|
5
|
+
binding_of_caller (0.6.9)
|
6
|
+
debug_inspector (>= 0.0.1)
|
7
|
+
coderay (1.0.8)
|
8
|
+
debug_inspector (0.0.2)
|
9
|
+
hero (0.1.7)
|
10
|
+
method_source (0.8.1)
|
11
|
+
micro_mock (1.1.0)
|
12
|
+
micro_test (0.3.5)
|
13
|
+
os
|
14
|
+
os (0.9.6)
|
15
|
+
pry (0.9.12)
|
16
|
+
coderay (~> 1.0.5)
|
17
|
+
method_source (~> 0.8)
|
18
|
+
slop (~> 3.4)
|
19
|
+
pry-exception_explorer (0.2.3)
|
20
|
+
pry-stack_explorer (>= 0.4.6)
|
21
|
+
pry-stack_explorer (0.4.8)
|
22
|
+
binding_of_caller (~> 0.6.8)
|
23
|
+
pry (~> 0.9.11)
|
24
|
+
slop (3.4.3)
|
25
|
+
state_jacket (0.0.7)
|
26
|
+
yell (1.2.3)
|
27
|
+
|
28
|
+
PLATFORMS
|
29
|
+
ruby
|
30
|
+
|
31
|
+
DEPENDENCIES
|
32
|
+
awesome_print
|
33
|
+
hero (~> 0.1.7)
|
34
|
+
micro_mock
|
35
|
+
micro_test
|
36
|
+
pry
|
37
|
+
pry-exception_explorer
|
38
|
+
pry-stack_explorer
|
39
|
+
state_jacket (~> 0.0.7)
|
40
|
+
yell
|
data/README.md
CHANGED
@@ -1,29 +1,78 @@
|
|
1
|
-
# Ellington
|
1
|
+
# Ellington
|
2
|
+
Named after [Duke Ellington](http://www.dukeellington.com/) whose signature tune was ["Take the 'A' Train"](http://en.wikipedia.org/wiki/Take_the_%22A%22_Train).
|
3
|
+
The song was written about [New York City's A train](http://en.wikipedia.org/wiki/A_%28New_York_City_Subway_service%29).
|
2
4
|
|
3
|
-
|
5
|
+

|
4
6
|
|
5
|
-
|
7
|
+
#### Ellington is an architecture for modeling complex business processes.
|
6
8
|
|
7
|
-
|
9
|
+
Ellington is a collection of simple concepts designed to bring discipline, organization, and modularity to a project.
|
10
|
+
The base implementation is very light, weighing in around 500 lines.
|
8
11
|
|
9
|
-
|
12
|
+
The nomenclature is taken from [New York's subway system](http://en.wikipedia.org/wiki/New_York_City_Subway).
|
13
|
+
We've found that using cohesive physical metaphors helps people reason more clearly about the complexities of software.
|
14
|
+
*The subway analogy isn't perfect but gets pretty close.*
|
10
15
|
|
11
|
-
|
16
|
+
### Important
|
12
17
|
|
13
|
-
|
18
|
+
The Ellington architecture should only be applied **after** a good understanding of the problem domain has been established.
|
19
|
+
*We recommend [spiking a solution](http://en.wikipedia.org/wiki/Software_prototyping) to learn your project's requirements and then coming back to Ellington.*
|
14
20
|
|
15
|
-
|
21
|
+
## Goals
|
16
22
|
|
17
|
-
|
23
|
+
- Provide a nomenclature simple enough to be shared by engineering and the business team.
|
24
|
+
- Establish constraints to ensure that complex projects are easy to manage, develop, and maintain.
|
18
25
|
|
19
|
-
##
|
26
|
+
## Lexicon
|
20
27
|
|
21
|
-
|
28
|
+
- **[Conductor](https://github.com/hopsoft/ellington/wiki/Conductor)** - A supervisor responsible for gathering `passengers` and putting them on a `route`.
|
29
|
+
- **[Passenger](https://github.com/hopsoft/ellington/wiki/Passenger)** - The stateful context that will be riding the virtual subway.
|
30
|
+
- **[Route](https://github.com/hopsoft/ellington/wiki/Route)** - A collection of `lines` and their `connections`.
|
31
|
+
- **[Line](https://github.com/hopsoft/ellington/wiki/Line)** - A linear track that moves the `passenger` from point A to point B.
|
32
|
+
- **[Station](https://github.com/hopsoft/ellington/wiki/Station)** - A discreet chunk of business logic.
|
33
|
+
- **[State](https://github.com/hopsoft/ellington/wiki/State)** - A status or disposition assigned to the `passenger`.
|
22
34
|
|
23
|
-
|
35
|
+

|
36
|
+
|
37
|
+
#### Additional Terms
|
38
|
+
|
39
|
+
- **[State Catalog](https://github.com/hopsoft/ellington/wiki/State)** - A collection of states and their transitions.
|
40
|
+
- **[State Transition](https://github.com/hopsoft/ellington/wiki/State)** - The `transition`, performed on the `passenger`, from one `state` to another.
|
41
|
+
- **[Ticket](https://github.com/hopsoft/ellington/wiki/Ticket)** - An authorization token that indicates the `passenger` can ride a specific `route`.
|
42
|
+
- **[Goal](https://github.com/hopsoft/ellington/wiki/Goal)** - A list of expected `states`.
|
43
|
+
- **[Connection](https://github.com/hopsoft/ellington/wiki/Connection)** - A link between `lines`.
|
44
|
+
- **[Transfer](Transfer)** - A link between `routes`.
|
45
|
+
- **[Network](Network)** - An entire system of `routes` & `transfers` designed to work together.
|
46
|
+
|
47
|
+
|
48
|
+
|
49
|
+
|
50
|
+
|
51
|
+
|
52
|
+
|
53
|
+
|
54
|
+
|
55
|
+
|
56
|
+
|
57
|
+
|
58
|
+
|
59
|
+
|
60
|
+
|
61
|
+
|
62
|
+
|
63
|
+
|
64
|
+
|
65
|
+
|
66
|
+
|
67
|
+
```
|
68
|
+
Route class
|
69
|
+
composed of line instances
|
70
|
+
each line instance holds a ref to this route's class
|
71
|
+
|
72
|
+
Line class
|
73
|
+
composed of station instances
|
74
|
+
each station instance holds a ref to this line's class
|
75
|
+
|
76
|
+
Station class
|
77
|
+
```
|
24
78
|
|
25
|
-
1. Fork it
|
26
|
-
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
|
-
3. Commit your changes (`git commit -am 'Add some feature'`)
|
28
|
-
4. Push to the branch (`git push origin my-new-feature`)
|
29
|
-
5. Create new Pull Request
|
data/lib/ellington.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
|
-
require "
|
1
|
+
require "bundler"
|
2
|
+
Bundler.require
|
3
|
+
#Bundler.require :development, :test
|
4
|
+
|
5
|
+
path = File.join(File.dirname(__FILE__), "ellington/**/*.rb")
|
6
|
+
Dir[path].each { |file| require file }
|
2
7
|
|
3
|
-
module Ellington
|
4
|
-
# Your code goes here...
|
5
|
-
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Ellington
|
2
|
+
|
3
|
+
class Attendant
|
4
|
+
attr_reader :station, :passenger_transitions
|
5
|
+
|
6
|
+
def initialize(station)
|
7
|
+
@station = station
|
8
|
+
@passenger_transitions = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def update(transition_info)
|
12
|
+
passenger_transitions << transition_info
|
13
|
+
end
|
14
|
+
|
15
|
+
def approve?
|
16
|
+
passenger_transitions.length == 1 &&
|
17
|
+
station.states.include?(passenger_transitions.first.new_state)
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require "thread"
|
2
|
+
|
3
|
+
module Ellington
|
4
|
+
class Conductor
|
5
|
+
attr_reader :route, :conducting
|
6
|
+
|
7
|
+
def initialize(route)
|
8
|
+
@route = route
|
9
|
+
@conducting = false
|
10
|
+
end
|
11
|
+
|
12
|
+
def start(delay=0)
|
13
|
+
return if conducting
|
14
|
+
|
15
|
+
mutex.synchronize do
|
16
|
+
@stop = false
|
17
|
+
@conducting = true
|
18
|
+
end
|
19
|
+
|
20
|
+
thread = Thread.new do
|
21
|
+
loop do
|
22
|
+
if @stop
|
23
|
+
mutex.synchronize { @conducting = false }
|
24
|
+
break
|
25
|
+
end
|
26
|
+
gather_passengers.each do |passenger|
|
27
|
+
escort(passenger)
|
28
|
+
end
|
29
|
+
sleep delay
|
30
|
+
end
|
31
|
+
end
|
32
|
+
thread.join
|
33
|
+
end
|
34
|
+
|
35
|
+
def stop
|
36
|
+
mutex.synchronize { @stop = true }
|
37
|
+
end
|
38
|
+
|
39
|
+
def verify(passenger)
|
40
|
+
true
|
41
|
+
end
|
42
|
+
|
43
|
+
def gather_passengers
|
44
|
+
raise Ellington::NotImplementedError
|
45
|
+
end
|
46
|
+
|
47
|
+
def escort(passenger)
|
48
|
+
return unless verify(passenger) && passenger.locked?
|
49
|
+
route.lines.first.board passenger
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def mutex
|
55
|
+
@mutex ||= Mutex.new
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require "delegate"
|
2
|
+
module Ellington
|
3
|
+
class ConnectionList < SimpleDelegator
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@inner_list = []
|
7
|
+
super inner_list
|
8
|
+
end
|
9
|
+
|
10
|
+
def push(connection)
|
11
|
+
check connection
|
12
|
+
inner_list.push connection
|
13
|
+
end
|
14
|
+
|
15
|
+
alias_method :<<, :push
|
16
|
+
|
17
|
+
protected
|
18
|
+
|
19
|
+
attr_reader :inner_list
|
20
|
+
|
21
|
+
def check(connection)
|
22
|
+
matches = inner_list.select do |c|
|
23
|
+
c.line == connection && c.states == connection.states
|
24
|
+
end
|
25
|
+
raise Ellington::ListAlreadyContainsConnection unless matches.empty?
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Ellington
|
2
|
+
class ListAlreadyContainsType < StandardError
|
3
|
+
end
|
4
|
+
|
5
|
+
class InvalidStates < StandardError
|
6
|
+
end
|
7
|
+
|
8
|
+
class AttendandDisapproves < StandardError
|
9
|
+
end
|
10
|
+
|
11
|
+
class InvalidStateTransition < StandardError
|
12
|
+
end
|
13
|
+
|
14
|
+
class NotImplementedError < StandardError
|
15
|
+
end
|
16
|
+
|
17
|
+
class StationAlreadyBelongsToLine < StandardError
|
18
|
+
end
|
19
|
+
|
20
|
+
class PassengerAlreadyAssignedToTicket < StandardError
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Ellington
|
2
|
+
module HasTargets
|
3
|
+
|
4
|
+
def fail_target
|
5
|
+
@non_goal_target ||= begin
|
6
|
+
state_list = states.keys - goal
|
7
|
+
state_list.reject! { |state| state.to_s =~ /\AERROR/ }
|
8
|
+
Ellington::Target.new *state_list
|
9
|
+
end
|
10
|
+
end
|
11
|
+
alias_method :failed, :fail_target
|
12
|
+
|
13
|
+
def error_target
|
14
|
+
@error_target ||= begin
|
15
|
+
Ellington::Target.new states.keys - goal - fail_target
|
16
|
+
end
|
17
|
+
end
|
18
|
+
alias_method :errored, :error_target
|
19
|
+
|
20
|
+
def state(passenger)
|
21
|
+
case
|
22
|
+
when passed.satisfied?(passenger) then "PASS"
|
23
|
+
when failed.satisfied?(passenger) then "FAIL"
|
24
|
+
when errored.satisfied?(passenger) then "ERROR"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require "forwardable"
|
2
|
+
require "observer"
|
3
|
+
|
4
|
+
module Ellington
|
5
|
+
class Line
|
6
|
+
class << self
|
7
|
+
include Observable
|
8
|
+
attr_accessor :route
|
9
|
+
|
10
|
+
def stations
|
11
|
+
@stations ||= Ellington::StationList.new(self)
|
12
|
+
end
|
13
|
+
|
14
|
+
def pass_target(*states)
|
15
|
+
@goal ||= Ellington::Target.new(*states)
|
16
|
+
end
|
17
|
+
alias_method :passed, :pass_target
|
18
|
+
alias_method :goal, :pass_target
|
19
|
+
|
20
|
+
def connections
|
21
|
+
@connections ||= {}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
extend Forwardable
|
26
|
+
include Observable
|
27
|
+
include HasTargets
|
28
|
+
|
29
|
+
def_delegators :"self.class",
|
30
|
+
:route,
|
31
|
+
:route=,
|
32
|
+
:stations,
|
33
|
+
:pass_target,
|
34
|
+
:passed,
|
35
|
+
:goal
|
36
|
+
|
37
|
+
def board(passenger, options={})
|
38
|
+
formula.run passenger, options
|
39
|
+
end
|
40
|
+
|
41
|
+
def name
|
42
|
+
@name ||= "#{self.class.name}::#{route.name}"
|
43
|
+
end
|
44
|
+
|
45
|
+
def formula
|
46
|
+
@formula ||= begin
|
47
|
+
Hero::Formula[name]
|
48
|
+
Hero::Formula[name].steps.clear
|
49
|
+
stations.each do |station|
|
50
|
+
Hero::Formula[name].add_step station
|
51
|
+
end
|
52
|
+
Hero::Formula[name]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def states
|
57
|
+
@states ||= begin
|
58
|
+
catalog = StateJacket::Catalog.new
|
59
|
+
stations.each_with_index do |station, index|
|
60
|
+
catalog.merge! station.states
|
61
|
+
if index < stations.length - 1
|
62
|
+
next_station = stations[index + 1]
|
63
|
+
catalog[station.passed] = next_station.states.keys
|
64
|
+
end
|
65
|
+
end
|
66
|
+
catalog.lock
|
67
|
+
catalog
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def station_completed(station_info)
|
72
|
+
line_info = Ellington::LineInfo.new(self, station_info)
|
73
|
+
if line_info.station == stations.last ||
|
74
|
+
line_info.passenger.current_state == line_info.station.failed
|
75
|
+
log line_info, :station_completed => true
|
76
|
+
log line_info, :line_completed => true
|
77
|
+
changed
|
78
|
+
notify_observers line_info
|
79
|
+
else
|
80
|
+
log line_info, :station_completed => true
|
81
|
+
if line_info.passenger.current_state == line_info.station.errored
|
82
|
+
Ellington.logger.info "\n" if Ellington.logger
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
private
|
88
|
+
|
89
|
+
def log(line_info, options={})
|
90
|
+
return unless Ellington.logger
|
91
|
+
Ellington.logger.info line_info.log_message(options)
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
end
|