silicon 0.3.0 → 0.4.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.
- checksums.yaml +4 -4
- data/README.md +56 -287
- data/lib/silicon/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7da11fceb3dbf78f8953e3971ac3125af7e5254c
|
4
|
+
data.tar.gz: 707b2eadfa9b4e814171e513342e2d4efee2579e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 32062840418619974ae913c5dd9c3d818c8a57defbed2c936767237a15a4074ef177b8b19c7d0f067ed63badff31df205557ecd444c3ee653774aaeeb524df83
|
7
|
+
data.tar.gz: f65e787d25330b351b704c68710be73fcb55ff7cc0f0e1a81b82555d051ef05d27edb4ab32a0586d5c8fedfd040f25245042f8ea1a3b0744aca9b49e372b10ee
|
data/README.md
CHANGED
@@ -1,349 +1,121 @@
|
|
1
1
|
# Silicon
|
2
|
+
|
2
3
|
Silicon is a minimalistic web API framework. Its main idea is to implement an approach
|
3
4
|
from Clean Architecture principles - "web is just a delivery mechanism".
|
4
5
|
Silicon is build on top of IoC container Hypo and forces to use dependency injection
|
5
|
-
approach everywhere.
|
6
|
-
|
7
|
-
## Architecture
|
6
|
+
approach everywhere.
|
8
7
|
|
9
|
-
|
8
|
+
## Basic Concepts
|
10
9
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
web-application know that controllers become to be fat. Splitting controllers by context can a bit improve
|
15
|
-
the situation but it's a visual trick, it still looks like:
|
10
|
+
1. [Dependency Injection](https://en.wikipedia.org/wiki/Dependency_injection) is a heart of Silicon. Atomic actions can depend on request parameters, services, repositories, output of other
|
11
|
+
actions and other stuff. You can easily inject such dependencies in your actions through a constructor.
|
12
|
+
Dependency Injection significantly improves development experience: isolate your components, enjoy writing unit tests.
|
16
13
|
|
17
14
|
```ruby
|
18
|
-
class
|
19
|
-
def
|
20
|
-
|
15
|
+
class LoadPost
|
16
|
+
def initialize(id, post_storage)
|
17
|
+
@id = id
|
18
|
+
@storage = post_storage
|
21
19
|
end
|
22
20
|
|
23
|
-
def show
|
24
|
-
# ...
|
25
|
-
end
|
26
|
-
|
27
|
-
def update
|
28
|
-
# ...
|
29
|
-
end
|
30
|
-
|
31
|
-
# ...
|
32
|
-
end
|
33
|
-
```
|
34
|
-
|
35
|
-
It roughly violates one of main OOP rules - [Single Responsibility Principle](). Somebody can say "controller is only intended
|
36
|
-
to control the process of request handling", but it's too large responsibility. Actually it's responsible for creating,
|
37
|
-
updating and deleting models, showing views, services invocation and so on. And it's an ideal picture. Usual situation is
|
38
|
-
when domain code is located in controllers.
|
39
|
-
Silicon replaces regular controllers with a chain of atomic actions. It doesn't protect you to use bad practices but
|
40
|
-
allows to easily separate \[controller-\]actions. Every action is a simple brick:
|
41
|
-
|
42
|
-
```ruby
|
43
|
-
class ShowUsers
|
44
|
-
def call
|
45
|
-
# ...
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
class UpdateUser
|
50
21
|
def call
|
51
|
-
|
22
|
+
@storage.find(@id)
|
52
23
|
end
|
53
24
|
end
|
54
|
-
```
|
55
|
-
|
56
|
-
A route can represent much more complicated logic containing several steps. For that purpose Silicon provides an ability
|
57
|
-
to chain actions:
|
58
|
-
|
59
|
-
```
|
60
|
-
-> update_user -> notify_admin -> log_action
|
61
|
-
```
|
25
|
+
```
|
62
26
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
### Router
|
67
|
-
|
68
|
-
Silicon Router is completely new vision of how to create flexible, lightweight, language-independent DSL for designing
|
69
|
-
a schema of web-application routing. Rails, Hanami, Sinatra and others provide more or less visually similar DSL for
|
70
|
-
defining routing schema. Their routers use Ruby blocks-based DSL for that. Ruby is a power, Ruby is a weakness:
|
71
|
-
for such purpose Ruby is too verbose. An example demonstrating all features:
|
27
|
+
2. Instead of boring Ruby block-style routes definition like in Rails, Sinatra and others Silicon uses its
|
28
|
+
own language for that. Small example:
|
72
29
|
|
73
30
|
```
|
74
31
|
:receive
|
75
32
|
.->
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
$id ->
|
84
|
-
:before -> load_post
|
85
|
-
|
86
|
-
GET -> show_post
|
87
|
-
PATCH -> update_post -> :respond =200
|
88
|
-
DELETE -> remove_post -> :respond =200
|
89
|
-
|
90
|
-
/comments ->
|
91
|
-
GET -> list_comments
|
92
|
-
POST => add_comment => notify_author =* notify_subscribers -> :respond <- comment_plain =201
|
93
|
-
|
94
|
-
$comment_id ->
|
95
|
-
GET -> show_comment -> :respond <- comment
|
96
|
-
DELETE -> remove_comment
|
33
|
+
:before -> load_current_user
|
34
|
+
/posts ->
|
35
|
+
$id ->
|
36
|
+
:before -> load_post
|
37
|
+
/comments ->
|
38
|
+
POST -> add_comment -> notify_author -> notify_subscribers -> :respond <- comment_test =201
|
97
39
|
:catch -> handle_errors
|
98
|
-
```
|
99
|
-
|
100
|
-
Routes configuration by default is located in file `app/app.routes`. You can change it's location in `silicon.yml` file.
|
101
|
-
|
102
|
-
#### Action path
|
103
|
-
Routes definition has tree-like structure. There're two root entries - :receive and :catch.
|
104
|
-
|
105
|
-
`:receive` section describes regular flow of incoming request.
|
106
|
-
|
107
|
-
`:catch` section defines an action which calls when an error raised somewhere in the regular flow.
|
108
|
-
|
109
|
-
`:receive` section should start from `.` - root point of the routing.
|
110
|
-
|
111
|
-
Every line of the definition is a piece of path to target action or chain.
|
112
|
-
|
113
|
-
Symbols `->` and `<TAB>` emulates directory structure. Configuration demonstrated above can be interpreted like:
|
114
|
-
```
|
115
|
-
GET /auth/posts/$id (show_post)
|
116
|
-
DELETE /auth/posts/$id/comments/$comment_id (remove_comment)
|
117
|
-
```
|
118
|
-
Symbol `$` allows to receive request parameters.
|
119
|
-
|
120
|
-
#### Action chaining
|
121
|
-
As you can see, some routes reference to a chain of actions, like:
|
122
|
-
|
123
|
-
```
|
124
|
-
-> update_user -> notify_admin -> log_action
|
125
|
-
```
|
126
|
-
It means that you can define a number of atomic operations in order to reach request goals.
|
127
|
-
|
128
|
-
`->` is a simple sequential type of action. Process flow waits until finishing of it's execution before starting the next
|
129
|
-
action or making a response.
|
130
|
-
|
131
|
-
More interesting case:
|
132
|
-
|
133
|
-
```
|
134
|
-
=> add_comment => notify_author =* notify_subscribers
|
135
40
|
```
|
136
|
-
|
137
|
-
`=>` is a parallel operation. All parallel operations complete before the next sequential (`->`) or ending of the chain.
|
138
|
-
|
139
|
-
`=*` is an asynchronous operation. It must not be completed before next sequential and even ending of the chain.
|
140
41
|
|
141
|
-
The
|
142
|
-
|
143
|
-
|
144
|
-
By default Silicon responds with HTTP status 200 (201 for POST) and empty body. You can define :respond instructions
|
145
|
-
for sending custom status and specific response body:
|
42
|
+
3. The combination of flexible router and dependency injection breaks existing dogmas.
|
43
|
+
Silicon framework introduces Abstract Chain pattern as a replacement for Model-View-Controller
|
44
|
+
and other ancient approaches. Every request handles by a set of atomic actions.
|
146
45
|
|
147
46
|
```
|
148
|
-
|
149
|
-
|
47
|
+
# POST /posts/$id/comments
|
48
|
+
... -> load_post -> add_comment -> ...
|
150
49
|
```
|
151
50
|
|
152
|
-
Expression `:respond <-` declares a view ("comment_plain") and a status `=` (201). Details about views formatting are described below.
|
153
|
-
|
154
|
-
|
155
|
-
### Dependency Injection
|
156
|
-
Dependency Injection (DI) is a heart of Silicon web-application. Every Silicon action can utilize known variables/entities
|
157
|
-
in the application. In example:
|
158
|
-
|
159
|
-
when you have:
|
160
|
-
|
161
|
-
```
|
162
|
-
GET /auth/posts/$id (show_post)
|
163
|
-
```
|
164
|
-
|
165
|
-
you can easily use request parameter:
|
166
|
-
|
167
51
|
```ruby
|
168
|
-
class
|
169
|
-
def initialize(id)
|
52
|
+
class LoadPost
|
53
|
+
def initialize(id, post_storage)
|
170
54
|
@id = id
|
55
|
+
@storage = post_storage
|
171
56
|
end
|
172
57
|
|
173
58
|
def call
|
174
|
-
|
175
|
-
end
|
176
|
-
end
|
177
|
-
```
|
178
|
-
|
179
|
-
Every action returns a result that automatically registers in the container and can be used in further actions:
|
180
|
-
|
181
|
-
```
|
182
|
-
GET /auth/posts/$id (load_post, show_post)
|
183
|
-
```
|
184
|
-
|
185
|
-
```ruby
|
186
|
-
class ShowPost
|
187
|
-
def initialize(load_post_result)
|
188
|
-
@post = load_post_result
|
59
|
+
@storage.find(@id)
|
189
60
|
end
|
190
|
-
|
191
|
-
end
|
192
|
-
```
|
193
|
-
|
194
|
-
You can customize the name of action result:
|
195
|
-
|
196
|
-
```ruby
|
197
|
-
class LoadPost
|
198
|
-
#...
|
61
|
+
|
199
62
|
def result_name
|
200
63
|
'post'
|
201
64
|
end
|
202
65
|
end
|
203
|
-
```
|
204
|
-
|
205
|
-
and use:
|
206
66
|
|
207
|
-
|
208
|
-
|
209
|
-
def initialize(post)
|
67
|
+
class AddComment
|
68
|
+
def initialize(post, silicon_data, user, comment_storage)
|
210
69
|
@post = post
|
70
|
+
@data = silicon_data
|
71
|
+
@user = user
|
72
|
+
@storage = comment_storage
|
211
73
|
end
|
212
74
|
|
213
|
-
|
75
|
+
def call
|
76
|
+
@storage.create(post: @post, message: @data[:message], user: @user)
|
77
|
+
end
|
214
78
|
end
|
215
79
|
```
|
216
80
|
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
path:
|
221
|
-
dependencies:
|
222
|
-
- actions # default location
|
223
|
-
- services/injectable # additional dependencies location
|
224
|
-
```
|
225
|
-
|
226
|
-
#### Objects lifetime
|
227
|
-
As mentioned before, dependency injection is a heart of Silicon. And as you probably noticed we register dependencies
|
228
|
-
for every new request. In order to avoid leaking the memory for request-specific objects Silicon uses Hypo::Scope lifetime
|
229
|
-
style for registered objects. Every time when request is ending the dependencies remove from the container.
|
81
|
+
4. Silicon is a micro-framework for micro-services. It's not intended to create monolithic giants!
|
82
|
+
In terms of [Domain Driven Design](https://en.wikipedia.org/wiki/Domain-driven_design)
|
83
|
+
concepts one Silicon application should wrap only one [Bounded Context](https://en.wikipedia.org/wiki/Domain-driven_design#Bounded_context).
|
230
84
|
|
231
|
-
|
232
|
-
[Unit of Work](https://martinfowler.com/eaaCatalog/unitOfWork.html) pattern.
|
85
|
+
## Getting Started
|
233
86
|
|
234
|
-
|
235
|
-
class DbSession
|
236
|
-
include Hypo::Scope
|
237
|
-
|
238
|
-
def initialize
|
239
|
-
@transaction = Transaction.new
|
240
|
-
end
|
87
|
+
1. Install Silicon:
|
241
88
|
|
242
|
-
def finalize
|
243
|
-
# unexpected behavior handling is in :catch section implementation
|
244
|
-
@transaction.commit
|
245
|
-
end
|
246
|
-
end
|
247
89
|
```
|
248
|
-
|
249
|
-
### Views
|
250
|
-
By default Silicon handles only JSON requests using [JBuilder](https://github.com/rails/jbuilder) engine. But you can extend a number of engines using
|
251
|
-
method `add_view_builder` in your `app.rb`:
|
252
|
-
|
253
|
-
```ruby
|
254
|
-
class App < Silicon::App
|
255
|
-
def initialize
|
256
|
-
super
|
257
|
-
|
258
|
-
add_view_builder(MyHtmlViewBuilder, 'html')
|
259
|
-
end
|
260
|
-
end
|
90
|
+
$ gem install silicon
|
261
91
|
```
|
262
92
|
|
263
|
-
|
93
|
+
2. At the command prompt, create a new Silicon application:
|
94
|
+
|
264
95
|
```
|
265
|
-
|
266
|
-
views: custom/views/location
|
96
|
+
$ silicon new silicon-app
|
267
97
|
```
|
268
98
|
|
269
|
-
|
99
|
+
3. Go to directory `silicon-app` and start the application using a server you prefer:
|
270
100
|
|
271
101
|
```
|
272
|
-
|
273
|
-
-> load_user -> load_post -> load_comments -> :respond <- show_post
|
102
|
+
$ puma -p 8000 config.ru
|
274
103
|
```
|
275
104
|
|
276
|
-
|
105
|
+
or just
|
277
106
|
|
278
|
-
```ruby
|
279
|
-
class LoadPost
|
280
|
-
def initialize(id)
|
281
|
-
@id = id
|
282
|
-
end
|
283
|
-
|
284
|
-
def call
|
285
|
-
# Not a real ORM, just for the demo.
|
286
|
-
# Posts.find(id)
|
287
|
-
end
|
288
|
-
|
289
|
-
def result_name
|
290
|
-
'post'
|
291
|
-
end
|
292
|
-
end
|
293
|
-
|
294
|
-
class LoadComments
|
295
|
-
def initialize(id)
|
296
|
-
@id = id
|
297
|
-
end
|
298
|
-
|
299
|
-
def call
|
300
|
-
# Not a real ORM, just for the demo.
|
301
|
-
# Comments.where(post: post)
|
302
|
-
end
|
303
|
-
|
304
|
-
def result_name
|
305
|
-
# as you probably noticed this annoying action can be replaced
|
306
|
-
# with a convention in your own base class for application actions.
|
307
|
-
# Also instead of this declaration you can still use default name
|
308
|
-
# for actions like 'load_comments_result'.
|
309
|
-
|
310
|
-
'comments'
|
311
|
-
end
|
312
|
-
end
|
313
107
|
```
|
314
|
-
|
315
|
-
Draw the view:
|
316
|
-
|
317
|
-
```ruby
|
318
|
-
json.post do
|
319
|
-
json.title @post.title
|
320
|
-
# ...
|
321
|
-
|
322
|
-
json.comments @comments do |comment|
|
323
|
-
json.message comment.message
|
324
|
-
# ...
|
325
|
-
end
|
326
|
-
end
|
108
|
+
$ rackup -p 8000 config.ru
|
327
109
|
```
|
328
110
|
|
329
|
-
|
330
|
-
|
331
|
-
Add this line to your application's Gemfile:
|
111
|
+
4. Using a browser, go to http://localhost:8000 and you'll see:
|
332
112
|
|
333
|
-
```
|
334
|
-
|
113
|
+
```json
|
114
|
+
{"message": "Welcome to silicon-app!"}
|
335
115
|
```
|
336
116
|
|
337
|
-
|
338
|
-
|
339
|
-
$ bundle
|
340
|
-
|
341
|
-
Or install it yourself as:
|
342
|
-
|
343
|
-
$ gem install silicon
|
344
|
-
|
345
|
-
|
346
|
-
## Getting Started
|
117
|
+
5. Investigate example application code, it will explain most of Silicon aspects.
|
118
|
+
6. For more details visit our [Wiki](https://github.com/cylon-v/silicon/wiki).
|
347
119
|
|
348
120
|
## Development
|
349
121
|
|
@@ -353,9 +125,6 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
|
353
125
|
To install this gem onto your local machine, run `bundle exec rake install`.
|
354
126
|
To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
355
127
|
|
356
|
-
##
|
357
|
-
|
358
|
-
|
359
128
|
## Contributing
|
360
129
|
|
361
130
|
Bug reports and pull requests are welcome on GitHub at https://github.com/cylon-v/silicon.
|
data/lib/silicon/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: silicon
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Vladimir Kalinkin
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-01-
|
11
|
+
date: 2018-01-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: hypo
|