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 +4 -4
- data/README.md +22 -12
- data/lib/service_mesh_nats/runtime.rb +23 -25
- data/lib/service_mesh_nats/subject.rb +9 -13
- data/lib/service_mesh_nats/version.rb +1 -1
- metadata +4 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8766fe267e81c95787f56181bf3124d3a4a12de3f88da6acf7eb6297f181b6fb
|
|
4
|
+
data.tar.gz: 558e29c69edc0287eca76e4ba26963551d6f9670937b5fc98d0fca00dfdf06ee
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
4
|
-
[Service Mesh API Specification](https://github.com/Paymentbox-com/service-mesh-api)
|
|
5
|
-
|
|
6
|
-
|
|
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.
|
|
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
|
|
53
|
-
`runtime.stop` closes it. A process that only requests and
|
|
54
|
-
|
|
55
|
-
`
|
|
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):
|
|
62
|
-
- [
|
|
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
|
-
|
|
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
|
|
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
|
-
@
|
|
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
|
|
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
|
|
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
|
-
@
|
|
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
|
|
117
|
-
|
|
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,
|
|
123
|
-
|
|
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|
|
|
127
|
-
subscribers.map { |s|
|
|
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,
|
|
132
|
-
pool.post { serve(nc,
|
|
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,
|
|
138
|
-
inbound = ServiceMesh::Message.new(target:
|
|
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 =
|
|
141
|
-
return unless
|
|
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 #{
|
|
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 #{
|
|
150
|
-
reply(nc, msg.reply, {HANDLER_ERROR_HEADER => e.message}, "") if
|
|
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
|
|
37
|
-
#
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
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.
|
|
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-
|
|
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.
|
|
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.
|
|
41
|
+
version: '0.5'
|
|
42
42
|
description:
|
|
43
43
|
email:
|
|
44
44
|
- bryantreadmorrill@gmail.com
|