event-pub-sub 1.1.6
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 +7 -0
- data/.gitignore +64 -0
- data/.rspec +4 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +94 -0
- data/Guardfile +51 -0
- data/LICENSE.txt +22 -0
- data/README.md +167 -0
- data/Rakefile +2 -0
- data/event_pub_sub.gemspec +30 -0
- data/lib/config/event.yml +31 -0
- data/lib/config/initializers/setup_event.rb +26 -0
- data/lib/config/listeners.yml +7 -0
- data/lib/event_pub_sub.rb +14 -0
- data/lib/event_pub_sub/broker_handler.rb +65 -0
- data/lib/event_pub_sub/listener.rb +7 -0
- data/lib/event_pub_sub/message_consumer.rb +65 -0
- data/lib/event_pub_sub/message_producer.rb +36 -0
- data/lib/event_pub_sub/railtie.rb +11 -0
- data/lib/event_pub_sub/version.rb +3 -0
- data/lib/tasks/event_tasks.rake +21 -0
- data/spec/integration/broker_handler_spec.rb +72 -0
- data/spec/spec_helper.rb +6 -0
- data/spec/unit/broker_handler_spec.rb +102 -0
- data/spec/unit/listener_spec.rb +16 -0
- data/spec/unit/message_consumer_spec.rb +85 -0
- data/spec/unit/message_producer_spec.rb +65 -0
- metadata +173 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: 73f22c5c6e9637d5795f7d8730690d760eaca0a9
|
|
4
|
+
data.tar.gz: dfb2258f9b8981dacc99a8d295c6e2ac4c8a65d3
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: faab525873900f49e3d36ef61d949a4c0c8fe742bd30d6e5d7592425816d2299797b26f2b8130e3dba643f8101a84115e14b2b66395d9be6e1c19da2f1765dac
|
|
7
|
+
data.tar.gz: 5f2051b5550451e5b5ead0c7a01b2da6bf5f0e91a5f528f6cd8b622226dd9f0b6d9d4c40965397dc0daa8a1ad038d65076af020bdc980d1be6e09be57426cdf2
|
data/.gitignore
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
*.rbc
|
|
2
|
+
capybara-*.html
|
|
3
|
+
/log
|
|
4
|
+
/tmp
|
|
5
|
+
/db/*.sqlite3
|
|
6
|
+
/public/system
|
|
7
|
+
/coverage/
|
|
8
|
+
/spec/tmp
|
|
9
|
+
run_test.sh
|
|
10
|
+
**.orig
|
|
11
|
+
rerun.txt
|
|
12
|
+
pickle-email-*.html
|
|
13
|
+
|
|
14
|
+
# TODO Comment out these rules if you are OK with secrets being uploaded to the repo
|
|
15
|
+
config/initializers/secret_token.rb
|
|
16
|
+
config/secrets.yml
|
|
17
|
+
|
|
18
|
+
## Environment normalisation:
|
|
19
|
+
/.bundle
|
|
20
|
+
/vendor/bundle
|
|
21
|
+
|
|
22
|
+
# these should all be checked in to normalise the environment:
|
|
23
|
+
# Gemfile.lock, .ruby-version, .ruby-gemset
|
|
24
|
+
|
|
25
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
|
26
|
+
.rvmrc
|
|
27
|
+
|
|
28
|
+
# if using bower-rails ignore default bower_components path bower.json files
|
|
29
|
+
/vendor/assets/bower_components
|
|
30
|
+
*.bowerrc
|
|
31
|
+
bower.json
|
|
32
|
+
# See https://help.github.com/articles/ignoring-files for more about ignoring files.
|
|
33
|
+
#
|
|
34
|
+
# If you find yourself ignoring temporary files generated by your text editor
|
|
35
|
+
# or operating system, you probably want to add a global ignore instead:
|
|
36
|
+
# git config --global core.excludesfile '~/.gitignore_global'
|
|
37
|
+
|
|
38
|
+
# Ignore bundler config.
|
|
39
|
+
/.bundle
|
|
40
|
+
|
|
41
|
+
# Ignore the default SQLite database.
|
|
42
|
+
/db/*.sqlite3
|
|
43
|
+
/db/*.sqlite3-journal
|
|
44
|
+
|
|
45
|
+
# Ignore all logfiles and tempfiles.
|
|
46
|
+
/log/*
|
|
47
|
+
!/log/.keep
|
|
48
|
+
/tmp
|
|
49
|
+
|
|
50
|
+
*.swp
|
|
51
|
+
*.swo
|
|
52
|
+
*.swn
|
|
53
|
+
|
|
54
|
+
# Ignore redis dump file
|
|
55
|
+
dump.rdb
|
|
56
|
+
|
|
57
|
+
# .ruby-gemset e .ruby-version nas engines
|
|
58
|
+
engines/**/.ruby-*
|
|
59
|
+
|
|
60
|
+
.editorconfig
|
|
61
|
+
tags
|
|
62
|
+
.byebug_history
|
|
63
|
+
|
|
64
|
+
.idea
|
data/.rspec
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
event-pub-sub (1.1.6)
|
|
5
|
+
activesupport (= 4.2.5.2)
|
|
6
|
+
bunny (= 2.3.0)
|
|
7
|
+
|
|
8
|
+
GEM
|
|
9
|
+
remote: https://rubygems.org/
|
|
10
|
+
specs:
|
|
11
|
+
activesupport (4.2.5.2)
|
|
12
|
+
i18n (~> 0.7)
|
|
13
|
+
json (~> 1.7, >= 1.7.7)
|
|
14
|
+
minitest (~> 5.1)
|
|
15
|
+
thread_safe (~> 0.3, >= 0.3.4)
|
|
16
|
+
tzinfo (~> 1.1)
|
|
17
|
+
amq-protocol (2.0.1)
|
|
18
|
+
bunny (2.3.0)
|
|
19
|
+
amq-protocol (>= 2.0.1)
|
|
20
|
+
byebug (8.2.1)
|
|
21
|
+
celluloid (0.16.0)
|
|
22
|
+
timers (~> 4.0.0)
|
|
23
|
+
coderay (1.1.0)
|
|
24
|
+
diff-lcs (1.2.5)
|
|
25
|
+
ffi (1.9.6)
|
|
26
|
+
formatador (0.2.5)
|
|
27
|
+
guard (2.11.1)
|
|
28
|
+
formatador (>= 0.2.4)
|
|
29
|
+
listen (~> 2.7)
|
|
30
|
+
lumberjack (~> 1.0)
|
|
31
|
+
nenv (~> 0.1)
|
|
32
|
+
notiffany (~> 0.0)
|
|
33
|
+
pry (>= 0.9.12)
|
|
34
|
+
shellany (~> 0.0)
|
|
35
|
+
thor (>= 0.18.1)
|
|
36
|
+
guard-compat (1.2.1)
|
|
37
|
+
guard-rspec (4.5.0)
|
|
38
|
+
guard (~> 2.1)
|
|
39
|
+
guard-compat (~> 1.1)
|
|
40
|
+
rspec (>= 2.99.0, < 4.0)
|
|
41
|
+
hitimes (1.2.2)
|
|
42
|
+
i18n (0.7.0)
|
|
43
|
+
json (1.8.3)
|
|
44
|
+
listen (2.8.5)
|
|
45
|
+
celluloid (>= 0.15.2)
|
|
46
|
+
rb-fsevent (>= 0.9.3)
|
|
47
|
+
rb-inotify (>= 0.9)
|
|
48
|
+
lumberjack (1.0.9)
|
|
49
|
+
method_source (0.8.2)
|
|
50
|
+
minitest (5.8.4)
|
|
51
|
+
nenv (0.2.0)
|
|
52
|
+
notiffany (0.0.3)
|
|
53
|
+
nenv (~> 0.1)
|
|
54
|
+
shellany (~> 0.0)
|
|
55
|
+
pry (0.10.1)
|
|
56
|
+
coderay (~> 1.1.0)
|
|
57
|
+
method_source (~> 0.8.1)
|
|
58
|
+
slop (~> 3.4)
|
|
59
|
+
rake (10.4.2)
|
|
60
|
+
rb-fsevent (0.9.4)
|
|
61
|
+
rb-inotify (0.9.5)
|
|
62
|
+
ffi (>= 0.5.0)
|
|
63
|
+
rspec (3.2.0)
|
|
64
|
+
rspec-core (~> 3.2.0)
|
|
65
|
+
rspec-expectations (~> 3.2.0)
|
|
66
|
+
rspec-mocks (~> 3.2.0)
|
|
67
|
+
rspec-core (3.2.0)
|
|
68
|
+
rspec-support (~> 3.2.0)
|
|
69
|
+
rspec-expectations (3.2.0)
|
|
70
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
71
|
+
rspec-support (~> 3.2.0)
|
|
72
|
+
rspec-mocks (3.2.0)
|
|
73
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
74
|
+
rspec-support (~> 3.2.0)
|
|
75
|
+
rspec-support (3.2.1)
|
|
76
|
+
shellany (0.0.1)
|
|
77
|
+
slop (3.6.0)
|
|
78
|
+
thor (0.19.1)
|
|
79
|
+
thread_safe (0.3.5)
|
|
80
|
+
timers (4.0.1)
|
|
81
|
+
hitimes
|
|
82
|
+
tzinfo (1.2.2)
|
|
83
|
+
thread_safe (~> 0.1)
|
|
84
|
+
|
|
85
|
+
PLATFORMS
|
|
86
|
+
ruby
|
|
87
|
+
|
|
88
|
+
DEPENDENCIES
|
|
89
|
+
bundler (~> 1.7)
|
|
90
|
+
byebug (= 8.2.1)
|
|
91
|
+
event-pub-sub!
|
|
92
|
+
guard-rspec (= 4.5.0)
|
|
93
|
+
rake (~> 10.0)
|
|
94
|
+
rspec (= 3.2.0)
|
data/Guardfile
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# A sample Guardfile
|
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
|
3
|
+
|
|
4
|
+
## Uncomment and set this to only include directories you want to watch
|
|
5
|
+
#directories %w(lib config spec)
|
|
6
|
+
|
|
7
|
+
## Uncomment to clear the screen before every task
|
|
8
|
+
# clearing :on
|
|
9
|
+
|
|
10
|
+
## Guard internally checks for changes in the Guardfile and exits.
|
|
11
|
+
## If you want Guard to automatically start up again, run guard in a
|
|
12
|
+
## shell loop, e.g.:
|
|
13
|
+
##
|
|
14
|
+
## $ while bundle exec guard; do echo "Restarting Guard..."; done
|
|
15
|
+
##
|
|
16
|
+
## Note: if you are using the `directories` clause above and you are not
|
|
17
|
+
## watching the project directory ('.'), the you will want to move the Guardfile
|
|
18
|
+
## to a watched dir and symlink it back, e.g.
|
|
19
|
+
#
|
|
20
|
+
# $ mkdir config
|
|
21
|
+
# $ mv Guardfile config/
|
|
22
|
+
# $ ln -s config/Guardfile .
|
|
23
|
+
#
|
|
24
|
+
# and, you'll have to watch "config/Guardfile" instead of "Guardfile"
|
|
25
|
+
|
|
26
|
+
# Note: The cmd option is now required due to the increasing number of ways
|
|
27
|
+
# rspec may be run, below are examples of the most common uses.
|
|
28
|
+
# * bundler: 'bundle exec rspec'
|
|
29
|
+
# * bundler binstubs: 'bin/rspec'
|
|
30
|
+
# * spring: 'bin/rspec' (This will use spring if running and you have
|
|
31
|
+
# installed the spring binstubs per the docs)
|
|
32
|
+
# * zeus: 'zeus rspec' (requires the server to be started separately)
|
|
33
|
+
# * 'just' rspec: 'rspec'
|
|
34
|
+
|
|
35
|
+
guard :rspec, cmd: "bundle exec rspec" do
|
|
36
|
+
watch(%r{^lib/(.+)\.rb$}) do |m|
|
|
37
|
+
[
|
|
38
|
+
"spec/unit/#{m[1]}_spec.rb",
|
|
39
|
+
"spec/integration/#{m[1]}_spec.rb"
|
|
40
|
+
]
|
|
41
|
+
end
|
|
42
|
+
#models
|
|
43
|
+
watch(%r{^lib/event/(.+)\.rb$}) do |m|
|
|
44
|
+
[
|
|
45
|
+
"spec/unit/#{m[1]}_spec.rb",
|
|
46
|
+
"spec/integration/#{m[1]}_spec.rb"
|
|
47
|
+
]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
watch(%r{^spec/.+_spec\.rb$})
|
|
51
|
+
end
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Copyright (c) 2015 Sergio Azevedo
|
|
2
|
+
|
|
3
|
+
MIT License
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
6
|
+
a copy of this software and associated documentation files (the
|
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
11
|
+
the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be
|
|
14
|
+
included in all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
[](https://semaphoreapp.com/darkseid/event)
|
|
2
|
+
# Event
|
|
3
|
+
|
|
4
|
+
O objetivo desta GEM é permitir que as Engines que compõem a nova Plataforma da Estante Virutal possam se comunicar sem que estas precisem "se conhecer", garantindo assim um baixo acoplamento entre as Engines.
|
|
5
|
+
|
|
6
|
+
A solução é baseada no pattern Publish-Subscribe Channel descrito pelo Fowler em seu livro Enterprise Integration Patterns. No caso, o papel do MessageChannel será exercido pelo RabbitMQ.
|
|
7
|
+
|
|
8
|
+
## Instalação
|
|
9
|
+
|
|
10
|
+
Como dito anteriormente, esta gem utiliza o RabbitMQ. Por isso precisamos ter esta dependência instalada. Para realizar a instalação basta seguir o passo a passo descrito neste [link](https://www.rabbitmq.com/download.html)
|
|
11
|
+
|
|
12
|
+
Depois, você precisa adicionar um novo source ao seu Gemfile. Este novo source deve ser a primeira linha do seu Gemfile:
|
|
13
|
+
|
|
14
|
+
```ruby
|
|
15
|
+
source 'https://repo.fury.io/sergioazevedo'
|
|
16
|
+
source 'https://rubygems.org'
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Se vc estiver instalando em uma Engine deve alterar o arquivo 'suaengine.gemspec' e adicionar a dependencia
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
s.add_dependency "event","~>1.0.0"
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Caso seja um projeto Ruby normal ou uma Rails app adicione a linha abaixo no seu Gemfile:
|
|
26
|
+
|
|
27
|
+
```ruby
|
|
28
|
+
gem "event","~>1.0.0"
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
E executar:
|
|
32
|
+
$ bundle
|
|
33
|
+
|
|
34
|
+
## Configuração
|
|
35
|
+
Os arquivos abaixo são necessários para se configurar esta gem.
|
|
36
|
+
```
|
|
37
|
+
config/event.yml
|
|
38
|
+
config/initializes/setup_event.rb
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Para gerá-los basta rodar a rake task: ```rake app:event:install```.
|
|
42
|
+
|
|
43
|
+
####config/event.yml
|
|
44
|
+
Neste arquivo você deve fornecer as configuracoes para conexão com o Broker RabbitMQ.
|
|
45
|
+
Mas este arquivo contém uma configuração muito importante, e está na chave: **base_routing_key**. Você **precisa** usar nesta chave o nome da sua engine _(ou algo semelhante, que identifique o módulo)_.
|
|
46
|
+
Exemplo:
|
|
47
|
+
Imagine o modulo de Ecommerce
|
|
48
|
+
|
|
49
|
+
```yaml
|
|
50
|
+
development:
|
|
51
|
+
base_routing_key: ecommerce
|
|
52
|
+
broker:
|
|
53
|
+
ip: localhost
|
|
54
|
+
port: 5672
|
|
55
|
+
username: guest
|
|
56
|
+
password: guest
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
####config/initializers/setup_event.rb
|
|
60
|
+
Você só vai precisar alterar este arquivo no caso de querer "assinar" algum evento.
|
|
61
|
+
|
|
62
|
+
## Utilização
|
|
63
|
+
|
|
64
|
+
### Publicando Eventos / Enviando mensagens
|
|
65
|
+
Publicar eventos é muito fácil, basta usar Event.pubish.
|
|
66
|
+
|
|
67
|
+
```ruby
|
|
68
|
+
Event.publish(:sku_added, name: 'Caneta Azul', price: 1.99, qty: 10 )
|
|
69
|
+
```
|
|
70
|
+
No exempo acima o nome do evento é **:sku_added** e os dados do evento estão no hash: **{name: 'Caneta Azul', price: 1.99, qty: 10}**
|
|
71
|
+
|
|
72
|
+
###Sobre os Eventos
|
|
73
|
+
Eventos são sempre coisas que já aconteceram, por este motivo é de boa prática que os nomes dos eventos descrevam claramente que fato ocorreu e com quem. Exemplos abaixo:
|
|
74
|
+
- Um produto novo sku foi adicionado - :new_product_added
|
|
75
|
+
- Um sku novo foi adicionado - :new_sku_added
|
|
76
|
+
- Um sku foi vendido - :sku_saled
|
|
77
|
+
- Produto teve seu preço alterado - product_price_updated
|
|
78
|
+
|
|
79
|
+
Sobre os dados do evento, isto fica a cargo de quem publica o evento. Mas em geral é bacana enviar informações sobre o contexto do evento.
|
|
80
|
+
|
|
81
|
+
Exemplo:
|
|
82
|
+
|
|
83
|
+
- Um sku foi vendido
|
|
84
|
+
```ruby
|
|
85
|
+
Event.publish(:sku_saled, sku_id: 10 )
|
|
86
|
+
```
|
|
87
|
+
- Preço de um sku foi alterado
|
|
88
|
+
```ruby
|
|
89
|
+
Event.publish(:sku_price_changed, sku_id: 10, old_price: 1.99, new_price: 2.87 )
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Assinando os Eventos / Recebendo mensagens
|
|
93
|
+
|
|
94
|
+
Assinar eventos é um processo de duas etapas:
|
|
95
|
+
|
|
96
|
+
1. Escrever uma classe Listener para o evento desejado
|
|
97
|
+
2. Registrar o Listener para o evento desejado em config/initializes/setup_event.rb
|
|
98
|
+
|
|
99
|
+
####1. Escrever uma classe Listener para o evento desejado
|
|
100
|
+
Como dito antes você vai precisar escrever uma classe para cada listener que desejar.
|
|
101
|
+
Esta classe não precisa herdar de nada, basta que ela tenha os métodos **#initialize** e **notify**. O Listener é como um [Observer _GoF_](http://en.wikipedia.org/wiki/Observer_pattern).
|
|
102
|
+
|
|
103
|
+
Atenção, o listener deve receber toda a informação necessária para sua excução no método de inicialização (new), ou seja, os dados do evento/mensagem.
|
|
104
|
+
|
|
105
|
+
Exemplo de Listener:
|
|
106
|
+
|
|
107
|
+
Considere a mensagem abaixo:
|
|
108
|
+
|
|
109
|
+
```ruby
|
|
110
|
+
Event.publish(:sku_price_changed, sku_id: 10, old_price: 1.99, new_price: 2.87 )
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
```ruby
|
|
115
|
+
class SkuPriceChangedListener
|
|
116
|
+
def initialize(data={})
|
|
117
|
+
@sku_id = data[:sku_id]
|
|
118
|
+
@old_price = data[:old_price]
|
|
119
|
+
@new_price = data[:new_price]
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def notify
|
|
123
|
+
#aqui vai o seu código.
|
|
124
|
+
#vc pode, e em geral vai acontecer isso, instanciar um UseCase e executa-lo.
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
````
|
|
128
|
+
|
|
129
|
+
##### As Operações executadas por um listener devem ser Idempotentes sempre!!!
|
|
130
|
+
Por razões de segurança, e para garantia da consistencia dos nossos dados, as operações realizadas por um Listener deve ser idempontentes.
|
|
131
|
+
|
|
132
|
+
>Idempotente quer dizer que múltiplas requisições ao mesmo recurso usando o método devem ter o mesmo resultado que teria uma requisição apenas. A título de curiosidade, idempotente é a propriedade de um número que, multiplicado por ele mesmo, tem ele mesmo como resultado (n x n = n), em termos de números reais, apenas 0 e 1 têm essa propriedade. Em termos de métodos de requisição HTTP, os métodos GET, HEAD, PUT e DELETE são os que possuem a propriedade de ser idempotentes
|
|
133
|
+
- <cite>[Dicionario Informal](http://www.dicionarioinformal.com.br/idempotente/)</cite>
|
|
134
|
+
|
|
135
|
+
##### Onde colocar meus listeners
|
|
136
|
+
Em geral, costuma-se criar dentro de app/ uma pasta chamada listeners ou event_listeners.
|
|
137
|
+
|
|
138
|
+
####2. Registrar o Listener para o evento
|
|
139
|
+
No arquivo setup_events.rb existe uma seção dedicada ao registro dos listeners.
|
|
140
|
+
Esta seção vem toda comentada por padrão, veja:
|
|
141
|
+
```ruby
|
|
142
|
+
# Register your listeners Here!!!! Example:
|
|
143
|
+
# Event.register_listeners do |config|
|
|
144
|
+
# config.add_listeners(:event_name, ['EventNameListener'])
|
|
145
|
+
# config.add_listeners(:another_event_name, ['AnotherEventListener1', 'Listner2'])
|
|
146
|
+
# end
|
|
147
|
+
```
|
|
148
|
+
Basta descomentar e seguir como no exemplo. Imaginando que queremos registrar o nosso SkuPriceChangedListener, esta configuracao ficaria assim:
|
|
149
|
+
|
|
150
|
+
```ruby
|
|
151
|
+
# Register your listeners Here!!!! Example:
|
|
152
|
+
Event.register_listeners do |config|
|
|
153
|
+
config.add_listeners(:sku_price_changed, ['SkuPriceChanged'])
|
|
154
|
+
end
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**Note que você sempre deve fornecer o nome do Listener com uma String dentro de um Array.**
|
|
158
|
+
|
|
159
|
+
**Observação:** Como este arquivo setup_events.rb é carregado na inicialização da aplicação, toda vez que você registrar um evento novo vai precisar reinicar a app.
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
## Contributing
|
|
163
|
+
1. Fork it ( https://github.com/[my-github-username]/event/fork )
|
|
164
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
|
165
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
|
166
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
|
167
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
# coding: utf-8
|
|
3
|
+
lib = File.expand_path('../lib', __FILE__)
|
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
5
|
+
require 'event_pub_sub/version'
|
|
6
|
+
|
|
7
|
+
Gem::Specification.new do |spec|
|
|
8
|
+
spec.name = 'event-pub-sub'
|
|
9
|
+
spec.version = EventPubSub::VERSION
|
|
10
|
+
spec.authors = ['Estante Virutal']
|
|
11
|
+
spec.email = ['equipe_ti@estantevirtual.com.br']
|
|
12
|
+
spec.summary = %q{Pub/Sub de Eventos baseado em RabbitMQ.}
|
|
13
|
+
spec.description = %q{Pub/Sub de Eventos baseado em RabbitMQ.}
|
|
14
|
+
spec.homepage = 'http://www.estantevirtual.com.br'
|
|
15
|
+
|
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
|
19
|
+
spec.require_paths = ['lib']
|
|
20
|
+
|
|
21
|
+
spec.add_dependency 'bunny', '2.3.0'
|
|
22
|
+
spec.add_dependency 'activesupport', '4.2.5.2'
|
|
23
|
+
|
|
24
|
+
spec.add_development_dependency 'bundler', '~> 1.7'
|
|
25
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
|
26
|
+
spec.add_development_dependency 'rspec', '3.2.0'
|
|
27
|
+
spec.add_development_dependency 'guard-rspec', '4.5.0'
|
|
28
|
+
spec.add_development_dependency 'byebug', '8.2.1'
|
|
29
|
+
|
|
30
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
development:
|
|
2
|
+
base_routing_key: module_name
|
|
3
|
+
broker:
|
|
4
|
+
ip: localhost
|
|
5
|
+
port: 5672
|
|
6
|
+
username: guest
|
|
7
|
+
password: guest
|
|
8
|
+
|
|
9
|
+
test:
|
|
10
|
+
base_routing_key: module_name
|
|
11
|
+
broker:
|
|
12
|
+
ip: localhost
|
|
13
|
+
port: 5672
|
|
14
|
+
username: guest
|
|
15
|
+
password: guest
|
|
16
|
+
|
|
17
|
+
staging:
|
|
18
|
+
base_routing_key: module_name
|
|
19
|
+
broker:
|
|
20
|
+
ip: <%=ENV['BROKER_IP'] %>
|
|
21
|
+
port: <%=ENV['BROKER_PORT'] %>
|
|
22
|
+
username: <%=ENV['BROKER_USERNAME'] %>
|
|
23
|
+
password: <%=ENV['BROKER_PASSWORD'] %>
|
|
24
|
+
|
|
25
|
+
production:
|
|
26
|
+
base_routing_key: module_name
|
|
27
|
+
broker:
|
|
28
|
+
ip: <%=ENV['BROKER_IP'] %>
|
|
29
|
+
port: <%=ENV['BROKER_PORT'] %>
|
|
30
|
+
username: <%=ENV['BROKER_USERNAME'] %>
|
|
31
|
+
password: <%=ENV['BROKER_PASSWORD'] %>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# require 'event'
|
|
2
|
+
# require 'yaml'
|
|
3
|
+
|
|
4
|
+
# module YourModuleNameHere
|
|
5
|
+
# class Event
|
|
6
|
+
# broker_data = File.read( File.expand_path('../../event.yml',__FILE__) )
|
|
7
|
+
# broker_config = YAML.load(ERB.new(broker_data).result).with_indifferent_access
|
|
8
|
+
# @@message_producer = ::Event::MessageProducer.new(broker_config[Rails.env], Rails.logger)
|
|
9
|
+
|
|
10
|
+
# if !Rails.env.test?
|
|
11
|
+
# listener_data = File.read( File.expand_path('../../listeners.yml',__FILE__) )
|
|
12
|
+
# listeners = YAML.load(listener_data).with_indifferent_access
|
|
13
|
+
|
|
14
|
+
# if listeners.any?
|
|
15
|
+
# Thread.new do
|
|
16
|
+
# consumer = ::Event::MessageConsumer.new(broker_config[Rails.env], Rails.logger)
|
|
17
|
+
# consumer.execute(listeners)
|
|
18
|
+
# end
|
|
19
|
+
# end
|
|
20
|
+
# end
|
|
21
|
+
|
|
22
|
+
# def self.publish(event_name, data={})
|
|
23
|
+
# @@message_producer.publish(event_name.to_sym, data)
|
|
24
|
+
# end
|
|
25
|
+
# end
|
|
26
|
+
# end
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
#event: ['ClassName']
|
|
2
|
+
#event: ['Module::ClassName']
|
|
3
|
+
### - Se vc tiver um caso onde um ou mais listeners precisam ser executados para qualquer evento,
|
|
4
|
+
### ou seja, se indepentende do evento que aconteceu vc quer que aquele listener rode use a sintaxe abaixo:
|
|
5
|
+
#any_event: [ClassName]
|
|
6
|
+
|
|
7
|
+
##PS: as sintaxes acimma nao são excludentes, vc pode usar as duas em conjunto
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
require 'time'
|
|
2
|
+
require 'bunny'
|
|
3
|
+
require 'logger'
|
|
4
|
+
require "event_pub_sub/version"
|
|
5
|
+
require 'event_pub_sub/broker_handler'
|
|
6
|
+
require 'event_pub_sub/message_producer'
|
|
7
|
+
require 'event_pub_sub/message_consumer'
|
|
8
|
+
require 'event_pub_sub/listener'
|
|
9
|
+
require 'json'
|
|
10
|
+
require 'securerandom'
|
|
11
|
+
|
|
12
|
+
module EventPubSub
|
|
13
|
+
|
|
14
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
module EventPubSub
|
|
2
|
+
class BrokerHandler
|
|
3
|
+
|
|
4
|
+
def initialize(config, logger)
|
|
5
|
+
raise ArgumentError, "missing broker ip" unless config[:ip]
|
|
6
|
+
raise ArgumentError, "missing broker port" unless config[:port]
|
|
7
|
+
raise ArgumentError, "missing broker username" unless config[:username]
|
|
8
|
+
raise ArgumentError, "missing broker password" unless config[:password]
|
|
9
|
+
@config = config
|
|
10
|
+
@logger = logger
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def start_connection
|
|
14
|
+
@connection = build_connection
|
|
15
|
+
@connection.start
|
|
16
|
+
rescue => e
|
|
17
|
+
@logger.error "#{e.message} - #{e.class}\n #{e.backtrace.join("\n")}"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def close_connection
|
|
21
|
+
@connection.close
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def publish(message, routing_key)
|
|
25
|
+
topic.publish( message, persistent: true, routing_key: routing_key )
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def setup_queue(queue_name)
|
|
29
|
+
@queue = channel.queue(queue_name, durable: true, auto_delete: false)
|
|
30
|
+
@queue.bind(topic, routing_key: '#')
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def subscribe(consumer_name, params={ack: true, block: true}, &block)
|
|
34
|
+
params[:consumer_tag] = consumer_name
|
|
35
|
+
@queue.subscribe(params) do |delivery_info, properties, payload|
|
|
36
|
+
begin
|
|
37
|
+
block.call(delivery_info, properties, payload)
|
|
38
|
+
channel.ack(delivery_info.delivery_tag)
|
|
39
|
+
rescue => e
|
|
40
|
+
channel.nack(delivery_info.delivery_tag, false, true)
|
|
41
|
+
raise e
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
def build_connection
|
|
48
|
+
Bunny.new(
|
|
49
|
+
host: @config[:ip],
|
|
50
|
+
port: @config[:port],
|
|
51
|
+
user: @config[:username],
|
|
52
|
+
pass: @config[:password]
|
|
53
|
+
)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def topic
|
|
57
|
+
@topic ||= channel.topic('topic_events', durable: true)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def channel
|
|
62
|
+
@channel ||= @connection.create_channel
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
module EventPubSub
|
|
2
|
+
class MessageConsumer
|
|
3
|
+
def initialize(config, logger)
|
|
4
|
+
raise ArgumentError, "missing module base_routing_key " unless config[:base_routing_key]
|
|
5
|
+
@queue_name = config[:base_routing_key]
|
|
6
|
+
@logger = logger
|
|
7
|
+
@broker_handler = BrokerHandler.new(config[:broker], @logger)
|
|
8
|
+
@logger.info '[MessageConsumer] - Starting Connection'
|
|
9
|
+
@broker_handler.start_connection
|
|
10
|
+
@broker_handler.setup_queue(@queue_name)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def execute(all_listeners)
|
|
14
|
+
@logger.info '[MessageConsumer] - Waiting Messages...'
|
|
15
|
+
consumer_name = "#{@queue_name}_event_consumer"
|
|
16
|
+
@broker_handler.subscribe(consumer_name) do |delivery_info, properties, payload|
|
|
17
|
+
message = JSON.parse(payload).with_indifferent_access
|
|
18
|
+
event_name = message['event_name']
|
|
19
|
+
@logger.debug "[MessageConsumer] - Message Received: #{event_name}"
|
|
20
|
+
listeners_to_execute = get_listeners_of(event_name, all_listeners)
|
|
21
|
+
fire_listeners(listeners_to_execute, message)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def close_connection
|
|
26
|
+
@logger.info '[MessageConsumer] - Closing Connection'
|
|
27
|
+
@broker_handler.close_conection
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def get_listeners_of(event_name, listeners_definitions)
|
|
31
|
+
listeners = listeners_definitions[event_name.to_sym] || []
|
|
32
|
+
if has_mappings_for_any_event?(listeners_definitions)
|
|
33
|
+
listeners.concat get_listeners_for_any_event(listeners_definitions)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
listeners
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
def fire_listeners(listeners_to_execute, data)
|
|
41
|
+
if listeners_to_execute
|
|
42
|
+
listeners_to_execute.each do |listener_klass_name|
|
|
43
|
+
@logger.info "[Event Handling] - using listener class '#{listener_klass_name}'"
|
|
44
|
+
@logger.debug "[Event Handling] - notify event '#{@event_name}' using listener class '#{listener_klass_name}' with args '#{data}'"
|
|
45
|
+
begin
|
|
46
|
+
listener_klass = Object.const_get(listener_klass_name)
|
|
47
|
+
listener_klass.new(data).notify
|
|
48
|
+
rescue => e
|
|
49
|
+
@logger.error "[Event Handling] - #{e} \n #{e.backtrace.join("\n")}"
|
|
50
|
+
raise e
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def has_mappings_for_any_event?(listeners_definitions)
|
|
57
|
+
listeners_definitions[:any_event] && listeners_definitions[:any_event].any?
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def get_listeners_for_any_event(listeners_definitions)
|
|
61
|
+
listeners_definitions[:any_event]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
module EventPubSub
|
|
2
|
+
class MessageProducer
|
|
3
|
+
def initialize(config, logger)
|
|
4
|
+
raise ArgumentError, "missing module base_routing_key " unless config[:base_routing_key]
|
|
5
|
+
@base_routing_key = config[:base_routing_key]
|
|
6
|
+
@logger = logger
|
|
7
|
+
|
|
8
|
+
@broker_handler = BrokerHandler.new(config[:broker], @logger)
|
|
9
|
+
@logger.info '[MessageProducer] - Starting Connection'
|
|
10
|
+
@broker_handler.start_connection
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def publish(event_name, msg={})
|
|
14
|
+
key = routing_key(event_name)
|
|
15
|
+
enrich_message(event_name, msg)
|
|
16
|
+
@logger.info "[MessageProducer] - Sending event: #{event_name}"
|
|
17
|
+
@logger.debug "[MessageProducer] - Sending event: #{event_name} with data: #{msg.inspect} using as routing_key: #{key}"
|
|
18
|
+
@broker_handler.publish( JSON.generate(msg), key )
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
def routing_key(event_name)
|
|
23
|
+
"#{@base_routing_key}.#{event_name}"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def enrich_message(event_name, msg)
|
|
27
|
+
msg[:event_id] = SecureRandom.uuid
|
|
28
|
+
msg[:event_name] = event_name
|
|
29
|
+
if defined?(Rails)
|
|
30
|
+
msg[:event_date] = Time.zone.now
|
|
31
|
+
else
|
|
32
|
+
msg[:event_date] = Time.now.utc
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
require 'fileutils'
|
|
2
|
+
namespace :event do
|
|
3
|
+
desc "Installs required stuff to event"
|
|
4
|
+
task :install do
|
|
5
|
+
Rails.logger = Logger.new(STDOUT)
|
|
6
|
+
source = File.join(Gem.loaded_specs["event"].full_gem_path, "lib/config", "event.yml")
|
|
7
|
+
target = File.join("config", "event.yml")
|
|
8
|
+
Rails.logger.info "Creating file #{target}"
|
|
9
|
+
FileUtils.cp_r(source, target)
|
|
10
|
+
|
|
11
|
+
source = File.join(Gem.loaded_specs["event"].full_gem_path, "lib/config", "listeners.yml")
|
|
12
|
+
target = File.join("config", "listeners.yml")
|
|
13
|
+
Rails.logger.info "Creating file #{target}"
|
|
14
|
+
FileUtils.cp_r(source, target)
|
|
15
|
+
|
|
16
|
+
source = File.join(Gem.loaded_specs["event"].full_gem_path, "lib/config/initializers", "setup_event.rb")
|
|
17
|
+
target = File.join("config/initializers", "setup_event.rb")
|
|
18
|
+
Rails.logger.info "Creating file #{target}"
|
|
19
|
+
FileUtils.cp_r(source, target)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
module EventPubSub
|
|
3
|
+
describe BrokerHandler do
|
|
4
|
+
let(:logger){ double('logger', debug: true)}
|
|
5
|
+
let(:running_server_config) do
|
|
6
|
+
{
|
|
7
|
+
ip: "localhost",
|
|
8
|
+
port: "5672",
|
|
9
|
+
username: "guest",
|
|
10
|
+
password: "guest",
|
|
11
|
+
}
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
let(:stopped_server_config) do
|
|
15
|
+
{
|
|
16
|
+
ip: "localhost",
|
|
17
|
+
port: "4000",
|
|
18
|
+
username: "guest",
|
|
19
|
+
password: "guest",
|
|
20
|
+
}
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
describe "#start_connection" do
|
|
24
|
+
context "given a up and running RabbitMQ server at localhost:5672" do
|
|
25
|
+
subject{ BrokerHandler.new(running_server_config, logger) }
|
|
26
|
+
it "stabilish connection" do
|
|
27
|
+
expect{subject.start_connection}.to_not raise_error
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
context "given a stoped RabbitMQ server" do
|
|
32
|
+
subject{ BrokerHandler.new(stopped_server_config, logger) }
|
|
33
|
+
it "raise error" do
|
|
34
|
+
expect{ subject.start_connection }.to raise_error
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
describe "#close_connection" do
|
|
40
|
+
context "given a up and running RabbitMQ server at localhost:5672" do
|
|
41
|
+
subject{ BrokerHandler.new(running_server_config, logger) }
|
|
42
|
+
|
|
43
|
+
it "closes connection" do
|
|
44
|
+
subject.start_connection
|
|
45
|
+
expect{subject.close_connection}.to_not raise_error
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it "closes the channel" do
|
|
49
|
+
channel = subject.start_connection
|
|
50
|
+
expect(channel).to receive(:close).once
|
|
51
|
+
subject.close_connection
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
describe "#publish" do
|
|
57
|
+
context "given a up and running RabbitMQ server at localhost:5672" do
|
|
58
|
+
subject do
|
|
59
|
+
bus = BrokerHandler.new(running_server_config, logger)
|
|
60
|
+
bus.start_connection
|
|
61
|
+
|
|
62
|
+
bus
|
|
63
|
+
end
|
|
64
|
+
it "publish the event using base_routing_key" do
|
|
65
|
+
expect{ subject.publish( "'data': 'sample data'", 'routing.key') }.to_not raise_error
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
data/spec/spec_helper.rb
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
module EventPubSub
|
|
4
|
+
describe BrokerHandler do
|
|
5
|
+
let(:logger) { double(:log, debug: true) }
|
|
6
|
+
let(:config) do
|
|
7
|
+
{
|
|
8
|
+
ip: "localhost",
|
|
9
|
+
port: "5672",
|
|
10
|
+
username: "guest",
|
|
11
|
+
password: "guest"
|
|
12
|
+
}
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
describe "API" do
|
|
16
|
+
subject{ BrokerHandler.new(config, logger)}
|
|
17
|
+
it { should respond_to :start_connection }
|
|
18
|
+
it { should respond_to :publish }
|
|
19
|
+
it { should respond_to :close_connection }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
describe "initialize" do
|
|
23
|
+
it "raise Argument Error when no config is given" do
|
|
24
|
+
expect{BrokerHandler.new}.to raise_error ArgumentError
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it "raise ArgumentError error with a invalid config" do
|
|
28
|
+
expect{BrokerHandler.new('aaaaa')}.to raise_error ArgumentError
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it "raise ArgumentError error with a incomplete config" do
|
|
32
|
+
expect{BrokerHandler.new({})}.to raise_error ArgumentError
|
|
33
|
+
expect{BrokerHandler.new({ip: 1})}.to raise_error ArgumentError
|
|
34
|
+
expect{BrokerHandler.new({ip: 1, port: 2})}.to raise_error ArgumentError
|
|
35
|
+
expect{BrokerHandler.new({ip: 1, port: 2, username: 'a'})}.to raise_error ArgumentError
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it "not raise error with a valid config" do
|
|
39
|
+
expect{BrokerHandler.new(config, logger)}.to_not raise_error
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
describe "#start_connection" do
|
|
44
|
+
context "Given a RabbitMQ Broker and a BrokerHandler with valid configuration" do
|
|
45
|
+
let(:mocked_rabbitmq_lib){double}
|
|
46
|
+
let(:connection){ double }
|
|
47
|
+
subject{ BrokerHandler.new(config, logger) }
|
|
48
|
+
|
|
49
|
+
it "returns a RabbitMQ Connection" do
|
|
50
|
+
expect(subject).to receive(:build_connection).once.and_return(mocked_rabbitmq_lib)
|
|
51
|
+
expect(mocked_rabbitmq_lib).to receive(:start).once.and_return(connection)
|
|
52
|
+
expect(subject.start_connection).to be connection
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
describe "#close_connection" do
|
|
58
|
+
context "Given BrokerHandler with a open connection" do
|
|
59
|
+
let(:mocked_rabbitmq_lib){double}
|
|
60
|
+
let(:connection){ double }
|
|
61
|
+
|
|
62
|
+
before do
|
|
63
|
+
allow(subject).to receive(:build_connection).and_return(mocked_rabbitmq_lib)
|
|
64
|
+
allow(mocked_rabbitmq_lib).to receive(:start).once.and_return(connection)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
subject{ BrokerHandler.new(config, logger) }
|
|
68
|
+
|
|
69
|
+
it "closes a RabbitMQ Connection" do
|
|
70
|
+
subject.start_connection
|
|
71
|
+
expect(mocked_rabbitmq_lib).to receive(:close).once
|
|
72
|
+
subject.close_connection
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
describe "#publish" do
|
|
78
|
+
context "given a up and running RabbitMQ server at localhost:5672" do
|
|
79
|
+
let(:mocked_rabbitmq_lib){double(:bunny_mock)}
|
|
80
|
+
let(:channel){double(:channel, topic: true)}
|
|
81
|
+
let(:connection){double(:connection_mock)}
|
|
82
|
+
let(:topic){double(:topic_mock)}
|
|
83
|
+
before do
|
|
84
|
+
allow(subject).to receive(:build_connection).and_return(mocked_rabbitmq_lib)
|
|
85
|
+
allow(mocked_rabbitmq_lib).to receive(:start).once.and_return(connection)
|
|
86
|
+
allow(mocked_rabbitmq_lib).to receive(:create_channel).and_return(channel)
|
|
87
|
+
allow(channel).to receive(:topic).and_return(topic)
|
|
88
|
+
end
|
|
89
|
+
subject{ BrokerHandler.new(config, logger) }
|
|
90
|
+
|
|
91
|
+
it "publish the event using base_routing_key" do
|
|
92
|
+
subject.start_connection
|
|
93
|
+
expect(topic).to receive(:publish).once
|
|
94
|
+
subject.publish('message', 'routing.key')
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
module EventPubSub
|
|
4
|
+
describe Listener do
|
|
5
|
+
describe "api" do
|
|
6
|
+
it { is_expected.to respond_to :notify }
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
describe "#notify" do
|
|
10
|
+
it "raises NotImplementedError in order to remember the developer to override this operation on subclasses" do
|
|
11
|
+
expect{suject.notify}.to raise_error
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
module EventPubSub
|
|
4
|
+
|
|
5
|
+
describe MessageConsumer do
|
|
6
|
+
let(:logger) { double(:log, debug: true, info: true) }
|
|
7
|
+
let(:config) do
|
|
8
|
+
{ broker: {
|
|
9
|
+
ip: "localhost",
|
|
10
|
+
port: "5672",
|
|
11
|
+
username: "guest",
|
|
12
|
+
password: "guest"
|
|
13
|
+
},
|
|
14
|
+
base_routing_key: "module_name"
|
|
15
|
+
}
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
describe "API" do
|
|
19
|
+
subject{ MessageConsumer.new(config, logger) }
|
|
20
|
+
it { should respond_to(:execute) }
|
|
21
|
+
it { should respond_to(:get_listeners_of) }
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
describe ".initialize" do
|
|
25
|
+
it "raise ArgumentError when no config was given" do
|
|
26
|
+
expect{ MessageConsumer.new }.to raise_error(ArgumentError)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it "raise ArgumentError if invalid config was given" do
|
|
30
|
+
expect{ MessageConsumer.new({}) }.to raise_error(ArgumentError)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it "no raise error if a valid config was given" do
|
|
34
|
+
expect{ MessageConsumer.new(config, logger) }.to_not raise_error
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
describe "#get_listeners_of" do
|
|
39
|
+
context "Given a event called new_sku_added with ExampleListener class associated" do
|
|
40
|
+
let(:listener_definitions) do
|
|
41
|
+
{ new_sku_added: ["ExampleListenerClass"] }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
subject{ MessageConsumer.new(config, logger) }
|
|
45
|
+
|
|
46
|
+
it "returns [ExampleListenerClass]" do
|
|
47
|
+
result = subject.get_listeners_of('new_sku_added', listener_definitions)
|
|
48
|
+
expect(result).to eql ['ExampleListenerClass']
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
context "Given a listener called AllEventsListener mapped for any event" do
|
|
53
|
+
let(:listener_definitions) do
|
|
54
|
+
{ any_event: ["AllEventListenerClass"] }
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
subject{ MessageConsumer.new(config, logger) }
|
|
58
|
+
|
|
59
|
+
it "returns [AllEventListenerClass]" do
|
|
60
|
+
result = subject.get_listeners_of('foo_bar_updated', listener_definitions)
|
|
61
|
+
expect(result).to eql ['AllEventListenerClass']
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
context "Given a listener called AllEventsListener mapped for any event and new_sku_added mapped to ExampleListener" do
|
|
66
|
+
let(:listener_definitions) do
|
|
67
|
+
{ new_sku_added: ["ExampleListenerClass"], any_event: ["AllEventListenerClass"] }
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
subject{ MessageConsumer.new(config, logger) }
|
|
71
|
+
|
|
72
|
+
it "for 'new_sku_added' event returns [ExampleListenerClass, AllEventListenerClass]" do
|
|
73
|
+
result = subject.get_listeners_of('new_sku_added', listener_definitions)
|
|
74
|
+
expect(result).to eql ['ExampleListenerClass', 'AllEventListenerClass']
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
it "for 'product_selled' event returns [AllEventListenerClass]" do
|
|
78
|
+
result = subject.get_listeners_of('product_selled', listener_definitions)
|
|
79
|
+
expect(result).to eql ['AllEventListenerClass']
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
module EventPubSub
|
|
3
|
+
describe MessageProducer do
|
|
4
|
+
let(:logger) { double(:log, debug: true, info: true) }
|
|
5
|
+
let(:config) do
|
|
6
|
+
{
|
|
7
|
+
broker: {
|
|
8
|
+
ip: "localhost",
|
|
9
|
+
port: "5672",
|
|
10
|
+
username: "guest",
|
|
11
|
+
password: "guest"
|
|
12
|
+
},
|
|
13
|
+
base_routing_key: "module_name"
|
|
14
|
+
}
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
describe "API" do
|
|
18
|
+
subject{ MessageProducer.new(config, logger)}
|
|
19
|
+
it { should respond_to :publish }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
describe "initialize" do
|
|
23
|
+
it "raise Argument Error when no config is given" do
|
|
24
|
+
expect{MessageProducer.new}.to raise_error ArgumentError
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it "raise ArgumentError error with a invalid config" do
|
|
28
|
+
expect{MessageProducer.new('aaaaa')}.to raise_error ArgumentError
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it "raise ArgumentError error with a incomplete config" do
|
|
32
|
+
expect{MessageProducer.new({})}.to raise_error ArgumentError
|
|
33
|
+
expect{MessageProducer.new({ip: 1})}.to raise_error ArgumentError
|
|
34
|
+
expect{MessageProducer.new({ip: 1, port: 2})}.to raise_error ArgumentError
|
|
35
|
+
expect{MessageProducer.new({ip: 1, port: 2, username: 'a'})}.to raise_error ArgumentError
|
|
36
|
+
expect{MessageProducer.new({ip: 1, port: 2, username: 'a', password: "123"})}.to raise_error ArgumentError
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it "not raise error with a valid config" do
|
|
40
|
+
expect{MessageProducer.new(config, logger)}.to_not raise_error
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
describe "#publish" do
|
|
45
|
+
context "Given a event named 'sku_created' with data => {code: 10, price: 20.87, product_id: 2}" do
|
|
46
|
+
let(:event_name){ :sku_created }
|
|
47
|
+
let(:event_data) do
|
|
48
|
+
{ code: 10, price: 20.87, product_id: 2, event_name: 'sku_created' }
|
|
49
|
+
end
|
|
50
|
+
let(:expected_message){ JSON.generate(event_data) }
|
|
51
|
+
let(:fake_broker){double('broker', start_connection: true )}
|
|
52
|
+
|
|
53
|
+
subject{ MessageProducer.new(config, logger)}
|
|
54
|
+
|
|
55
|
+
it "calls BrokerHandler#publish with message => {code: 10, price: 20.87, product_id: 2} and routing_key => module_name.sku_created" do
|
|
56
|
+
allow(BrokerHandler).to receive(:new).and_return fake_broker
|
|
57
|
+
expect(fake_broker).to receive(:publish).once
|
|
58
|
+
subject.publish(event_name, event_data)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
metadata
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: event-pub-sub
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.1.6
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Estante Virutal
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2016-03-10 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: bunny
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - '='
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: 2.3.0
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - '='
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: 2.3.0
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: activesupport
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - '='
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: 4.2.5.2
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - '='
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: 4.2.5.2
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: bundler
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '1.7'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '1.7'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: rake
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - "~>"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '10.0'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - "~>"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '10.0'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: rspec
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - '='
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: 3.2.0
|
|
76
|
+
type: :development
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - '='
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: 3.2.0
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: guard-rspec
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - '='
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: 4.5.0
|
|
90
|
+
type: :development
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - '='
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: 4.5.0
|
|
97
|
+
- !ruby/object:Gem::Dependency
|
|
98
|
+
name: byebug
|
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
|
100
|
+
requirements:
|
|
101
|
+
- - '='
|
|
102
|
+
- !ruby/object:Gem::Version
|
|
103
|
+
version: 8.2.1
|
|
104
|
+
type: :development
|
|
105
|
+
prerelease: false
|
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
107
|
+
requirements:
|
|
108
|
+
- - '='
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: 8.2.1
|
|
111
|
+
description: Pub/Sub de Eventos baseado em RabbitMQ.
|
|
112
|
+
email:
|
|
113
|
+
- equipe_ti@estantevirtual.com.br
|
|
114
|
+
executables: []
|
|
115
|
+
extensions: []
|
|
116
|
+
extra_rdoc_files: []
|
|
117
|
+
files:
|
|
118
|
+
- ".gitignore"
|
|
119
|
+
- ".rspec"
|
|
120
|
+
- Gemfile
|
|
121
|
+
- Gemfile.lock
|
|
122
|
+
- Guardfile
|
|
123
|
+
- LICENSE.txt
|
|
124
|
+
- README.md
|
|
125
|
+
- Rakefile
|
|
126
|
+
- event_pub_sub.gemspec
|
|
127
|
+
- lib/config/event.yml
|
|
128
|
+
- lib/config/initializers/setup_event.rb
|
|
129
|
+
- lib/config/listeners.yml
|
|
130
|
+
- lib/event_pub_sub.rb
|
|
131
|
+
- lib/event_pub_sub/broker_handler.rb
|
|
132
|
+
- lib/event_pub_sub/listener.rb
|
|
133
|
+
- lib/event_pub_sub/message_consumer.rb
|
|
134
|
+
- lib/event_pub_sub/message_producer.rb
|
|
135
|
+
- lib/event_pub_sub/railtie.rb
|
|
136
|
+
- lib/event_pub_sub/version.rb
|
|
137
|
+
- lib/tasks/event_tasks.rake
|
|
138
|
+
- spec/integration/broker_handler_spec.rb
|
|
139
|
+
- spec/spec_helper.rb
|
|
140
|
+
- spec/unit/broker_handler_spec.rb
|
|
141
|
+
- spec/unit/listener_spec.rb
|
|
142
|
+
- spec/unit/message_consumer_spec.rb
|
|
143
|
+
- spec/unit/message_producer_spec.rb
|
|
144
|
+
homepage: http://www.estantevirtual.com.br
|
|
145
|
+
licenses: []
|
|
146
|
+
metadata: {}
|
|
147
|
+
post_install_message:
|
|
148
|
+
rdoc_options: []
|
|
149
|
+
require_paths:
|
|
150
|
+
- lib
|
|
151
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
152
|
+
requirements:
|
|
153
|
+
- - ">="
|
|
154
|
+
- !ruby/object:Gem::Version
|
|
155
|
+
version: '0'
|
|
156
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
157
|
+
requirements:
|
|
158
|
+
- - ">="
|
|
159
|
+
- !ruby/object:Gem::Version
|
|
160
|
+
version: '0'
|
|
161
|
+
requirements: []
|
|
162
|
+
rubyforge_project:
|
|
163
|
+
rubygems_version: 2.4.6
|
|
164
|
+
signing_key:
|
|
165
|
+
specification_version: 4
|
|
166
|
+
summary: Pub/Sub de Eventos baseado em RabbitMQ.
|
|
167
|
+
test_files:
|
|
168
|
+
- spec/integration/broker_handler_spec.rb
|
|
169
|
+
- spec/spec_helper.rb
|
|
170
|
+
- spec/unit/broker_handler_spec.rb
|
|
171
|
+
- spec/unit/listener_spec.rb
|
|
172
|
+
- spec/unit/message_consumer_spec.rb
|
|
173
|
+
- spec/unit/message_producer_spec.rb
|