u-service 0.12.0 → 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 020c6ab3c508004af1b58aa443ed52b67ec587fbc23ce11c4c410dd2806af3cc
4
- data.tar.gz: 4b528fe880ce14dadf398a1491cd9ae7b36a6a08a1d4a8cf0e8d9de48f6a0bf8
3
+ metadata.gz: f637ed45a67693473bf30661b6a4822f1d6ea15246d46b24df3051546781a16b
4
+ data.tar.gz: ec7c4d4c8052ff1f293b9323bdb447505880dd658a1d0e6219dd2233a40a2b1a
5
5
  SHA512:
6
- metadata.gz: 1ece16f4c64119f8485ebbf1dbfe23f82c3a7cda4bd4e60da2f92fb0992d192ecf59383a5d8624f8d0fd125dd4c7e88f37ffebab51182c366bc29fac6e85f40f
7
- data.tar.gz: 16abd91aa04f7db43b9ff21e2f0ed8d2cb6fdeb6605d6356cd24cadc4feba78090f7236533b5572923e2ae5b186827216f496bdeda7353edc68554aed4bae82a
6
+ metadata.gz: 5339646f0ce88b4842c9f8d0425ddb16c5edacb81f97dea46eb695da625a2b142fb24a15bf7256f16952f5fb45526c0d812ab8e7a54ac045ce49959ff5a1b637
7
+ data.tar.gz: 1927b9f7da2f9a5ca5ea369c768173485ff704e3fb8142eccaa612484e6c83a9aeba7589c11f61d573ed7a0b341ba437003f5e9e57bb74eb76ef5789fde84169
data/README.md CHANGED
@@ -23,6 +23,7 @@ The main goals of this project are:
23
23
  - [What is a strict Service Object?](#what-is-a-strict-service-object)
24
24
  - [How to validate Service Object attributes?](#how-to-validate-service-object-attributes)
25
25
  - [It's possible to compose pipelines with other pipelines?](#its-possible-to-compose-pipelines-with-other-pipelines)
26
+ - [Examples](#examples)
26
27
  - [Comparisons](#comparisons)
27
28
  - [Benchmarks](#benchmarks)
28
29
  - [Development](#development)
@@ -110,7 +111,7 @@ class Double < Micro::Service::Base
110
111
  return Failure(:invalid) { 'the number must be a numeric value' } unless number.is_a?(Numeric)
111
112
  return Failure(:lte_zero) { 'the number must be greater than 0' } if number <= 0
112
113
 
113
- Success(number * number)
114
+ Success(number * 2)
114
115
  end
115
116
  end
116
117
 
@@ -125,7 +126,7 @@ Double
125
126
  .on_failure(:lte_zero) { |msg| raise ArgumentError, msg }
126
127
 
127
128
  # The output when is a success:
128
- # 9
129
+ # 6
129
130
 
130
131
  #=============================#
131
132
  # Raising an error if failure #
@@ -251,8 +252,8 @@ Note: To do this your application must have the [activemodel >= 3.2](https://rub
251
252
  # any kind of service attribute can be validated.
252
253
  #
253
254
  class Multiply < Micro::Service::Base
254
- attribute :a
255
- attribute :b
255
+ attributes :a, :b
256
+
256
257
  validates :a, :b, presence: true, numericality: true
257
258
 
258
259
  def call!
@@ -266,13 +267,18 @@ end
266
267
  # But if do you want an automatic way to fail
267
268
  # your services if there is some invalid data.
268
269
  # You can use:
269
- require 'micro/service/with_validation'
270
+
271
+ # In some file. e.g: A Rails initializer
272
+ require 'micro/service/with_validation' # or require 'u-service/with_validation'
273
+
274
+ # In the Gemfile
275
+ gem 'u-service', '~> 0.12.0', require: 'u-service/with_validation'
270
276
 
271
277
  # Using this approach, you can rewrite the previous sample with fewer lines of code.
272
278
 
273
- class Multiply < Micro::Service::WithValidation
274
- attribute :a
275
- attribute :b
279
+ class Multiply < Micro::Service::Base
280
+ attributes :a, :b
281
+
276
282
  validates :a, :b, presence: true, numericality: true
277
283
 
278
284
  def call!
@@ -281,8 +287,8 @@ class Multiply < Micro::Service::WithValidation
281
287
  end
282
288
 
283
289
  # Note:
284
- # There is a strict variation for Micro::Service::WithValidation
285
- # Use Micro::Service::Strict::Validation if do you want this behavior.
290
+ # After requiring the validation mode, the
291
+ # Micro::Service::Strict classes will inherit this new behavior.
286
292
  ```
287
293
 
288
294
  ### It's possible to compose pipelines with other pipelines?
@@ -333,20 +339,30 @@ DoubleAllNumbers = Steps::ConvertToNumbers >> Steps::Double
333
339
  SquareAllNumbers = Steps::ConvertToNumbers >> Steps::Square
334
340
  DoubleAllNumbersAndAdd2 = DoubleAllNumbers >> Steps::Add2
335
341
  SquareAllNumbersAndAdd2 = SquareAllNumbers >> Steps::Add2
336
- DoubleAllNumbersAndSquareThem = DoubleAllNumbers >> SquareAllNumbersAndAdd2
337
- SquareAllNumbersAndDoubleThem = SquareAllNumbersAndAdd2 >> DoubleAllNumbers
342
+ SquareAllNumbersAndDouble = SquareAllNumbersAndAdd2 >> DoubleAllNumbers
343
+ DoubleAllNumbersAndSquareAndAdd2 = DoubleAllNumbers >> SquareAllNumbersAndAdd2
338
344
 
339
- DoubleAllNumbersAndSquareThem
345
+ SquareAllNumbersAndDouble
340
346
  .call(numbers: %w[1 1 2 2 3 4])
341
- .on_success { |value| p value[:numbers] } # [6, 6, 18, 18, 38, 66]
347
+ .on_success { |value| p value[:numbers] } # [6, 6, 12, 12, 22, 36]
342
348
 
343
- SquareAllNumbersAndDoubleThem
349
+ DoubleAllNumbersAndSquareAndAdd2
344
350
  .call(numbers: %w[1 1 2 2 3 4])
345
- .on_success { |value| p value[:numbers] } # [6, 6, 12, 12, 22, 36]
351
+ .on_success { |value| p value[:numbers] } # [6, 6, 18, 18, 38, 66]
346
352
  ```
347
353
 
348
354
  Note: You can blend any of the [syntaxes/approaches to create the pipelines](#how-to-create-a-pipeline-of-service-objects)) - [examples](https://github.com/serradura/u-service/blob/master/test/micro/service/pipeline/blend_test.rb#L7-L34).
349
355
 
356
+ ## Examples
357
+
358
+ 1. [Rescuing an exception inside of service objects](https://github.com/serradura/u-service/blob/master/examples/rescuing_exceptions.rb)
359
+ 2. [Users creation](https://github.com/serradura/u-service/blob/master/examples/users_creation.rb)
360
+
361
+ An example of how to use services pipelines to sanitize and validate the input data, and how to represents a common use case, like: create an user.
362
+ 3. [CLI calculator](https://github.com/serradura/u-service/tree/master/examples/calculator)
363
+
364
+ A more complex example which use rake tasks to demonstrate how to handle user data, and how to use different failures type to control the app flow.
365
+
350
366
  ## Comparisons
351
367
 
352
368
  Check it out implementations of the same use case with different libs (abstractions).
@@ -360,6 +376,7 @@ Check it out implementations of the same use case with different libs (abstracti
360
376
 
361
377
  https://github.com/serradura/u-service/tree/master/benchmarks/interactor
362
378
 
379
+ ![interactor VS u-service](https://github.com/serradura/u-service/blob/master/assets/u-service_benchmarks.png?raw=true)
363
380
 
364
381
  ## Development
365
382
 
Binary file
@@ -5,11 +5,43 @@ gemfile do
5
5
 
6
6
  gem 'benchmark-ips', '~> 2.7', '>= 2.7.2'
7
7
  gem 'interactor', '~> 3.1', '>= 3.1.1'
8
- gem 'u-service', '~> 0.11.0'
8
+ gem 'u-service', '~> 0.12.0'
9
9
  end
10
10
 
11
11
  require 'benchmark/ips'
12
12
 
13
+ module IT
14
+ class ConvertToNumbers
15
+ include Interactor
16
+
17
+ def call
18
+ numbers = context.numbers
19
+
20
+ if numbers.all? { |value| String(value) =~ /\d+/ }
21
+ context.numbers = numbers.map(&:to_i)
22
+ else
23
+ context.fail! numbers: 'must contain only numeric types'
24
+ end
25
+ end
26
+ end
27
+
28
+ class Add2
29
+ include Interactor
30
+
31
+ def call
32
+ numbers = context.numbers
33
+
34
+ context.numbers = numbers.map { |number| number + 2 }
35
+ end
36
+ end
37
+
38
+ class Add2ToAllNumbers
39
+ include Interactor::Organizer
40
+
41
+ organize ConvertToNumbers, Add2
42
+ end
43
+ end
44
+
13
45
  module MSB
14
46
  class ConvertToNumbers < Micro::Service::Base
15
47
  attribute :numbers
@@ -58,38 +90,6 @@ module MSS
58
90
  Add2ToAllNumbers = ConvertToNumbers >> Add2
59
91
  end
60
92
 
61
- module IT
62
- class ConvertToNumbers
63
- include Interactor
64
-
65
- def call
66
- numbers = context.numbers
67
-
68
- if numbers.all? { |value| String(value) =~ /\d+/ }
69
- context.numbers = numbers.map(&:to_i)
70
- else
71
- context.fail! numbers: 'must contain only numeric types'
72
- end
73
- end
74
- end
75
-
76
- class Add2
77
- include Interactor
78
-
79
- def call
80
- numbers = context.numbers
81
-
82
- context.numbers = numbers.map { |number| number + 2 }
83
- end
84
- end
85
-
86
- class Add2ToAllNumbers
87
- include Interactor::Organizer
88
-
89
- organize ConvertToNumbers, Add2
90
- end
91
- end
92
-
93
93
  NUMBERS = {numbers: %w[1 1 2 2 c 4]}
94
94
 
95
95
  Benchmark.ips do |x|
@@ -98,6 +98,10 @@ Benchmark.ips do |x|
98
98
  x.time = 5
99
99
  x.warmup = 2
100
100
 
101
+ x.report('Interactor::Organizer') do
102
+ IT::Add2ToAllNumbers.call(NUMBERS)
103
+ end
104
+
101
105
  x.report('Pipeline of Micro::Service::Base') do
102
106
  MSB::Add2ToAllNumbers.call(NUMBERS)
103
107
  end
@@ -106,29 +110,25 @@ Benchmark.ips do |x|
106
110
  MSS::Add2ToAllNumbers.call(NUMBERS)
107
111
  end
108
112
 
109
- x.report('Interactor::Organizer') do
110
- IT::Add2ToAllNumbers.call(NUMBERS)
111
- end
112
-
113
113
  x.compare!
114
114
  end
115
115
 
116
116
  # Warming up --------------------------------------
117
+ # Interactor::Organizer
118
+ # 2.355k i/100ms
117
119
  # Pipeline of Micro::Service::Base
118
- # 5.437k i/100ms
120
+ # 15.483k i/100ms
119
121
  # Pipeline of Micro::Service::Strict
120
- # 5.192k i/100ms
121
- # Interactor::Organizer
122
- # 2.236k i/100ms
122
+ # 13.467k i/100ms
123
123
  # Calculating -------------------------------------
124
+ # Interactor::Organizer
125
+ # 23.767k (± 2.1%) i/s - 120.105k in 5.055726s
124
126
  # Pipeline of Micro::Service::Base
125
- # 56.665k (± 1.9%) i/s - 288.161k in 5.087185s
127
+ # 166.013k (± 1.8%) i/s - 836.082k in 5.037938s
126
128
  # Pipeline of Micro::Service::Strict
127
- # 52.914k (± 2.0%) i/s - 264.792k in 5.006157s
128
- # Interactor::Organizer
129
- # 22.940k (± 2.9%) i/s - 116.272k in 5.072931s
129
+ # 141.545k (± 2.1%) i/s - 713.751k in 5.044932s
130
130
 
131
131
  # Comparison:
132
- # Pipeline of Micro::Service::Base: 56665.3 i/s
133
- # Pipeline of Micro::Service::Strict: 52914.3 i/s - 1.07x slower
134
- # Interactor::Organizer: 22940.3 i/s - 2.47x slower
132
+ # Pipeline of Micro::Service::Base: 166013.1 i/s
133
+ # Pipeline of Micro::Service::Strict: 141545.4 i/s - 1.17x slower
134
+ # Interactor::Organizer: 23766.6 i/s - 6.99x slower
@@ -5,11 +5,43 @@ gemfile do
5
5
 
6
6
  gem 'benchmark-ips', '~> 2.7', '>= 2.7.2'
7
7
  gem 'interactor', '~> 3.1', '>= 3.1.1'
8
- gem 'u-service', '~> 0.11.0'
8
+ gem 'u-service', '~> 0.12.0'
9
9
  end
10
10
 
11
11
  require 'benchmark/ips'
12
12
 
13
+ module IT
14
+ class ConvertToNumbers
15
+ include Interactor
16
+
17
+ def call
18
+ numbers = context.numbers
19
+
20
+ if numbers.all? { |value| String(value) =~ /\d+/ }
21
+ context.numbers = numbers.map(&:to_i)
22
+ else
23
+ context.fail! numbers: 'must contain only numeric types'
24
+ end
25
+ end
26
+ end
27
+
28
+ class Add2
29
+ include Interactor
30
+
31
+ def call
32
+ numbers = context.numbers
33
+
34
+ context.numbers = numbers.map { |number| number + 2 }
35
+ end
36
+ end
37
+
38
+ class Add2ToAllNumbers
39
+ include Interactor::Organizer
40
+
41
+ organize ConvertToNumbers, Add2
42
+ end
43
+ end
44
+
13
45
  module MSB
14
46
  class ConvertToNumbers < Micro::Service::Base
15
47
  attribute :numbers
@@ -58,38 +90,6 @@ module MSS
58
90
  Add2ToAllNumbers = ConvertToNumbers >> Add2
59
91
  end
60
92
 
61
- module IT
62
- class ConvertToNumbers
63
- include Interactor
64
-
65
- def call
66
- numbers = context.numbers
67
-
68
- if numbers.all? { |value| String(value) =~ /\d+/ }
69
- context.numbers = numbers.map(&:to_i)
70
- else
71
- context.fail! numbers: 'must contain only numeric types'
72
- end
73
- end
74
- end
75
-
76
- class Add2
77
- include Interactor
78
-
79
- def call
80
- numbers = context.numbers
81
-
82
- context.numbers = numbers.map { |number| number + 2 }
83
- end
84
- end
85
-
86
- class Add2ToAllNumbers
87
- include Interactor::Organizer
88
-
89
- organize ConvertToNumbers, Add2
90
- end
91
- end
92
-
93
93
  NUMBERS = {numbers: %w[1 1 2 2 3 4]}
94
94
 
95
95
  Benchmark.ips do |x|
@@ -98,6 +98,10 @@ Benchmark.ips do |x|
98
98
  x.time = 5
99
99
  x.warmup = 2
100
100
 
101
+ x.report('Interactor::Organizer') do
102
+ IT::Add2ToAllNumbers.call(NUMBERS)
103
+ end
104
+
101
105
  x.report('Pipeline of Micro::Service::Base') do
102
106
  MSB::Add2ToAllNumbers.call(NUMBERS)
103
107
  end
@@ -106,29 +110,25 @@ Benchmark.ips do |x|
106
110
  MSS::Add2ToAllNumbers.call(NUMBERS)
107
111
  end
108
112
 
109
- x.report('Interactor::Organizer') do
110
- IT::Add2ToAllNumbers.call(NUMBERS)
111
- end
112
-
113
113
  x.compare!
114
114
  end
115
115
 
116
116
  # Warming up --------------------------------------
117
+ # Interactor::Organizer
118
+ # 5.047k i/100ms
117
119
  # Pipeline of Micro::Service::Base
118
- # 3.578k i/100ms
120
+ # 8.069k i/100ms
119
121
  # Pipeline of Micro::Service::Strict
120
- # 3.177k i/100ms
121
- # Interactor::Organizer
122
- # 4.695k i/100ms
122
+ # 6.706k i/100ms
123
123
  # Calculating -------------------------------------
124
+ # Interactor::Organizer
125
+ # 50.656k (± 2.4%) i/s - 257.397k in 5.084184s
124
126
  # Pipeline of Micro::Service::Base
125
- # 36.087k6.9%) i/s - 182.478k in 5.084835s
127
+ # 83.309k1.5%) i/s - 419.588k in 5.037749s
126
128
  # Pipeline of Micro::Service::Strict
127
- # 31.329k6.7%) i/s - 158.850k in 5.094012s
128
- # Interactor::Organizer
129
- # 46.294k (± 6.6%) i/s - 234.750k in 5.093117s
129
+ # 69.195k1.7%) i/s - 348.712k in 5.041089s
130
130
 
131
131
  # Comparison:
132
- # Interactor::Organizer: 46293.6 i/s
133
- # Pipeline of Micro::Service::Base: 36086.5 i/s - 1.28x slower
134
- # Pipeline of Micro::Service::Strict: 31328.5 i/s - 1.48x slower
132
+ # Pipeline of Micro::Service::Base: 83309.3 i/s
133
+ # Pipeline of Micro::Service::Strict: 69195.1 i/s - 1.20x slower
134
+ # Interactor::Organizer: 50656.5 i/s - 1.64x slower
@@ -5,24 +5,27 @@ gemfile do
5
5
 
6
6
  gem 'benchmark-ips', '~> 2.7', '>= 2.7.2'
7
7
  gem 'interactor', '~> 3.1', '>= 3.1.1'
8
- gem 'u-service', '~> 0.11.0'
8
+ gem 'u-service', '~> 0.12.0'
9
9
  end
10
10
 
11
11
  require 'benchmark/ips'
12
12
 
13
- class MSB_Multiply < Micro::Service::Base
14
- attributes :a, :b
13
+ class IT_Multiply
14
+ include Interactor
15
+
16
+ def call
17
+ a = context.a
18
+ b = context.b
15
19
 
16
- def call!
17
20
  if a.is_a?(Numeric) && b.is_a?(Numeric)
18
- Success(a * b)
21
+ context.number = a * b
19
22
  else
20
- Failure(:invalid_data)
23
+ context.fail!(type: :invalid_data)
21
24
  end
22
25
  end
23
26
  end
24
27
 
25
- class MSS_Multiply < Micro::Service::Strict
28
+ class MSB_Multiply < Micro::Service::Base
26
29
  attributes :a, :b
27
30
 
28
31
  def call!
@@ -34,17 +37,14 @@ class MSS_Multiply < Micro::Service::Strict
34
37
  end
35
38
  end
36
39
 
37
- class IT_Multiply
38
- include Interactor
39
-
40
- def call
41
- a = context.a
42
- b = context.b
40
+ class MSS_Multiply < Micro::Service::Strict
41
+ attributes :a, :b
43
42
 
43
+ def call!
44
44
  if a.is_a?(Numeric) && b.is_a?(Numeric)
45
- context.number = a * b
45
+ Success(a * b)
46
46
  else
47
- context.fail!(type: :invalid_data)
47
+ Failure(:invalid_data)
48
48
  end
49
49
  end
50
50
  end
@@ -58,6 +58,11 @@ Benchmark.ips do |x|
58
58
  x.time = 5
59
59
  x.warmup = 2
60
60
 
61
+ x.report('Interactor') do
62
+ IT_Multiply.call(SYMBOL_KEYS)
63
+ IT_Multiply.call(STRING_KEYS)
64
+ end
65
+
61
66
  x.report('Micro::Service::Base') do
62
67
  MSB_Multiply.call(SYMBOL_KEYS)
63
68
  MSB_Multiply.call(STRING_KEYS)
@@ -68,26 +73,21 @@ Benchmark.ips do |x|
68
73
  MSS_Multiply.call(STRING_KEYS)
69
74
  end
70
75
 
71
- x.report('Interactor') do
72
- IT_Multiply.call(SYMBOL_KEYS)
73
- IT_Multiply.call(STRING_KEYS)
74
- end
75
-
76
76
  x.compare!
77
77
  end
78
78
 
79
79
  # Warming up --------------------------------------
80
- # Micro::Service::Base 5.304k i/100ms
81
- # Micro::Service::Strict
82
- # 4.516k i/100ms
83
80
  # Interactor 1.507k i/100ms
81
+ # Micro::Service::Base 12.902k i/100ms
82
+ # Micro::Service::Strict
83
+ # 9.758k i/100ms
84
84
  # Calculating -------------------------------------
85
- # Micro::Service::Base 54.444k (± 2.8%) i/s - 275.808k in 5.070215s
85
+ # Interactor 15.482k (± 2.6%) i/s - 78.364k in 5.065166s
86
+ # Micro::Service::Base 134.861k (± 1.3%) i/s - 683.806k in 5.071263s
86
87
  # Micro::Service::Strict
87
- # 45.996k (± 1.8%) i/s - 230.316k in 5.008931s
88
- # Interactor 15.363k (± 2.2%) i/s - 76.857k in 5.005209s
88
+ # 101.331k (± 1.5%) i/s - 507.416k in 5.008688s
89
89
 
90
90
  # Comparison:
91
- # Micro::Service::Base: 54444.5 i/s
92
- # Micro::Service::Strict: 45995.8 i/s - 1.18x slower
93
- # Interactor: 15363.0 i/s - 3.54x slower
91
+ # Micro::Service::Base: 134861.1 i/s
92
+ # Micro::Service::Strict: 101331.5 i/s - 1.33x slower
93
+ # Interactor: 15482.0 i/s - 8.71x slower
@@ -5,24 +5,27 @@ gemfile do
5
5
 
6
6
  gem 'benchmark-ips', '~> 2.7', '>= 2.7.2'
7
7
  gem 'interactor', '~> 3.1', '>= 3.1.1'
8
- gem 'u-service', '~> 0.11.0'
8
+ gem 'u-service', '~> 0.12.0'
9
9
  end
10
10
 
11
11
  require 'benchmark/ips'
12
12
 
13
- class MSB_Multiply < Micro::Service::Base
14
- attributes :a, :b
13
+ class IT_Multiply
14
+ include Interactor
15
+
16
+ def call
17
+ a = context.a
18
+ b = context.b
15
19
 
16
- def call!
17
20
  if a.is_a?(Numeric) && b.is_a?(Numeric)
18
- Success(a * b)
21
+ context.number = a * b
19
22
  else
20
- Failure(:invalid_data)
23
+ context.fail!(:invalid_data)
21
24
  end
22
25
  end
23
26
  end
24
27
 
25
- class MSS_Multiply < Micro::Service::Strict
28
+ class MSB_Multiply < Micro::Service::Base
26
29
  attributes :a, :b
27
30
 
28
31
  def call!
@@ -34,17 +37,14 @@ class MSS_Multiply < Micro::Service::Strict
34
37
  end
35
38
  end
36
39
 
37
- class IT_Multiply
38
- include Interactor
39
-
40
- def call
41
- a = context.a
42
- b = context.b
40
+ class MSS_Multiply < Micro::Service::Strict
41
+ attributes :a, :b
43
42
 
43
+ def call!
44
44
  if a.is_a?(Numeric) && b.is_a?(Numeric)
45
- context.number = a * b
45
+ Success(a * b)
46
46
  else
47
- context.fail!(:invalid_data)
47
+ Failure(:invalid_data)
48
48
  end
49
49
  end
50
50
  end
@@ -58,6 +58,11 @@ Benchmark.ips do |x|
58
58
  x.time = 5
59
59
  x.warmup = 2
60
60
 
61
+ x.report('Interactor') do
62
+ IT_Multiply.call(SYMBOL_KEYS)
63
+ IT_Multiply.call(STRING_KEYS)
64
+ end
65
+
61
66
  x.report('Micro::Service::Base') do
62
67
  MSB_Multiply.call(SYMBOL_KEYS)
63
68
  MSB_Multiply.call(STRING_KEYS)
@@ -68,26 +73,21 @@ Benchmark.ips do |x|
68
73
  MSS_Multiply.call(STRING_KEYS)
69
74
  end
70
75
 
71
- x.report('Interactor') do
72
- IT_Multiply.call(SYMBOL_KEYS)
73
- IT_Multiply.call(STRING_KEYS)
74
- end
75
-
76
76
  x.compare!
77
77
  end
78
78
 
79
79
  # Warming up --------------------------------------
80
- # Micro::Service::Base 5.365k i/100ms
80
+ # Interactor 2.943k i/100ms
81
+ # Micro::Service::Base 12.540k i/100ms
81
82
  # Micro::Service::Strict
82
- # 4.535k i/100ms
83
- # Interactor 2.620k i/100ms
83
+ # 9.584k i/100ms
84
84
  # Calculating -------------------------------------
85
- # Micro::Service::Base 51.795k4.7%) i/s - 262.885k in 5.086671s
85
+ # Interactor 29.874k2.7%) i/s - 150.093k in 5.027909s
86
+ # Micro::Service::Base 131.440k (± 1.9%) i/s - 664.620k in 5.058327s
86
87
  # Micro::Service::Strict
87
- # 46.253k1.6%) i/s - 231.285k in 5.001748s
88
- # Interactor 29.561k (± 3.3%) i/s - 149.340k in 5.057720s
88
+ # 99.111k2.2%) i/s - 498.368k in 5.031006s
89
89
 
90
90
  # Comparison:
91
- # Micro::Service::Base: 51794.5 i/s
92
- # Micro::Service::Strict: 46253.0 i/s - 1.12x slower
93
- # Interactor: 29561.5 i/s - 1.75x slower
91
+ # Micro::Service::Base: 131440.3 i/s
92
+ # Micro::Service::Strict: 99111.3 i/s - 1.33x slower
93
+ # Interactor: 29873.6 i/s - 4.40x slower
@@ -0,0 +1,56 @@
1
+ # μ-service - Calculator example
2
+
3
+ This example uses [rake](http://rubygems.org/gems/rake) to expose a CLI calculator.
4
+
5
+ ## Installation instructions
6
+ ```sh
7
+ gem install rake
8
+ gem install u-service -v 0.12.0
9
+ ```
10
+
11
+ ### Usage
12
+
13
+ ![gif](https://github.com/serradura/u-service/blob/master/examples/calculator/assets/usage.gif?raw=true)
14
+
15
+ #### Listing the available rake tasks
16
+ ```sh
17
+ rake -T
18
+
19
+ # rake calc:add[a,b] # adds two numbers
20
+ # rake calc:divide[a,b] # divides two numbers
21
+ # rake calc:multiply[a,b] # multiplies two numbers
22
+ # rake calc:subtract[a,b] # subtracts two numbers
23
+ ```
24
+
25
+ #### Calculating integer numbers
26
+ ```sh
27
+ rake calc:add[3,2]
28
+ # 3 + 2 = 5
29
+
30
+ rake calc:subtract[3,2]
31
+ # 3 - 2 = 1
32
+
33
+ rake calc:multiply[3,2]
34
+ # 3 x 2 = 6
35
+
36
+ rake calc:divide[3,2]
37
+ # 3 / 2 = 1
38
+ ```
39
+
40
+ #### Calculating float numbers
41
+ ```sh
42
+ rake calc:divide[3.0,2.0]
43
+ # 3.0 / 2.0 = 1.5
44
+
45
+ rake calc:divide[-3.0,2.0]
46
+ # -3.0 / 2.0 = -1.5
47
+ ```
48
+
49
+ #### Calculation errors
50
+ ```sh
51
+ rake calc:add[1,a]
52
+ # ERROR: The arguments must contain only numeric values
53
+
54
+ rake calc:add[-\ 1,2]
55
+ # ERROR: Arguments can't have spaces: a: "- 1", b: "2"
56
+ ```
@@ -0,0 +1,60 @@
1
+ require 'u-service'
2
+
3
+ require_relative 'calc/operation'
4
+ require_relative 'calc/normalize_args'
5
+ require_relative 'calc/transform_into_numbers'
6
+
7
+ module Calc
8
+ TransformArgsIntoNumbers = NormalizeArgs >> TransformIntoNumbers
9
+
10
+ module With
11
+ Add = TransformArgsIntoNumbers >> Operation::Add
12
+ Divide = TransformArgsIntoNumbers >> Operation::Divide
13
+ Subtract = TransformArgsIntoNumbers >> Operation::Subtract
14
+ Multiply = TransformArgsIntoNumbers >> Operation::Multiply
15
+ end
16
+ end
17
+
18
+ class PerformTask < Micro::Service::Base
19
+ attributes :args, :operation
20
+
21
+ def call!
22
+ operation
23
+ .call(args: args)
24
+ .on_success { |value| print_operation(value) }
25
+ .on_failure(:not_a_number) { |message| puts "ERROR: #{message}" }
26
+ .on_failure(:arguments_with_space_chars) do |(a, b)|
27
+ puts "ERROR: Arguments can't have spaces: a: #{a}, b: #{b}"
28
+ end
29
+ end
30
+
31
+ private def print_operation(a:, operator:, b:, result:)
32
+ puts "#{a} #{operator} #{b} = #{result}"
33
+ end
34
+ end
35
+
36
+ namespace :calc do
37
+ desc 'adds two numbers'
38
+ task :add, [:a, :b] do |_task, args|
39
+ PerformTask.call(args: args, operation: Calc::With::Add)
40
+ end
41
+
42
+ desc 'divides two numbers'
43
+ task :divide, [:a, :b] do |_task, args|
44
+ begin
45
+ PerformTask.call(args: args, operation: Calc::With::Divide)
46
+ rescue ZeroDivisionError => e
47
+ puts "ERROR: #{e.message}"
48
+ end
49
+ end
50
+
51
+ desc 'subtracts two numbers'
52
+ task :subtract, [:a, :b] do |_task, args|
53
+ PerformTask.call(args: args, operation: Calc::With::Subtract)
54
+ end
55
+
56
+ desc 'multiplies two numbers'
57
+ task :multiply, [:a, :b] do |_task, args|
58
+ PerformTask.call(args: args, operation: Calc::With::Multiply)
59
+ end
60
+ end
@@ -0,0 +1,17 @@
1
+ module Calc
2
+ class NormalizeArgs < Micro::Service::Base
3
+ attributes :args
4
+
5
+ def call!
6
+ a, b = normalize(args[:a]), normalize(args[:b])
7
+
8
+ return Success(a: a, b: b) if a !~ /\s/ && b !~ /\s/
9
+
10
+ Failure(:arguments_with_space_chars) { [a.inspect, b.inspect] }
11
+ end
12
+
13
+ private def normalize(value)
14
+ String(value).strip
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,39 @@
1
+ class Operation < Micro::Service::Base
2
+ attributes :a, :b
3
+
4
+ private def result_of(operation_result)
5
+ attributes(:a, :operator, :b).merge(result: operation_result)
6
+ end
7
+
8
+ class Add < Operation
9
+ attribute :operator, '+'
10
+
11
+ def call!
12
+ Success(result_of(a + b))
13
+ end
14
+ end
15
+
16
+ class Subtract < Operation
17
+ attribute :operator, '-'
18
+
19
+ def call!
20
+ Success(result_of(a - b))
21
+ end
22
+ end
23
+
24
+ class Multiply < Operation
25
+ attribute :operator, 'x'
26
+
27
+ def call!
28
+ Success(result_of(a * b))
29
+ end
30
+ end
31
+
32
+ class Divide < Operation
33
+ attribute :operator, '/'
34
+
35
+ def call!
36
+ Success(result_of(a / b))
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,18 @@
1
+ class TransformIntoNumbers < Micro::Service::Base
2
+ attributes :a, :b
3
+
4
+ def call!
5
+ number_a, number_b = number(a), number(b)
6
+
7
+ if number_a && number_b
8
+ Success(a: number(a), b: number(b))
9
+ else
10
+ Failure(:not_a_number) { 'The arguments must contain only numeric values' }
11
+ end
12
+ end
13
+
14
+ private def number(value)
15
+ return value.to_i if value =~ /\A[\-,=]?\d+\z/
16
+ return value.to_f if value =~ /\A[\-,=]?\d+\.\d+\z/
17
+ end
18
+ end
@@ -0,0 +1,53 @@
1
+ require 'bundler/inline'
2
+
3
+ gemfile do
4
+ source 'https://rubygems.org'
5
+
6
+ gem 'u-service', '~> 0.12.0'
7
+ end
8
+
9
+ class Divide < Micro::Service::Base
10
+ attributes :a, :b
11
+
12
+ def call!
13
+ return Failure('numbers must be greater than 0') if a < 0 || b < 0
14
+
15
+ Success(a / b)
16
+ rescue => e
17
+ Failure(e.message)
18
+ end
19
+ end
20
+
21
+ #---------------------------------#
22
+ puts "\n-- Success scenario --\n\n"
23
+ #---------------------------------#
24
+
25
+ result = Divide.call(a: 4, b: 2)
26
+
27
+ puts result.value if result.success?
28
+
29
+ #----------------------------------#
30
+ puts "\n-- Failure scenarios --\n\n"
31
+ #----------------------------------#
32
+
33
+ result = Divide.call(a: 4, b: 0)
34
+
35
+ puts result.value if result.failure?
36
+
37
+ puts ''
38
+
39
+ result = Divide.call(a: -4, b: 2)
40
+
41
+ puts result.value if result.failure?
42
+
43
+ # :: example of the output: ::
44
+
45
+ # -- Success scenario --
46
+ #
47
+ # 2
48
+ #
49
+ # -- Failure scenarios --
50
+ #
51
+ # divided by 0
52
+ #
53
+ # numbers must be greater than 0
@@ -0,0 +1,133 @@
1
+ require 'bundler/inline'
2
+
3
+ gemfile do
4
+ source 'https://rubygems.org'
5
+
6
+ # NOTE: I used an older version of the Activemodel only to show the compatibility with its older versions.
7
+ gem 'activemodel', '~> 3.2', '>= 3.2.22.5'
8
+ gem 'u-service', '~> 0.12.0'
9
+ end
10
+
11
+ require 'micro/service/with_validation'
12
+
13
+ module Users
14
+ class Entity
15
+ include Micro::Attributes.with(:initialize)
16
+
17
+ attributes :id, :name, :email
18
+
19
+ def persisted?
20
+ !id.nil?
21
+ end
22
+ end
23
+
24
+ module Creation
25
+ require 'uri'
26
+ require 'securerandom'
27
+
28
+ class ProcessParams < Micro::Service::Base
29
+ attributes :name, :email
30
+
31
+ def call!
32
+ Success(name: normalized_name, email: String(email).downcase.strip)
33
+ end
34
+
35
+ private def normalized_name
36
+ String(name).strip.gsub(/\s+/, ' ')
37
+ end
38
+ end
39
+
40
+ class ValidateParams < Micro::Service::WithValidation
41
+ attributes :name, :email
42
+
43
+ validates :name, presence: true
44
+ validates :email, format: { with: URI::MailTo::EMAIL_REGEXP }
45
+
46
+ def call!
47
+ Success(attributes(:name, :email))
48
+ end
49
+ end
50
+
51
+ class Persist < Micro::Service::Base
52
+ attributes :name, :email
53
+
54
+ def call!
55
+ Success(user: Entity.new(user_data))
56
+ end
57
+
58
+ private def user_data
59
+ attributes.merge(id: SecureRandom.uuid)
60
+ end
61
+ end
62
+
63
+ class SendToCRM < Micro::Service::Base
64
+ attribute :user
65
+
66
+ def call!
67
+ return Success(user_id: user.id, crm_id: send_to_crm) if user.persisted?
68
+
69
+ Failure(:crm_error) { 'User can\'t be sent to the CRM' }
70
+ end
71
+
72
+ private def send_to_crm
73
+ # Do some integration stuff...
74
+ SecureRandom.uuid
75
+ end
76
+ end
77
+
78
+ Process = ProcessParams >> ValidateParams >> Persist >> SendToCRM
79
+ end
80
+ end
81
+
82
+ require 'pp'
83
+
84
+ params = {
85
+ "name" => " Rodrigo \n Serradura ",
86
+ "email" => " RoDRIGo.SERRAdura@gmail.com "
87
+ }
88
+
89
+ #--------------------------------------#
90
+ puts "\n-- Parameters processing --\n\n"
91
+ #--------------------------------------#
92
+
93
+ print 'Before: '
94
+ p params
95
+
96
+ print ' After: '
97
+ Users::Creation::ProcessParams.call(params).on_success { |value| p value }
98
+
99
+ #---------------------------------#
100
+ puts "\n-- Success scenario --\n\n"
101
+ #---------------------------------#
102
+
103
+ Users::Creation::Process
104
+ .call(params)
105
+ .on_success do |user_id:, crm_id:|
106
+ puts " CRM ID: #{crm_id}"
107
+ puts "USER ID: #{user_id}"
108
+ end
109
+
110
+ #---------------------------------#
111
+ puts "\n-- Failure scenario --\n\n"
112
+ #---------------------------------#
113
+
114
+ Users::Creation::Process
115
+ .call(name: '', email: '')
116
+ .on_failure { |errors:| p errors.full_messages }
117
+
118
+
119
+ # :: example of the output: ::
120
+
121
+ # -- Parameters processing --
122
+ #
123
+ # Before: {"name"=>" Rodrigo \n Serradura ", "email"=>" RoDRIGo.SERRAdura@gmail.com "}
124
+ # After: {:name=>"Rodrigo Serradura", :email=>"rodrigo.serradura@gmail.com"}
125
+ #
126
+ # -- Success scenario --
127
+ #
128
+ # CRM ID: f3f189f6-ba6a-40ab-998c-c86773c41c83
129
+ # USER ID: c140ffc3-6a7c-4554-972a-c2a0d59f8cb1
130
+ #
131
+ # -- Failure scenarios --
132
+ #
133
+ # ["Name can't be blank", "Email is invalid"]
@@ -5,11 +5,11 @@ module Micro
5
5
  class Base
6
6
  include Micro::Attributes.without(:strict_initialize)
7
7
 
8
- INVALID_RESULT = '#call! must return a Micro::Service::Result instance'.freeze
9
-
8
+ UNEXPECTED_RESULT = '#call! must return a Micro::Service::Result instance'.freeze
9
+ InvalidResultInstance = ArgumentError.new('argument must be an instance of Micro::Service::Result'.freeze)
10
10
  ResultIsAlreadyDefined = ArgumentError.new('result is already defined'.freeze)
11
11
 
12
- private_constant :INVALID_RESULT, :ResultIsAlreadyDefined
12
+ private_constant :UNEXPECTED_RESULT, :ResultIsAlreadyDefined, :InvalidResultInstance
13
13
 
14
14
  def self.>>(service)
15
15
  Micro::Service::Pipeline[self, service]
@@ -31,29 +31,34 @@ module Micro
31
31
  end
32
32
 
33
33
  def call
34
- result = call!
35
- return result if result.is_a?(Service::Result)
36
- raise TypeError, self.class.name + INVALID_RESULT
34
+ __call
37
35
  end
38
36
 
39
37
  def __set_result__(result)
38
+ raise InvalidResultInstance unless result.is_a?(Result)
40
39
  raise ResultIsAlreadyDefined if @__result
41
40
  @__result = result
42
41
  end
43
42
 
44
43
  private
45
44
 
45
+ def __call
46
+ result = call!
47
+ return result if result.is_a?(Service::Result)
48
+ raise TypeError, self.class.name + UNEXPECTED_RESULT
49
+ end
50
+
46
51
  def __get_result__
47
52
  @__result ||= Result.new
48
53
  end
49
54
 
50
- def Success(arg=nil)
51
- value, type = block_given? ? [yield, arg] : [arg, nil]
55
+ def Success(arg = :ok)
56
+ block_given? ? (value, type = yield, arg) : (value, type = arg, :ok)
52
57
  __get_result__.__set__(true, value, type)
53
58
  end
54
59
 
55
- def Failure(arg=nil)
56
- value, type = block_given? ? [yield, arg] : [arg, nil]
60
+ def Failure(arg = :error)
61
+ block_given? ? (value, type = yield, arg) : (value, type = arg, :error)
57
62
  __get_result__.__set__(false, value, type)
58
63
  end
59
64
  end
@@ -28,7 +28,7 @@ module Micro
28
28
  @services = services
29
29
  end
30
30
 
31
- def call(arg={})
31
+ def call(arg = {})
32
32
  @services.reduce(initial_result(arg)) do |result, service|
33
33
  break result if result.failure?
34
34
  service.__new__(result, result.value).call
@@ -45,7 +45,7 @@ module Micro
45
45
  return arg.call if arg_to_call?(arg)
46
46
  return arg if arg.is_a?(Micro::Service::Result)
47
47
  result = Micro::Service::Result.new
48
- result.__set__(true, arg, nil)
48
+ result.__set__(true, arg, :ok)
49
49
  end
50
50
 
51
51
  def arg_to_call?(arg)
@@ -64,7 +64,7 @@ module Micro
64
64
  @__pipeline = Reducer.build(args)
65
65
  end
66
66
 
67
- def call(options={})
67
+ def call(options = {})
68
68
  new(options).call
69
69
  end
70
70
  end
@@ -3,12 +3,12 @@
3
3
  module Micro
4
4
  module Service
5
5
  class Result
6
- InvalidType = TypeError.new('type must be nil or a symbol'.freeze)
6
+ InvalidType = TypeError.new('type must be a symbol'.freeze)
7
7
 
8
8
  attr_reader :value, :type
9
9
 
10
10
  def __set__(is_success, value, type)
11
- raise InvalidType unless type.nil? || type.is_a?(Symbol)
11
+ raise InvalidType unless type.is_a?(Symbol)
12
12
 
13
13
  @success, @value, @type = is_success, value, type
14
14
 
@@ -23,22 +23,22 @@ module Micro
23
23
  !success?
24
24
  end
25
25
 
26
- def on_success(arg=nil)
26
+ def on_success(arg = :ok)
27
27
  self.tap { yield(value) if success_type?(arg) }
28
28
  end
29
29
 
30
- def on_failure(arg=nil)
30
+ def on_failure(arg = :error)
31
31
  self.tap{ yield(value) if failure_type?(arg) }
32
32
  end
33
33
 
34
34
  private
35
35
 
36
36
  def success_type?(arg)
37
- success? && (arg.nil? || arg == type)
37
+ success? && (arg == :ok || arg == type)
38
38
  end
39
39
 
40
40
  def failure_type?(arg)
41
- failure? && (arg.nil? || arg == type)
41
+ failure? && (arg == :error || arg == type)
42
42
  end
43
43
  end
44
44
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Micro
4
4
  module Service
5
- VERSION = '0.12.0'.freeze
5
+ VERSION = '0.13.0'.freeze
6
6
  end
7
7
  end
@@ -3,17 +3,15 @@
3
3
  require 'micro/service'
4
4
 
5
5
  module Micro
6
- class Service::WithValidation < Micro::Service::Base
7
- include Micro::Attributes::Features::ActiveModelValidations
6
+ module Service
7
+ class Base
8
+ include Micro::Attributes::Features::ActiveModelValidations
8
9
 
9
- def call
10
- return Failure(errors: self.errors) unless valid?
10
+ def call
11
+ return Failure(:validation_error) { {errors: self.errors, service: self} } unless valid?
11
12
 
12
- super
13
+ __call
14
+ end
13
15
  end
14
16
  end
15
-
16
- class Service::Strict::Validation < Service::WithValidation
17
- include Micro::Attributes::Features::StrictInitialize
18
- end
19
17
  end
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'micro/service/with_validation'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: u-service
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.12.0
4
+ version: 0.13.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rodrigo Serradura
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-08-14 00:00:00.000000000 Z
11
+ date: 2019-08-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: u-attributes
@@ -68,6 +68,7 @@ files:
68
68
  - LICENSE.txt
69
69
  - README.md
70
70
  - Rakefile
71
+ - assets/u-service_benchmarks.png
71
72
  - benchmarks/interactor/pipeline_failure.rb
72
73
  - benchmarks/interactor/pipeline_success.rb
73
74
  - benchmarks/interactor/service_failure.rb
@@ -76,6 +77,14 @@ files:
76
77
  - bin/setup
77
78
  - comparisons/interactor.rb
78
79
  - comparisons/u-service.rb
80
+ - examples/calculator/README.md
81
+ - examples/calculator/Rakefile
82
+ - examples/calculator/assets/usage.gif
83
+ - examples/calculator/calc/normalize_args.rb
84
+ - examples/calculator/calc/operation.rb
85
+ - examples/calculator/calc/transform_into_numbers.rb
86
+ - examples/rescuing_exceptions.rb
87
+ - examples/users_creation.rb
79
88
  - lib/micro/service.rb
80
89
  - lib/micro/service/base.rb
81
90
  - lib/micro/service/pipeline.rb
@@ -84,6 +93,7 @@ files:
84
93
  - lib/micro/service/version.rb
85
94
  - lib/micro/service/with_validation.rb
86
95
  - lib/u-service.rb
96
+ - lib/u-service/with_validation.rb
87
97
  - test.sh
88
98
  - u-service.gemspec
89
99
  homepage: https://github.com/serradura/u-service