messaging 3.4.2 → 3.5.4
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/.gitignore +1 -0
- data/Gemfile +3 -2
- data/Gemfile.lock +278 -79
- data/README.md +2 -52
- data/_config.yml +78 -1
- data/_data/navigation.yml +17 -0
- data/_layouts/single.html +95 -0
- data/docs/adapters.md +4 -0
- data/docs/consuming.md +4 -0
- data/docs/installation.md +4 -0
- data/docs/messages.md +13 -0
- data/docs/publishing.md +4 -0
- data/docs/routing_and_handlers.md +64 -0
- data/lib/messaging.rb +23 -1
- data/lib/messaging/adapters.rb +2 -0
- data/lib/messaging/adapters/postgres/categories.rb +27 -0
- data/lib/messaging/adapters/postgres/category.rb +40 -0
- data/lib/messaging/adapters/postgres/serialized_message.rb +2 -1
- data/lib/messaging/adapters/postgres/store.rb +23 -0
- data/lib/messaging/adapters/postgres/stream.rb +4 -0
- data/lib/messaging/adapters/test/categories.rb +19 -0
- data/lib/messaging/adapters/test/category.rb +21 -0
- data/lib/messaging/adapters/test/store.rb +20 -0
- data/lib/messaging/adapters/test/stream.rb +4 -0
- data/lib/messaging/message.rb +12 -0
- data/lib/messaging/routes.rb +13 -0
- data/lib/messaging/routing.rb +28 -2
- data/lib/messaging/routing/enqueued_route.rb +1 -1
- data/lib/messaging/routing/route.rb +3 -3
- data/lib/messaging/version.rb +1 -1
- data/messaging.gemspec +1 -1
- metadata +15 -4
- data/lib/messaging/base_handler.rb +0 -45
data/README.md
CHANGED
@@ -1,53 +1,3 @@
|
|
1
|
-
|
1
|
+
A library for decoupling applications by using messaging to communicate between components.
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
1. [The three main concepts](#the-three-main-concepts)
|
7
|
-
1. [Messages](#messages)
|
8
|
-
2. [Topics](#topics)
|
9
|
-
3. [Handlers](#handlers)
|
10
|
-
2. [Usage](#usage)
|
11
|
-
1. [Creating messages](#creating-messages)
|
12
|
-
2. [Publishing messages](#publishing-messages)
|
13
|
-
3. [Receiving messages](#receiving-messages)
|
14
|
-
|
15
|
-
## The three main concepts
|
16
|
-
|
17
|
-
The usage of this gem is based on three main concepts: messages, topics and handlers.
|
18
|
-
|
19
|
-
### Messages
|
20
|
-
|
21
|
-
A message represents the actual messages we send and receive, example:
|
22
|
-
|
23
|
-
```ruby
|
24
|
-
Module Events
|
25
|
-
class LotViewed
|
26
|
-
include ::Messaging::Message
|
27
|
-
|
28
|
-
attribute :lot_id, Integer
|
29
|
-
attribute :item_id, Integer
|
30
|
-
attribute :customer_id, Integer
|
31
|
-
|
32
|
-
end
|
33
|
-
end
|
34
|
-
```
|
35
|
-
|
36
|
-
Messages are published to Kafka topics. By default the topic has the same name as the message class (but underscored and / are replaced with - as Kafka doesn't allow '/' in topic names).
|
37
|
-
|
38
|
-
In our example above, Events::LotViewed would be published to a topic named events-lot_viewed.
|
39
|
-
|
40
|
-
### Topics
|
41
|
-
|
42
|
-
### Handlers
|
43
|
-
|
44
|
-
## Usage
|
45
|
-
|
46
|
-
### Creating messages
|
47
|
-
### Publishing messages
|
48
|
-
### Receiving messages
|
49
|
-
|
50
|
-
## Making a new version
|
51
|
-
|
52
|
-
Increase the version in version.rb
|
53
|
-
run be rake release
|
3
|
+
More documentation and the code will be released in the near future...
|
data/_config.yml
CHANGED
@@ -1 +1,78 @@
|
|
1
|
-
|
1
|
+
# Welcome to Jekyll!
|
2
|
+
#
|
3
|
+
# This config file is meant for settings that affect your whole blog, values
|
4
|
+
# which you are expected to set up once and rarely edit after that. If you find
|
5
|
+
# yourself editing this file very often, consider using Jekyll's data files
|
6
|
+
# feature for the data you need to update frequently.
|
7
|
+
#
|
8
|
+
# For technical reasons, this file is *NOT* reloaded automatically when you use
|
9
|
+
# 'bundle exec jekyll serve'. If you change this file, please restart the server process.
|
10
|
+
|
11
|
+
# Site settings
|
12
|
+
# These are used to personalize your new site. If you look in the HTML files,
|
13
|
+
# you will see them accessed via {{ site.title }}, {{ site.email }}, and so on.
|
14
|
+
# You can create any custom variable you would like, and they will be accessible
|
15
|
+
# in the templates via {{ site.myvariable }}.
|
16
|
+
name: Bukowskis
|
17
|
+
title: Messaging
|
18
|
+
email:
|
19
|
+
description: >- # this means to ignore newlines until "baseurl:"
|
20
|
+
github_username: bukowskis
|
21
|
+
minimal_mistakes_skin: default
|
22
|
+
search: false
|
23
|
+
|
24
|
+
# Build settings
|
25
|
+
markdown: kramdown
|
26
|
+
remote_theme: mmistakes/minimal-mistakes
|
27
|
+
# Outputting
|
28
|
+
permalink: /:categories/:title/
|
29
|
+
timezone: CET # https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
|
30
|
+
|
31
|
+
include:
|
32
|
+
- docs
|
33
|
+
|
34
|
+
# Exclude from processing.
|
35
|
+
# The following items will not be processed, by default. Create a custom list
|
36
|
+
# to override the default setting.
|
37
|
+
# exclude:
|
38
|
+
# - Gemfile
|
39
|
+
# - Gemfile.lock
|
40
|
+
# - node_modules
|
41
|
+
# - vendor/bundle/
|
42
|
+
# - vendor/cache/
|
43
|
+
# - vendor/gems/
|
44
|
+
# - vendor/ruby/
|
45
|
+
|
46
|
+
# Plugins (previously gems:)
|
47
|
+
plugins:
|
48
|
+
- jekyll-sitemap
|
49
|
+
- jekyll-gist
|
50
|
+
- jemoji
|
51
|
+
- jekyll-include-cache
|
52
|
+
|
53
|
+
author:
|
54
|
+
name : "Bukowskis"
|
55
|
+
avatar : "https://d5u8cl9t8qko.cloudfront.net/assets/bukowskis-e2de8e05a8661722556f9bd8f265777a45a0fef027b3ef6a77da5633df93447c.svg"
|
56
|
+
bio : ""
|
57
|
+
links:
|
58
|
+
- label: "Website"
|
59
|
+
icon: "fas fa-fw fa-link"
|
60
|
+
url: "https://www.bukowskis.com"
|
61
|
+
- label: "GitHub"
|
62
|
+
icon: "fab fa-fw fa-github"
|
63
|
+
url: "https://github.com/bukowskis"
|
64
|
+
|
65
|
+
footer:
|
66
|
+
links:
|
67
|
+
- label: "GitHub"
|
68
|
+
icon: "fab fa-fw fa-github"
|
69
|
+
url: "https://github.com/bukowskis/messaging"
|
70
|
+
|
71
|
+
|
72
|
+
defaults:
|
73
|
+
- scope:
|
74
|
+
path: ""
|
75
|
+
values:
|
76
|
+
layout: single
|
77
|
+
sidebar:
|
78
|
+
nav: "docs"
|
@@ -0,0 +1,17 @@
|
|
1
|
+
docs:
|
2
|
+
- title: Getting Started
|
3
|
+
children:
|
4
|
+
- title: "Installlation"
|
5
|
+
url: /docs/installation/
|
6
|
+
- title: "Adapters"
|
7
|
+
url: /docs/adapters/
|
8
|
+
- title: Messaging
|
9
|
+
children:
|
10
|
+
- title: "Messages"
|
11
|
+
url: /docs/messages/
|
12
|
+
- title: "Routing and handlers"
|
13
|
+
url: /docs/routing_and_handlers/
|
14
|
+
- title: "Publishing messages"
|
15
|
+
url: /docs/publishing/
|
16
|
+
- title: "Consuming messages"
|
17
|
+
url: /docs/consuming/
|
@@ -0,0 +1,95 @@
|
|
1
|
+
---
|
2
|
+
layout: default
|
3
|
+
---
|
4
|
+
|
5
|
+
{% if page.header.overlay_color or page.header.overlay_image or page.header.image %}
|
6
|
+
{% include page__hero.html %}
|
7
|
+
{% elsif page.header.video.id and page.header.video.provider %}
|
8
|
+
{% include page__hero_video.html %}
|
9
|
+
{% endif %}
|
10
|
+
|
11
|
+
{% if page.url != "/" and site.breadcrumbs %}
|
12
|
+
{% unless paginator %}
|
13
|
+
{% include breadcrumbs.html %}
|
14
|
+
{% endunless %}
|
15
|
+
{% endif %}
|
16
|
+
|
17
|
+
<div id="main" role="main">
|
18
|
+
{% include sidebar.html %}
|
19
|
+
|
20
|
+
<article class="page" itemscope itemtype="https://schema.org/CreativeWork">
|
21
|
+
{% if page.title %}<meta itemprop="headline" content="{{ page.title | markdownify | strip_html | strip_newlines | escape_once }}">{% endif %}
|
22
|
+
{% if page.excerpt %}<meta itemprop="description" content="{{ page.excerpt | markdownify | strip_html | strip_newlines | escape_once }}">{% endif %}
|
23
|
+
{% if page.date %}<meta itemprop="datePublished" content="{{ page.date | date: "%B %d, %Y" }}">{% endif %}
|
24
|
+
{% if page.last_modified_at %}<meta itemprop="dateModified" content="{{ page.last_modified_at | date: "%B %d, %Y" }}">{% endif %}
|
25
|
+
|
26
|
+
<div class="page__inner-wrap">
|
27
|
+
{% unless page.header.overlay_color or page.header.overlay_image %}
|
28
|
+
<header>
|
29
|
+
{% if page.title %}<h1 id="page-title" class="page__title" itemprop="headline">{{ page.title | markdownify | remove: "<p>" | remove: "</p>" }}</h1>{% endif %}
|
30
|
+
{% if page.read_time %}
|
31
|
+
<p class="page__meta"><i class="far fa-clock" aria-hidden="true"></i> {% include read-time.html %}</p>
|
32
|
+
{% endif %}
|
33
|
+
</header>
|
34
|
+
{% endunless %}
|
35
|
+
|
36
|
+
<section class="page__content" itemprop="text" style="font-size: 0.9em">
|
37
|
+
{% if page.toc %}
|
38
|
+
<aside class="sidebar__right {% if page.toc_sticky %}sticky{% endif %}">
|
39
|
+
<nav class="toc">
|
40
|
+
<header><h4 class="nav__title"><i class="fas fa-{{ page.toc_icon | default: 'file-alt' }}"></i> {{ page.toc_label | default: site.data.ui-text[site.locale].toc_label | default: "On this page" }}</h4></header>
|
41
|
+
{% include toc.html sanitize=true html=content h_min=1 h_max=6 class="toc__menu" %}
|
42
|
+
</nav>
|
43
|
+
</aside>
|
44
|
+
{% endif %}
|
45
|
+
{{ content }}
|
46
|
+
{% if page.link %}<div><a href="{{ page.link }}" class="btn btn--primary">{{ site.data.ui-text[site.locale].ext_link_label | default: "Direct Link" }}</a></div>{% endif %}
|
47
|
+
</section>
|
48
|
+
|
49
|
+
<footer class="page__meta">
|
50
|
+
{% if site.data.ui-text[site.locale].meta_label %}
|
51
|
+
<h4 class="page__meta-title">{{ site.data.ui-text[site.locale].meta_label }}</h4>
|
52
|
+
{% endif %}
|
53
|
+
{% include page__taxonomy.html %}
|
54
|
+
{% if page.last_modified_at %}
|
55
|
+
<p class="page__date"><strong><i class="fas fa-fw fa-calendar-alt" aria-hidden="true"></i> {{ site.data.ui-text[site.locale].date_label | default: "Updated:" }}</strong> <time datetime="{{ page.last_modified_at | date: "%Y-%m-%d" }}">{{ page.last_modified_at | date: "%B %d, %Y" }}</time></p>
|
56
|
+
{% elsif page.date %}
|
57
|
+
<p class="page__date"><strong><i class="fas fa-fw fa-calendar-alt" aria-hidden="true"></i> {{ site.data.ui-text[site.locale].date_label | default: "Updated:" }}</strong> <time datetime="{{ page.date | date_to_xmlschema }}">{{ page.date | date: "%B %d, %Y" }}</time></p>
|
58
|
+
{% endif %}
|
59
|
+
</footer>
|
60
|
+
|
61
|
+
{% if page.share %}{% include social-share.html %}{% endif %}
|
62
|
+
|
63
|
+
{% include post_pagination.html %}
|
64
|
+
</div>
|
65
|
+
|
66
|
+
{% if jekyll.environment == 'production' and site.comments.provider and page.comments %}
|
67
|
+
{% include comments.html %}
|
68
|
+
{% endif %}
|
69
|
+
</article>
|
70
|
+
|
71
|
+
{% comment %}<!-- only show related on a post page when `related: true` -->{% endcomment %}
|
72
|
+
{% if page.id and page.related and site.related_posts.size > 0 %}
|
73
|
+
<div class="page__related">
|
74
|
+
<h4 class="page__related-title">{{ site.data.ui-text[site.locale].related_label | default: "You May Also Enjoy" }}</h4>
|
75
|
+
<div class="grid__wrapper">
|
76
|
+
{% for post in site.related_posts limit:4 %}
|
77
|
+
{% include archive-single.html type="grid" %}
|
78
|
+
{% endfor %}
|
79
|
+
</div>
|
80
|
+
</div>
|
81
|
+
{% comment %}<!-- otherwise show recent posts if no related when `related: true` -->{% endcomment %}
|
82
|
+
{% elsif page.id and page.related %}
|
83
|
+
<div class="page__related">
|
84
|
+
<h4 class="page__related-title">{{ site.data.ui-text[site.locale].related_label | default: "You May Also Enjoy" }}</h4>
|
85
|
+
<div class="grid__wrapper">
|
86
|
+
{% for post in site.posts limit:4 %}
|
87
|
+
{% if post.id == page.id %}
|
88
|
+
{% continue %}
|
89
|
+
{% endif %}
|
90
|
+
{% include archive-single.html type="grid" %}
|
91
|
+
{% endfor %}
|
92
|
+
</div>
|
93
|
+
</div>
|
94
|
+
{% endif %}
|
95
|
+
</div>
|
data/docs/adapters.md
ADDED
data/docs/consuming.md
ADDED
data/docs/messages.md
ADDED
data/docs/publishing.md
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
---
|
2
|
+
permalink: /docs/routing_and_handlers/
|
3
|
+
title: "Routing and handlers"
|
4
|
+
toc: true
|
5
|
+
---
|
6
|
+
## Handlers
|
7
|
+
|
8
|
+
A handler is a piece of code that does something with a message.
|
9
|
+
There is no module or superclass that you need to use to implement a handler.
|
10
|
+
All that is needed is something that responds to .call with one argument, the message.
|
11
|
+
|
12
|
+
The following class can serve as a typical handler:
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
class NotifyCompetingBidders
|
16
|
+
def self.call(bid_placed)
|
17
|
+
other_bidders.each { |bidder| SendOutbidNotice.call(recipient: bidder) }
|
18
|
+
end
|
19
|
+
end
|
20
|
+
```
|
21
|
+
|
22
|
+
But a handler can also be a plain block in a route:
|
23
|
+
```ruby
|
24
|
+
class HandleBiddingEvents
|
25
|
+
include Messaging::Routing
|
26
|
+
|
27
|
+
def self.call(message)
|
28
|
+
new.handle(message)
|
29
|
+
end
|
30
|
+
|
31
|
+
# A block used as a handler
|
32
|
+
on BidPlaced do |bid_placed|
|
33
|
+
log_bid_event bid_placed
|
34
|
+
end
|
35
|
+
end
|
36
|
+
```
|
37
|
+
|
38
|
+
## Routing
|
39
|
+
|
40
|
+
There are multiple ways to route messages to handlers.
|
41
|
+
|
42
|
+
|
43
|
+
### Synchronous handlers
|
44
|
+
Use the call: keyword in a route to make the handler synchronous.
|
45
|
+
```ruby
|
46
|
+
Messaging.routes.draw do
|
47
|
+
on Events::BidPlaced, call: NotifyCompetingBidders
|
48
|
+
end
|
49
|
+
```
|
50
|
+
**Be aware!** The handler would trigger in the same thread as the code that publishes the event.
|
51
|
+
This may or may not be a problem. But think twice before using synchronous handlers.
|
52
|
+
{: .notice}
|
53
|
+
|
54
|
+
### Enqueued handlers
|
55
|
+
Use the enqueue: keyword to make a handler be called by a background worker like Sidekiq.
|
56
|
+
```ruby
|
57
|
+
Messaging.routes.draw do
|
58
|
+
on Events::BidPlaced, enqueue: NotifyCompetingBidders
|
59
|
+
end
|
60
|
+
```
|
61
|
+
**Be aware!** Sidekiq / Resque / ActiveJob does not guarantee that the jobs will be
|
62
|
+
processed in any specific order. If the order in which handlers gets called is important
|
63
|
+
you should probably use a Consumer instead
|
64
|
+
{: .notice}
|
data/lib/messaging.rb
CHANGED
@@ -14,7 +14,6 @@ require 'messaging/message'
|
|
14
14
|
require 'messaging/message/from_json'
|
15
15
|
require 'messaging/routing'
|
16
16
|
require 'messaging/routes'
|
17
|
-
require 'messaging/base_handler'
|
18
17
|
require 'messaging/adapters'
|
19
18
|
require 'messaging/middleware'
|
20
19
|
require 'messaging/publish'
|
@@ -53,11 +52,34 @@ module Messaging
|
|
53
52
|
@routes ||= Routes.new
|
54
53
|
end
|
55
54
|
|
55
|
+
def self.inline!(&block)
|
56
|
+
routes.inline!(&block)
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.without_dispatch(&block)
|
60
|
+
current_dispatcher = Config.dispatcher.adapter
|
61
|
+
Config.dispatcher.adapter = :null_adapter
|
62
|
+
|
63
|
+
result = block.call
|
64
|
+
|
65
|
+
ensure
|
66
|
+
Config.dispatcher.adapter = current_dispatcher
|
67
|
+
result
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.category(name)
|
71
|
+
message_store.category(name)
|
72
|
+
end
|
73
|
+
|
56
74
|
def self.stream(name)
|
57
75
|
name = name.stream_name if name.respond_to?(:stream_name)
|
58
76
|
message_store.stream(name)
|
59
77
|
end
|
60
78
|
|
79
|
+
def self.messages_in_streams(*streams)
|
80
|
+
message_store.messages_in_streams(*streams)
|
81
|
+
end
|
82
|
+
|
61
83
|
def self.defined_messages
|
62
84
|
ObjectSpace.each_object(Class).select { |c| c.included_modules.include? Messaging::Message }
|
63
85
|
end
|
data/lib/messaging/adapters.rb
CHANGED
@@ -0,0 +1,27 @@
|
|
1
|
+
module Messaging
|
2
|
+
module Adapters
|
3
|
+
class Postgres
|
4
|
+
class Categories
|
5
|
+
include Enumerable
|
6
|
+
|
7
|
+
def each
|
8
|
+
return enum_for(:each) unless block_given?
|
9
|
+
|
10
|
+
all_categories.each do |name|
|
11
|
+
yield Category.new(name)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def [](name)
|
16
|
+
Category.new(name)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def all_categories
|
22
|
+
SerializedMessage.distinct.pluck(:stream_category).lazy
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Messaging
|
2
|
+
module Adapters
|
3
|
+
class Postgres
|
4
|
+
class Category
|
5
|
+
# @return [String] the name of the category
|
6
|
+
attr_reader :name
|
7
|
+
|
8
|
+
# Should not be used directly.
|
9
|
+
# Use {Messaging.category} or {Store#category}
|
10
|
+
# @api private
|
11
|
+
# @see Messaging.category
|
12
|
+
# @see Store.category
|
13
|
+
def initialize(name)
|
14
|
+
@name = name
|
15
|
+
end
|
16
|
+
|
17
|
+
# Access to all messages in the category sorted by created_at
|
18
|
+
# @return [ActiveRecord::Relation]
|
19
|
+
def messages
|
20
|
+
SerializedMessage.where(stream_category: name).order(:created_at)
|
21
|
+
end
|
22
|
+
|
23
|
+
def messages_older_than(time)
|
24
|
+
messages.where('created_at < ?', time)
|
25
|
+
end
|
26
|
+
|
27
|
+
def delete_messages_older_than!(time)
|
28
|
+
SerializedMessage.transaction do
|
29
|
+
ActiveRecord::Base.connection.execute "SET LOCAL statement_timeout = '0'"
|
30
|
+
messages_older_than(time).delete_all
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def inspect
|
35
|
+
"#<Category:#{name}>>"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|