silicon 0.3.0 → 0.4.0

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