service-mesh-ruby
This version implements the Service Mesh API Specification v0.4.0.
service-mesh-ruby is the Ruby contract for the
Service Mesh API Specification.
It is packaged as the gem service_mesh. It holds what every transport and every
caller must agree on, and nothing that moves bytes. Transports are separate
gems that depend on it and implement Client and Runtime.
Install
# Gemfile
gem "service_mesh"Requires Ruby 3.3 or newer. The gem has no runtime dependencies.
What it Implements
- The value types from the specification:
ServiceMesh::Target,ServiceMesh::ServiceMap,ServiceMesh::Message,ServiceMesh::Endpoint, andServiceMesh::Subscriber. - Target Kinds are implemented as
:routeand:topic. ATargetbuilt with a kind outside the two raisesKindMismatch. -
Message#payloadis alwaysEncoding::BINARY. -
Target#same_channel?compares segments and kind and ignores metadata. - The configuration key the specification defines,
DEPLOYMENT_GROUP_KEY, which is runtime configuration. - The consumer group is the
consumer_groupmember ofEndpointandSubscriber, defaulting tonil.CONSUMER_GROUP_NONEis the value for no group. - The reserved metadata prefix
RESERVED_PREFIX(Mesh-). - The metadata keys the specification defines:
HANDLER_ERROR_KEY,TIMEOUT_KEY,DEADLINE_KEY,DELIVERY_ATTEMPT_KEY, andMESSAGE_ID_KEY. - The errors defined by the specification as
ServiceMesh::Error:ServiceMesh::KindMismatch,ServiceMesh::InvalidTarget, andServiceMesh::NoDeploymentGroup. - A conformance suite a transport runs against its own
ClientandRuntime.
Client and Runtime are duck types. The specification names their methods,
and a transport satisfies the contract by responding to them and by passing the
conformance suite.
Usage
A transport that implements Client and Runtime according to the specification uses the
types defined here.
require "service_mesh"
TARGET = ServiceMesh::Target.new(segments: %w[accounts lookup], kind: :route)
def lookup(client, id)
reply = client.request(ServiceMesh::Message.new(target: TARGET, payload: id))
reply.payload
endConformance
A transport implemented to use these types should include the shared examples defined in this library in its own spec suite and supply its own constructors and a pair of valid targets:
require "service_mesh/rspec"
RSpec.describe MyTransport do
it_behaves_like "a service mesh transport" do
let(:new_runtime) do
->(client, config, endpoints:, subscribers:) { MyTransport::Runtime.new(client, config, endpoints:, subscribers:) }
end
let(:new_client) { ->(config, map) { MyTransport::Client.new(config, map) } }
let(:runtime_config) { {"deployment_group" => "test", "url" => url} }
let(:client_config) { {"url" => url} }
let(:route_target) { ServiceMesh::Target.new(segments: %w[test echo], kind: :route) }
let(:topic_target) { ServiceMesh::Target.new(segments: %w[test event], kind: :topic) }
end
endA transport that needs its own settings for the suite to pass supplies any of five optional lets, each defaulting to an empty Hash:
let(:target_metadata) { {...} } # merged under the metadata of every Target the suite builds
let(:endpoint_metadata) { {...} } # merged under the metadata of every Endpoint the suite builds
let(:subscriber_metadata) { {...} } # merged under the metadata of every Subscriber the suite builds
let(:request_options) { {...} } # the options of every request the suite makes
let(:publish_options) { {...} } # the options of every publish the suite makesMetadata an example sets itself wins over target_metadata, endpoint_metadata, and
subscriber_metadata.
The suite covers:
- kind checks
- the deployment group requirement
- request and reply with metadata both ways
- publish
- the consumer-group delivery permutations across two runtimes
- a standalone client refusing requests after close
- the runtime's client being the one it was given and closed by stop
- the lifecycle state machine
- drain completing and expiring
Anything that names a transport's own errors, config keys, or address syntax stays in the transport's specs.
Development
mise install
just install
just check # lint, test, build
just test # tests only
Releasing is described in publishing.md. just bump patch,
just bump minor, or just bump major raises the version in
lib/service_mesh/version.rb and commits that file.