fluence-gateway-client

Ruby client for service-to-service communication through the Fluence API Gateway. Handles OAuth2 client_credentials authentication transparently, builds backend URLs from a service name, and forwards end-user tokens when needed.

Piece Role
Fluence::Gateway::Client Thread-safe singleton exposing get / post / put / patch / delete. Token refresh, path building, and end-user token forwarding live here.
Fluence::Gateway.configure Configuration DSL (credentials, gateway and appcenter URLs, timeout).
Fluence::Gateway::Client.with_service(:name) { } Thread-local scope that prefixes every path in the block with /backend/<service>/.
Fluence::Gateway::Client.with_user_token(token) { } Thread-local scope that forwards an end-user Bearer token instead of the service token.
Fluence::Gateway::Error and subclasses AuthenticationError (OAuth2 failure) and ConnectionError (gateway unreachable).

Requires Ruby ≥ 3.2.


Installation

# Gemfile
source 'https://rubygems.pkg.github.com/fluence-eu' do
  gem 'fluence-gateway-client'
end
bundle install

Configuration

Configure once at boot (Rails: config/initializers/fluence_gateway.rb).

Fluence::Gateway.configure do |c|
  c.client_id     = ENV.fetch('GATEWAY_CLIENT_ID')
  c.client_secret = ENV.fetch('GATEWAY_CLIENT_SECRET')
end
Setting Type Default Behaviour
client_id String OAuth2 client_credentials identifier. Required.
client_secret String OAuth2 client_credentials secret. Required.
gateway_url String https://gateway.fluence-europe.cloud Base URL of the API gateway.
appcenter_url String https://appcenter.fluence-europe.cloud Base URL of the OAuth2 server (token endpoint).
timeout Integer 30 HTTP timeout, in seconds.

Only client_id and client_secret have no defaults and must be set before the first call.


Usage

Calling a service

Pass the target service via the service: kwarg. The path is auto-prefixed with /backend/<service>/.

Fluence::Gateway::Client.get('/api/v1/valuations', service: :base_valeur)
# → GET /backend/base-valeur/api/v1/valuations

Fluence::Gateway::Client.post('/api/v1/valuations',
                              service: :base_valeur,
                              body: { asset_id: 42 })

Symbol service names have their underscores converted to dashes (:base_valeurbase-valeur). String service names are used as-is. When no service: is given, the path is sent to the gateway unchanged.

All verbs return parsed JSON (Hash or Array).

Scoping many calls to the same service

with_service stores the active service in the current thread, so every call inside the block inherits it:

Fluence::Gateway::Client.with_service(:base_valeur) do
  Fluence::Gateway::Client.get('/api/v1/valuations')
  Fluence::Gateway::Client.post('/api/v1/valuations', body: { asset_id: 42 })
end

A service: kwarg on the call site always overrides the block.

Forwarding an end-user token

When a request is made on behalf of an authenticated end-user, forward their Bearer so the gateway identifies the user instead of the calling service.

# Per call
Fluence::Gateway::Client.get('/api/v1/me',
                             service: :base_valeur,
                             user_token: current_user.access_token)

# Block — every call inside forwards the same token (thread-local)
Fluence::Gateway::Client.with_user_token(current_user.access_token) do
  Fluence::Gateway::Client.get('/api/v1/me', service: :base_valeur)
  Fluence::Gateway::Client.post('/api/v1/things',
                                service: :base_valeur,
                                body: { name: 'x' })
end

The service client_credentials token is cached separately: when a user_token: is present, it is wrapped in an ephemeral OAuth2::AccessToken for that request only. A user_token: kwarg always wins over a with_user_token block.


Error handling

begin
  Fluence::Gateway::Client.get('/api/v1/me', service: :base_valeur)
rescue Fluence::Gateway::AuthenticationError => e
  # OAuth2 credentials invalid or token endpoint returned 4xx/5xx — do not retry
rescue Fluence::Gateway::ConnectionError => e
  # Gateway unreachable (timeout, connection refused) — safe to retry with backoff
rescue Fluence::Gateway::Error => e
  # Anything else raised by the gem
end

Token lifecycle

The service client_credentials token is fetched lazily on the first call and cached in memory. When it expires, the client:

  1. Calls refresh on the cached token if a refresh token is available;
  2. Otherwise, requests a fresh token via client_credentials.

Refresh is guarded by a Mutex, so concurrent callers share a single refresh round-trip.


Development

bin/setup
bundle exec rspec             # tests
bundle exec rubocop           # linting
bundle exec rake doc:build    # generate YARD documentation in doc/
bundle exec rake doc:verify   # check yardstick documentation-quality threshold

Contributing guidelines are in CONTRIBUTING.md.