u-case 3.0.0.rc4 → 3.0.0.rc9
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/.travis.sh +8 -4
- data/Gemfile +2 -3
- data/README.md +251 -229
- data/README.pt-BR.md +1420 -0
- data/lib/micro/case.rb +22 -16
- data/lib/micro/case/config.rb +1 -1
- data/lib/micro/case/result.rb +68 -28
- data/lib/micro/case/utils.rb +7 -0
- data/lib/micro/case/version.rb +1 -1
- data/lib/micro/cases/flow.rb +20 -32
- data/u-case.gemspec +2 -2
- metadata +5 -4
data/README.pt-BR.md
ADDED
@@ -0,0 +1,1420 @@
|
|
1
|
+

|
2
|
+
[](https://rubygems.org/gems/u-case)
|
3
|
+
[](https://travis-ci.com/serradura/u-case)
|
4
|
+
[](https://codeclimate.com/github/serradura/u-case/maintainability)
|
5
|
+
[](https://codeclimate.com/github/serradura/u-case/test_coverage)
|
6
|
+
|
7
|
+
<img src="./assets/ucase_logo_v1.png" alt="u-case - Crie simples e poderosos casos de uso como objetos em Ruby.">
|
8
|
+
|
9
|
+
Crie simples e poderosos casos de uso como objetos em Ruby.
|
10
|
+
|
11
|
+
Principais objetivos deste projeto:
|
12
|
+
1. Fácil de usar e aprender ( entrada **>>** processamento **>>** saída ).
|
13
|
+
2. Promover imutabilidade (transformar dados ao invés de modificar) e integridade de dados.
|
14
|
+
3. Nada de callbacks (ex: before, after, around) para evitar indireções no código que possam comprometer o estado e entendimento dos fluxos da aplicação.
|
15
|
+
4. Resolver regras de negócio complexas, ao permitir uma composição de casos de uso (criação de fluxos).
|
16
|
+
5. Ser rápido e otimizado (verifique a [seção de benchmarks](#benchmarks)).
|
17
|
+
|
18
|
+
> **Nota:** Verifique o repo https://github.com/serradura/from-fat-controllers-to-use-cases para ver uma aplicação Ruby on Rails que utiliza está gem para resolver as regras de negócio.
|
19
|
+
|
20
|
+
## Documentação <!-- omit in toc -->
|
21
|
+
|
22
|
+
Versão | Documentação
|
23
|
+
--------- | -------------
|
24
|
+
3.0.0.rc9 | https://github.com/serradura/u-case/blob/main/README.md
|
25
|
+
2.6.0 | https://github.com/serradura/u-case/blob/v2.x/README.md
|
26
|
+
1.1.0 | https://github.com/serradura/u-case/blob/v1.x/README.md
|
27
|
+
|
28
|
+
## Índice <!-- omit in toc -->
|
29
|
+
- [Compatibilidade](#compatibilidade)
|
30
|
+
- [Dependências](#dependências)
|
31
|
+
- [Instalação](#instalação)
|
32
|
+
- [Uso](#uso)
|
33
|
+
- [`Micro::Case` - Como definir um caso de uso?](#microcase---como-definir-um-caso-de-uso)
|
34
|
+
- [`Micro::Case::Result` - O que é o resultado de um caso de uso?](#microcaseresult---o-que-é-o-resultado-de-um-caso-de-uso)
|
35
|
+
- [O que são os tipos de resultados?](#o-que-são-os-tipos-de-resultados)
|
36
|
+
- [Como difinir tipos customizados de resultados?](#como-difinir-tipos-customizados-de-resultados)
|
37
|
+
- [É possíve definir um tipo sem definit os dados do resultado?](#é-possíve-definir-um-tipo-sem-definit-os-dados-do-resultado)
|
38
|
+
- [Como utilizar os hooks dos resultados?](#como-utilizar-os-hooks-dos-resultados)
|
39
|
+
- [Por que o hook sem um tipo definido expõe o próprio resultado?](#por-que-o-hook-sem-um-tipo-definido-expõe-o-próprio-resultado)
|
40
|
+
- [Usando decomposição para acessar os dados e tipo do resultado](#usando-decomposição-para-acessar-os-dados-e-tipo-do-resultado)
|
41
|
+
- [O que acontece se um hook de resultado for declarado múltiplas vezes?](#o-que-acontece-se-um-hook-de-resultado-for-declarado-múltiplas-vezes)
|
42
|
+
- [Como usar o método `Micro::Case::Result#then`?](#como-usar-o-método-microcaseresultthen)
|
43
|
+
- [O que acontece quando um `Micro::Case::Result#then` recebe um bloco?](#o-que-acontece-quando-um-microcaseresultthen-recebe-um-bloco)
|
44
|
+
- [Como fazer injeção de dependência usando este recurso?](#como-fazer-injeção-de-dependência-usando-este-recurso)
|
45
|
+
- [`Micro::Cases::Flow` - Como compor casos de uso?](#microcasesflow---como-compor-casos-de-uso)
|
46
|
+
- [É possível compor um fluxo com outros fluxos?](#é-possível-compor-um-fluxo-com-outros-fluxos)
|
47
|
+
- [É possível que um fluxo acumule sua entrada e mescle cada resultado de sucesso para usar como argumento dos próximos casos de uso?](#é-possível-que-um-fluxo-acumule-sua-entrada-e-mescle-cada-resultado-de-sucesso-para-usar-como-argumento-dos-próximos-casos-de-uso)
|
48
|
+
- [Como entender o que aconteceu durante a execução de um flow?](#como-entender-o-que-aconteceu-durante-a-execução-de-um-flow)
|
49
|
+
- [`Micro::Case::Result#transitions` schema](#microcaseresulttransitions-schema)
|
50
|
+
- [É possível desabilitar o `Micro::Case::Result#transitions`?](#é-possível-desabilitar-o-microcaseresulttransitions)
|
51
|
+
- [É possível declarar um fluxo que inclui o próprio caso de uso?](#é-possível-declarar-um-fluxo-que-inclui-o-próprio-caso-de-uso)
|
52
|
+
- [`Micro::Case::Strict` - O que é um caso de uso estrito?](#microcasestrict---o-que-é-um-caso-de-uso-estrito)
|
53
|
+
- [`Micro::Case::Safe` - Existe algum recurso para lidar automaticamente com exceções dentro de um caso de uso ou fluxo?](#microcasesafe---existe-algum-recurso-para-lidar-automaticamente-com-exceções-dentro-de-um-caso-de-uso-ou-fluxo)
|
54
|
+
- [`Micro::Case::Result#on_exception`](#microcaseresulton_exception)
|
55
|
+
- [`Micro::Cases::Safe::Flow`](#microcasessafeflow)
|
56
|
+
- [`Micro::Case::Result#on_exception`](#microcaseresulton_exception-1)
|
57
|
+
- [`u-case/with_activemodel_validation` - Como validar os atributos do caso de uso?](#u-casewith_activemodel_validation---como-validar-os-atributos-do-caso-de-uso)
|
58
|
+
- [Se eu habilitei a validação automática, é possível desabilitá-la apenas em casos de uso específicos?](#se-eu-habilitei-a-validação-automática-é-possível-desabilitá-la-apenas-em-casos-de-uso-específicos)
|
59
|
+
- [`Kind::Validator`](#kindvalidator)
|
60
|
+
- [`Micro::Case.config`](#microcaseconfig)
|
61
|
+
- [Benchmarks](#benchmarks)
|
62
|
+
- [`Micro::Case` (v3.0.0)](#microcase-v300)
|
63
|
+
- [Success results](#success-results)
|
64
|
+
- [Failure results](#failure-results)
|
65
|
+
- [`Micro::Cases::Flow` (v3.0.0)](#microcasesflow-v300)
|
66
|
+
- [Comparações](#comparações)
|
67
|
+
- [Exemplos](#exemplos)
|
68
|
+
- [1️⃣ Criação de usuários](#1️⃣-criação-de-usuários)
|
69
|
+
- [2️⃣ Rails App (API)](#2️⃣-rails-app-api)
|
70
|
+
- [3️⃣ CLI calculator](#3️⃣-cli-calculator)
|
71
|
+
- [4️⃣ Interceptando exceções dentro dos casos de uso](#4️⃣-interceptando-exceções-dentro-dos-casos-de-uso)
|
72
|
+
- [Desenvolvimento](#desenvolvimento)
|
73
|
+
- [Contribuindo](#contribuindo)
|
74
|
+
- [Licença](#licença)
|
75
|
+
- [Código de conduta](#código-de-conduta)
|
76
|
+
|
77
|
+
## Compatibilidade
|
78
|
+
|
79
|
+
| u-case | branch | ruby | activemodel |
|
80
|
+
| -------------- | ------- | -------- | ------------- |
|
81
|
+
| 3.0.0.rc9 | main | >= 2.2.0 | >= 3.2, < 6.1 |
|
82
|
+
| 2.6.0 | v2.x | >= 2.2.0 | >= 3.2, < 6.1 |
|
83
|
+
| 1.1.0 | v1.x | >= 2.2.0 | >= 3.2, < 6.1 |
|
84
|
+
|
85
|
+
> Nota: O activemodel é uma dependência opcional, esse módulo que [pode ser habilitado](#u-casewith_activemodel_validation---como-validar-os-atributos-do-caso-de-uso) para validar os atributos dos casos de uso.
|
86
|
+
|
87
|
+
## Dependências
|
88
|
+
|
89
|
+
1. Gem [`kind`](https://github.com/serradura/kind).
|
90
|
+
|
91
|
+
Sistema de tipos simples (em runtime) para Ruby.
|
92
|
+
|
93
|
+
É usado para validar os inputs de alguns métodos do u-case, além de expor um validador de tipos através do [`activemodel validation`](https://github.com/serradura/kind#kindvalidator-activemodelvalidations) ([veja como habilitar]((#u-casewith_activemodel_validation---how-to-validate-use-case-attributes))). Por fim, ele também expõe dois verificadores de tipo: [`Kind::Of::Micro::Case`, `Kind::Of::Micro::Case::Result`](https://github.com/serradura/kind#registering-new-custom-type-checker).
|
94
|
+
2. [`u-attributes`](https://github.com/serradura/u-attributes) gem.
|
95
|
+
|
96
|
+
Essa gem permite definir atributos de leitura (read-only), ou seja, os seus objetos só terão getters para acessar os dados dos seus atributos.
|
97
|
+
Ela é usada para definir os atributos dos casos de uso.
|
98
|
+
|
99
|
+
## Instalação
|
100
|
+
|
101
|
+
Adicione essa linha ao Gemfile da sua aplicação:
|
102
|
+
|
103
|
+
```ruby
|
104
|
+
gem 'u-case', '~> 3.0.0.rc9'
|
105
|
+
```
|
106
|
+
|
107
|
+
E então execute:
|
108
|
+
|
109
|
+
$ bundle
|
110
|
+
|
111
|
+
Ou instale manualmente:
|
112
|
+
|
113
|
+
$ gem install u-case --pre
|
114
|
+
|
115
|
+
## Uso
|
116
|
+
|
117
|
+
### `Micro::Case` - Como definir um caso de uso?
|
118
|
+
|
119
|
+
```ruby
|
120
|
+
class Multiply < Micro::Case
|
121
|
+
# 1. Defina o input como atributos
|
122
|
+
attributes :a, :b
|
123
|
+
|
124
|
+
# 2. Defina o método `call!` com a regra de negócio
|
125
|
+
def call!
|
126
|
+
|
127
|
+
# 3. Envolva o resultado do caso de uso com os métodos `Success(result: *)` ou `Failure(result: *)`
|
128
|
+
if a.is_a?(Numeric) && b.is_a?(Numeric)
|
129
|
+
Success result: { number: a * b }
|
130
|
+
else
|
131
|
+
Failure result: { message: '`a` and `b` attributes must be numeric' }
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
#===========================#
|
137
|
+
# Executando um caso de uso #
|
138
|
+
#===========================#
|
139
|
+
|
140
|
+
# Resultado de sucesso
|
141
|
+
|
142
|
+
result = Multiply.call(a: 2, b: 2)
|
143
|
+
|
144
|
+
result.success? # true
|
145
|
+
result.data # { number: 4 }
|
146
|
+
|
147
|
+
# Resultado de falha
|
148
|
+
|
149
|
+
bad_result = Multiply.call(a: 2, b: '2')
|
150
|
+
|
151
|
+
bad_result.failure? # true
|
152
|
+
bad_result.data # { message: "`a` and `b` attributes must be numeric" }
|
153
|
+
|
154
|
+
# Nota:
|
155
|
+
# ----
|
156
|
+
# O resultado de um Micro::Case.call é uma instância de Micro::Case::Result
|
157
|
+
```
|
158
|
+
|
159
|
+
[⬆️ Voltar para o índice](#índice-)
|
160
|
+
|
161
|
+
### `Micro::Case::Result` - O que é o resultado de um caso de uso?
|
162
|
+
|
163
|
+
Um `Micro::Case::Result` armazena os dados de output de um caso de uso. Esses são seus métodos:
|
164
|
+
- `#success?` retorna true se for um resultado de sucesso.
|
165
|
+
- `#failure?` retorna true se for um resultado de falha.
|
166
|
+
- `#use_case` retorna o caso de uso reponsável pelo resultado. Essa funcionalidade é útil para lidar com falhas em flows (esse tópico será abordado mais a frente).
|
167
|
+
- `#type` retorna um Symbol que dá significado ao resultado, isso é útil para declarar diferentes tipos de falha e sucesso.
|
168
|
+
- `#data` os dados do resultado (um Hash).
|
169
|
+
- `#[]` e `#values_at` são atalhos para acessar as propriedades do `#data`.
|
170
|
+
- `#key?` retorna `true` se a chave estiver present no `#data`.
|
171
|
+
- `#value?` retorna `true` se o valor estiver present no `#data`.
|
172
|
+
- `#slice` retorna um novo hash que inclui apenas as chaves fornecidas. Se as chaves fornecidas não existirem, um hash vazio será retornado.
|
173
|
+
- `#on_success` or `#on_failure` são métodos de hooks que te auxiliam a definir o fluxo da aplicação.
|
174
|
+
- `#then` este método permite aplicar novos casos de uso ao resultado atual se ele for sucesso. A idia dessa feature é a criação de fluxos dinâmicos.
|
175
|
+
- `#transitions` retorna um array com todoas as transformações que um resultado [teve durante um flow](#como-entender-o-que-aconteceu-durante-a-execução-de-um-flow).
|
176
|
+
|
177
|
+
> **Nota:** por conta de retrocompatibilidade, você pode usar o método `#value` como um alias para o método `#data`.
|
178
|
+
|
179
|
+
[⬆️ Voltar para o índice](#índice-)
|
180
|
+
|
181
|
+
#### O que são os tipos de resultados?
|
182
|
+
|
183
|
+
Todo resultado tem um tipo (type), e estão são os valores padrões:
|
184
|
+
- `:ok` quando sucesso
|
185
|
+
- `:error` or `:exception` quando falhas
|
186
|
+
|
187
|
+
```ruby
|
188
|
+
class Divide < Micro::Case
|
189
|
+
attributes :a, :b
|
190
|
+
|
191
|
+
def call!
|
192
|
+
if invalid_attributes.empty?
|
193
|
+
Success result: { number: a / b }
|
194
|
+
else
|
195
|
+
Failure result: { invalid_attributes: invalid_attributes }
|
196
|
+
end
|
197
|
+
rescue => exception
|
198
|
+
Failure result: exception
|
199
|
+
end
|
200
|
+
|
201
|
+
private def invalid_attributes
|
202
|
+
attributes.select { |_key, value| !value.is_a?(Numeric) }
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
# Resultado de sucesso
|
207
|
+
|
208
|
+
result = Divide.call(a: 2, b: 2)
|
209
|
+
|
210
|
+
result.type # :ok
|
211
|
+
result.data # { number: 1 }
|
212
|
+
result.success? # true
|
213
|
+
result.use_case # #<Divide:0x0000 @__attributes={"a"=>2, "b"=>2}, @a=2, @b=2, @__result=...>
|
214
|
+
|
215
|
+
# Resultado de falha (type == :error)
|
216
|
+
|
217
|
+
bad_result = Divide.call(a: 2, b: '2')
|
218
|
+
|
219
|
+
bad_result.type # :error
|
220
|
+
bad_result.data # { invalid_attributes: { "b"=>"2" } }
|
221
|
+
bad_result.failure? # true
|
222
|
+
bad_result.use_case # #<Divide:0x0000 @__attributes={"a"=>2, "b"=>"2"}, @a=2, @b="2", @__result=...>
|
223
|
+
|
224
|
+
# Resultado de falha (type == :exception)
|
225
|
+
|
226
|
+
err_result = Divide.call(a: 2, b: 0)
|
227
|
+
|
228
|
+
err_result.type # :exception
|
229
|
+
err_result.data # { exception: <ZeroDivisionError: divided by 0> }
|
230
|
+
err_result.failure? # true
|
231
|
+
err_result.use_case # #<Divide:0x0000 @__attributes={"a"=>2, "b"=>0}, @a=2, @b=0, @__result=#<Micro::Case::Result:0x0000 @use_case=#<Divide:0x0000 ...>, @type=:exception, @value=#<ZeroDivisionError: divided by 0>, @success=false>
|
232
|
+
|
233
|
+
# Nota:
|
234
|
+
# ----
|
235
|
+
# Toda instância de Exception será envolvida pelo método
|
236
|
+
# Failure(result: *) que receberá o tipo `:exception` ao invés de `:error`.
|
237
|
+
```
|
238
|
+
|
239
|
+
[⬆️ Voltar para o índice](#índice-)
|
240
|
+
|
241
|
+
#### Como difinir tipos customizados de resultados?
|
242
|
+
|
243
|
+
Resposta: Use um Symbol com argumento dos métodos `Success()`, `Failure()` e declare o `result:` keyword para definir os dados do resultado.
|
244
|
+
|
245
|
+
```ruby
|
246
|
+
class Multiply < Micro::Case
|
247
|
+
attributes :a, :b
|
248
|
+
|
249
|
+
def call!
|
250
|
+
if a.is_a?(Numeric) && b.is_a?(Numeric)
|
251
|
+
Success result: { number: a * b }
|
252
|
+
else
|
253
|
+
Failure :invalid_data, result: {
|
254
|
+
attributes: attributes.reject { |_, input| input.is_a?(Numeric) }
|
255
|
+
}
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
# Resultado de sucesso
|
261
|
+
|
262
|
+
result = Multiply.call(a: 3, b: 2)
|
263
|
+
|
264
|
+
result.type # :ok
|
265
|
+
result.data # { number: 6 }
|
266
|
+
result.success? # true
|
267
|
+
|
268
|
+
# Resultado de falha
|
269
|
+
|
270
|
+
bad_result = Multiply.call(a: 3, b: '2')
|
271
|
+
|
272
|
+
bad_result.type # :invalid_data
|
273
|
+
bad_result.data # { attributes: {"b"=>"2"} }
|
274
|
+
bad_result.failure? # true
|
275
|
+
```
|
276
|
+
|
277
|
+
[⬆️ Voltar para o índice](#índice-)
|
278
|
+
|
279
|
+
#### É possíve definir um tipo sem definit os dados do resultado?
|
280
|
+
|
281
|
+
Resposta: Sim, é possível. Mas isso terá um comportamento especial por conta dos dados do resultado ser um hash com o tipo definido como chave e `true` como o valor.
|
282
|
+
|
283
|
+
```ruby
|
284
|
+
class Multiply < Micro::Case
|
285
|
+
attributes :a, :b
|
286
|
+
|
287
|
+
def call!
|
288
|
+
if a.is_a?(Numeric) && b.is_a?(Numeric)
|
289
|
+
Success result: { number: a * b }
|
290
|
+
else
|
291
|
+
Failure(:invalid_data)
|
292
|
+
end
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
result = Multiply.call(a: 2, b: '2')
|
297
|
+
|
298
|
+
result.failure? # true
|
299
|
+
result.data # { :invalid_data => true }
|
300
|
+
result.type # :invalid_data
|
301
|
+
result.use_case.attributes # {"a"=>2, "b"=>"2"}
|
302
|
+
|
303
|
+
# Nota:
|
304
|
+
# ----
|
305
|
+
# Essa funcionalidade será muito útil para lidar com resultados de falha de um Flow
|
306
|
+
# (este tópico será coberto em breve).
|
307
|
+
```
|
308
|
+
|
309
|
+
[⬆️ Voltar para o índice](#índice-)
|
310
|
+
|
311
|
+
#### Como utilizar os hooks dos resultados?
|
312
|
+
|
313
|
+
Como [mencionando anteriormente](#microcaseresult---o-que-é-o-resultado-de-um-caso-de-uso), o `Micro::Case::Result` tem dois métodoso para melhorar o controle do fluxo da aplicação. São eles:
|
314
|
+
`#on_success`, `on_failure`.
|
315
|
+
|
316
|
+
Os exemplos abaixo demonstram eles em uso:
|
317
|
+
|
318
|
+
```ruby
|
319
|
+
class Double < Micro::Case
|
320
|
+
attribute :number
|
321
|
+
|
322
|
+
def call!
|
323
|
+
return Failure :invalid, result: { msg: 'number must be a numeric value' } unless number.is_a?(Numeric)
|
324
|
+
return Failure :lte_zero, result: { msg: 'number must be greater than 0' } if number <= 0
|
325
|
+
|
326
|
+
Success result: { number: number * 2 }
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
#================================#
|
331
|
+
# Imprimindo o output se sucesso #
|
332
|
+
#================================#
|
333
|
+
|
334
|
+
Double
|
335
|
+
.call(number: 3)
|
336
|
+
.on_success { |result| p result[:number] }
|
337
|
+
.on_failure(:invalid) { |result| raise TypeError, result[:msg] }
|
338
|
+
.on_failure(:lte_zero) { |result| raise ArgumentError, result[:msg] }
|
339
|
+
|
340
|
+
# O output será:
|
341
|
+
# 6
|
342
|
+
|
343
|
+
#===================================#
|
344
|
+
# Lançando um erro em caso de falha #
|
345
|
+
#===================================#
|
346
|
+
|
347
|
+
Double
|
348
|
+
.call(number: -1)
|
349
|
+
.on_success { |result| p result[:number] }
|
350
|
+
.on_failure { |_result, use_case| puts "#{use_case.class.name} was the use case responsible for the failure" }
|
351
|
+
.on_failure(:invalid) { |result| raise TypeError, result[:msg] }
|
352
|
+
.on_failure(:lte_zero) { |result| raise ArgumentError, result[:msg] }
|
353
|
+
|
354
|
+
# O output será:
|
355
|
+
#
|
356
|
+
# 1. Imprimirá a mensagem: Double was the use case responsible for the failure
|
357
|
+
# 2. Lançará a exception: ArgumentError (the number must be greater than 0)
|
358
|
+
|
359
|
+
# Nota:
|
360
|
+
# ----
|
361
|
+
# O caso de uso responsável estará sempre acessível como o segundo argumento do hook
|
362
|
+
```
|
363
|
+
|
364
|
+
#### Por que o hook sem um tipo definido expõe o próprio resultado?
|
365
|
+
|
366
|
+
Resposta: Para permitir que você defina o controle de fluxo da aplicação usando alguma estrutura condicional como um `if` ou `case when`.
|
367
|
+
|
368
|
+
```ruby
|
369
|
+
class Double < Micro::Case
|
370
|
+
attribute :number
|
371
|
+
|
372
|
+
def call!
|
373
|
+
return Failure(:invalid) unless number.is_a?(Numeric)
|
374
|
+
return Failure :lte_zero, result: attributes(:number) if number <= 0
|
375
|
+
|
376
|
+
Success result: { number: number * 2 }
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
Double
|
381
|
+
.call(number: -1)
|
382
|
+
.on_failure do |result, use_case|
|
383
|
+
case result.type
|
384
|
+
when :invalid then raise TypeError, "number must be a numeric value"
|
385
|
+
when :lte_zero then raise ArgumentError, "number `#{result[:number]}` must be greater than 0"
|
386
|
+
else raise NotImplementedError
|
387
|
+
end
|
388
|
+
end
|
389
|
+
|
390
|
+
# O output será uma exception:
|
391
|
+
#
|
392
|
+
# ArgumentError (number `-1` must be greater than 0)
|
393
|
+
```
|
394
|
+
|
395
|
+
> **Nota:** O mesmo que foi feito no exemplo anterior poderá ser feito com o hook `#on_success`!
|
396
|
+
|
397
|
+
##### Usando decomposição para acessar os dados e tipo do resultado
|
398
|
+
|
399
|
+
A sintaxe para decompor um Array pode ser usada na declaração de variáveis e nos argumentos de métodos/blocos.
|
400
|
+
Se você não sabia disso, confira a [documentação do Ruby](https://ruby-doc.org/core-2.2.0/doc/syntax/assignment_rdoc.html#label-Array+Decomposition).
|
401
|
+
|
402
|
+
```ruby
|
403
|
+
# O objeto exposto em hook sem um tipo é um Micro::Case::Result e ele pode ser decomposto. Exemplo:
|
404
|
+
|
405
|
+
Double
|
406
|
+
.call(number: -2)
|
407
|
+
.on_failure do |(data, type), use_case|
|
408
|
+
case type
|
409
|
+
when :invalid then raise TypeError, 'number must be a numeric value'
|
410
|
+
when :lte_zero then raise ArgumentError, "number `#{data[:number]}` must be greater than 0"
|
411
|
+
else raise NotImplementedError
|
412
|
+
end
|
413
|
+
end
|
414
|
+
|
415
|
+
# O output será a exception:
|
416
|
+
#
|
417
|
+
# ArgumentError (the number `-2` must be greater than 0)
|
418
|
+
```
|
419
|
+
|
420
|
+
> **Nota:** O que mesmo pode ser feito com o `#on_success` hook!
|
421
|
+
|
422
|
+
[⬆️ Voltar para o índice](#índice-)
|
423
|
+
|
424
|
+
#### O que acontece se um hook de resultado for declarado múltiplas vezes?
|
425
|
+
|
426
|
+
Resposta: Se o tipo do resultado for identificado o hook será sempre executado.
|
427
|
+
|
428
|
+
```ruby
|
429
|
+
class Double < Micro::Case
|
430
|
+
attributes :number
|
431
|
+
|
432
|
+
def call!
|
433
|
+
if number.is_a?(Numeric)
|
434
|
+
Success :computed, result: { number: number * 2 }
|
435
|
+
else
|
436
|
+
Failure :invalid, result: { msg: 'number must be a numeric value' }
|
437
|
+
end
|
438
|
+
end
|
439
|
+
end
|
440
|
+
|
441
|
+
result = Double.call(number: 3)
|
442
|
+
result.data # { number: 6 }
|
443
|
+
result[:number] * 4 # 24
|
444
|
+
|
445
|
+
accum = 0
|
446
|
+
|
447
|
+
result
|
448
|
+
.on_success { |result| accum += result[:number] }
|
449
|
+
.on_success { |result| accum += result[:number] }
|
450
|
+
.on_success(:computed) { |result| accum += result[:number] }
|
451
|
+
.on_success(:computed) { |result| accum += result[:number] }
|
452
|
+
|
453
|
+
accum # 24
|
454
|
+
|
455
|
+
result[:number] * 4 == accum # true
|
456
|
+
```
|
457
|
+
|
458
|
+
#### Como usar o método `Micro::Case::Result#then`?
|
459
|
+
|
460
|
+
Este método permite você criar fluxos dinâmicos, então, com ele, você pode adicionar novos casos de uso ou fluxos para continuar a transformação de um resultado. por exemplo:
|
461
|
+
|
462
|
+
```ruby
|
463
|
+
class ForbidNegativeNumber < Micro::Case
|
464
|
+
attribute :number
|
465
|
+
|
466
|
+
def call!
|
467
|
+
return Success result: attributes if number >= 0
|
468
|
+
|
469
|
+
Failure result: attributes
|
470
|
+
end
|
471
|
+
end
|
472
|
+
|
473
|
+
class Add3 < Micro::Case
|
474
|
+
attribute :number
|
475
|
+
|
476
|
+
def call!
|
477
|
+
Success result: { number: number + 3 }
|
478
|
+
end
|
479
|
+
end
|
480
|
+
|
481
|
+
result1 =
|
482
|
+
ForbidNegativeNumber
|
483
|
+
.call(number: -1)
|
484
|
+
.then(Add3)
|
485
|
+
|
486
|
+
result1.data # {'number' => -1}
|
487
|
+
result1.failure? # true
|
488
|
+
|
489
|
+
# ---
|
490
|
+
|
491
|
+
result2 =
|
492
|
+
ForbidNegativeNumber
|
493
|
+
.call(number: 1)
|
494
|
+
.then(Add3)
|
495
|
+
|
496
|
+
result2.data # {'number' => 4}
|
497
|
+
result2.success? # true
|
498
|
+
```
|
499
|
+
|
500
|
+
> **Nota:** este método altera o [`Micro::Case::Result#transitions`](#como-entender-o-que-aconteceu-durante-a-execução-de-um-flow).
|
501
|
+
|
502
|
+
[⬆️ Voltar para o índice](#índice-)
|
503
|
+
|
504
|
+
##### O que acontece quando um `Micro::Case::Result#then` recebe um bloco?
|
505
|
+
|
506
|
+
Ele passará o próprio resultado (uma instância do `Micro::Case::Result`) como argumento do bloco, e retornará o output do bloco ao invés dele mesmo. e.g:
|
507
|
+
|
508
|
+
```ruby
|
509
|
+
class Add < Micro::Case
|
510
|
+
attributes :a, :b
|
511
|
+
|
512
|
+
def call!
|
513
|
+
if Kind.of?(Numeric, a, b)
|
514
|
+
Success result: { sum: a + b }
|
515
|
+
else
|
516
|
+
Failure(:attributes_arent_numbers)
|
517
|
+
end
|
518
|
+
end
|
519
|
+
end
|
520
|
+
|
521
|
+
# --
|
522
|
+
|
523
|
+
success_result =
|
524
|
+
Add
|
525
|
+
.call(a: 2, b: 2)
|
526
|
+
.then { |result| result.success? ? result[:sum] : 0 }
|
527
|
+
|
528
|
+
puts success_result # 4
|
529
|
+
|
530
|
+
# --
|
531
|
+
|
532
|
+
failure_result =
|
533
|
+
Add
|
534
|
+
.call(a: 2, b: '2')
|
535
|
+
.then { |result| result.success? ? result[:sum] : 0 }
|
536
|
+
|
537
|
+
puts failure_result # 0
|
538
|
+
```
|
539
|
+
|
540
|
+
[⬆️ Voltar para o índice](#índice-)
|
541
|
+
|
542
|
+
##### Como fazer injeção de dependência usando este recurso?
|
543
|
+
|
544
|
+
Passe um `Hash` como segundo argumento do método `Micro::Case::Result#then`.
|
545
|
+
|
546
|
+
```ruby
|
547
|
+
Todo::FindAllForUser
|
548
|
+
.call(user: current_user, params: params)
|
549
|
+
.then(Paginate)
|
550
|
+
.then(Serialize::PaginatedRelationAsJson, serializer: Todo::Serializer)
|
551
|
+
.on_success { |result| render_json(200, data: result[:todos]) }
|
552
|
+
```
|
553
|
+
|
554
|
+
[⬆️ Voltar para o índice](#índice-)
|
555
|
+
|
556
|
+
### `Micro::Cases::Flow` - Como compor casos de uso?
|
557
|
+
|
558
|
+
Chamamos de **fluxo** uma composição de casos de uso. A ideia principal desse recurso é usar/reutilizar casos de uso como etapas de um novo caso de uso. Exemplo:
|
559
|
+
|
560
|
+
```ruby
|
561
|
+
module Steps
|
562
|
+
class ConvertTextToNumbers < Micro::Case
|
563
|
+
attribute :numbers
|
564
|
+
|
565
|
+
def call!
|
566
|
+
if numbers.all? { |value| String(value) =~ /\d+/ }
|
567
|
+
Success result: { numbers: numbers.map(&:to_i) }
|
568
|
+
else
|
569
|
+
Failure result: { message: 'numbers must contain only numeric types' }
|
570
|
+
end
|
571
|
+
end
|
572
|
+
end
|
573
|
+
|
574
|
+
class Add2 < Micro::Case::Strict
|
575
|
+
attribute :numbers
|
576
|
+
|
577
|
+
def call!
|
578
|
+
Success result: { numbers: numbers.map { |number| number + 2 } }
|
579
|
+
end
|
580
|
+
end
|
581
|
+
|
582
|
+
class Double < Micro::Case::Strict
|
583
|
+
attribute :numbers
|
584
|
+
|
585
|
+
def call!
|
586
|
+
Success result: { numbers: numbers.map { |number| number * 2 } }
|
587
|
+
end
|
588
|
+
end
|
589
|
+
|
590
|
+
class Square < Micro::Case::Strict
|
591
|
+
attribute :numbers
|
592
|
+
|
593
|
+
def call!
|
594
|
+
Success result: { numbers: numbers.map { |number| number * number } }
|
595
|
+
end
|
596
|
+
end
|
597
|
+
end
|
598
|
+
|
599
|
+
#-----------------------------------------#
|
600
|
+
# Criando um flow com Micro::Cases.flow() #
|
601
|
+
#-----------------------------------------#
|
602
|
+
|
603
|
+
Add2ToAllNumbers = Micro::Cases.flow([
|
604
|
+
Steps::ConvertTextToNumbers,
|
605
|
+
Steps::Add2
|
606
|
+
])
|
607
|
+
|
608
|
+
result = Add2ToAllNumbers.call(numbers: %w[1 1 2 2 3 4])
|
609
|
+
|
610
|
+
result.success? # true
|
611
|
+
result.data # {:numbers => [3, 3, 4, 4, 5, 6]}
|
612
|
+
|
613
|
+
#--------------------------------#
|
614
|
+
# Criando um flow usando classes #
|
615
|
+
#--------------------------------#
|
616
|
+
|
617
|
+
class DoubleAllNumbers < Micro::Case
|
618
|
+
flow Steps::ConvertTextToNumbers,
|
619
|
+
Steps::Double
|
620
|
+
end
|
621
|
+
|
622
|
+
DoubleAllNumbers.
|
623
|
+
call(numbers: %w[1 1 b 2 3 4]).
|
624
|
+
on_failure { |result| puts result[:message] } # "numbers must contain only numeric types"
|
625
|
+
```
|
626
|
+
|
627
|
+
Ao ocorrer uma falha, o caso de uso responsável ficará acessível no resultado. Exemplo:
|
628
|
+
|
629
|
+
```ruby
|
630
|
+
result = DoubleAllNumbers.call(numbers: %w[1 1 b 2 3 4])
|
631
|
+
|
632
|
+
result.failure? # true
|
633
|
+
result.use_case.is_a?(Steps::ConvertTextToNumbers) # true
|
634
|
+
|
635
|
+
result.on_failure do |_message, use_case|
|
636
|
+
puts "#{use_case.class.name} was the use case responsible for the failure" # Steps::ConvertTextToNumbers was the use case responsible for the failure
|
637
|
+
end
|
638
|
+
```
|
639
|
+
|
640
|
+
[⬆️ Voltar para o índice](#índice-)
|
641
|
+
|
642
|
+
#### É possível compor um fluxo com outros fluxos?
|
643
|
+
|
644
|
+
Resposta: Sim, é possível.
|
645
|
+
|
646
|
+
```ruby
|
647
|
+
module Steps
|
648
|
+
class ConvertTextToNumbers < Micro::Case
|
649
|
+
attribute :numbers
|
650
|
+
|
651
|
+
def call!
|
652
|
+
if numbers.all? { |value| String(value) =~ /\d+/ }
|
653
|
+
Success result: { numbers: numbers.map(&:to_i) }
|
654
|
+
else
|
655
|
+
Failure result: { message: 'numbers must contain only numeric types' }
|
656
|
+
end
|
657
|
+
end
|
658
|
+
end
|
659
|
+
|
660
|
+
class Add2 < Micro::Case::Strict
|
661
|
+
attribute :numbers
|
662
|
+
|
663
|
+
def call!
|
664
|
+
Success result: { numbers: numbers.map { |number| number + 2 } }
|
665
|
+
end
|
666
|
+
end
|
667
|
+
|
668
|
+
class Double < Micro::Case::Strict
|
669
|
+
attribute :numbers
|
670
|
+
|
671
|
+
def call!
|
672
|
+
Success result: { numbers: numbers.map { |number| number * 2 } }
|
673
|
+
end
|
674
|
+
end
|
675
|
+
|
676
|
+
class Square < Micro::Case::Strict
|
677
|
+
attribute :numbers
|
678
|
+
|
679
|
+
def call!
|
680
|
+
Success result: { numbers: numbers.map { |number| number * number } }
|
681
|
+
end
|
682
|
+
end
|
683
|
+
end
|
684
|
+
|
685
|
+
DoubleAllNumbers =
|
686
|
+
Micro::Cases.flow([Steps::ConvertTextToNumbers, Steps::Double])
|
687
|
+
|
688
|
+
SquareAllNumbers =
|
689
|
+
Micro::Cases.flow([Steps::ConvertTextToNumbers, Steps::Square])
|
690
|
+
|
691
|
+
DoubleAllNumbersAndAdd2 =
|
692
|
+
Micro::Cases.flow([DoubleAllNumbers, Steps::Add2])
|
693
|
+
|
694
|
+
SquareAllNumbersAndAdd2 =
|
695
|
+
Micro::Cases.flow([SquareAllNumbers, Steps::Add2])
|
696
|
+
|
697
|
+
SquareAllNumbersAndDouble =
|
698
|
+
Micro::Cases.flow([SquareAllNumbersAndAdd2, DoubleAllNumbers])
|
699
|
+
|
700
|
+
DoubleAllNumbersAndSquareAndAdd2 =
|
701
|
+
Micro::Cases.flow([DoubleAllNumbers, SquareAllNumbersAndAdd2])
|
702
|
+
|
703
|
+
SquareAllNumbersAndDouble
|
704
|
+
.call(numbers: %w[1 1 2 2 3 4])
|
705
|
+
.on_success { |result| p result[:numbers] } # [6, 6, 12, 12, 22, 36]
|
706
|
+
|
707
|
+
DoubleAllNumbersAndSquareAndAdd2
|
708
|
+
.call(numbers: %w[1 1 2 2 3 4])
|
709
|
+
.on_success { |result| p result[:numbers] } # [6, 6, 18, 18, 38, 66]
|
710
|
+
```
|
711
|
+
|
712
|
+
> **Nota:** Você pode mesclar qualquer [approach](#é-possível-compor-um-fluxo-com-outros-fluxos) para criar flows - [exemplos](https://github.com/serradura/u-case/blob/714c6b658fc6aa02617e6833ddee09eddc760f2a/test/micro/cases/flow/blend_test.rb#L5-L35).
|
713
|
+
|
714
|
+
[⬆️ Voltar para o índice](#índice-)
|
715
|
+
|
716
|
+
#### É possível que um fluxo acumule sua entrada e mescle cada resultado de sucesso para usar como argumento dos próximos casos de uso?
|
717
|
+
|
718
|
+
Resposta: Sim, é possível! Veja o exemplo abaixo para entender como funciona o acumulp de dados dentro da execução de um fluxo.
|
719
|
+
|
720
|
+
```ruby
|
721
|
+
module Users
|
722
|
+
class FindByEmail < Micro::Case
|
723
|
+
attribute :email
|
724
|
+
|
725
|
+
def call!
|
726
|
+
user = User.find_by(email: email)
|
727
|
+
|
728
|
+
return Success result: { user: user } if user
|
729
|
+
|
730
|
+
Failure(:user_not_found)
|
731
|
+
end
|
732
|
+
end
|
733
|
+
end
|
734
|
+
|
735
|
+
module Users
|
736
|
+
class ValidatePassword < Micro::Case::Strict
|
737
|
+
attributes :user, :password
|
738
|
+
|
739
|
+
def call!
|
740
|
+
return Failure(:user_must_be_persisted) if user.new_record?
|
741
|
+
return Failure(:wrong_password) if user.wrong_password?(password)
|
742
|
+
|
743
|
+
return Success result: attributes(:user)
|
744
|
+
end
|
745
|
+
end
|
746
|
+
end
|
747
|
+
|
748
|
+
module Users
|
749
|
+
Authenticate = Micro::Cases.flow([
|
750
|
+
FindByEmail,
|
751
|
+
ValidatePassword
|
752
|
+
])
|
753
|
+
end
|
754
|
+
|
755
|
+
Users::Authenticate
|
756
|
+
.call(email: 'somebody@test.com', password: 'password')
|
757
|
+
.on_success { |result| sign_in(result[:user]) }
|
758
|
+
.on_failure(:wrong_password) { render status: 401 }
|
759
|
+
.on_failure(:user_not_found) { render status: 404 }
|
760
|
+
```
|
761
|
+
|
762
|
+
Primeiro, vamos ver os atributos usados por cada caso de uso:
|
763
|
+
|
764
|
+
```ruby
|
765
|
+
class Users::FindByEmail < Micro::Case
|
766
|
+
attribute :email
|
767
|
+
end
|
768
|
+
|
769
|
+
class Users::ValidatePassword < Micro::Case
|
770
|
+
attributes :user, :password
|
771
|
+
end
|
772
|
+
```
|
773
|
+
|
774
|
+
Como você pode ver, `Users::ValidatePassword` espera um usuário como sua entrada. Então, como ele recebe o usuário?
|
775
|
+
R: Ele recebe o usuário do resultado de sucesso `Users::FindByEmail`!
|
776
|
+
|
777
|
+
E este é o poder da composição de casos de uso porque o output de uma etapa irá compor a entrada do próximo caso de uso no fluxo!
|
778
|
+
|
779
|
+
> input **>>** processamento **>>** output
|
780
|
+
|
781
|
+
> **Nota:** Verifique esses exemplos de teste [Micro::Cases::Flow](https://github.com/serradura/u-case/blob/c96a3650469da40dc9f83ff678204055b7015d01/test/micro/cases/flow/result_transitions_test.rb) e [Micro::Cases::Safe::Flow](https://github.com/serradura/u-case/blob/c96a3650469da40dc9f83ff678204055b7015d01/test/micro/cases/safe/flow/result_transitions_test.rb) para ver diferentes casos de uso tendo acesso aos dados de um fluxo.
|
782
|
+
|
783
|
+
[⬆️ Voltar para o índice](#índice-)
|
784
|
+
|
785
|
+
#### Como entender o que aconteceu durante a execução de um flow?
|
786
|
+
|
787
|
+
Use `Micro::Case::Result#transitions`!
|
788
|
+
|
789
|
+
Vamos usar os [exemplos da seção anterior](#is-it-possible-a-flow-accumulates-its-input-and-merges-each-success-result-to-use-as-the-argument-of-the-next-use-cases) para ilustrar como utilizar essa feature.
|
790
|
+
|
791
|
+
```ruby
|
792
|
+
user_authenticated =
|
793
|
+
Users::Authenticate.call(email: 'rodrigo@test.com', password: user_password)
|
794
|
+
|
795
|
+
user_authenticated.transitions
|
796
|
+
[
|
797
|
+
{
|
798
|
+
:use_case => {
|
799
|
+
:class => Users::FindByEmail,
|
800
|
+
:attributes => { :email => "rodrigo@test.com" }
|
801
|
+
},
|
802
|
+
:success => {
|
803
|
+
:type => :ok,
|
804
|
+
:result => {
|
805
|
+
:user => #<User:0x00007fb57b1c5f88 @email="rodrigo@test.com" ...>
|
806
|
+
}
|
807
|
+
},
|
808
|
+
:accessible_attributes => [ :email, :password ]
|
809
|
+
},
|
810
|
+
{
|
811
|
+
:use_case => {
|
812
|
+
:class => Users::ValidatePassword,
|
813
|
+
:attributes => {
|
814
|
+
:user => #<User:0x00007fb57b1c5f88 @email="rodrigo@test.com" ...>
|
815
|
+
:password => "123456"
|
816
|
+
}
|
817
|
+
},
|
818
|
+
:success => {
|
819
|
+
:type => :ok,
|
820
|
+
:result => {
|
821
|
+
:user => #<User:0x00007fb57b1c5f88 @email="rodrigo@test.com" ...>
|
822
|
+
}
|
823
|
+
},
|
824
|
+
:accessible_attributes => [ :email, :password, :user ]
|
825
|
+
}
|
826
|
+
]
|
827
|
+
```
|
828
|
+
|
829
|
+
O exemplo acima mostra a saída gerada pelas `Micro::Case::Result#transitions`.
|
830
|
+
Com ele é possível analisar a ordem de execução dos casos de uso e quais foram os `inputs` fornecidos (`[:attributes]`) e `outputs` (`[:success][:result]`) em toda a execução.
|
831
|
+
|
832
|
+
E observe a propriedade `accessible_attributes`, ela mostra quais atributos são acessíveis nessa etapa do fluxo. Por exemplo, na última etapa, você pode ver que os atributos `accessible_attributes` aumentaram devido ao [acúmulo de fluxo de dados](#é-possível-que-um-fluxo-acumule-sua-entrada-e-mescle-cada-resultado-de-sucesso-para-usar-como-argumento-dos-próximos-casos-de-uso).
|
833
|
+
|
834
|
+
> **Nota:** O [`Micro::Case::Result#then`](#how-to-use-the-microcaseresultthen-method) incrementa o `Micro::Case::Result#transitions`.
|
835
|
+
|
836
|
+
##### `Micro::Case::Result#transitions` schema
|
837
|
+
```ruby
|
838
|
+
[
|
839
|
+
{
|
840
|
+
use_case: {
|
841
|
+
class: <Micro::Case>,# Caso de uso que será executado
|
842
|
+
attributes: <Hash> # (Input) Os atributos do caso de uso
|
843
|
+
},
|
844
|
+
[success:, failure:] => { # (Output)
|
845
|
+
type: <Symbol>, # Tipo do resultado. Padrões:
|
846
|
+
# Success = :ok, Failure = :error or :exception
|
847
|
+
result: <Hash> # Os dados retornados pelo resultado do use case
|
848
|
+
},
|
849
|
+
accessible_attributes: <Array>, # Propriedades que podem ser acessadas pelos atributos do caso de uso,
|
850
|
+
# começando com Hash usado para invocá-lo e que são incrementados
|
851
|
+
# com os valores de resultado de cada caso de uso do fluxo.
|
852
|
+
}
|
853
|
+
]
|
854
|
+
|
855
|
+
```
|
856
|
+
|
857
|
+
##### É possível desabilitar o `Micro::Case::Result#transitions`?
|
858
|
+
|
859
|
+
Resposta: Sim! Você pode usar o `Micro::Case.config` para fazer isso. [Link para](#microcaseconfig) essa seção.
|
860
|
+
|
861
|
+
#### É possível declarar um fluxo que inclui o próprio caso de uso?
|
862
|
+
|
863
|
+
Resposta: Sim! Você pode usar a macro `self` ou `self.call!`. Exemplo:
|
864
|
+
|
865
|
+
```ruby
|
866
|
+
class ConvertTextToNumber < Micro::Case
|
867
|
+
attribute :text
|
868
|
+
|
869
|
+
def call!
|
870
|
+
Success result: { number: text.to_i }
|
871
|
+
end
|
872
|
+
end
|
873
|
+
|
874
|
+
class ConvertNumberToText < Micro::Case
|
875
|
+
attribute :number
|
876
|
+
|
877
|
+
def call!
|
878
|
+
Success result: { text: number.to_s }
|
879
|
+
end
|
880
|
+
end
|
881
|
+
|
882
|
+
class Double < Micro::Case
|
883
|
+
flow ConvertTextToNumber,
|
884
|
+
self.call!,
|
885
|
+
ConvertNumberToText
|
886
|
+
|
887
|
+
attribute :number
|
888
|
+
|
889
|
+
def call!
|
890
|
+
Success result: { number: number * 2 }
|
891
|
+
end
|
892
|
+
end
|
893
|
+
|
894
|
+
result = Double.call(text: '4')
|
895
|
+
|
896
|
+
result.success? # true
|
897
|
+
result[:number] # "8"
|
898
|
+
```
|
899
|
+
|
900
|
+
> **Note:** Essa funcionalidade pode ser usada com Micro::Case::Safe. Verifique esse teste para ver um example: https://github.com/serradura/u-case/blob/714c6b658fc6aa02617e6833ddee09eddc760f2a/test/micro/case/safe/with_inner_flow_test.rb
|
901
|
+
|
902
|
+
[⬆️ Voltar para o índice](#índice-)
|
903
|
+
|
904
|
+
### `Micro::Case::Strict` - O que é um caso de uso estrito?
|
905
|
+
|
906
|
+
Resposta: é um tipo de caso de uso que exigirá todas as palavras-chave (atributos) em sua inicialização.
|
907
|
+
|
908
|
+
```ruby
|
909
|
+
class Double < Micro::Case::Strict
|
910
|
+
attribute :numbers
|
911
|
+
|
912
|
+
def call!
|
913
|
+
Success result: { numbers: numbers.map { |number| number * 2 } }
|
914
|
+
end
|
915
|
+
end
|
916
|
+
|
917
|
+
Double.call({})
|
918
|
+
|
919
|
+
# O output será:
|
920
|
+
# ArgumentError (missing keyword: :numbers)
|
921
|
+
```
|
922
|
+
|
923
|
+
[⬆️ Voltar para o índice](#índice-)
|
924
|
+
|
925
|
+
### `Micro::Case::Safe` - Existe algum recurso para lidar automaticamente com exceções dentro de um caso de uso ou fluxo?
|
926
|
+
|
927
|
+
Sim, assim como `Micro::Case::Strict`, o `Micro::Case::Safe` é outro tipo de caso de uso. Ele tem a capacidade de interceptar automaticamente qualquer exceção como um resultado de falha. Exemplo:
|
928
|
+
|
929
|
+
```ruby
|
930
|
+
require 'logger'
|
931
|
+
|
932
|
+
AppLogger = Logger.new(STDOUT)
|
933
|
+
|
934
|
+
class Divide < Micro::Case::Safe
|
935
|
+
attributes :a, :b
|
936
|
+
|
937
|
+
def call!
|
938
|
+
if a.is_a?(Integer) && b.is_a?(Integer)
|
939
|
+
Success result: { number: a / b}
|
940
|
+
else
|
941
|
+
Failure(:not_an_integer)
|
942
|
+
end
|
943
|
+
end
|
944
|
+
end
|
945
|
+
|
946
|
+
result = Divide.call(a: 2, b: 0)
|
947
|
+
result.type == :exception # true
|
948
|
+
result.data # { exception: #<ZeroDivisionError...> }
|
949
|
+
result[:exception].is_a?(ZeroDivisionError) # true
|
950
|
+
|
951
|
+
result.on_failure(:exception) do |result|
|
952
|
+
AppLogger.error(result[:exception].message) # E, [2019-08-21T00:05:44.195506 #9532] ERROR -- : divided by 0
|
953
|
+
end
|
954
|
+
```
|
955
|
+
|
956
|
+
#### `Micro::Case::Result#on_exception`
|
957
|
+
|
958
|
+
Se você precisar lidar com um erro específico, recomendo o uso de uma instrução case. por exemplo:
|
959
|
+
|
960
|
+
```ruby
|
961
|
+
result.on_failure(:exception) do |data, use_case|
|
962
|
+
case exception = data[:exception]
|
963
|
+
when ZeroDivisionError then AppLogger.error(exception.message)
|
964
|
+
else AppLogger.debug("#{use_case.class.name} was the use case responsible for the exception")
|
965
|
+
end
|
966
|
+
end
|
967
|
+
```
|
968
|
+
|
969
|
+
> **Note:** É possível resgatar uma exceção mesmo quando é um caso de uso seguro. Exemplos: https://github.com/serradura/u-case/blob/714c6b658fc6aa02617e6833ddee09eddc760f2a/test/micro/case/safe_test.rb#L90-L118
|
970
|
+
|
971
|
+
|
972
|
+
[⬆️ Voltar para o índice](#índice-)
|
973
|
+
|
974
|
+
#### `Micro::Cases::Safe::Flow`
|
975
|
+
|
976
|
+
Como casos de uso seguros, os fluxos seguros podem interceptar uma exceção em qualquer uma de suas etapas. Estas são as maneiras de definir um:
|
977
|
+
|
978
|
+
```ruby
|
979
|
+
module Users
|
980
|
+
Create = Micro::Cases.safe_flow([
|
981
|
+
ProcessParams,
|
982
|
+
ValidateParams,
|
983
|
+
Persist,
|
984
|
+
SendToCRM
|
985
|
+
])
|
986
|
+
end
|
987
|
+
```
|
988
|
+
|
989
|
+
Definindo dentro das classes:
|
990
|
+
|
991
|
+
```ruby
|
992
|
+
module Users
|
993
|
+
class Create < Micro::Case::Safe
|
994
|
+
flow ProcessParams,
|
995
|
+
ValidateParams,
|
996
|
+
Persist,
|
997
|
+
SendToCRM
|
998
|
+
end
|
999
|
+
end
|
1000
|
+
```
|
1001
|
+
|
1002
|
+
[⬆️ Voltar para o índice](#índice-)
|
1003
|
+
|
1004
|
+
#### `Micro::Case::Result#on_exception`
|
1005
|
+
|
1006
|
+
Na programação funcional os erros/exceções são tratados como dados comuns, a ideia é transformar a saída mesmo quando ocorre um comportamento inesperado. Para muitos, [as exceções são muito semelhantes à instrução GOTO](https://softwareengineering.stackexchange.com/questions/189222/are-exceptions-as-control-flow-considered-a-serious-antipattern-if-so-why), pulando o fluxo do programa para caminhos que podem ser difíceis de descobrir como as coisas funcionam em um sistema.
|
1007
|
+
|
1008
|
+
Para resolver isso, o `Micro::Case::Result` tem um hook especial `#on_exception` para ajudá-lo a lidar com o fluxo de controle no caso de exceções.
|
1009
|
+
|
1010
|
+
> **Note**: essa funcionalidade funcionará melhor se for usada com um flow ou caso de uso `Micro::Case::Safe`.
|
1011
|
+
|
1012
|
+
**Como ele funciona?**
|
1013
|
+
|
1014
|
+
```ruby
|
1015
|
+
class Divide < Micro::Case::Safe
|
1016
|
+
attributes :a, :b
|
1017
|
+
|
1018
|
+
def call!
|
1019
|
+
Success result: { division: a / b }
|
1020
|
+
end
|
1021
|
+
end
|
1022
|
+
|
1023
|
+
Divide
|
1024
|
+
.call(a: 2, b: 0)
|
1025
|
+
.on_success { |result| puts result[:division] }
|
1026
|
+
.on_exception(TypeError) { puts 'Please, use only numeric attributes.' }
|
1027
|
+
.on_exception(ZeroDivisionError) { |_error| puts "Can't divide a number by 0." }
|
1028
|
+
.on_exception { |_error, _use_case| puts 'Oh no, something went wrong!' }
|
1029
|
+
|
1030
|
+
# Output:
|
1031
|
+
# -------
|
1032
|
+
# Can't divide a number by 0
|
1033
|
+
# Oh no, something went wrong!
|
1034
|
+
|
1035
|
+
Divide.
|
1036
|
+
.call(a: 2, b: '2').
|
1037
|
+
.on_success { |result| puts result[:division] }
|
1038
|
+
.on_exception(TypeError) { puts 'Please, use only numeric attributes.' }
|
1039
|
+
.on_exception(ZeroDivisionError) { |_error| puts "Can't divide a number by 0." }
|
1040
|
+
.on_exception { |_error, _use_case| puts 'Oh no, something went wrong!' }
|
1041
|
+
|
1042
|
+
# Output:
|
1043
|
+
# -------
|
1044
|
+
# Please, use only numeric attributes.
|
1045
|
+
# Oh no, something went wrong!
|
1046
|
+
```
|
1047
|
+
|
1048
|
+
Como você pode ver, este hook tem o mesmo comportamento de `result.on_failure(:exception)`, mas, a ideia aqui é ter uma melhor comunicação no código, fazendo uma referência explícita quando alguma falha acontecer por causa de uma exceção.
|
1049
|
+
|
1050
|
+
[⬆️ Voltar para o índice](#índice-)
|
1051
|
+
|
1052
|
+
### `u-case/with_activemodel_validation` - Como validar os atributos do caso de uso?
|
1053
|
+
|
1054
|
+
**Requisitos:**
|
1055
|
+
|
1056
|
+
Para fazer isso a sua aplicação deverá ter o [activemodel >= 3.2, < 6.1.0](https://rubygems.org/gems/activemodel) como dependência.
|
1057
|
+
|
1058
|
+
Por padrão, se a sua aplicação tiver o ActiveModel como uma dependência, qualquer tipo de caso de uso pode fazer uso dele para validar seus atributos.
|
1059
|
+
|
1060
|
+
```ruby
|
1061
|
+
class Multiply < Micro::Case
|
1062
|
+
attributes :a, :b
|
1063
|
+
|
1064
|
+
validates :a, :b, presence: true, numericality: true
|
1065
|
+
|
1066
|
+
def call!
|
1067
|
+
return Failure :validation_error, result: { errors: self.errors } if invalid?
|
1068
|
+
|
1069
|
+
Success result: { number: a * b }
|
1070
|
+
end
|
1071
|
+
end
|
1072
|
+
```
|
1073
|
+
|
1074
|
+
Mas se você deseja uma maneira automática de falhar seus casos de uso em erros de validação, você poderá fazer:
|
1075
|
+
|
1076
|
+
1. **require 'u-case/with_activemodel_validation'** no Gemfile
|
1077
|
+
|
1078
|
+
```ruby
|
1079
|
+
gem 'u-case', require: 'u-case/with_activemodel_validation'
|
1080
|
+
```
|
1081
|
+
|
1082
|
+
2. Usar o `Micro::Case.config` para habilitar ele. [Link para](#microcaseconfig) essa seção.
|
1083
|
+
|
1084
|
+
Usando essa abordagem, você pode reescrever o exemplo anterior com menos código. Exemplo:
|
1085
|
+
|
1086
|
+
```ruby
|
1087
|
+
require 'u-case/with_activemodel_validation'
|
1088
|
+
|
1089
|
+
class Multiply < Micro::Case
|
1090
|
+
attributes :a, :b
|
1091
|
+
|
1092
|
+
validates :a, :b, presence: true, numericality: true
|
1093
|
+
|
1094
|
+
def call!
|
1095
|
+
Success result: { number: a * b }
|
1096
|
+
end
|
1097
|
+
end
|
1098
|
+
```
|
1099
|
+
|
1100
|
+
> **Nota:** Após habilitar o modo de validação, as classes `Micro::Case::Strict` e `Micro::Case::Safe` irão herdar este novo comportamento.
|
1101
|
+
|
1102
|
+
#### Se eu habilitei a validação automática, é possível desabilitá-la apenas em casos de uso específicos?
|
1103
|
+
|
1104
|
+
Resposta: Sim, é possível. Para fazer isso, você só precisará usar a macro `disable_auto_validation`. Exemplo:
|
1105
|
+
|
1106
|
+
```ruby
|
1107
|
+
require 'u-case/with_activemodel_validation'
|
1108
|
+
|
1109
|
+
class Multiply < Micro::Case
|
1110
|
+
disable_auto_validation
|
1111
|
+
|
1112
|
+
attribute :a
|
1113
|
+
attribute :b
|
1114
|
+
validates :a, :b, presence: true, numericality: true
|
1115
|
+
|
1116
|
+
def call!
|
1117
|
+
Success result: { number: a * b }
|
1118
|
+
end
|
1119
|
+
end
|
1120
|
+
|
1121
|
+
Multiply.call(a: 2, b: 'a')
|
1122
|
+
|
1123
|
+
# O output será:
|
1124
|
+
# TypeError (String can't be coerced into Integer)
|
1125
|
+
```
|
1126
|
+
|
1127
|
+
[⬆️ Voltar para o índice](#índice-)
|
1128
|
+
|
1129
|
+
#### `Kind::Validator`
|
1130
|
+
|
1131
|
+
A [gem kind](https://github.com/serradura/kind) possui um módulo para habilitar a validação do tipo de dados através do [`ActiveModel validations`](https://guides.rubyonrails.org/active_model_basics.html#validations). Então, quando você fizer o require do `'u-case/with_activemodel_validation'`, este módulo também irá fazer o require do [`Kind::Validator`](https://github.com/serradura/kind#kindvalidator-activemodelvalidations).
|
1132
|
+
|
1133
|
+
O exemplo abaixo mostra como validar os tipos de atributos.
|
1134
|
+
|
1135
|
+
```ruby
|
1136
|
+
class Todo::List::AddItem < Micro::Case
|
1137
|
+
attributes :user, :params
|
1138
|
+
|
1139
|
+
validates :user, kind: User
|
1140
|
+
validates :params, kind: ActionController::Parameters
|
1141
|
+
|
1142
|
+
def call!
|
1143
|
+
todo_params = params.require(:todo).permit(:title, :due_at)
|
1144
|
+
|
1145
|
+
todo = user.todos.create(todo_params)
|
1146
|
+
|
1147
|
+
Success result: { todo: todo }
|
1148
|
+
rescue ActionController::ParameterMissing => e
|
1149
|
+
Failure :parameter_missing, result: { message: e.message }
|
1150
|
+
end
|
1151
|
+
end
|
1152
|
+
```
|
1153
|
+
|
1154
|
+
[⬆️ Voltar para o índice](#índice-)
|
1155
|
+
|
1156
|
+
## `Micro::Case.config`
|
1157
|
+
|
1158
|
+
A ideia deste recurso é permitir a configuração de algumas funcionalidades/módulos do `u-case`.
|
1159
|
+
Eu recomendo que você use apenas uma vez em sua base de código. Exemplo: Em um inicializador do Rails.
|
1160
|
+
|
1161
|
+
Você pode ver abaixo todas as configurações disponíveis com seus valores padrão:
|
1162
|
+
|
1163
|
+
```ruby
|
1164
|
+
Micro::Case.config do |config|
|
1165
|
+
# Use ActiveModel para auto-validar os atributos dos seus casos de uso.
|
1166
|
+
config.enable_activemodel_validation = false
|
1167
|
+
|
1168
|
+
# Use para habilitar/desabilitar o `Micro::Case::Results#transitions`.
|
1169
|
+
config.enable_transitions = true
|
1170
|
+
end
|
1171
|
+
```
|
1172
|
+
|
1173
|
+
[⬆️ Voltar para o índice](#índice-)
|
1174
|
+
|
1175
|
+
## Benchmarks
|
1176
|
+
|
1177
|
+
### `Micro::Case` (v3.0.0)
|
1178
|
+
|
1179
|
+
#### Success results
|
1180
|
+
|
1181
|
+
| Gem / Abstração | Iterações por segundo | Comparação |
|
1182
|
+
| ----------------- | --------------------: | ----------------: |
|
1183
|
+
| Dry::Monads | 141730.1 | _**O mais rápido**_ |
|
1184
|
+
| **Micro::Case** | 103541.3 | 1.37x slower |
|
1185
|
+
| Interactor | 29100.8 | 4.87x slower |
|
1186
|
+
| Trailblazer::Operation | 15031.4 | 9.43x slower |
|
1187
|
+
| Dry::Transaction | 5674.0 | 24.98x slower |
|
1188
|
+
|
1189
|
+
<details>
|
1190
|
+
<summary>Show the full <a href="https://github.com/evanphx/benchmark-ips">benchmark/ips</a> results.</summary>
|
1191
|
+
|
1192
|
+
```ruby
|
1193
|
+
# Warming up --------------------------------------
|
1194
|
+
# Interactor 2.915k i/100ms
|
1195
|
+
# Trailblazer::Operation 1.543k i/100ms
|
1196
|
+
# Dry::Monads 14.288k i/100ms
|
1197
|
+
# Dry::Transaction 571.000 i/100ms
|
1198
|
+
# Micro::Case 10.418k i/100ms
|
1199
|
+
# Micro::Case::Strict 8.296k i/100ms
|
1200
|
+
# Micro::Case::Safe 10.254k i/100ms
|
1201
|
+
|
1202
|
+
# Calculating -------------------------------------
|
1203
|
+
# Interactor 29.101k (± 2.1%) i/s - 145.750k in 5.010660s
|
1204
|
+
# Trailblazer::Operation 15.031k (± 2.0%) i/s - 75.607k in 5.032071s
|
1205
|
+
# Dry::Monads 141.730k (± 3.1%) i/s - 714.400k in 5.045546s
|
1206
|
+
# Dry::Transaction 5.674k (± 1.9%) i/s - 28.550k in 5.033564s
|
1207
|
+
# Micro::Case 103.541k (± 1.6%) i/s - 520.900k in 5.032077s
|
1208
|
+
# Micro::Case::Strict 83.045k (± 2.4%) i/s - 423.096k in 5.098031s
|
1209
|
+
# Micro::Case::Safe 101.662k (± 1.5%) i/s - 512.700k in 5.044386s
|
1210
|
+
|
1211
|
+
# Comparison:
|
1212
|
+
# Dry::Monads: 141730.1 i/s
|
1213
|
+
# Micro::Case: 103541.3 i/s - 1.37x (± 0.00) slower
|
1214
|
+
# Micro::Case::Safe: 101662.2 i/s - 1.39x (± 0.00) slower
|
1215
|
+
# Micro::Case::Strict: 83044.6 i/s - 1.71x (± 0.00) slower
|
1216
|
+
# Interactor: 29100.8 i/s - 4.87x (± 0.00) slower
|
1217
|
+
# Trailblazer::Operation: 15031.4 i/s - 9.43x (± 0.00) slower
|
1218
|
+
# Dry::Transaction: 5674.0 i/s - 24.98x (± 0.00) slower
|
1219
|
+
```
|
1220
|
+
</details>
|
1221
|
+
|
1222
|
+
https://github.com/serradura/u-case/blob/main/benchmarks/use_case/with_success_result.rb
|
1223
|
+
|
1224
|
+
#### Failure results
|
1225
|
+
|
1226
|
+
| Gem / Abstração | Iterações por segundo | Comparação |
|
1227
|
+
| ----------------- | --------------------: | ----------------: |
|
1228
|
+
| **Micro::Case** | 98820.8 | _**O mais rápido**_ |
|
1229
|
+
| Dry::Monads | 71329.7 | 1.39x slower |
|
1230
|
+
| Trailblazer::Operation | 15034.9 | 6.57x slower |
|
1231
|
+
| Interactor | 13958.7 | 7.08x slower |
|
1232
|
+
| Dry::Transaction | 5067.5 | 19.50x slower |
|
1233
|
+
|
1234
|
+
<details>
|
1235
|
+
<summary>Mostrar o resultado completo do <a href="https://github.com/evanphx/benchmark-ips">benchmark/ips</a>.</summary>
|
1236
|
+
|
1237
|
+
```ruby
|
1238
|
+
# Warming up --------------------------------------
|
1239
|
+
# Interactor 1.324k i/100ms
|
1240
|
+
# Trailblazer::Operation 1.525k i/100ms
|
1241
|
+
# Dry::Monads 7.126k i/100ms
|
1242
|
+
# Dry::Transaction 499.000 i/100ms
|
1243
|
+
# Micro::Case 9.919k i/100ms
|
1244
|
+
# Micro::Case::Strict 7.837k i/100ms
|
1245
|
+
# Micro::Case::Safe 9.762k i/100ms
|
1246
|
+
|
1247
|
+
# Calculating -------------------------------------
|
1248
|
+
# Interactor 13.959k (± 2.5%) i/s - 70.172k in 5.030240s
|
1249
|
+
# Trailblazer::Operation 15.035k (± 2.2%) i/s - 76.250k in 5.074108s
|
1250
|
+
# Dry::Monads 71.330k (± 2.4%) i/s - 363.426k in 5.097993s
|
1251
|
+
# Dry::Transaction 5.068k (± 1.9%) i/s - 25.449k in 5.023922s
|
1252
|
+
# Micro::Case 98.821k (± 2.9%) i/s - 495.950k in 5.023421s
|
1253
|
+
# Micro::Case::Strict 79.936k (± 3.1%) i/s - 399.687k in 5.005435s
|
1254
|
+
# Micro::Case::Safe 98.695k (± 1.9%) i/s - 497.862k in 5.046246s
|
1255
|
+
|
1256
|
+
# Comparison:
|
1257
|
+
# Micro::Case: 98820.8 i/s
|
1258
|
+
# Micro::Case::Safe: 98695.0 i/s - same-ish: difference falls within error
|
1259
|
+
# Micro::Case::Strict: 79935.9 i/s - 1.24x (± 0.00) slower
|
1260
|
+
# Dry::Monads: 71329.7 i/s - 1.39x (± 0.00) slower
|
1261
|
+
# Trailblazer::Operation: 15034.9 i/s - 6.57x (± 0.00) slower
|
1262
|
+
# Interactor: 13958.7 i/s - 7.08x (± 0.00) slower
|
1263
|
+
# Dry::Transaction: 5067.5 i/s - 19.50x (± 0.00) slower
|
1264
|
+
```
|
1265
|
+
</details>
|
1266
|
+
|
1267
|
+
https://github.com/serradura/u-case/blob/main/benchmarks/use_case/with_failure_result.rb
|
1268
|
+
|
1269
|
+
---
|
1270
|
+
|
1271
|
+
### `Micro::Cases::Flow` (v3.0.0)
|
1272
|
+
|
1273
|
+
| Gem / Abstração | [Resultados de sucesso](https://github.com/serradura/u-case/blob/main/benchmarks/flow/with_success_result.rb#L40) | [Resultados de falha](https://github.com/serradura/u-case/blob/main/benchmarks/flow/with_failure_result.rb#L40) |
|
1274
|
+
| ------------------------------------------- | ----------------: | ----------------: |
|
1275
|
+
| Micro::Case internal flow (private methods) | _**O mais rápido**_ | _**O mais rápido**_ |
|
1276
|
+
| Micro::Case internal flow (through lambdas) | 1.03x slower | 1.04x slower |
|
1277
|
+
| Micro::Case `then` method | 1.49x slower | 0x slower |
|
1278
|
+
| Micro::Cases.flow | 1.53x slower | 1.04x slower |
|
1279
|
+
| Micro::Cases.safe_flow | 1.54x slower | 1.04x slower |
|
1280
|
+
| Interactor::Organizer | 2.05x slower | 6.27x slower |
|
1281
|
+
|
1282
|
+
\* As gems `Dry::Monads`, `Dry::Transaction`, `Trailblazer::Operation` estão fora desta análise por não terem esse tipo de funcionalidade.
|
1283
|
+
|
1284
|
+
<details>
|
1285
|
+
<summary><strong>Resultados de sucesso</strong> - Mostrar o resultado completo do benchmark/ips.</summary>
|
1286
|
+
|
1287
|
+
```ruby
|
1288
|
+
# Warming up --------------------------------------
|
1289
|
+
# Interactor::Organizer
|
1290
|
+
# 4.837k i/100ms
|
1291
|
+
# Micro::Cases.flow([])
|
1292
|
+
# 6.755k i/100ms
|
1293
|
+
# Micro::Cases::safe_flow([])
|
1294
|
+
# 6.809k i/100ms
|
1295
|
+
# Micro::Case flow using `then` method
|
1296
|
+
# 6.968k i/100ms
|
1297
|
+
# Micro::Case flow using private methods
|
1298
|
+
# 10.362k i/100ms
|
1299
|
+
# Micro::Case flow using private methods through lambdas
|
1300
|
+
# 10.258k i/100ms
|
1301
|
+
|
1302
|
+
# Calculating -------------------------------------
|
1303
|
+
# Interactor::Organizer
|
1304
|
+
# 50.731k (± 1.6%) i/s - 256.361k in 5.054694s
|
1305
|
+
# Micro::Cases.flow([])
|
1306
|
+
# 67.757k (± 1.6%) i/s - 344.505k in 5.085681s
|
1307
|
+
# Micro::Cases::safe_flow([])
|
1308
|
+
# 67.613k (± 1.6%) i/s - 340.450k in 5.036562s
|
1309
|
+
# Micro::Case flow using `then` method
|
1310
|
+
# 69.483k (± 1.5%) i/s - 348.400k in 5.015351s
|
1311
|
+
# Micro::Case flow using private methods
|
1312
|
+
# 103.788k (± 1.0%) i/s - 528.462k in 5.092240s
|
1313
|
+
# Micro::Case flow using private methods through lambdas
|
1314
|
+
# 101.081k (± 1.2%) i/s - 512.900k in 5.074904s
|
1315
|
+
|
1316
|
+
# Comparison:
|
1317
|
+
# Micro::Case flow using private methods: 103787.5 i/s
|
1318
|
+
# Micro::Case flow using private methods through lambdas: 101080.6 i/s - 1.03x (± 0.00) slower
|
1319
|
+
# Micro::Case flow using `then` method: 69483.3 i/s - 1.49x (± 0.00) slower
|
1320
|
+
# Micro::Cases.flow([]): 67757.2 i/s - 1.53x (± 0.00) slower
|
1321
|
+
# Micro::Cases::safe_flow([]): 67613.3 i/s - 1.54x (± 0.00) slower
|
1322
|
+
# Interactor::Organizer: 50730.8 i/s - 2.05x (± 0.00) slower
|
1323
|
+
```
|
1324
|
+
</details>
|
1325
|
+
|
1326
|
+
<details>
|
1327
|
+
<summary><strong>Resultados de falha</strong> - Mostrar o resultado completo do benchmark/ips.</summary>
|
1328
|
+
|
1329
|
+
```ruby
|
1330
|
+
# Warming up --------------------------------------
|
1331
|
+
# Interactor::Organizer
|
1332
|
+
# 2.299k i/100ms
|
1333
|
+
# Micro::Cases.flow([])
|
1334
|
+
# 14.187k i/100ms
|
1335
|
+
# Micro::Cases::safe_flow([])
|
1336
|
+
# 13.609k i/100ms
|
1337
|
+
# Micro::Case flow using `then` method
|
1338
|
+
# 14.578k i/100ms
|
1339
|
+
# Micro::Case flow using private methods
|
1340
|
+
# 14.101k i/100ms
|
1341
|
+
# Micro::Case flow using private methods through lambdas
|
1342
|
+
# 13.670k i/100ms
|
1343
|
+
# Calculating -------------------------------------
|
1344
|
+
# Interactor::Organizer
|
1345
|
+
# 23.306k (± 2.1%) i/s - 117.249k in 5.033171s
|
1346
|
+
# Micro::Cases.flow([])
|
1347
|
+
# 140.111k (± 1.6%) i/s - 709.350k in 5.064041s
|
1348
|
+
# Micro::Cases::safe_flow([])
|
1349
|
+
# 139.927k (± 1.7%) i/s - 707.668k in 5.058971s
|
1350
|
+
# Micro::Case flow using `then` method
|
1351
|
+
# 146.073k (± 2.0%) i/s - 743.478k in 5.091741s
|
1352
|
+
# Micro::Case flow using private methods
|
1353
|
+
# 142.092k (± 1.5%) i/s - 719.151k in 5.062298s
|
1354
|
+
# Micro::Case flow using private methods through lambdas
|
1355
|
+
# 140.791k (± 1.2%) i/s - 710.840k in 5.049584s
|
1356
|
+
|
1357
|
+
# Comparison:
|
1358
|
+
# Micro::Case flow using `then` method: 146073.0 i/s
|
1359
|
+
# Micro::Case flow using private methods: 142091.7 i/s - same-ish: difference falls within error
|
1360
|
+
# Micro::Case flow using private methods through lambdas: 140791.1 i/s - 1.04x (± 0.00) slower
|
1361
|
+
# Micro::Cases.flow([]): 140110.8 i/s - 1.04x (± 0.00) slower
|
1362
|
+
# Micro::Cases::safe_flow([]): 139926.6 i/s - 1.04x (± 0.00) slower
|
1363
|
+
# Interactor::Organizer: 23305.9 i/s - 6.27x (± 0.00) slower
|
1364
|
+
```
|
1365
|
+
</details>
|
1366
|
+
|
1367
|
+
https://github.com/serradura/u-case/tree/main/benchmarks/flow
|
1368
|
+
|
1369
|
+
### Comparações
|
1370
|
+
|
1371
|
+
Confira as implementações do mesmo caso de uso com diferentes gems/abstrações.
|
1372
|
+
|
1373
|
+
* [interactor](https://github.com/serradura/u-case/blob/main/comparisons/interactor.rb)
|
1374
|
+
* [u-case](https://github.com/serradura/u-case/blob/main/comparisons/u-case.rb)
|
1375
|
+
|
1376
|
+
[⬆️ Voltar para o índice](#índice-)
|
1377
|
+
|
1378
|
+
## Exemplos
|
1379
|
+
|
1380
|
+
### 1️⃣ Criação de usuários
|
1381
|
+
|
1382
|
+
> Um exemplo de fluxo que define etapas para higienizar, validar e persistir seus dados de entrada. Ele tem todas as abordagens possíveis para representar casos de uso com a gem `u-case`.
|
1383
|
+
>
|
1384
|
+
> Link: https://github.com/serradura/u-case/blob/main/examples/users_creation
|
1385
|
+
|
1386
|
+
### 2️⃣ Rails App (API)
|
1387
|
+
|
1388
|
+
> Este projeto mostra diferentes tipos de arquitetura (uma por commit), e na última, como usar a gem `Micro::Case` para lidar com a lógica de negócios da aplicação.
|
1389
|
+
>
|
1390
|
+
> Link: https://github.com/serradura/from-fat-controllers-to-use-cases
|
1391
|
+
|
1392
|
+
### 3️⃣ CLI calculator
|
1393
|
+
|
1394
|
+
> Rake tasks para demonstrar como lidar com os dados do usuário e como usar diferentes tipos de falha para controlar o fluxo do programa.
|
1395
|
+
>
|
1396
|
+
> Link: https://github.com/serradura/u-case/tree/main/examples/calculator
|
1397
|
+
|
1398
|
+
### 4️⃣ Interceptando exceções dentro dos casos de uso
|
1399
|
+
|
1400
|
+
> Link: https://github.com/serradura/u-case/blob/main/examples/rescuing_exceptions.rb
|
1401
|
+
|
1402
|
+
[⬆️ Voltar para o índice](#índice-)
|
1403
|
+
|
1404
|
+
## Desenvolvimento
|
1405
|
+
|
1406
|
+
Após fazer o checking out do repo, execute `bin/setup` para instalar dependências. Então, execute `./test.sh` para executar os testes. Você pode executar `bin/console` para ter um prompt interativo que permitirá você experimenta-lá.
|
1407
|
+
|
1408
|
+
Para instalar esta gem em sua máquina local, execute `bundle exec rake install`. Para lançar uma nova versão, atualize o número da versão em `version.rb` e execute` bundle exec rake release`, que criará uma tag git para a versão, enviará git commits e tags e enviará o arquivo `.gem`para [rubygems.org](https://rubygems.org).
|
1409
|
+
|
1410
|
+
## Contribuindo
|
1411
|
+
|
1412
|
+
Reportar bugs e solicitar pull requests são bem-vindos no GitHub em https://github.com/serradura/u-case. Este projeto pretende ser um espaço seguro e acolhedor para colaboração, e espera-se que os colaboradores sigam o código de conduta do [Covenant do Contribuidor](http://contributor-covenant.org).
|
1413
|
+
|
1414
|
+
## Licença
|
1415
|
+
|
1416
|
+
A gem está disponível como código aberto nos termos da [licença MIT](https://opensource.org/licenses/MIT).
|
1417
|
+
|
1418
|
+
## Código de conduta
|
1419
|
+
|
1420
|
+
Espera-se que todos que interagem com o codebase do projeto `Micro::Case`, issue trackers, chat rooms and mailing lists sigam o [código de conduta](https://github.com/serradura/u-case/blob/main/CODE_OF_CONDUCT.md).
|