gruf 2.7.0 → 2.10.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8fb4c36a5bd87553944bb4056fcca8875620cffe1d623f8757db6fa0e88f9aee
4
- data.tar.gz: b3903a27dd46302ac99db4cbfa512b7ffc6b3ad9a4faafe83ac22fe83469ae29
3
+ metadata.gz: b132efe87ccf85ffcf897a67f5c268b8696e60e5f2b280c93c52f08fea1aa122
4
+ data.tar.gz: 6a1d99886e075daac23eadeabd5e347c92d4fdc7659f7dd639d7e79e38c6b6f8
5
5
  SHA512:
6
- metadata.gz: ba2ccb4ffe9915044fa8026eefa6e078b746d392af52401a779ad62bcd12ae2ae92cdfce73dc8680564d635bad0f2412b2ff3c740dd0fbfb5291a6a3c3fe70f6
7
- data.tar.gz: c3db4263ae977cede3b9f3adb9cd90984e7906f72c1d005d3b61f977bb64eb3b442e8b4a092475f6fdd9af4ad5510d7e316255bbe59613c4f19d0d8231a48edf
6
+ metadata.gz: b5140ea90e6bc018cef228f7bece3096cd389bec36cc4b80f5e1a9b2ebefbab7afe494146c9f4843418f06ffe27d404885d470477c732986befe94b69191297d
7
+ data.tar.gz: fb3baf2ad10bf606b57c37f45d419e11c41bb34ce56c9801d3367cb471cfe22f097b42bbfa60aa77cbb3eda338c947eee48f909b2866df1bf660953772ac2b64
data/CHANGELOG.md CHANGED
@@ -2,6 +2,35 @@ Changelog for the gruf gem. This includes internal history before the gem was ma
2
2
 
3
3
  ### Pending release
4
4
 
5
+ ### 2.10.0
6
+
7
+ - Drop support for Ruby 2.4/2.5 to align with Ruby EOL schedule, supporting 2.6+ only
8
+ - Allow for float/TimeSpec timeout values on clients
9
+
10
+ ### 2.9.0
11
+
12
+ - Change to racially neutral terminology across library
13
+ - blacklist->blocklist
14
+ - master->main branch
15
+ - Explicitly declare development dependencies in gemspec
16
+ - Add script/e2e test for full e2e test in regression suite
17
+ - Explicitly declare [json gem](https://rubygems.org/gems/json) dependency
18
+ - Update to Rubocop 1.4, add in rubocop-rspec for spec tests
19
+ - Add Ruby 2.7, 3.0 support
20
+
21
+ ### 2.8.1
22
+
23
+ - Fix issue with --suppress-default-interceptors not working [#95]
24
+ - Loosen rake development dependency to >= 10.0 [#97]
25
+
26
+ ### 2.8.0
27
+
28
+ - Pass the controller request object into the request logging formatters [#92]
29
+
30
+ ### 2.7.1
31
+
32
+ - Add `channel_credentials` option to `Gruf::Client` and `default_channel_credentials` option to `Gruf::Configuration` [#85] [#87]
33
+
5
34
  ### 2.7.0
6
35
 
7
36
  - Add hook support for executing code paths before a server is started, and after a server stops
@@ -132,11 +161,11 @@ Gruf 2.0 is a major shift from Gruf 1.0. See [UPGRADING.md](UPGRADING.md) for de
132
161
  ### 1.2.4
133
162
 
134
163
  - Loosen explicit Protobuf dependency now that 3.4.0.2 is released
135
- - Guard against nil params in logger blacklist
164
+ - Guard against nil params in logger blocklist
136
165
 
137
166
  ### 1.2.3
138
167
 
139
- - Support nested blacklist parameters in path.to.key format
168
+ - Support nested blocklist parameters in path.to.key format
140
169
 
141
170
  ### 1.2.2
142
171
 
@@ -158,7 +187,7 @@ Gruf 2.0 is a major shift from Gruf 1.0. See [UPGRADING.md](UPGRADING.md) for de
158
187
  - Add configuration to set the error message when an uncaught exception is
159
188
  handled by gruf.
160
189
  - Add a request logging hook for Rails-style request logging, with optional
161
- parameter logging, blacklists, and formatter support
190
+ parameter logging, blocklists, and formatter support
162
191
  - Optimizations around Symbol casting within service calls
163
192
 
164
193
  ### 1.1.0
data/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # gruf - gRPC Ruby Framework
2
2
 
3
- [![CircleCI](https://circleci.com/gh/bigcommerce/gruf/tree/master.svg?style=svg)](https://circleci.com/gh/bigcommerce/gruf/tree/master) [![Gem Version](https://badge.fury.io/rb/gruf.svg)](https://badge.fury.io/rb/gruf) [![Documentation](https://inch-ci.org/github/bigcommerce/gruf.svg?branch=master)](https://inch-ci.org/github/bigcommerce/gruf?branch=master)
3
+ [![CircleCI](https://circleci.com/gh/bigcommerce/gruf/tree/main.svg?style=svg)](https://circleci.com/gh/bigcommerce/gruf/tree/main) [![Gem Version](https://badge.fury.io/rb/gruf.svg)](https://badge.fury.io/rb/gruf) [![Documentation](https://inch-ci.org/github/bigcommerce/gruf.svg?branch=main)](https://inch-ci.org/github/bigcommerce/gruf?branch=main)
4
4
 
5
- gruf is a Ruby framework that wraps the [gRPC Ruby library](https://github.com/grpc/grpc/tree/master/src/ruby) to
5
+ gruf is a Ruby framework that wraps the [gRPC Ruby library](https://github.com/grpc/grpc/tree/main/src/ruby) to
6
6
  provide a more streamlined integration into Ruby and Ruby on Rails applications.
7
7
 
8
8
  It provides an abstracted server and client for gRPC services, along with other tools to help get gRPC services in Ruby
@@ -17,515 +17,20 @@ up fast and efficiently at scale. Some of its features include:
17
17
  still preserving gRPC BadStatus codes
18
18
  * Server and client execution timings in responses
19
19
 
20
- gruf currently has active support for gRPC 1.10.x+. gruf is compatible and tested with Ruby 2.2-2.5.
20
+ gruf currently has active support for gRPC 1.10.x+. gruf is compatible and tested with Ruby 2.2-2.7.
21
21
  gruf is also not [Rails](https://github.com/rails/rails)-specific, and can be used in any Ruby framework
22
- (such as [Grape](https://github.com/ruby-grape/grape), for instance).
22
+ (such as [Grape](https://github.com/ruby-grape/grape) or [dry-rb](https://dry-rb.org/), for instance).
23
23
 
24
- ## Installation
24
+ ### Getting Started
25
25
 
26
- ```ruby
27
- gem 'gruf'
28
- ```
29
-
30
- Then in an initializer or before use:
31
-
32
- ```ruby
33
- require 'gruf'
34
- ```
35
-
36
- Make sure to review [UPGRADING.md](https://github.com/bigcommerce/gruf/blob/master/UPGRADING.md)
37
- if you are upgrading gruf between minor or major versions.
38
-
39
- ### Client
40
-
41
- Add an initializer:
42
-
43
- ```ruby
44
- require 'gruf'
45
-
46
- Gruf.configure do |c|
47
- c.default_client_host = 'grpc.service.com:9003'
48
- end
49
- ```
50
-
51
- If you don't explicitly set `default_client_host`, you will need to pass it into the options, like so:
52
-
53
- ```ruby
54
- client = ::Gruf::Client.new(service: ::Demo::ThingService, options: {hostname: 'grpc.service.com:9003'})
55
- ```
56
-
57
- From there, you can instantiate a client given a stub service (say on an SslCertificates proto with a GetSslCertificate call):
58
-
59
- ```ruby
60
- require 'gruf'
61
-
62
- id = args[:id].to_i.presence || 1
63
-
64
- begin
65
- client = ::Gruf::Client.new(service: ::Demo::ThingService)
66
- response = client.call(:GetMyThing, id: id)
67
- puts response.message.inspect
68
- rescue Gruf::Client::Error => e
69
- puts e.error.inspect
70
- end
71
- ```
72
-
73
- Note this returns a response object. The response object can provide `trailing_metadata` as well as a `execution_time`.
74
-
75
- ### SynchronizedClient
76
-
77
- SynchronizedClient wraps Client with some additional behavior to help prevent generating spikes
78
- of redundant requests. If multiple calls to the same endpoint with the same parameters are made,
79
- the first one will be executed and the following ones will block, waiting for the first result.
80
-
81
- ```ruby
82
- require 'gruf'
83
- require 'thwait'
84
-
85
- id = args[:id].to_i.presence || 1
86
- client = ::Gruf::SynchronizedClient.new(service: ::Demo::ThingService)
87
- thread1 = Thread.new { client.call(:GetMyThing, id: id) }
88
- thread2 = Thread.new { client.call(:GetMyThing, id: id) }
89
- ThreadsWait.all_waits(thread1, thread2)
90
- ```
91
-
92
- In the above example, thread1 will make the rpc call, thread2 will block until the call is complete, and then
93
- will get the same value without making a second rpc call.
94
-
95
- You can also skip this behavior for certain methods if desired.
96
-
97
- ```ruby
98
- require 'gruf'
99
- require 'thwait'
100
-
101
- id = args[:id].to_i.presence || 1
102
- client = ::Gruf::SynchronizedClient.new(service: ::Demo::ThingService, options: { unsynchronized_methods: [:GetMyThing] })
103
- thread1 = Thread.new { client.call(:GetMyThing, id: id) }
104
- thread2 = Thread.new { client.call(:GetMyThing, id: id) }
105
- ThreadsWait.all_waits(thread1, thread2)
106
- ```
107
-
108
- In the above example, thread1 and thread2 will make rpc calls in parallel, in the same way as if you had used
109
- `Gruf::Client`.
110
-
111
- ### Client Interceptors
112
-
113
- Gruf comes with an assistance class for client interceptors that you can use - or you can use the native gRPC core
114
- interceptors. Either way, you pass them into the `client_options` when creating a client:
115
-
116
- ```ruby
117
- class MyInterceptor < Gruf::Interceptors::ClientInterceptor
118
- def call(request_context:)
119
- logger.info "Got method #{request_context.method}!"
120
- yield
121
- end
122
- end
123
-
124
- ::Gruf::Client.new(
125
- service: ::Demo::ThingService,
126
- client_options: {
127
- interceptors: [MyInterceptor.new]
128
- })
129
- ```
130
-
131
- The `interceptors` option in `client_options` can accept either a `GRPC::ClientInterceptor` class or a
132
- `Gruf::Interceptors::ClientInterceptor`, since the latter just extends the former. The gruf client interceptors
133
- take an optional alternative approach: rather than having separate methods for each request type, it provides a default
134
- `call` method that passes in a `RequestContext` object, which has the following attributes:
135
-
136
- * *type* - A Symbol of the type of request (`request_response`, `server_streamer`, etc)
137
- * *requests* An enumerable of requests being sent. For unary requests, this is a single request in an array
138
- * *call* - The `GRPC::ActiveCall` object
139
- * *method* - The Method being called
140
- * *metadata* - The hash of outgoing metadata
141
-
142
- Note that you _must_ yield back the block when building a client interceptor, so that the call can be executed.
143
-
144
- ### Server
145
-
146
- Add an initializer:
147
-
148
- ```ruby
149
- require 'gruf'
150
-
151
- Gruf.configure do |c|
152
- c.server_binding_url = 'grpc.service.com:9003'
153
- end
154
- ```
155
-
156
- Next, setup some handlers based on your proto configurations in `/app/rpc/`. For example, for the Thing service, with a
157
- GetThingReq/GetThingResp call based on this proto:
158
-
159
- ```proto
160
- syntax = "proto3";
161
-
162
- package demo;
163
-
164
- service Jobs {
165
- rpc GetJob(GetJobReq) returns (GetJobResp) { }
166
- }
167
-
168
- message GetJobReq {
169
- uint64 id = 1;
170
- }
171
-
172
- message GetJobResp {
173
- uint64 id = 1;
174
- string name = 2;
175
- }
176
- ```
177
-
178
- You'd have this handler in `/app/rpc/demo/job_controller.rb`
179
-
180
- ```ruby
181
- module Demo
182
- class JobController < ::Gruf::Controllers::Base
183
- bind ::Demo::Jobs::Service
184
-
185
- ##
186
- # @return [Demo::GetJobResp] The job response
187
- #
188
- def get_job
189
- thing = Job.find(request.message.id)
190
-
191
- Demo::GetJobResp.new(id: thing.id)
192
- rescue
193
- fail!(:not_found, :job_not_found, "Failed to find Job with ID: #{request.message.id}")
194
- end
195
- end
196
- end
197
- ```
198
-
199
- Finally, you can start the server by running:
200
-
201
- ```bash
202
- bundle exec gruf
203
- ```
204
-
205
- ### Command-Line Options
206
-
207
- Gruf comes baked in with a few command-line options for the binstub:
208
-
209
- | Option | Description |
210
- | ------ | ----------- |
211
- | -h, --help | Displays the help message |
212
- | -v, --version | Displays the gruf version |
213
- | --host | Specify the server binding host |
214
- | --suppress-default-interceptors | Do not use the default interceptors for the server |
215
- | --backtrace-on-error | Push backtraces on exceptions to the error serializer |
216
-
217
- These options will override whatever is passed in the Gruf configure block or
218
- initializer.
219
-
220
- ### Basic Authentication
221
-
222
- Gruf comes packaged in with a Basic Authentication interceptor. It takes in an array of supported
223
- username and password pairs (or password-only credentials).
224
-
225
- In Server:
226
-
227
- ```ruby
228
- Gruf.configure do |c|
229
- c.interceptors.use(
230
- Gruf::Interceptors::Authentication::Basic,
231
- credentials: [{
232
- username: 'my-username-here',
233
- password: 'my-password-here',
234
- },{
235
- username: 'another-username',
236
- password: 'another-password',
237
- },{
238
- password: 'a-password-only'
239
- }]
240
- )
241
- end
242
- ```
243
-
244
- In Client:
245
-
246
- ```ruby
247
- require 'gruf'
248
-
249
- id = args[:id].to_i.presence || 1
250
-
251
- options = {
252
- username: ENV.fetch('DEMO_THING_SERVICE_USERNAME'),
253
- password: ENV.fetch('DEMO_THING_SERVICE_PASSWORD')
254
- }
255
-
256
- begin
257
- client = ::Gruf::Client.new(service: ::Demo::ThingService, options: options)
258
- response = client.call(:GetMyThing, id: id)
259
- puts response.message.inspect
260
- rescue Gruf::Client::Error => e
261
- puts e.error.inspect
262
- end
263
- ```
264
-
265
- Supporting an array of credentials allow for unique credentials per service, or for easy credential
266
- rotation with zero downtime.
267
-
268
- ### SSL Configuration
269
-
270
- We don't recommend using TLS for gRPC, but instead using something like [linkerd](https://linkerd.io) for TLS
271
- encryption between services. If you need it, however, this library supports TLS.
272
-
273
- For the client, you'll need to point to the public certificate:
274
-
275
- ```ruby
276
- ::Gruf::Client.new(
277
- service: Demo::ThingService,
278
- options: {
279
- ssl_certificate: 'x509 public certificate here',
280
- # OR
281
- ssl_certificate_file: '/path/to/my.crt'
282
- }
283
- )
284
- ```
285
-
286
- If you want to run a server you'll need both the CRT and the key file if you want to do credentialed auth:
287
-
288
- ```ruby
289
- Gruf.configure do |c|
290
- c.use_ssl = true
291
- c.ssl_crt_file = "#{Rails.root}/config/ssl/#{Rails.env}.crt"
292
- c.ssl_key_file = "#{Rails.root}/config/ssl/#{Rails.env}.key"
293
- end
294
- ```
295
-
296
- ### GRPC::RpcServer configuration
297
- To customize parameters for the underlying GRPC::RpcServer, such as the size of the gRPC thread pool,
298
- you can pass them in via Gruf.rpc\_server\_options.
299
-
300
- ```ruby
301
- Gruf.configure do |c|
302
- # The size of the underlying thread pool. No more concurrent requests can be made
303
- # than the size of the thread pool.
304
- c.rpc_server_options[:pool_size] = 100
305
- end
306
- ```
307
-
308
- ## Server Interceptors
309
-
310
- gruf supports interceptors around the grpc server calls, allowing you to perform actions around your service
311
- method calls. This can be used to add tracing data, connection resets in the grpc thread pool, further
312
- instrumentation, and other things.
313
-
314
- Adding a hook is as simple as creating a class that extends `Gruf::Interceptor::ServerInterceptor`,
315
- and a `call` method that yields control to get the method result:
316
-
317
- ```ruby
318
- class MyInterceptor < ::Gruf::Interceptors::ServerInterceptor
319
- def call
320
- yield
321
- end
322
- end
323
- ```
324
-
325
- Interceptors have access to the `request` object, which is the `Gruf::Controller::Request` object
326
- described above.
327
-
328
- ### Failing in an Interceptor
329
-
330
- Interceptors can fail requests with the same method calls as a controller:
331
-
332
- ```ruby
333
- class MyFailingInterceptor < ::Gruf::Interceptors::ServerInterceptor
334
- def call
335
- result = yield # this returns the protobuf message
336
- unless result.dont_hijack
337
- # we'll assume this "dont_hijack" attribute exists on the message for this example
338
- fail!(:internal, :hijacked, 'Hijack all the things!')
339
- end
340
- result
341
- end
342
- end
343
- ```
344
-
345
- Similarly, you can raise `GRPC::BadStatus` calls to trigger similar errors without accompanying metadata.
346
-
347
- ### Configuring Interceptors
348
-
349
- From there, the interceptor can be added to the server manually (if not executing via `bundle exec gruf`):
350
-
351
- ```ruby
352
- server = Gruf::Server.new
353
- server.add_interceptor(MyInterceptor, option_foo: 'value 123')
354
- ```
355
-
356
- Or, alternatively, the more common method of passing them into the `interceptors` configuration hash:
357
-
358
- ```ruby
359
- Gruf.configure do |c|
360
- c.interceptors.use(MyInterceptor, option_foo: 'value 123')
361
- end
362
- ```
363
-
364
- Interceptors each wrap the call and are run recursively within each other. This means that if you have
365
- three interceptors - `Interceptor1`, `Interceptor2`, and `Interceptor3` - they will run in FIFO
366
- (first in, first out) order. `Interceptor1` will run, yielding to `Interceptor2`,
367
- which will then yield to `Interceptor3`, which will then yield to your service method call,
368
- ending the chain.
369
-
370
- You can utilize the `insert_before` and `insert_after` methods to maintain order:
371
-
372
- ```ruby
373
- Gruf.configure do |c|
374
- c.interceptors.use(Interceptor1)
375
- c.interceptors.use(Interceptor2)
376
- c.interceptors.insert_before(Interceptor2, Interceptor3) # 3 will now happen before 2
377
- c.interceptors.insert_after(Interceptor1, Interceptor4) # 4 will now happen after 1
378
- end
379
- ```
380
-
381
- By default, the ActiveRecord Connection Reset interceptor and Output Metadata Timing interceptor
382
- are loaded into gruf unless explicitly told not to via the `use_default_interceptors` configuration
383
- parameter.
384
-
385
- ## Hooks
386
-
387
- Hooks, unlike interceptors, are executed outside of the request chain, such as when a server starts
388
- or stops. They run in FIFO order sequentially and do not wrap one another. They can be used to provide
389
- custom boot sequences, external instrumentation support, or shutdown alerting.
390
-
391
- You can create a hook by extending the `Gruf::Hooks::Base` class and defining the methods
392
- on the hook you wish to implement:
393
-
394
- ```ruby
395
- class MyHook < Gruf::Hooks::Base
396
- def before_server_start(server:)
397
- # do my thing before the server starts
398
- end
399
-
400
- def after_server_stop(server:)
401
- # do my thing after the server stops
402
- end
403
- end
404
-
405
- # Then in an initializer:
406
-
407
- Gruf.configure do |c|
408
- c.hooks.use(MyHook, option_foo: 'value 123')
409
- end
410
- ```
411
-
412
- Exceptions raised in hooks will halt the execution chain and bubble up the stack appropriately.
413
-
414
- ### Available Hook Insertion Points
415
-
416
- Current hook insertion points are:
417
-
418
- * `before_server_start` - Right before the gRPC server starts
419
- * `after_server_stop` - Right after the gRPC server is shutdown
420
-
421
- Note that exceptions raised in `before_server_start` will halt the execution chain for the remaining
422
- `before_server_start` hooks, but will still execute the `after_server_stop` hooks as expected. Exceptions raised
423
- in `after_server_stop` will prevent further `after_server_stop` hooks from running.
424
-
425
- ## Instrumentation
426
-
427
- gruf comes out of the box with a couple of instrumentation interceptors packed in:
428
- output metadata timings and StatsD support.
429
-
430
- ### Output Metadata Timing
431
-
432
- Enabled by default, this will push timings for _successful responses_ through the response output
433
- metadata back to the client.
434
-
435
- ### StatsD
436
-
437
- The StatsD support is not enabled by default. To enable it, you'll want to do:
438
-
439
- ```ruby
440
- Gruf.configure do |c|
441
- c.interceptors.use(
442
- Gruf::Interceptors::Instrumentation::Statsd,
443
- client: ::Statsd.new('my.statsd.host', 8125),
444
- prefix: 'my_application_prefix.rpc'
445
- )
446
- end
447
- ```
448
-
449
- This will measure counts and timings for each endpoint.
450
-
451
- ### Request Logging
452
-
453
- Gruf 1.2+ comes built with request logging out of the box; you'll get Rails-style logs with your
454
- gRPC calls:
455
-
456
- ```
457
- # plain
458
- I, [2017-07-14T09:50:54.200506 #70571] INFO -- : [GRPC::Ok] (thing_service.get_thing) [0.348ms]
459
- # logstash
460
- I, [2017-07-14T09:51:03.299050 #70595] INFO -- : {"message":"[GRPC::Ok] (thing_service.get_thing) [0.372ms]","service":"thing_service","method":"thing_service.get_thing","grpc_status":"GRPC::Ok"}
461
- ```
462
-
463
- It supports formatters (including custom ones) that you can use to specify the formatting of the logging:
464
-
465
- ```ruby
466
- Gruf.configure do |c|
467
- c.interceptors.use(
468
- Gruf::Interceptors::Instrumentation::RequestLogging::Interceptor,
469
- formatter: :logstash
470
- )
471
- end
472
- ```
473
-
474
- It comes with a few more options as well:
475
-
476
- | Option | Description | Default |
477
- | ------ | ----------- | ------- |
478
- | formatter | The formatter to use. By default `:plain` and `:logstash` are supported. | `:logstash` |
479
- | log_parameters | If set to true, will log parameters in the response | `false` |
480
- | blacklist | An array of parameter key names to redact from logging, in path.to.key format | `[]` |
481
- | redacted_string | The string to use for redacted parameters. | `REDACTED` |
482
- | ignore_methods | An array of method names to ignore from logging. E.g. `['namespace.health.check']` | `[]` |
483
-
484
- It's important to maintain a safe blacklist should you decide to log parameters; gruf does no
485
- parameter sanitization on its own. We also recommend blacklisting parameters that may contain
486
- very large values (such as binary or json data).
487
-
488
- ## Testing with RSpec
489
-
490
- There is a gem specifically for easy testing with RSpec: [gruf-rspec](https://github.com/bigcommerce/gruf-rspec). Take
491
- a look at its README for more information.
492
-
493
- ## Plugins
494
-
495
- You can build your own hooks and middleware for gruf; here's a list of known open source gems for
496
- gruf that you can use today:
497
-
498
- * [gruf-lightstep](https://github.com/bigcommerce/gruf-lightstep) - Provides a seamless
499
- [LightStep](https://lightstep.com) integration
500
- * [gruf-zipkin](https://github.com/bigcommerce/gruf-zipkin) - Provides a [Zipkin](https://zipkin.io)
501
- integration
502
- * [gruf-newrelic](https://github.com/bigcommerce/gruf-newrelic) - Easy [New Relic](https://newrelic.com/) integration
503
- * [gruf-commander](https://github.com/bigcommerce/gruf-commander) - Request/command-style validation and
504
- execution patterns for services
505
- * [gruf-profiler](https://github.com/bigcommerce/gruf-profiler) - Profiles and provides memory usage
506
- reports for clients and services
507
- * [gruf-circuit-breaker](https://github.com/bigcommerce/gruf-circuit-breaker) - Circuit breaker
508
- support for services
26
+ Please see the [gruf wiki](https://github.com/bigcommerce/gruf/wiki) for detailed information on getting started
27
+ using gruf.
509
28
 
510
29
  ## Demo Rails App
511
30
 
512
31
  There is a [demonstration Rails application here](https://github.com/bigcommerce/gruf-demo) you can
513
32
  view and clone that shows how to integrate Gruf into an existing Rails application.
514
33
 
515
- ## Roadmap
516
-
517
- ### Gruf 3.0
518
-
519
- * Change configuration to an injectable object to ensure thread safety on chained server/client interactions
520
- * Move all references to `Gruf.` configuration into injectable parameters
521
- * Redo server configuration to be fully injectable
522
-
523
- ## Companies Using Gruf
524
-
525
- Using gruf and want to show your support? Let us know and we'll add your name here.
526
-
527
- * [BigCommerce](https://www.bigcommerce.com/)
528
-
529
34
  ## License
530
35
 
531
36
  Copyright (c) 2017-present, BigCommerce Pty. Ltd. All rights reserved