service_mesh_nats 0.5.1 → 0.7.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 20a1213855e080bc361b292f2708b4f023ba38a5051c3ebddffc39cf55b91f7c
4
- data.tar.gz: fd6939fad816e2beb146fbd8c3697b1221dabb3938a08bdbd73c23664488d71f
3
+ metadata.gz: 8766fe267e81c95787f56181bf3124d3a4a12de3f88da6acf7eb6297f181b6fb
4
+ data.tar.gz: 558e29c69edc0287eca76e4ba26963551d6f9670937b5fc98d0fca00dfdf06ee
5
5
  SHA512:
6
- metadata.gz: 411c35df69a5c8bf43b9ce66e088d666e129c34e64e93a6be3408a471775813f427cb32813a1e47a49bdbaa0e69f56e449ee8e73ee720c397646a70b187c6c0e
7
- data.tar.gz: 9fbde8fda5e37e58e4d08b0ffe9fe341098bdf5f84d241ddba5bace38003fcf974517bd2cd009deeb5f28cf45f828e3f1f79aa48f2593e1eec4c8f2355dd5f55
6
+ metadata.gz: f10332c9b690bf5d33606d588ad142a31e484e6ef70e8dc63d66dee5183e1fbf07dcfe8f0018ed75a230202f8cf8a6720255c7155404e395aca44422eca5e8eb
7
+ data.tar.gz: 7fd4f7fad61c811ccdd388e966cd9da91f504945691926d957dab6b77fcdc900a5ca7f4fc40549638039a2dda6c88fc2d829c97f8775b887349540fd34afe914
data/README.md CHANGED
@@ -1,9 +1,20 @@
1
1
  # service-mesh-nats-ruby
2
2
 
3
- `service_mesh_nats` is the NATS transport implementation in Ruby for the
4
- [Service Mesh API Specification](https://github.com/Paymentbox-com/service-mesh-api).
5
-
6
- This gem depends on the contract types and errors in [service-mesh-ruby](https://github.com/Paymentbox-com/service-mesh-ruby).
3
+ service-mesh-nats-ruby is the Ruby implementation of the
4
+ [Service Mesh API Specification](https://github.com/Paymentbox-com/service-mesh-api)
5
+ over NATS. It implements the Ruby contract in the `service_mesh` gem, from
6
+ [service-mesh-ruby](https://github.com/Paymentbox-com/service-mesh-ruby), on
7
+ top of the `nats-pure` client.
8
+
9
+ The `ServiceMeshNats` module provides:
10
+ * `Client`, which implements the contract's `Client` over one NATS connection
11
+ * `Runtime`, which implements the contract's `Runtime` on a `Client`'s connection
12
+ * the configuration keys it reads
13
+ * the errors it defines
14
+
15
+ The contract types it works with, such as `ServiceMesh::Target`,
16
+ `ServiceMesh::Message`, `ServiceMesh::Endpoint`, and `ServiceMesh::Subscriber`,
17
+ come from the `service_mesh` gem.
7
18
 
8
19
  ## Install
9
20
 
@@ -12,7 +23,7 @@ This gem depends on the contract types and errors in [service-mesh-ruby](https:/
12
23
  gem "service_mesh_nats"
13
24
  ```
14
25
 
15
- Requires Ruby 3.3 or newer and a reachable NATS server. Depends on
26
+ Requires Ruby 3.3 or newer and a reachable NATS server. The gem depends on
16
27
  `service_mesh` and `nats-pure`.
17
28
 
18
29
  ## Usage
@@ -49,15 +60,14 @@ reply = client.request(ServiceMesh::Message.new(target: echo, payload: "hi"))
49
60
  client.publish(ServiceMesh::Message.new(target: created, payload: "order 42"))
50
61
  ```
51
62
 
52
- The client is the runtime's connection. `runtime.client` returns it, and
53
- `runtime.stop` closes it. A process that only requests and
54
- publishes builds a client itself and calls `close` when done.
55
- `runtime.service_map` and `client.service_map` return the map the client was
56
- built with.
63
+ The client is the runtime's connection. `runtime.client` returns it in every
64
+ state, and `runtime.stop` closes it. A process that only requests and publishes
65
+ builds a client itself and calls `close` when done. `runtime.service_map` and
66
+ `client.service_map` return the map the client was built with.
57
67
 
58
68
  ## Documentation
59
69
 
60
70
  - [Examples](docs/examples.md): a server process, a call-only client process, and consumer groups
61
- - [Public API](docs/public-api.md): the constants this gem exposes and how `Client` and `Runtime` behave
62
- - [What the NATS Runtime Decides](docs/runtime-behavior.md): targets, configuration, metadata, delivery, handler failure, timeouts, concurrency, lifecycle, and transport errors
71
+ - [Public API](docs/public-api.md): every public constant and method in `ServiceMeshNats`
72
+ - [Transport Specific Implementation](docs/transport-specific-implementation.md): subjects, configuration, metadata, delivery, handler failure, timeouts, concurrency, lifecycle, and errors
63
73
  - [Development](docs/development.md): the recipes and the tests
@@ -9,7 +9,7 @@ module ServiceMeshNats
9
9
  # connection and runs their handlers on a bounded thread pool.
10
10
  class Runtime
11
11
  # A validated Endpoint or Subscriber. queue nil means a plain subscription.
12
- Binding = Data.define(:subject, :queue, :target, :handler, :replies)
12
+ Subscription = Data.define(:subject, :queue, :target, :handler, :replies)
13
13
 
14
14
  FLUSH_BUDGET = 5.0
15
15
 
@@ -19,13 +19,13 @@ module ServiceMeshNats
19
19
  # Reads ServiceMesh::DEPLOYMENT_GROUP_KEY and CONCURRENCY_KEY from +config+;
20
20
  # connection keys are ignored, the client carries them. Raises
21
21
  # ServiceMesh::NoDeploymentGroup, BadConfig, ServiceMesh::KindMismatch, or
22
- # ServiceMesh::InvalidTarget. Two bindings on one subject become two
23
- # subscriptions. The connection is not touched until start.
22
+ # ServiceMesh::InvalidTarget. Two endpoints or subscribers on one subject
23
+ # become two subscriptions. The connection is not touched until start.
24
24
  def initialize(client, config, endpoints: [], subscribers: [], logger: Logger.new($stderr))
25
25
  @settings = RuntimeSettings.parse(config)
26
26
  @client = client
27
27
  @logger = logger
28
- @bindings = bind_all(endpoints, subscribers)
28
+ @subscriptions = prepare_all(endpoints, subscribers)
29
29
 
30
30
  @lock = Mutex.new
31
31
  @state = :created
@@ -42,11 +42,11 @@ module ServiceMeshNats
42
42
  @client.service_map
43
43
  end
44
44
 
45
- # Subscribes every binding on the client's connection and begins
45
+ # Subscribes every endpoint and subscriber on the client's connection and begins
46
46
  # receiving. Raises AlreadyStarted on a running runtime, Stopped after
47
47
  # stop, and Closed when the client has been closed. A failure while
48
- # subscribing closes the client, marks the runtime stopped, and passes
49
- # through.
48
+ # subscribing or flushing unsubscribes what was subscribed, leaves the
49
+ # runtime and the client as they were, and passes through.
50
50
  def start
51
51
  @lock.synchronize do
52
52
  raise AlreadyStarted if @state == :running
@@ -57,7 +57,7 @@ module ServiceMeshNats
57
57
  pool = Concurrent::FixedThreadPool.new(@settings.concurrency, name: "service_mesh_nats")
58
58
  subs = []
59
59
  begin
60
- @bindings.each do |b|
60
+ @subscriptions.each do |b|
61
61
  opts = b.queue ? {queue: b.queue} : {}
62
62
  subs << nc.subscribe(b.subject, opts) { |msg| dispatch(pool, nc, b, msg) }
63
63
  end
@@ -66,8 +66,6 @@ module ServiceMeshNats
66
66
  rescue => e
67
67
  subs.each { |s| s.unsubscribe rescue nil } # rubocop:disable Style/RescueModifier
68
68
  pool.kill
69
- @client.close
70
- @state = :stopped
71
69
  raise e
72
70
  end
73
71
 
@@ -113,41 +111,41 @@ module ServiceMeshNats
113
111
  @logger.warn("service_mesh_nats: flush during stop failed: #{e.message}")
114
112
  end
115
113
 
116
- def bind_all(endpoints, subscribers)
117
- bind = lambda do |target, want, use, metadata, handler, replies|
114
+ def prepare_all(endpoints, subscribers)
115
+ prepare = lambda do |target, want, use, metadata, handler, replies|
118
116
  Subject.check_kind!(target, want, use)
119
117
  subject = Subject.format(target)
120
118
  raise ArgumentError, "#{use} #{subject} handler must respond to call" unless handler.respond_to?(:call)
121
119
 
122
- queue = Subject.consumer_group(metadata, target.metadata, @settings.deployment_group)
123
- Binding.new(subject: subject, queue: queue, target: target, handler: handler, replies: replies)
120
+ queue = Subject.consumer_group(metadata, @settings.deployment_group)
121
+ Subscription.new(subject: subject, queue: queue, target: target, handler: handler, replies: replies)
124
122
  end
125
123
 
126
- endpoints.map { |e| bind.call(e.target, :route, "Endpoint", e.metadata, e.handler, true) } +
127
- subscribers.map { |s| bind.call(s.target, :topic, "Subscriber", s.metadata, s.handler, false) }
124
+ endpoints.map { |e| prepare.call(e.target, :route, "Endpoint", e.metadata, e.handler, true) } +
125
+ subscribers.map { |s| prepare.call(s.target, :topic, "Subscriber", s.metadata, s.handler, false) }
128
126
  end
129
127
 
130
128
  # Runs on nats-pure's subscription thread; hands the message to the pool.
131
- def dispatch(pool, nc, binding, msg)
132
- pool.post { serve(nc, binding, msg) }
129
+ def dispatch(pool, nc, subscription, msg)
130
+ pool.post { serve(nc, subscription, msg) }
133
131
  rescue Concurrent::RejectedExecutionError
134
132
  # The pool is shutting down; the subscription is already gone.
135
133
  end
136
134
 
137
- def serve(nc, binding, msg)
138
- inbound = ServiceMesh::Message.new(target: binding.target, metadata: msg.header || {}, payload: msg.data.to_s)
135
+ def serve(nc, subscription, msg)
136
+ inbound = ServiceMesh::Message.new(target: subscription.target, metadata: msg.header || {}, payload: msg.data.to_s)
139
137
 
140
- out = binding.handler.call(inbound)
141
- return unless binding.replies && msg.reply
138
+ out = subscription.handler.call(inbound)
139
+ return unless subscription.replies && msg.reply
142
140
 
143
141
  unless out.is_a?(ServiceMesh::Message)
144
- raise TypeError, "endpoint #{binding.subject} handler returned #{out.class}, expected ServiceMesh::Message"
142
+ raise TypeError, "endpoint #{subscription.subject} handler returned #{out.class}, expected ServiceMesh::Message"
145
143
  end
146
144
 
147
145
  reply(nc, msg.reply, out.metadata, out.payload)
148
146
  rescue => e
149
- @logger.error("service_mesh_nats: handler failed on #{binding.subject}: #{e.class}: #{e.message}")
150
- reply(nc, msg.reply, {HANDLER_ERROR_HEADER => e.message}, "") if binding.replies && msg.reply
147
+ @logger.error("service_mesh_nats: handler failed on #{subscription.subject}: #{e.class}: #{e.message}")
148
+ reply(nc, msg.reply, {HANDLER_ERROR_HEADER => e.message}, "") if subscription.replies && msg.reply
151
149
  end
152
150
 
153
151
  def reply(nc, subject, metadata, payload)
@@ -33,19 +33,15 @@ module ServiceMeshNats
33
33
  raise ServiceMesh::KindMismatch, "#{use} requires a #{want} target, got #{target.kind}"
34
34
  end
35
35
 
36
- # The queue group for a binding. Binding metadata wins, then target
37
- # metadata, then the deployment group. nil means a plain subscription.
38
- def consumer_group(binding_metadata, target_metadata, deployment_group)
39
- [binding_metadata, target_metadata].each do |md|
40
- next unless md.key?(ServiceMesh::CONSUMER_GROUP_KEY)
41
-
42
- value = md[ServiceMesh::CONSUMER_GROUP_KEY].to_s
43
- next if value.empty?
44
- return nil if value == ServiceMesh::CONSUMER_GROUP_NONE
45
-
46
- return value
47
- end
48
- deployment_group
36
+ # The queue group of an Endpoint or Subscriber, from its metadata, falling
37
+ # back to the runtime's deployment group. nil means a plain subscription.
38
+ # A Target carries no consumer group, so its metadata is not read.
39
+ def consumer_group(metadata, deployment_group)
40
+ value = metadata[ServiceMesh::CONSUMER_GROUP_KEY].to_s
41
+ return deployment_group if value.empty?
42
+ return nil if value == ServiceMesh::CONSUMER_GROUP_NONE
43
+
44
+ value
49
45
  end
50
46
  end
51
47
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ServiceMeshNats
4
- VERSION = "0.5.1"
4
+ VERSION = "0.7.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: service_mesh_nats
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bryant Morrill
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2026-09-29 00:00:00.000000000 Z
12
+ date: 2026-09-30 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: nats-pure
@@ -31,14 +31,14 @@ dependencies:
31
31
  requirements:
32
32
  - - "~>"
33
33
  - !ruby/object:Gem::Version
34
- version: '0.4'
34
+ version: '0.5'
35
35
  type: :runtime
36
36
  prerelease: false
37
37
  version_requirements: !ruby/object:Gem::Requirement
38
38
  requirements:
39
39
  - - "~>"
40
40
  - !ruby/object:Gem::Version
41
- version: '0.4'
41
+ version: '0.5'
42
42
  description:
43
43
  email:
44
44
  - bryantreadmorrill@gmail.com