messaging 3.4.2 → 3.5.4

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,53 +1,3 @@
1
- # Messaging
1
+ A library for decoupling applications by using messaging to communicate between components.
2
2
 
3
- Messaging is a library used to send and receive messages via Kafka.
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...
@@ -1 +1,78 @@
1
- theme: jekyll-theme-minimal
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>
@@ -0,0 +1,4 @@
1
+ ---
2
+ permalink: /docs/adapters/
3
+ title: "Adapters"
4
+ ---
@@ -0,0 +1,4 @@
1
+ ---
2
+ permalink: /docs/consuming/
3
+ title: "Consuming"
4
+ ---
@@ -0,0 +1,4 @@
1
+ ---
2
+ permalink: /docs/installation/
3
+ title: "Installation"
4
+ ---
@@ -0,0 +1,13 @@
1
+ ---
2
+ permalink: /docs/messages/
3
+ title: "Messages"
4
+ ---
5
+
6
+ Messages are the core building block of messaging.
7
+ Define a message by including the message module like this:
8
+
9
+ ```ruby
10
+ class MyMessage
11
+ include Messaging::Message
12
+ end
13
+ ```
@@ -0,0 +1,4 @@
1
+ ---
2
+ permalink: /docs/publishing/
3
+ title: "Publishing"
4
+ ---
@@ -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}
@@ -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
@@ -30,6 +30,8 @@ module Messaging
30
30
  end
31
31
  end
32
32
 
33
+ Messaging::Adapters::Dispatcher.register(:null_adapter, memoize: true) { proc {} }
34
+
33
35
  require 'messaging/adapters/kafka'
34
36
  require 'messaging/adapters/postgres'
35
37
  require 'messaging/adapters/test'
@@ -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