wabur 0.1.0d1
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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +58 -0
- data/lib/wab/controller.rb +175 -0
- data/lib/wab/data.rb +73 -0
- data/lib/wab/model.rb +136 -0
- data/lib/wab/shell.rb +46 -0
- data/lib/wab/version.rb +5 -0
- data/lib/wab/view.rb +21 -0
- data/lib/wab.rb +5 -0
- data/pages/Architecture.md +200 -0
- data/pages/Goals.md +110 -0
- data/pages/Plan.md +11 -0
- metadata +64 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: f881d414a328e743c262513bde6977d3a28a3ec3
|
4
|
+
data.tar.gz: f3d99084f2ad9ccb619ceb3572a4c0bc66863c5f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0b4ca9f500c474908d6a4b740a86ec360f0386332e7d55bd724743124d041983c1854834cbac66a874c73b2a7f1b3c674224c830a4d0b3dee3f50204efe89b3f
|
7
|
+
data.tar.gz: b71155371f81f098034483486ff036e36ccaee5c460072dc61a7fe3012df97de643c2eea5514c9561f50ace56c5b1685cbde4cd04e3a322d5dd490ce15f71f1a
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2017 Peter Ohler
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
# WABuR (Web Application Builder using Ruby)
|
2
|
+
|
3
|
+
Ruby is a great language but for performance C is a better alternative. It is
|
4
|
+
possible to get the best of both as evident with [Oj](http://www.ohler.com/oj)
|
5
|
+
and [Ox](http://www.ohler.com/ox). C by itself allowed
|
6
|
+
[Piper](http://piperpushcache.com), a fast push web server to be developed and
|
7
|
+
is being used to develop [OpO](http://opo.technology) a high performance graph
|
8
|
+
and JSON database. This project takes from all of those projects for a hight
|
9
|
+
performance Ruby web framework.
|
10
|
+
|
11
|
+
Ruby on Rails has made Ruby main stream. While RoR is fine for some
|
12
|
+
applications there are others that might be better served with an alternative.
|
13
|
+
This project was started as an alternative to Ruby on Rails with a focus on
|
14
|
+
performance and easy of use.
|
15
|
+
|
16
|
+
Why develop an alternative to Rails? Rails popularity has been waning. It is
|
17
|
+
still huge but not as popular as it used to be. RoR is not going away any time
|
18
|
+
soon but for some applications alternatives are needed.
|
19
|
+
|
20
|
+
## Goals
|
21
|
+
|
22
|
+
Lets start with the assumption that we want to continue to use Ruby. The goal
|
23
|
+
of this project is to provide a high performance, easy to use, and fully
|
24
|
+
featured web framework with Ruby at the core. By keeping the core, the
|
25
|
+
business logic in Ruby but allowing options for other parts to be in different
|
26
|
+
languages the best use of each can be utilized.
|
27
|
+
|
28
|
+
Targets are a throughput of 100K page fetches per second at a latency of no
|
29
|
+
more than 1 millisecond on a desktop machine. That is more than an order of
|
30
|
+
magnitude faster than Rails and on par with other top of the performance tier
|
31
|
+
web frameworks across all languages.
|
32
|
+
|
33
|
+
[Continue reading ...](pages/Goals.md)
|
34
|
+
|
35
|
+
## Architecture
|
36
|
+
|
37
|
+
The architecture provides many options but it keeps clean and clear APIs
|
38
|
+
between modules. This pluggable design allows for unit test drivers and
|
39
|
+
various levels of deployment options from straight Ruby to a high performance
|
40
|
+
C shell that handles HTTP and data storage.
|
41
|
+
|
42
|
+

|
43
|
+
|
44
|
+
[Continue reading ...](pages/Architecture.md)
|
45
|
+
|
46
|
+
## Participate
|
47
|
+
|
48
|
+
If you like the idea and want to help out or become a core developer on the
|
49
|
+
project send me an [email](mailto:peter@ohler.com). Get in on the ground floor
|
50
|
+
and lets make something awesome together.
|
51
|
+
|
52
|
+
## Planning
|
53
|
+
|
54
|
+
The plan is informal and high level until more details are defined.
|
55
|
+
|
56
|
+
[Details ...](pages/Plan.md)
|
57
|
+
|
58
|
+
|
@@ -0,0 +1,175 @@
|
|
1
|
+
module WAB
|
2
|
+
|
3
|
+
# A Controller class or a duck-typed alternative should be created and
|
4
|
+
# registered with a Shell for any type that implements behavior other than
|
5
|
+
# the default REST API processing. If a public method is not found on the
|
6
|
+
# class instance then the default REST API processing will be used.
|
7
|
+
#
|
8
|
+
# A description of the available methods is included as private methods.
|
9
|
+
class Controller # :doc: all
|
10
|
+
attribute_accessor :shell
|
11
|
+
attribute_accessor :view
|
12
|
+
attribute_accessor :model
|
13
|
+
|
14
|
+
# Create a instance.
|
15
|
+
def initialize()
|
16
|
+
@shell = nil
|
17
|
+
@view = shell.view
|
18
|
+
@model = shell.model
|
19
|
+
# TBD
|
20
|
+
end
|
21
|
+
|
22
|
+
# Handler for paths that do not match the REST pattern. Only called on the
|
23
|
+
# default controller.
|
24
|
+
#
|
25
|
+
# Processing result are passed back to the view which forward the result
|
26
|
+
# on to the requester. The result, is not nil, should be a Data instance.
|
27
|
+
#
|
28
|
+
# path:: identifies operation and additional data in a / delimited
|
29
|
+
# format. It is up to the controller to decide how to process the
|
30
|
+
# request.
|
31
|
+
# data:: data to be processed
|
32
|
+
def handle(path, data)
|
33
|
+
nil
|
34
|
+
end
|
35
|
+
|
36
|
+
# To make the desired methods active while processing the desired method
|
37
|
+
# should be made public in the subclasses. If the methods remain private
|
38
|
+
# they will not be called.
|
39
|
+
private
|
40
|
+
|
41
|
+
# Should create a new data object.
|
42
|
+
#
|
43
|
+
# The return should be the identifier for the object created or if
|
44
|
+
# +with_data+ is true a Data object with an +id+ attribute and a +data+
|
45
|
+
# attribute that contains the full object details.
|
46
|
+
#
|
47
|
+
# On error an Exception should be raised.
|
48
|
+
#
|
49
|
+
# data:: the data to use as a new object.
|
50
|
+
# with_data:: flag indicating the response should include the new object
|
51
|
+
def create(data, with_data=false) # :doc:
|
52
|
+
# TBD implement the default behavior as an example or starting point
|
53
|
+
end
|
54
|
+
|
55
|
+
# Should return the object with the +id+ provided.
|
56
|
+
#
|
57
|
+
# The function should return the result of a fetch on the model with the
|
58
|
+
# provided +id+. The result should be a Data instance which is either the
|
59
|
+
# object data or a wrapper that include the object id in an +id+ attribute
|
60
|
+
# and the object data itself in a +data+ attribute depending on the value
|
61
|
+
# of the +with_id+ argument.
|
62
|
+
#
|
63
|
+
# id:: identifier of the object
|
64
|
+
# with_id:: if true wrap the object data with an envelope that includes
|
65
|
+
# the id as well as the object data.
|
66
|
+
def read(id, with_id=false) # :doc:
|
67
|
+
# TBD implement the default behavior as an example or starting point
|
68
|
+
end
|
69
|
+
|
70
|
+
# Should return the objects with attributes matching the +attrs+ argument.
|
71
|
+
#
|
72
|
+
# The return should be a Hash where the keys are the matching object
|
73
|
+
# identifiers and the value are the object data. An empty Hash or nil
|
74
|
+
# indicates there were no matches.
|
75
|
+
#
|
76
|
+
# attrs:: a Hash with keys matching paths into the target objects and value
|
77
|
+
# equal to the target attribute values. A path can be an array of
|
78
|
+
# keys used to walk a path to the target or a +.+ delimited set of
|
79
|
+
# keys.
|
80
|
+
def read_by_attrs(attrs) # :doc:
|
81
|
+
# TBD implement the default behavior as an example or starting point
|
82
|
+
end
|
83
|
+
|
84
|
+
# Replaces the object data for the identified object.
|
85
|
+
#
|
86
|
+
# The return should be the identifier for the object updated or if
|
87
|
+
# +with_data+ is true a Data object with an +id+ attribute and a +data+
|
88
|
+
# attribute that contains the full object details. Note that depending on
|
89
|
+
# the implemenation the identifier may change as a result of an update.
|
90
|
+
#
|
91
|
+
# On error an Exception should be raised.
|
92
|
+
#
|
93
|
+
# id:: identifier of the object to be replaced
|
94
|
+
# data:: the data to use as a new object.
|
95
|
+
# with_data:: flag indicating the response should include the new object
|
96
|
+
def update(id, data, with_data=false) # :doc:
|
97
|
+
# TBD implement the default behavior as an example or starting point
|
98
|
+
end
|
99
|
+
|
100
|
+
# Delete the identified object.
|
101
|
+
#
|
102
|
+
# On success the deleted object identifier is returned. If the object is
|
103
|
+
# not found then nil is returned. On error an Exception should be raised.
|
104
|
+
#
|
105
|
+
# id:: identifier of the object to be deleted
|
106
|
+
def delete(id) # :doc:
|
107
|
+
# TBD implement the default behavior as an example or starting point
|
108
|
+
end
|
109
|
+
|
110
|
+
# Delete all object that match the set of provided attribute values.
|
111
|
+
#
|
112
|
+
# An array of deleted object identifiers should be returned.
|
113
|
+
#
|
114
|
+
# attrs:: a Hash with keys matching paths into the target objects and value
|
115
|
+
# equal to the target attribute values. A path can be an array of
|
116
|
+
# keys used to walk a path to the target or a +.+ delimited set of
|
117
|
+
# keys.
|
118
|
+
def delete_by_attrs(attrs) # :doc:
|
119
|
+
# TBD implement the default behavior as an example or starting point
|
120
|
+
end
|
121
|
+
|
122
|
+
# Return a Hash of all the objects of the type associated with the
|
123
|
+
# controller.
|
124
|
+
#
|
125
|
+
# The return hash keys should be the identifiers of the objects and the
|
126
|
+
# the values should be either nil or the object data if the +with_data+
|
127
|
+
# flag is true. If the response will be larger than supported one of the
|
128
|
+
# keys should be the empty string which indicated additional instance
|
129
|
+
# exists and were not provided.
|
130
|
+
#
|
131
|
+
# Note that this could return a very large set of data. If the number of
|
132
|
+
# instances in the type is large the +search()+ might be more appropriate
|
133
|
+
# as it allows for paging of results and sorting.
|
134
|
+
#
|
135
|
+
# with_data:: flag indicating the return should include object data
|
136
|
+
def list(with_data=false) # :doc:
|
137
|
+
# TBD implement the default behavior as an example or starting point
|
138
|
+
end
|
139
|
+
|
140
|
+
# Search using a TQL SELECT.
|
141
|
+
#
|
142
|
+
# The provided TQL[http://opo.technology/pages/doc/tql/index.html] can be
|
143
|
+
# either the JSON syntax or the friendly syntax. The call exists on the
|
144
|
+
# controller to allow filtering and permission checking before
|
145
|
+
# execution. Only the default controller is expected to provide a public
|
146
|
+
# version of this method.
|
147
|
+
#
|
148
|
+
# query:: query
|
149
|
+
# format:: can be one of :TQL or :TQL_JSON. The :GraphQL option is
|
150
|
+
# reserved for the future.
|
151
|
+
def search(query, format=:TQL) # :doc:
|
152
|
+
# TBD implement the default behavior as an example or starting point
|
153
|
+
end
|
154
|
+
|
155
|
+
# Subscribe to changes in data pushed from the model that will be passed
|
156
|
+
# to the view with the +push+ method if it passes the supplied filter.
|
157
|
+
#
|
158
|
+
# The +view+ +changed+ method is called when changes in data cause
|
159
|
+
# the associated object to pass the provided filter.
|
160
|
+
#
|
161
|
+
# filter:: the filter to apply to the data. TBD the nature of the filter is pending.
|
162
|
+
def subscribe(filter)
|
163
|
+
# TBD
|
164
|
+
end
|
165
|
+
|
166
|
+
# Called by the model when data changes if supported by the model storage
|
167
|
+
# component.
|
168
|
+
#
|
169
|
+
# data:: the data that has changed
|
170
|
+
def changed(data) # :doc:
|
171
|
+
# TBD implement the default behavior as an example or starting point
|
172
|
+
end
|
173
|
+
|
174
|
+
end # Controller
|
175
|
+
end # WAB
|
data/lib/wab/data.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
|
2
|
+
module WAB
|
3
|
+
|
4
|
+
# The class representing the cananical data structure in WAB. Typically the
|
5
|
+
# Data instances are factory created by the Shell and will most likely not
|
6
|
+
# be instance of this class but rather a class that is a duck-type of this
|
7
|
+
# class (has the same methods and behavior).
|
8
|
+
class Data
|
9
|
+
|
10
|
+
# This method is included only for testing purposes of the Ruby base
|
11
|
+
# Shell. It should only be called by the Shell. Create a new Data instance
|
12
|
+
# with the initial value provided. The value must be a Hash or Array. The
|
13
|
+
# members of the Hash or Array must be nil, boolean, String, Integer,
|
14
|
+
# Float, BigDecimal, Array, Hash, Time, WAB::UUID, or WAB::IRI.
|
15
|
+
def initialize(value=nil)
|
16
|
+
# TBD
|
17
|
+
end
|
18
|
+
|
19
|
+
# Gets the Data element or value identified by the path where the path
|
20
|
+
# elements are separated by the '.' character. The path can also be a
|
21
|
+
# array of path node identifiers. For example, child.grandchild is the
|
22
|
+
# same as ['child', 'grandchild'].
|
23
|
+
def get(path)
|
24
|
+
# TBD
|
25
|
+
end
|
26
|
+
|
27
|
+
# Sets the node value identified by the path where the path elements are
|
28
|
+
# separated by the '.' character. The path can also be a array of path
|
29
|
+
# node identifiers. For example, child.grandchild is the same as ['child',
|
30
|
+
# 'grandchild']. The value must be one of the allowed data values
|
31
|
+
# described in the initialize method.
|
32
|
+
def set(path, value)
|
33
|
+
# TBD
|
34
|
+
end
|
35
|
+
|
36
|
+
# Each child of the Data instance is provided as an argument to a block
|
37
|
+
# when the each method is called.
|
38
|
+
def each()
|
39
|
+
# TBD
|
40
|
+
end
|
41
|
+
|
42
|
+
# Each leaf of the Data instance is provided as an argument to a block
|
43
|
+
# when the each method is called. A leaf is a primitive that has no
|
44
|
+
# children and will be nil, a Boolean, String, Numberic, Time, WAB::UUID,
|
45
|
+
# or WAB::IRI.
|
46
|
+
def each_leaf()
|
47
|
+
# TBD
|
48
|
+
end
|
49
|
+
|
50
|
+
# Make a deep copy of the Data instance.
|
51
|
+
def clone()
|
52
|
+
# TBD
|
53
|
+
end
|
54
|
+
|
55
|
+
# Returns the instance converted to native Ruby values such as a Hash,
|
56
|
+
# Array, etc.
|
57
|
+
def native()
|
58
|
+
# TBD
|
59
|
+
end
|
60
|
+
|
61
|
+
# Returns true if self and other are either the same or have the same
|
62
|
+
# contents. This is a deep comparison.
|
63
|
+
def eql?(other)
|
64
|
+
# TBD
|
65
|
+
end
|
66
|
+
|
67
|
+
# Encode the data as a JSON string.
|
68
|
+
def json(indent=0)
|
69
|
+
# TBD
|
70
|
+
end
|
71
|
+
|
72
|
+
end # Data
|
73
|
+
end # WAB
|
data/lib/wab/model.rb
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
|
2
|
+
module WAB
|
3
|
+
|
4
|
+
# Represents the model portion of a MVC design pattern. It must respond to
|
5
|
+
# request to update the store using either the CRUD type operations that
|
6
|
+
# match the controller.
|
7
|
+
class Model
|
8
|
+
|
9
|
+
# Should create a new data object.
|
10
|
+
#
|
11
|
+
# The return should be the identifier for the object created or if
|
12
|
+
# +with_data+ is true a Data object with an +id+ attribute and a +data+
|
13
|
+
# attribute that contains the full object details.
|
14
|
+
#
|
15
|
+
# On error an Exception should be raised.
|
16
|
+
#
|
17
|
+
# data:: the data to use as a new object.
|
18
|
+
# with_data:: flag indicating the response should include the new object
|
19
|
+
def create(data, with_data=false) # :doc:
|
20
|
+
# TBD implement the default behavior as an example or starting point
|
21
|
+
end
|
22
|
+
|
23
|
+
# Should return the object with the +id+ provided.
|
24
|
+
#
|
25
|
+
# The function should return the result of a fetch on the model with the
|
26
|
+
# provided +id+. The result should be a Data instance which is either the
|
27
|
+
# object data or a wrapper that include the object id in an +id+ attribute
|
28
|
+
# and the object data itself in a +data+ attribute depending on the value
|
29
|
+
# of the +with_id+ argument.
|
30
|
+
#
|
31
|
+
# id:: identifier of the object
|
32
|
+
# with_id:: if true wrap the object data with an envelope that includes
|
33
|
+
# the id as well as the object data.
|
34
|
+
def read(id, with_id=false) # :doc:
|
35
|
+
# TBD implement the default behavior as an example or starting point
|
36
|
+
end
|
37
|
+
|
38
|
+
# Should return the objects with attributes matching the +attrs+ argument.
|
39
|
+
#
|
40
|
+
# The return should be a Hash where the keys are the matching object
|
41
|
+
# identifiers and the value are the object data. An empty Hash or nil
|
42
|
+
# indicates there were no matches.
|
43
|
+
#
|
44
|
+
# attrs:: a Hash with keys matching paths into the target objects and value
|
45
|
+
# equal to the target attribute values. A path can be an array of
|
46
|
+
# keys used to walk a path to the target or a +.+ delimited set of
|
47
|
+
# keys.
|
48
|
+
def read_by_attrs(attrs) # :doc:
|
49
|
+
# TBD implement the default behavior as an example or starting point
|
50
|
+
end
|
51
|
+
|
52
|
+
# Replaces the object data for the identified object.
|
53
|
+
#
|
54
|
+
# The return should be the identifier for the object updated or if
|
55
|
+
# +with_data+ is true a Data object with an +id+ attribute and a +data+
|
56
|
+
# attribute that contains the full object details. Note that depending on
|
57
|
+
# the implemenation the identifier may change as a result of an update.
|
58
|
+
#
|
59
|
+
# On error an Exception should be raised.
|
60
|
+
#
|
61
|
+
# id:: identifier of the object to be replaced
|
62
|
+
# data:: the data to use as a new object.
|
63
|
+
# with_data:: flag indicating the response should include the new object
|
64
|
+
def update(id, data, with_data=false) # :doc:
|
65
|
+
# TBD implement the default behavior as an example or starting point
|
66
|
+
end
|
67
|
+
|
68
|
+
# Delete the identified object.
|
69
|
+
#
|
70
|
+
# On success the deleted object identifier is returned. If the object is
|
71
|
+
# not found then nil is returned. On error an Exception should be raised.
|
72
|
+
#
|
73
|
+
# id:: identifier of the object to be deleted
|
74
|
+
def delete(id) # :doc:
|
75
|
+
# TBD implement the default behavior as an example or starting point
|
76
|
+
end
|
77
|
+
|
78
|
+
# Delete all object that match the set of provided attribute values.
|
79
|
+
#
|
80
|
+
# An array of deleted object identifiers should be returned.
|
81
|
+
#
|
82
|
+
# attrs:: a Hash with keys matching paths into the target objects and value
|
83
|
+
# equal to the target attribute values. A path can be an array of
|
84
|
+
# keys used to walk a path to the target or a +.+ delimited set of
|
85
|
+
# keys.
|
86
|
+
def delete_by_attrs(attrs) # :doc:
|
87
|
+
# TBD implement the default behavior as an example or starting point
|
88
|
+
end
|
89
|
+
|
90
|
+
# Return a Hash of all the objects of the type associated with the
|
91
|
+
# controller.
|
92
|
+
#
|
93
|
+
# The return hash keys should be the identifiers of the objects and the
|
94
|
+
# the values should be either nil or the object data if the +with_data+
|
95
|
+
# flag is true. If the response will be larger than supported one of the
|
96
|
+
# keys should be the empty string which indicated additional instance
|
97
|
+
# exists and were not provided.
|
98
|
+
#
|
99
|
+
# Note that this could return a very large set of data. If the number of
|
100
|
+
# instances in the type is large the +search()+ might be more appropriate
|
101
|
+
# as it allows for paging of results and sorting.
|
102
|
+
#
|
103
|
+
# with_data:: flag indicating the return should include object data
|
104
|
+
def list(with_data=false) # :doc:
|
105
|
+
# TBD implement the default behavior as an example or starting point
|
106
|
+
end
|
107
|
+
|
108
|
+
# Search using a TQL SELECT.
|
109
|
+
#
|
110
|
+
# The provided TQL[http://opo.technology/pages/doc/tql/index.html] can be
|
111
|
+
# either the JSON syntax or the friendly syntax. The call exists on the
|
112
|
+
# controller to allow filtering and permission checking before
|
113
|
+
# execution. Only the default controller is expected to provide a public
|
114
|
+
# version of this method.
|
115
|
+
#
|
116
|
+
# query:: query
|
117
|
+
# format:: can be one of :TQL or :TQL_JSON. The :GraphQL option is
|
118
|
+
# reserved for the future.
|
119
|
+
def search(query, format=:TQL) # :doc:
|
120
|
+
# TBD implement the default behavior as an example or starting point
|
121
|
+
end
|
122
|
+
|
123
|
+
# Subscribe to changes in stored data and push changes to the controller
|
124
|
+
# if it passes the supplied filter.
|
125
|
+
#
|
126
|
+
# The +controller+ +changed+ method is called when changes in data cause
|
127
|
+
# the associated object to pass the provided filter.
|
128
|
+
#
|
129
|
+
# controller:: the controller to notify of changed
|
130
|
+
# filter:: the filter to apply to the data. Syntax is that TQL uses for the FILTER clause.
|
131
|
+
def subscribe(controller, filter)
|
132
|
+
# TBD
|
133
|
+
end
|
134
|
+
|
135
|
+
end # Model
|
136
|
+
end # WAB
|
data/lib/wab/shell.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
module WAB
|
2
|
+
|
3
|
+
# The Shell is a duck-typed class. Any shell alternative should implement
|
4
|
+
# all the methods defined in the class except the +initialize+ method. This
|
5
|
+
# class is also the default Ruby version of the shell that can be used for
|
6
|
+
# development and small systems.
|
7
|
+
class Shell
|
8
|
+
|
9
|
+
# Sets up the shell with a view, model, and type_key.
|
10
|
+
def initialize(view, model, type_key='kind')
|
11
|
+
@view = view
|
12
|
+
@model = model
|
13
|
+
@controllers = {}
|
14
|
+
@type_key = type_key
|
15
|
+
end
|
16
|
+
|
17
|
+
# Returns the view instance that can be used for pushing data to a view.
|
18
|
+
def view()
|
19
|
+
@view
|
20
|
+
end
|
21
|
+
|
22
|
+
# Returns the model instance that can be used to get and modify data in
|
23
|
+
# the data store.
|
24
|
+
def model()
|
25
|
+
@model
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns the path where a data type is located. The default is 'kind'.
|
29
|
+
def type_key()
|
30
|
+
@type_key
|
31
|
+
end
|
32
|
+
|
33
|
+
# Register a controller for a named type.
|
34
|
+
#
|
35
|
+
# If a request is received for an unregistered type the default controller
|
36
|
+
# will be used. The default controller is registered with a +nil+ key.
|
37
|
+
#
|
38
|
+
# type:: type name
|
39
|
+
# controller:: Controller instance for handling requests for the identified +type+
|
40
|
+
def register_controller(type, controller)
|
41
|
+
controller.shell = self
|
42
|
+
@controllers[type] = controller
|
43
|
+
end
|
44
|
+
|
45
|
+
end # Shell
|
46
|
+
end # WAB
|
data/lib/wab/version.rb
ADDED
data/lib/wab/view.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
module WAB
|
2
|
+
|
3
|
+
# Represents the view in the MVC design pattern. It is primarily used to
|
4
|
+
# call the controller with requests but can be used to push data out to a
|
5
|
+
# display if push is supported.
|
6
|
+
#
|
7
|
+
# This class is the default implementation.
|
8
|
+
class View
|
9
|
+
|
10
|
+
def initialize()
|
11
|
+
# TBD as the default implementation this should be an HTTP server that
|
12
|
+
# serves files as well as JSON to a Javascript enabled browser.
|
13
|
+
end
|
14
|
+
|
15
|
+
# Push changed data to a display.
|
16
|
+
def changed(data)
|
17
|
+
# TBD
|
18
|
+
end
|
19
|
+
|
20
|
+
end # View
|
21
|
+
end # WAB
|
@@ -0,0 +1,200 @@
|
|
1
|
+
# Architecture
|
2
|
+
|
3
|
+
The WAB architecture is a Mode View Controller with clear APIs between each
|
4
|
+
part of the MVC. The design allows the non-business related tasks such as the
|
5
|
+
HTTP server and data store to be treated as service to the Controller which
|
6
|
+
contains the business logic.
|
7
|
+
|
8
|
+

|
9
|
+
|
10
|
+
## MVC
|
11
|
+
|
12
|
+
The Model View Controller pattern is a well known and widely accepted design
|
13
|
+
pattern. It is also the pattern used by Rails. WAB adheres to this model with
|
14
|
+
well defined APIs that are used exclusively.
|
15
|
+
|
16
|
+
The MVC pattern has many variants with some functionality being in different
|
17
|
+
components depending on how the lines are drawn between those components. No
|
18
|
+
matter what the separation, if it is clear many issues can be avoided.
|
19
|
+
|
20
|
+
### Model
|
21
|
+
|
22
|
+
The Model component is responsible for persisting data, providing search and
|
23
|
+
retrieval capabilities, and assuring stored consistency. The Model does not
|
24
|
+
assure business logic consistency nor does it enforce relationships between
|
25
|
+
data elements. It is a data store only.
|
26
|
+
|
27
|
+
The data in the system is follows a JSON structural model. This make a NoSQL
|
28
|
+
database an ideal store. It does mean that the Model data is unstructured in
|
29
|
+
that there is no schema enforced by the data store. It does not mean that
|
30
|
+
relationships do not exist in the data store.
|
31
|
+
|
32
|
+
With well defined APIs between the Model and the Controller almost any data
|
33
|
+
store can be used as long as an adapter is written. This allows options such
|
34
|
+
as [MongoDB](https://www.mongodb.com), [Redis](https://redislabs.com),
|
35
|
+
[OpO](http://opo.technology), a file based store, or even an in memory store
|
36
|
+
for testing.
|
37
|
+
|
38
|
+
One feature that may not be supported by all stores is the ability to push
|
39
|
+
changes from the data store to the Controller and then up to the View to real
|
40
|
+
time feeds.
|
41
|
+
|
42
|
+
### View
|
43
|
+
|
44
|
+
An HTTP server provides the View environment. In addition to serving HTML,
|
45
|
+
CSS, images, and other files typically served by a web server the View server
|
46
|
+
also supports exchanging JSON data through a REST API. A WebSocket and SSE
|
47
|
+
capability is also expected and designed for in the API.
|
48
|
+
|
49
|
+
Javascript is the suggested language to use for web pages but any approach to
|
50
|
+
accepting and delivering JSON is fine. JaveScript helpers are provided that
|
51
|
+
support the REST API as well as the WebSocket and SSE push APIs.
|
52
|
+
|
53
|
+
### Controller
|
54
|
+
|
55
|
+
With two approaches to connecting the Controller to the View and Model there
|
56
|
+
are deployment options that allow trading off latency for throughput. The
|
57
|
+
Controller code is isolated from those deployment choices except for the
|
58
|
+
choice of language. If the Controller is written in Ruby then it can be either
|
59
|
+
embedded in the shell or access as an external application.
|
60
|
+
|
61
|
+
The Controller is a bridge between the View and Model and implements the
|
62
|
+
business logic as needed. In the case of a fetch, create, or update options
|
63
|
+
are available to bypass or use the default Controller. The bypass allows a
|
64
|
+
more direct conduit between the View and the Model with the Controller just
|
65
|
+
passing the data along to the model and vice versa.
|
66
|
+
|
67
|
+
The Controller can also receive callbacks on changes in the Model which can
|
68
|
+
then be forwarded to the View if a subscription to that data has been made.
|
69
|
+
|
70
|
+
## APIs
|
71
|
+
|
72
|
+
All APIs are described with Ruby code as the Controller is expected to be
|
73
|
+
Ruby. If an external Controller is to be used then the external glue API is
|
74
|
+
used. This API is a text based API over a pipe (Unix socket). Events can
|
75
|
+
arrive at the Controller from either the View or Model interface.
|
76
|
+
|
77
|
+
### Data Model
|
78
|
+
|
79
|
+
Data is loosely represented as JSON. There are some expectations that can be
|
80
|
+
relaxed if desired.
|
81
|
+
|
82
|
+
#### JSON
|
83
|
+
|
84
|
+
The data exchanged between components follows the JSON model for primitives
|
85
|
+
with a few optional additions. The JSON types can be used exclusively and are:
|
86
|
+
|
87
|
+
- `null` (nil)
|
88
|
+
- _boolean_ (`true` | `false`)
|
89
|
+
- _string_ (String)
|
90
|
+
- _number_ (Integer | Float | BigDecimal)
|
91
|
+
- _object_ (Hash)
|
92
|
+
- _array_ (Array)
|
93
|
+
|
94
|
+
All string must be UTF-8 or Unicode.
|
95
|
+
|
96
|
+
Some other types are also allowed. Each has a defined JSON representation that
|
97
|
+
can be used instead.
|
98
|
+
|
99
|
+
- _time_ (Ruby Time encoded in RFC 3339 format if a string)
|
100
|
+
- _UUID_ (WAB::UUID encoded as defined by RFC 4122)
|
101
|
+
- _IRI_ (WAB::IRI encoded as defined by RFC 3987)
|
102
|
+
|
103
|
+
These types are represented by the WAB::Data class.
|
104
|
+
|
105
|
+
#### Structure
|
106
|
+
|
107
|
+
While object can be any JSON or WAB::Data they are encouraged to be JSON
|
108
|
+
Objects (Hash) types. I addition one attribute should be used to identify the
|
109
|
+
type. The default is the 'kind' attribute. That attribute is not required to
|
110
|
+
be the 'kind' attribute but can be anywhere in the data tree as long as it is
|
111
|
+
consistent across all types. As an example it could be in 'meta.kind' where
|
112
|
+
that key represents a path with a 'kind' element in a 'meta' object (Hash).
|
113
|
+
|
114
|
+
Each object or for that matter every node in a Data tree is assigned a system
|
115
|
+
wide unique identifier. That is used to identfiy each object when using the
|
116
|
+
API. From the view perspective a REST over HTTP is used.
|
117
|
+
|
118
|
+
#### Just Data
|
119
|
+
|
120
|
+
- just data and data helpers (get, set, inspect, to_s)
|
121
|
+
- nothing like to_json
|
122
|
+
- reasoning it that different uses require different behavior
|
123
|
+
- different stores, view, processing, etc
|
124
|
+
- use of delgate is encouraged
|
125
|
+
|
126
|
+
|
127
|
+
### View/Controller
|
128
|
+
|
129
|
+
The View/Controller API is predominanty from View to Controller but the
|
130
|
+
ability for the Controller to push data to the View is also part of the
|
131
|
+
design. That allows pages to take advantage of WebSockets or SSEs to display
|
132
|
+
data changes as they occur.
|
133
|
+
|
134
|
+
See the Controller class documentation for further details.
|
135
|
+
|
136
|
+
### Controller/Model
|
137
|
+
|
138
|
+
The Contoller/Model API is driven mostly from Controller to Model with the
|
139
|
+
Model responding to queries. Like the View/Controller API the Model can also
|
140
|
+
push changes to the Controller.
|
141
|
+
|
142
|
+
The Model supports basic CRUD operations as well as a query API that uses
|
143
|
+
[TQL](http://opo.technology/pages/doc/tql/index.html) in both the friendly and
|
144
|
+
JSON formats. Support for GraphQL is anticipated but not for the first
|
145
|
+
iteration.
|
146
|
+
|
147
|
+
See the Model class documentation for further details.
|
148
|
+
|
149
|
+
## Shells
|
150
|
+
|
151
|
+
As noted in the architecture diagram, the WAB Shell can be either the C or
|
152
|
+
Ruby shell. The Ruby shell is intended for development and possibly small
|
153
|
+
installations. The C WAB Shell or Shells if more thna one is implemented are
|
154
|
+
intended to be high performance environments that the Controller code resides
|
155
|
+
in either as embedded code or through a piped connections.
|
156
|
+
|
157
|
+
### Embedded
|
158
|
+
|
159
|
+
A C WAB Shell with an embedded Controller utilizes the View and Model APIs
|
160
|
+
directly through the Ruby library. This approach gives more control to the
|
161
|
+
Shell in regard to utilizing threads and C libraries outside of Ruby. An
|
162
|
+
alternative approach is to call C extensions from Ruby but that approach is
|
163
|
+
left for future if it makes sense.
|
164
|
+
|
165
|
+
The Ruby WAB Shell implements an HTTP server as well as either an in memory
|
166
|
+
data store or a file based data store.
|
167
|
+
|
168
|
+
The APIs are designed to allow relatively direct modifications to the data
|
169
|
+
store if the store supports such.
|
170
|
+
|
171
|
+
The embedded Shell should have the lowest latency but may sacrifice throughput
|
172
|
+
depending on the complexity of the Controller code.
|
173
|
+
|
174
|
+
### External
|
175
|
+
|
176
|
+
Running external Controllers is implemented by the WAB Shell either spawning
|
177
|
+
the Controller application or connecting to an existing one. Multiple
|
178
|
+
Controllers can be active to allow parallel Controller processing. The
|
179
|
+
approach taken is the same as that used [Piper Push
|
180
|
+
Cache](http://piperpushcache.com) with the process flow [Spawn
|
181
|
+
Actor](http://piperpushcache.com/help_actor_spawn).
|
182
|
+
|
183
|
+
To run a Controller written in Ruby the External Glue is used to bridge the
|
184
|
+
gap between the WAB external API and the Controller APIs. This is a light
|
185
|
+
weught layer that converts to and from the text based pipe API and the Ruby
|
186
|
+
APIs.
|
187
|
+
|
188
|
+
With the ability to make use of multiple Controller instances which may reside
|
189
|
+
on different machines the throughput is expected to be higher than the
|
190
|
+
embedded Shell but with a degradation of the latency.
|
191
|
+
|
192
|
+
### Alternatives
|
193
|
+
|
194
|
+
A straight Ruby WAB Shell is the choice for testing and for small
|
195
|
+
installations. The Ruby shell is the first choice when getting started.
|
196
|
+
|
197
|
+
Outside of this project WAB C or other language shells can be written. The
|
198
|
+
first is expected to be a derivative of [OpO](http://opo.technology) as it
|
199
|
+
takes shape. This WAB Shell will also draw on the libraries use by [Piper
|
200
|
+
Push Cache](http://piperpushcache.com) to provide WebSocket and SSE support.
|
data/pages/Goals.md
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
# Goals
|
2
|
+
|
3
|
+
The goal is to provide a high performance, easy to use, and fully featured web
|
4
|
+
framework that uses Ruby for business logic. Following a Model/View/Controller
|
5
|
+
model Ruby is used to implement the Controller portion. Using the best
|
6
|
+
language for each portion of the MVC the View is implemented in JavaScript,
|
7
|
+
HTML, and CSS. The Model is simply a NoSQL data store.
|
8
|
+
|
9
|
+
To address the ease of use clean and clear APIs are used between each part of
|
10
|
+
the MVC and JSON is the data representation throughout. Ruby is straight Ruby
|
11
|
+
with no monkey patched core classes and no magic. Plain and simple Ruby.
|
12
|
+
|
13
|
+
## Performance
|
14
|
+
|
15
|
+
With a goal of building a high performance system the architecture is design
|
16
|
+
from the start to deliver the best performance possible. Code will also
|
17
|
+
developed with that goal in mind. All code will be unit tested and benchmarks
|
18
|
+
made where applicable.
|
19
|
+
|
20
|
+
### Approach
|
21
|
+
|
22
|
+
The architecture is module and follows a Model View Controller design pattern
|
23
|
+
with clear and defined APIs between each module. This will ensure a clear
|
24
|
+
division between the Model, View, and Controller modules.
|
25
|
+
|
26
|
+
With a modular design the most appropriate data store can be selected. By
|
27
|
+
being able to swap out different storage modules choices can be made between
|
28
|
+
hooking up to an existing SQL or NoSQL data store or using a faster or more
|
29
|
+
scaleable choice.
|
30
|
+
|
31
|
+
A separation of data and behavior is part of the overall approach. By keeping
|
32
|
+
the data separate and devoid of behavior the movement of data between the
|
33
|
+
elements of the MVC doesn't drag along methods not appropriate for the
|
34
|
+
component. It also allows behavior to be implemented without impacting the
|
35
|
+
data and updated and modified without a global replacement of the system as a
|
36
|
+
whole. Related to this approach is that there will be absolutely no monkey
|
37
|
+
patching of core Ruby classes. Monkey patching creates unexpected behavior and
|
38
|
+
cause conflicts between modules.
|
39
|
+
|
40
|
+
Serialization must be fast and reduced to a minimum. The APIs provide an
|
41
|
+
abstraction that allows optimized data structures to be used as long as they
|
42
|
+
follow a JSON model view through the access APIs.
|
43
|
+
|
44
|
+
Ruby code is kept as simple and direct as possible to reduce the overhead of
|
45
|
+
deeply nested calls and object creation.
|
46
|
+
|
47
|
+
There are parts of the system that can be written in higher performance
|
48
|
+
languages such as C. This includes the HTTP and data stores. By allowing use
|
49
|
+
of a mixed environment the best performance can be achieved.
|
50
|
+
|
51
|
+
### Targets
|
52
|
+
|
53
|
+
Targets are a throughput of 100K page fetches per second at a latency of no
|
54
|
+
more than 1 millisecond on a desktop machine. That is more than an order of
|
55
|
+
magnitude faster than Rails and on par with other top of the performance tier
|
56
|
+
web frameworks in all languages.
|
57
|
+
|
58
|
+
The throughput and latency targets are not unreasonable as
|
59
|
+
[OpO](http://opo.technology) as an HTTP server providing JSON documents with
|
60
|
+
the Ruby controller has demonstrated that 140K fetches per second with a
|
61
|
+
latency of 0.6 milliseconds is possible on a desktop machine. With a thin Ruby
|
62
|
+
controller or a bypass for simple fetches the additional overhead is minimal.
|
63
|
+
|
64
|
+
The system must be scaleable in at least one configuration. Possibly using
|
65
|
+
multiple Ruby instances as processes or threads.
|
66
|
+
|
67
|
+
In the final deployment the aim is for upper part of performance tier when
|
68
|
+
compared to other web framework across all languages. Right now in
|
69
|
+
[benchmarks](https://gist.github.com/omnibs/e5e72b31e6bd25caf39a) Rails and
|
70
|
+
Sinatra occupy the bottom slots. WAB will put Ruby near the top.
|
71
|
+
|
72
|
+
## Easy to Use
|
73
|
+
|
74
|
+
In addition to performance goals WAB must be easy to use not only for a
|
75
|
+
'Hello World' application but for advanced systems as well. The learning curve
|
76
|
+
must be shallow to allow new users to get started immediately and then
|
77
|
+
progress onto more advanced features.
|
78
|
+
|
79
|
+
### Simplicity, Clear, and Simple
|
80
|
+
|
81
|
+
The Ruby code used in WAB will be simple and direct. There will be no
|
82
|
+
magic. Clear documented class definitions with shallow class hierachy
|
83
|
+
throughout.
|
84
|
+
|
85
|
+
### Development
|
86
|
+
|
87
|
+
Development is a cycle of edit and test repeated over and over again. The
|
88
|
+
faster that cycle is the more friendly the development environment. Keep
|
89
|
+
modules encapsulated with well defined APIs allows this cycle to be fast as
|
90
|
+
unit tests just test against the APIs.
|
91
|
+
|
92
|
+
This strategy of well defined APIs and testing continues at all levels up to
|
93
|
+
system testing.
|
94
|
+
|
95
|
+
### Upgrade Path
|
96
|
+
|
97
|
+
It is expected that the development environment will use a Ruby shell while
|
98
|
+
larger scale production will use a C shell. That doesn't mean moving to a C
|
99
|
+
WAB shell is necessary but it can be used if needed.
|
100
|
+
|
101
|
+
## Best for Purpose
|
102
|
+
|
103
|
+
Ruby is appropriate for the business logic expected in the Controller in the
|
104
|
+
MVC model but there are no advantages to using Ruby to form HTML pages for the
|
105
|
+
View. JavaScript, HTML, and CSS are a better choice. Along similar lines,
|
106
|
+
there is no advantage of using Ruby to write the data store or for that matter
|
107
|
+
using Ruby to convert data into database calls. In the development shell it is
|
108
|
+
fine but the option to use another language should be available for
|
109
|
+
performance reasons. That allows the data store to be a service independent of
|
110
|
+
the Ruby code.
|
data/pages/Plan.md
ADDED
metadata
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: wabur
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0d1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Peter Ohler
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-06-03 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: 'Web Application Builder '
|
14
|
+
email: peter@ohler.com
|
15
|
+
executables: []
|
16
|
+
extensions: []
|
17
|
+
extra_rdoc_files:
|
18
|
+
- README.md
|
19
|
+
- pages/Architecture.md
|
20
|
+
- pages/Goals.md
|
21
|
+
- pages/Plan.md
|
22
|
+
files:
|
23
|
+
- LICENSE
|
24
|
+
- README.md
|
25
|
+
- lib/wab.rb
|
26
|
+
- lib/wab/controller.rb
|
27
|
+
- lib/wab/data.rb
|
28
|
+
- lib/wab/model.rb
|
29
|
+
- lib/wab/shell.rb
|
30
|
+
- lib/wab/version.rb
|
31
|
+
- lib/wab/view.rb
|
32
|
+
- pages/Architecture.md
|
33
|
+
- pages/Goals.md
|
34
|
+
- pages/Plan.md
|
35
|
+
homepage: http://github.com/ohler55/wabur
|
36
|
+
licenses:
|
37
|
+
- MIT
|
38
|
+
metadata: {}
|
39
|
+
post_install_message:
|
40
|
+
rdoc_options:
|
41
|
+
- "--title"
|
42
|
+
- WABuR
|
43
|
+
- "--main"
|
44
|
+
- README.md
|
45
|
+
- "--private"
|
46
|
+
require_paths:
|
47
|
+
- lib
|
48
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
49
|
+
requirements:
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: '0'
|
53
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - ">"
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: 1.3.1
|
58
|
+
requirements: []
|
59
|
+
rubyforge_project: wabur
|
60
|
+
rubygems_version: 2.6.11
|
61
|
+
signing_key:
|
62
|
+
specification_version: 4
|
63
|
+
summary: Web Application Builder
|
64
|
+
test_files: []
|