u-service 0.12.0 → 0.13.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 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