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.
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