command_service_object 0.5.10 → 0.5.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +72 -22
- data/lib/command_service_object.rb +1 -1
- data/lib/command_service_object/helpers/controller_helper.rb +2 -0
- data/lib/command_service_object/hooks.rb +6 -16
- data/lib/command_service_object/version.rb +1 -1
- data/lib/generators/service/install/templates/services/application_service.rb +15 -13
- data/lib/generators/service/install/templates/services/case_base.rb +4 -3
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 71a2cbd62d96248cddea4dc3cfbdeb6f07d1d43b62582eb573ca3e689322af01
|
4
|
+
data.tar.gz: 333ecebd2230edf5c67967d4fcfd526a81e0f51d50111ca95f264c543ae12d14
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: af30bd8432c0a1a045c152992e038381f64dc5cbd8882aff7bcc483ad8e5e86d24768df660c8502d2afeb2fad756f75edaeb9e12f28c67c40b604ff289906b42
|
7
|
+
data.tar.gz: dcace53060dc60e462025ca5dcb3d6ca93e440fc5f1ada54e587103ce18a6a35aa4dc50705a109d605c72212b8ec96600d1629edfd9d3cc39536568e0c147732
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -2,21 +2,27 @@
|
|
2
2
|
|
3
3
|
Rails Generator for command service object.
|
4
4
|
|
5
|
-
|
5
|
+
## Theory
|
6
|
+
|
6
7
|
[Command Design Pattern](https://en.wikipedia.org/wiki/Command_pattern) consists of `Command Object` and `Service Object` (Executor), Command object is responsible for containing `Client` requests and run input validations on it to ensure that the request is valid and set default values, then `Service Object` applies the business logic on that command.
|
7
8
|
|
8
|
-
### Implementation
|
9
|
-
|
9
|
+
### Implementation
|
10
|
+
|
11
|
+
Service consists of several objects { `Command Object` `Usecase Object` And `Error Object` (business logic error) }.
|
10
12
|
|
11
13
|
- **Command Object:** the object that responsible for containing `Client` requests and run input validations it's implemented using [Virtus](https://github.com/solnic/virtus) gem and can use `activerecord` for validations and it's existed under `commands` dir.
|
12
14
|
- **Usecase Object:** this object responsible for executing the business logic, Every `usecase` should execute one command type only so that command name should be the same as usecase object name, usecase object existed under 'usecases` dir.
|
13
|
-
- **
|
15
|
+
- **Micros:** small reusable logic under the same service.
|
16
|
+
|
17
|
+
#### Result Object
|
14
18
|
|
15
|
-
|
16
|
-
In case of successful or failure `ApplicationService` the responsible object for all services will return `service_result` object this object contain `value!` method containing successful call result, and `errors` method containing failure `errors` objects.
|
19
|
+
In case of successful or failure `ApplicationService` the responsible object for all services will return `service_result` object this object contain `value!` method containing successful call result, and `errors` method containing failure `errors` objects.
|
17
20
|
|
18
|
-
|
21
|
+
#### Business Logic Failures
|
19
22
|
|
23
|
+
To raise bussiness logic failures you can use `fail!` helper method with `message: String, extra_data: Hash` arguments.
|
24
|
+
|
25
|
+
> You can check if the result successful or not by using `ok?` method.
|
20
26
|
|
21
27
|
## Installation
|
22
28
|
|
@@ -25,49 +31,69 @@ Add this line to your application's Gemfile:
|
|
25
31
|
```ruby
|
26
32
|
gem 'command_service_object'
|
27
33
|
```
|
34
|
+
|
28
35
|
And then execute:
|
29
36
|
|
30
|
-
|
37
|
+
`bundle`
|
31
38
|
|
32
39
|
Or install it yourself as:
|
33
40
|
|
34
|
-
|
41
|
+
`gem install command_service_object`
|
35
42
|
|
36
43
|
Next, you need to run the generator:
|
37
44
|
|
38
|
-
|
39
|
-
$ rails generate service:install
|
40
|
-
```
|
45
|
+
`rails generate service:install`
|
41
46
|
|
42
47
|
## Usage
|
43
48
|
|
44
|
-
|
45
|
-
|
49
|
+
$ rails g service [service_name] [usecases usecases]
|
50
|
+
|
51
|
+
### Generate Service ex
|
46
52
|
|
47
|
-
|
53
|
+
$ rails g service auth login
|
48
54
|
output
|
49
55
|
|
50
56
|
```bash
|
51
57
|
app/services/
|
52
58
|
├── application_service.rb
|
59
|
+
├── external/
|
53
60
|
├── auth_service
|
54
61
|
│ ├── commands
|
55
62
|
│ │ └── login.rb
|
56
63
|
│ └── usecases
|
57
64
|
│ ├── login.rb
|
58
|
-
│ ├── login.rb
|
59
65
|
│ └── micros
|
60
|
-
│ └── user_profile_image.rb
|
61
66
|
├── case_base.rb
|
62
67
|
└── service_result.rb
|
63
68
|
```
|
64
69
|
|
65
|
-
### Generate micros ex
|
70
|
+
### Generate micros ex
|
66
71
|
|
67
|
-
|
72
|
+
$ rails g service:micro auth generate_jwt_token_for
|
73
|
+
|
74
|
+
```bash
|
75
|
+
app/services/
|
76
|
+
├── auth_service
|
77
|
+
│ └── usecases
|
78
|
+
│ └── micros
|
79
|
+
│ └── generate_jwt_token_for.rb
|
80
|
+
```
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
# app/services/auth_service/usecases/micros/generate_jwt_token_for.rb
|
84
|
+
|
85
|
+
module PaymentService::Usecases::Micros
|
86
|
+
class GenerateJwtTokenFor < CaseBase
|
87
|
+
def call
|
88
|
+
# <Payload>
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
```
|
68
93
|
|
69
94
|
then you can edit command params
|
70
95
|
> you can read [Virtus gem docs](https://github.com/solnic/virtus) for more info.
|
96
|
+
|
71
97
|
```ruby
|
72
98
|
# app/services/auth_service/commands/login.rb
|
73
99
|
# frozen_string_literal: true
|
@@ -87,7 +113,9 @@ module AuthService::Commands
|
|
87
113
|
end
|
88
114
|
end
|
89
115
|
```
|
116
|
+
|
90
117
|
and then add your business logic
|
118
|
+
|
91
119
|
```ruby
|
92
120
|
# app/services/auth_service/usecases/login.rb
|
93
121
|
# frozen_string_literal: true
|
@@ -95,14 +123,13 @@ and then add your business logic
|
|
95
123
|
module AuthService::Usecases
|
96
124
|
class Login < CaseBase
|
97
125
|
include CommandServiceObject::Hooks
|
98
|
-
micros :
|
126
|
+
micros :generate_jwt_token_for
|
99
127
|
#
|
100
128
|
# Your business logic goes here, keep [call] method clean by using private
|
101
129
|
# methods for Business logic.
|
102
130
|
#
|
103
131
|
def call
|
104
|
-
|
105
|
-
balance = user_balance # get user balance ex.
|
132
|
+
token = generate_jwt_token_for(user)
|
106
133
|
end
|
107
134
|
|
108
135
|
# This method will run if call method raise error
|
@@ -117,10 +144,33 @@ module AuthService::Usecases
|
|
117
144
|
end
|
118
145
|
end
|
119
146
|
end
|
147
|
+
```
|
120
148
|
|
149
|
+
### External APIs or Services
|
150
|
+
|
151
|
+
You can wrap external apis or services under `external/` dir
|
152
|
+
|
153
|
+
#### ex
|
154
|
+
|
155
|
+
```ruby
|
156
|
+
module External
|
157
|
+
class StripeService
|
158
|
+
class << self
|
159
|
+
def charge(customer:, amount:, currency:, description: nil)
|
160
|
+
Stripe::Charge.create(
|
161
|
+
customer: customer.id,
|
162
|
+
amount: (round_up(amount, currency) * 100).to_i,
|
163
|
+
description: description || customer.email,
|
164
|
+
currency: currency
|
165
|
+
)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
121
170
|
```
|
122
171
|
|
123
172
|
usage from controller
|
173
|
+
|
124
174
|
```ruby
|
125
175
|
class AuthenticationController < ApplicationController
|
126
176
|
default_service :auth_service
|
@@ -5,7 +5,7 @@ require 'command_service_object/helpers/model_helper'
|
|
5
5
|
require 'command_service_object/helpers/controller_helper'
|
6
6
|
require 'command_service_object/helpers/failure_helper'
|
7
7
|
require 'command_service_object/hooks'
|
8
|
-
|
8
|
+
require 'virtus'
|
9
9
|
|
10
10
|
if defined?(Rails) && Rails::VERSION::STRING >= '3.0'
|
11
11
|
require 'command_service_object/railtie'
|
@@ -26,32 +26,22 @@ module CommandServiceObject
|
|
26
26
|
_called_micros.reverse_each(&:rollback)
|
27
27
|
end
|
28
28
|
|
29
|
-
def setup_getters(getters)
|
30
|
-
getters.each do |getter|
|
31
|
-
method_name = getter.name.split('::').last.underscore
|
32
|
-
|
33
|
-
define_singleton_method(method_name) do |_payload|
|
34
|
-
getter.new.call(args)
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
29
|
def setup_micros(micros)
|
40
30
|
micros.each do |micro|
|
41
31
|
method_name = micro.name.split('::').last.underscore
|
42
32
|
|
43
33
|
# unrollable micros
|
44
|
-
define_singleton_method("#{method_name}!") do |
|
45
|
-
micro.new(
|
34
|
+
define_singleton_method("#{method_name}!") do |cmd|
|
35
|
+
micro.new(cmd).call
|
46
36
|
end
|
47
37
|
|
48
38
|
# rollable micros
|
49
|
-
define_singleton_method(method_name) do |
|
50
|
-
obj = micro.new(
|
51
|
-
obj.call
|
39
|
+
define_singleton_method(method_name) do |cmd|
|
40
|
+
obj = micro.new(cmd)
|
41
|
+
result = obj.call
|
52
42
|
|
53
43
|
_called_micros << obj
|
54
|
-
|
44
|
+
result
|
55
45
|
end
|
56
46
|
end
|
57
47
|
end
|
@@ -6,24 +6,26 @@ class ApplicationService
|
|
6
6
|
raise Errors::InvalidCommand, cmd.class if cmd.invalid?
|
7
7
|
|
8
8
|
usecase = usecase_for(cmd).new(cmd)
|
9
|
-
usecase.call
|
9
|
+
result = ServiceResult.new { usecase.call }
|
10
|
+
|
11
|
+
rollback(usecase, result, cmd) if result.error.present?
|
12
|
+
result
|
10
13
|
rescue StandardError => e
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
14
|
+
log_errors(e, cmd)
|
15
|
+
ServiceResult.new { raise e }
|
16
|
+
end
|
17
|
+
|
18
|
+
def rollback(usecase, result, cmd)
|
19
|
+
usecase.rollback_micros
|
20
|
+
usecase.rollback
|
21
|
+
log_errors(result.error, cmd)
|
17
22
|
end
|
18
23
|
|
19
|
-
def
|
20
|
-
|
21
|
-
return if failure.class.is_a?(CommandServiceObject::Failure)
|
22
|
-
#
|
24
|
+
def log_errors(err, _cmd)
|
25
|
+
return if err.class.is_a?(CommandServiceObject::Failure)
|
23
26
|
# Add your logging logic
|
24
27
|
# ex:
|
25
|
-
# Rollbar.error(
|
26
|
-
#
|
28
|
+
# Rollbar.error(err)
|
27
29
|
end
|
28
30
|
|
29
31
|
private
|
@@ -3,10 +3,11 @@
|
|
3
3
|
class CaseBase
|
4
4
|
include CommandServiceObject::FailureHelper
|
5
5
|
|
6
|
-
attr_reader :
|
6
|
+
attr_reader :cmd
|
7
|
+
alias_attribute :payload, :cmd
|
7
8
|
|
8
|
-
def initialize(
|
9
|
-
@
|
9
|
+
def initialize(cmd)
|
10
|
+
@cmd = cmd
|
10
11
|
end
|
11
12
|
|
12
13
|
def rollback; end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: command_service_object
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.5.
|
4
|
+
version: 0.5.11
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Adham EL-Deeb
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: exe
|
11
11
|
cert_chain: []
|
12
|
-
date: 2019-
|
12
|
+
date: 2019-08-17 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|