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.
Files changed (4) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +56 -287
  3. data/lib/silicon/version.rb +1 -1
  4. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b42a17f2843d05d79a364238d2acab60af7b58fb
4
- data.tar.gz: 3ef558e06e125ce333da1076ecf95e07deed709b
3
+ metadata.gz: 7da11fceb3dbf78f8953e3971ac3125af7e5254c
4
+ data.tar.gz: 707b2eadfa9b4e814171e513342e2d4efee2579e
5
5
  SHA512:
6
- metadata.gz: 6090adf8bb5518c4da8239908592d7be26aa35f7ae3af6c2b9c0d8ac2d2bb586ea5a99c9a2ac9e289820046c03400022d34a79d1b6090ca341c489c4258eeeeb
7
- data.tar.gz: a9892ea638f31505e4df44368e26bee167c93ee040bdff42f70b2f58b8e01f6a1836f6308d57362b31c13b3c63154721ae2541d5bb39595211b6df727baefcb5
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
- ![Silicon Architecture](https://user-images.githubusercontent.com/4575064/34541424-2b7b6c24-f0e9-11e7-8023-abc0df48c9c4.png)
8
+ ## Basic Concepts
10
9
 
11
- ### Separation of Concerns
12
-
13
- Model-View-Controller (MVC) now is a sign of bad taste. Everybody who built more or less serious production
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 UserController
19
- def index
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
- Every of enumerated above actions are classes respond to method "call". More details about chains construction are
64
- described below.
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
- /auth ->
77
- :before -> load_current_user
78
-
79
- /posts ->
80
- GET -> list_posts
81
- POST -> create_post
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 time of execution of asynchronous and parallel operations can be limited in config file (by default it's 10 seconds).
142
-
143
- #### Sending Response
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
- POST => add_comment => ... -> :respond <- comment_plain =201
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 ShowPost
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
- # somehow extract a post using @id
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
- ```ruby
208
- class ShowPost
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
- In order to support another types by DI you need to adjust the configuration `silicon.yml`:
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
- BTW, using Hypo::Scope and it's `finalize` method definition you can implement
232
- [Unit of Work](https://martinfowler.com/eaaCatalog/unitOfWork.html) pattern.
85
+ ## Getting Started
233
86
 
234
- ```ruby
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
- View templates are located in `views` directory; you can change default location in `silicon.yml`:
93
+ 2. At the command prompt, create a new Silicon application:
94
+
264
95
  ```
265
- path:
266
- views: custom/views/location
96
+ $ silicon new silicon-app
267
97
  ```
268
98
 
269
- In view template you can use any objects registered in the container. In example, you have a chain:
99
+ 3. Go to directory `silicon-app` and start the application using a server you prefer:
270
100
 
271
101
  ```
272
- GET /posts/$id
273
- -> load_user -> load_post -> load_comments -> :respond <- show_post
102
+ $ puma -p 8000 config.ru
274
103
  ```
275
104
 
276
- and it's implementation in Ruby:
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
- ## Installation
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
- ```ruby
334
- gem 'silicon'
113
+ ```json
114
+ {"message": "Welcome to silicon-app!"}
335
115
  ```
336
116
 
337
- And then execute:
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.
@@ -1,3 +1,3 @@
1
1
  module Silicon
2
- VERSION = '0.3.0'.freeze
2
+ VERSION = '0.4.0'.freeze
3
3
  end
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.3.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-07 00:00:00.000000000 Z
11
+ date: 2018-01-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: hypo