functions_framework 0.5.2 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +35 -0
- data/README.md +5 -5
- data/bin/functions-framework +4 -1
- data/bin/functions-framework-ruby +1 -1
- data/docs/deploying-functions.md +7 -11
- data/docs/overview.md +4 -4
- data/docs/testing-functions.md +50 -0
- data/docs/writing-functions.md +246 -7
- data/lib/functions_framework.rb +30 -3
- data/lib/functions_framework/cli.rb +98 -23
- data/lib/functions_framework/function.rb +190 -48
- data/lib/functions_framework/legacy_event_converter.rb +49 -22
- data/lib/functions_framework/registry.rb +34 -11
- data/lib/functions_framework/server.rb +22 -17
- data/lib/functions_framework/testing.rb +106 -18
- data/lib/functions_framework/version.rb +1 -1
- metadata +8 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6a4d1e19c5565de4604964e48ff818b05460b2211ef2c8cfbd100bd572d802d7
|
4
|
+
data.tar.gz: 380d987acf157075cfe7fca5bf038404eb800f48b80614ce9781f44cbd6de764
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c76cb765863334411e7ab3bd448912fabc14c7725ae66465426ee996b05557d44653f64d47c98eb429d077f0df558b550b228a3fce014dd5aa9e02e4933cbc62
|
7
|
+
data.tar.gz: af8d74d419c75dc8f850fa989d48780a26e2639045ff945d4b0a1cd29fa0085d3a82d058db6e49ed9c9f2c2855b3bc495a44ab530e129a846b24c111c76f79f2
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,40 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
### v0.9.0 / 2021-03-18
|
4
|
+
|
5
|
+
* BREAKING CHANGE: Servers are configured as single-threaded in production by default, matching the current behavior of Google Cloud Functions.
|
6
|
+
* FIXED: Fixed conversion of Firebase events to CloudEvents to conform to the specs used by Cloud Functions and Cloud Run.
|
7
|
+
* FIXED: Fixed an error when reading a global set to a Minitest::Mock. This will make it easier to write tests that use mocks for global resources.
|
8
|
+
|
9
|
+
### v0.8.0 / 2021-03-02
|
10
|
+
|
11
|
+
* ADDED: Support for lazily-initialized globals
|
12
|
+
|
13
|
+
### v0.7.1 / 2021-01-26
|
14
|
+
|
15
|
+
* DOCS: Fixed several errors in the writing-functions doc samples
|
16
|
+
* DOCS: Updated documentation to note public release of GCF support
|
17
|
+
|
18
|
+
### v0.7.0 / 2020-09-25
|
19
|
+
|
20
|
+
* Now requires Ruby 2.5 or later.
|
21
|
+
* BREAKING CHANGE: Renamed "context" hash to "globals" and made it read-only for normal functions.
|
22
|
+
* BREAKING CHANGE: Server config is no longer passed to startup blocks.
|
23
|
+
* ADDED: Provided a "logger" convenience method in the context object.
|
24
|
+
* ADDED: Globals can be set from startup blocks, which is useful for initializing shared resources.
|
25
|
+
* ADDED: Support for testing startup tasks in the Testing module.
|
26
|
+
* ADDED: Support for controlling logging in the Testing module.
|
27
|
+
* FIXED: Fixed crash introduced in 0.6.0 when a block didn't declare an expected argument.
|
28
|
+
* FIXED: Better support for running concurrent tests.
|
29
|
+
* DOCS: Expanded documentation on initialization, execution context, and shared resources.
|
30
|
+
* DEPRECATED: The functions-framework executable is deprecated. Use functions-framework-ruby instead.
|
31
|
+
|
32
|
+
### v0.6.0 / 2020-09-17
|
33
|
+
|
34
|
+
* ADDED: You can use the --version flag to print the framework version
|
35
|
+
* ADDED: You can use the --verify flag to verify that a given function is defined
|
36
|
+
* ADDED: You can now define blocks that are executed at server startup
|
37
|
+
|
3
38
|
### v0.5.2 / 2020-09-06
|
4
39
|
|
5
40
|
* FIXED: Use global $stderr rather than STDERR for logger
|
data/README.md
CHANGED
@@ -1,10 +1,10 @@
|
|
1
|
-
# Functions Framework [![Documentation](https://img.shields.io/badge/docs-FunctionsFramework-red.svg)](https://googlecloudplatform.github.io/functions-framework-ruby/latest) [![Gem Version](https://badge.fury.io/rb/functions_framework.svg)](https://badge.fury.io/rb/functions_framework)
|
1
|
+
# Functions Framework for Ruby [![Documentation](https://img.shields.io/badge/docs-FunctionsFramework-red.svg)](https://googlecloudplatform.github.io/functions-framework-ruby/latest) [![Gem Version](https://badge.fury.io/rb/functions_framework.svg)](https://badge.fury.io/rb/functions_framework)
|
2
2
|
|
3
3
|
An open source framework for writing lightweight, portable Ruby functions that
|
4
4
|
run in a serverless environment. Functions written to this Framework will run
|
5
5
|
in many different environments, including:
|
6
6
|
|
7
|
-
* [Google Cloud Functions](https://cloud.google.com/functions) *(
|
7
|
+
* [Google Cloud Functions](https://cloud.google.com/functions) *(public preview)*
|
8
8
|
* [Google Cloud Run](https://cloud.google.com/run)
|
9
9
|
* Any other [Knative](https://github.com/knative)-based environment
|
10
10
|
* Your local development machine
|
@@ -42,11 +42,11 @@ requiring an HTTP server or complicated request handling logic.
|
|
42
42
|
|
43
43
|
## Supported Ruby versions
|
44
44
|
|
45
|
-
This library is supported on Ruby 2.
|
45
|
+
This library is supported on Ruby 2.5+.
|
46
46
|
|
47
47
|
Google provides official support for Ruby versions that are actively supported
|
48
48
|
by Ruby Core—that is, Ruby versions that are either in normal maintenance or
|
49
|
-
in security maintenance, and not end of life. Currently, this means Ruby 2.
|
49
|
+
in security maintenance, and not end of life. Currently, this means Ruby 2.5
|
50
50
|
and later. Older versions of Ruby _may_ still work, but are unsupported and not
|
51
51
|
recommended. See https://www.ruby-lang.org/en/downloads/branches/ for details
|
52
52
|
about the Ruby support schedule.
|
@@ -60,7 +60,7 @@ Create a `Gemfile` listing the Functions Framework as a dependency:
|
|
60
60
|
```ruby
|
61
61
|
# Gemfile
|
62
62
|
source "https://rubygems.org"
|
63
|
-
gem "functions_framework", "~> 0.
|
63
|
+
gem "functions_framework", "~> 0.9"
|
64
64
|
```
|
65
65
|
|
66
66
|
Create a file called `app.rb` and include the following code. This defines a
|
data/bin/functions-framework
CHANGED
@@ -16,4 +16,7 @@
|
|
16
16
|
|
17
17
|
require "functions_framework/cli"
|
18
18
|
|
19
|
-
|
19
|
+
puts "WARNING: The functions-framework executable is deprecated and will be"
|
20
|
+
puts "removed in a future version. Please use functions-framework-ruby instead."
|
21
|
+
|
22
|
+
::FunctionsFramework::CLI.new.parse_args(::ARGV).run.complete
|
data/docs/deploying-functions.md
CHANGED
@@ -31,12 +31,8 @@ Functions Framework is designed especially for functions that can be hosted on
|
|
31
31
|
Cloud Functions.
|
32
32
|
|
33
33
|
You can run Ruby functions on Google Cloud Functions by selecting the `ruby26`
|
34
|
-
runtime
|
35
|
-
|
36
|
-
|
37
|
-
> **Note:** Ruby support on Cloud Functions is currently in limited alpha.
|
38
|
-
> It is not yet suitable for production workloads, and support is best-effort
|
39
|
-
> only. Access is currently limited to selected early-access users.
|
34
|
+
runtime or `ruby27` runtime to use a recent release of Ruby 2.6 or Ruby 2.7.
|
35
|
+
Support for Ruby 3.0 is forthcoming.
|
40
36
|
|
41
37
|
### Deploying and updating your function
|
42
38
|
|
@@ -64,7 +60,7 @@ Then, issue the gcloud command to deploy:
|
|
64
60
|
```sh
|
65
61
|
gcloud functions deploy $YOUR_FUNCTION_NAME \
|
66
62
|
--project=$YOUR_PROJECT_ID \
|
67
|
-
--runtime=
|
63
|
+
--runtime=ruby27 \
|
68
64
|
--trigger-http \
|
69
65
|
--entry-point=$YOUR_FUNCTION_TARGET
|
70
66
|
```
|
@@ -90,7 +86,7 @@ and above, set `FUNCTION_LOGGING_LEVEL` to `WARN` when deploying:
|
|
90
86
|
|
91
87
|
```sh
|
92
88
|
gcloud functions deploy $YOUR_FUNCTION_NAME --project=$YOUR_PROJECT_ID \
|
93
|
-
--runtime=
|
89
|
+
--runtime=ruby27 --trigger-http --source=$YOUR_FUNCTION_SOURCE \
|
94
90
|
--entry-point=$YOUR_FUNCTION_TARGET \
|
95
91
|
--set-env-vars=FUNCTION_LOGGING_LEVEL=WARN
|
96
92
|
```
|
@@ -125,7 +121,7 @@ Dockerfile that you can use as a starting point. Feel free to adjust it to the
|
|
125
121
|
needs of your project:
|
126
122
|
|
127
123
|
```
|
128
|
-
FROM ruby:2.
|
124
|
+
FROM ruby:2.7
|
129
125
|
WORKDIR /app
|
130
126
|
COPY . .
|
131
127
|
RUN gem install --no-document bundler \
|
@@ -151,8 +147,8 @@ command may ask you for permission to enable the Cloud Build API for the project
|
|
151
147
|
if it isn't already enabled.
|
152
148
|
|
153
149
|
Because you provide your own Docker image when deploying to Cloud Run, you can
|
154
|
-
use any version of Ruby supported by the Functions Framework, from 2.
|
155
|
-
|
150
|
+
use any version of Ruby supported by the Functions Framework, from 2.5 through
|
151
|
+
3.0.
|
156
152
|
|
157
153
|
### Deploying an image to Cloud Run
|
158
154
|
|
data/docs/overview.md
CHANGED
@@ -8,7 +8,7 @@ The Functions Framework is an open source framework for writing lightweight,
|
|
8
8
|
portable Ruby functions that run in a serverless environment. Functions written
|
9
9
|
to this Framework will run in many different environments, including:
|
10
10
|
|
11
|
-
* [Google Cloud Functions](https://cloud.google.com/functions) *(
|
11
|
+
* [Google Cloud Functions](https://cloud.google.com/functions) *(public preview)*
|
12
12
|
* [Google Cloud Run](https://cloud.google.com/run)
|
13
13
|
* Any other [Knative](https://github.com/knative)-based environment
|
14
14
|
* Your local development machine
|
@@ -46,11 +46,11 @@ requiring an HTTP server or complicated request handling logic.
|
|
46
46
|
|
47
47
|
## Supported Ruby versions
|
48
48
|
|
49
|
-
This library is supported on Ruby 2.
|
49
|
+
This library is supported on Ruby 2.5+.
|
50
50
|
|
51
51
|
Google provides official support for Ruby versions that are actively supported
|
52
52
|
by Ruby Core—that is, Ruby versions that are either in normal maintenance or
|
53
|
-
in security maintenance, and not end of life. Currently, this means Ruby 2.
|
53
|
+
in security maintenance, and not end of life. Currently, this means Ruby 2.5
|
54
54
|
and later. Older versions of Ruby _may_ still work, but are unsupported and not
|
55
55
|
recommended. See https://www.ruby-lang.org/en/downloads/branches/ for details
|
56
56
|
about the Ruby support schedule.
|
@@ -64,7 +64,7 @@ Create a `Gemfile` listing the Functions Framework as a dependency:
|
|
64
64
|
```ruby
|
65
65
|
# Gemfile
|
66
66
|
source "https://rubygems.org"
|
67
|
-
gem "functions_framework", "~> 0.
|
67
|
+
gem "functions_framework", "~> 0.9"
|
68
68
|
```
|
69
69
|
|
70
70
|
Create a file called `app.rb` and include the following code. This defines a
|
data/docs/testing-functions.md
CHANGED
@@ -165,3 +165,53 @@ class MyTest < Minitest::Test
|
|
165
165
|
end
|
166
166
|
end
|
167
167
|
```
|
168
|
+
|
169
|
+
## Testing startup tasks
|
170
|
+
|
171
|
+
When a functions server is starting up, it calls startup tasks automatically.
|
172
|
+
In the testing environment, when you call a function using the
|
173
|
+
{FunctionsFramework::Testing#call_http} or
|
174
|
+
{FunctionsFramework::Testing#call_event} methods, the testing environment will
|
175
|
+
also automatically execute any startup tasks for you.
|
176
|
+
|
177
|
+
You can also call startup tasks explicitly to test them in isolation, using the
|
178
|
+
{FunctionsFramework::Testing#run_startup_tasks} method. Pass the name of a
|
179
|
+
function, and the testing module will execute all defined startup blocks, in
|
180
|
+
order, as if the server were preparing that function for execution.
|
181
|
+
{FunctionsFramework::Testing#run_startup_tasks} returns the resulting globals
|
182
|
+
as a hash, so you can assert against its contents.
|
183
|
+
|
184
|
+
If you use {FunctionsFramework::Testing#run_startup_tasks} to run the startup
|
185
|
+
tasks explicitly, they will not be run again when you call the function itself
|
186
|
+
using {FunctionsFramework::Testing#call_http} or
|
187
|
+
{FunctionsFramework::Testing#call_event}. However, if startup tasks have
|
188
|
+
already been run implicitly by {FunctionsFramework::Testing#call_http} or
|
189
|
+
{FunctionsFramework::Testing#call_event}, then attempting to run them again
|
190
|
+
explicitly by calling {FunctionsFramework::Testing#run_startup_tasks} will
|
191
|
+
result in an exception.
|
192
|
+
|
193
|
+
There is currently no way to run a single startup block in isolation. If you
|
194
|
+
have multiple startup blocks defined, they are always executed together.
|
195
|
+
|
196
|
+
Following is an example test that runs startup tasks explicitly and asserts
|
197
|
+
against the effect on the globals.
|
198
|
+
|
199
|
+
```ruby
|
200
|
+
require "minitest/autorun"
|
201
|
+
require "functions_framework/testing"
|
202
|
+
|
203
|
+
class MyTest < Minitest::Test
|
204
|
+
include FunctionsFramework::Testing
|
205
|
+
|
206
|
+
def test_startup_tasks
|
207
|
+
load_temporary "app.rb" do
|
208
|
+
globals = run_startup_tasks "my_function"
|
209
|
+
assert_equal "foo", globals[:my_global]
|
210
|
+
|
211
|
+
request = make_get_request "https://example.com/foo"
|
212
|
+
response = call_http "my_function", request
|
213
|
+
assert_equal 200, response.status
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
```
|
data/docs/writing-functions.md
CHANGED
@@ -53,7 +53,7 @@ require "functions_framework"
|
|
53
53
|
|
54
54
|
FunctionsFramework.http "request_info_example" do |request|
|
55
55
|
# Include some request info in the response body.
|
56
|
-
"Received #{request.
|
56
|
+
"Received #{request.request_method} from #{request.url}!\n"
|
57
57
|
end
|
58
58
|
```
|
59
59
|
|
@@ -68,7 +68,7 @@ require "functions_framework"
|
|
68
68
|
|
69
69
|
FunctionsFramework.http "logging_example" do |request|
|
70
70
|
# Log some request info.
|
71
|
-
request.logger.info "I received #{request.
|
71
|
+
request.logger.info "I received #{request.request_method} from #{request.url}!"
|
72
72
|
# A simple response body.
|
73
73
|
"ok"
|
74
74
|
end
|
@@ -111,7 +111,7 @@ dependency on Sinatra in your `Gemfile`:
|
|
111
111
|
|
112
112
|
```ruby
|
113
113
|
source "https://rubygems.org"
|
114
|
-
gem "functions_framework", "~> 0.
|
114
|
+
gem "functions_framework", "~> 0.9"
|
115
115
|
gem "sinatra", "~> 2.0"
|
116
116
|
```
|
117
117
|
|
@@ -197,6 +197,240 @@ FunctionsFramework.http "error_reporter" do |request|
|
|
197
197
|
end
|
198
198
|
```
|
199
199
|
|
200
|
+
## The runtime environment
|
201
|
+
|
202
|
+
A serverless environment may be somewhat different from server-based runtime
|
203
|
+
environments you might be used to. Serverless runtimes often provide a simpler
|
204
|
+
programming model, transparent scaling, and cost savings, but they do so by
|
205
|
+
controlling how your code is managed and executed. The Functions Framework is
|
206
|
+
designed around a "functions-as-a-service" (FaaS) paradigm, which runs
|
207
|
+
self-contained stateless functions that have an input and a return value. It's
|
208
|
+
important to understand what that means for your Ruby code in order to get the
|
209
|
+
most out of a cloud serverless product.
|
210
|
+
|
211
|
+
For example, multithreading is a core element of the Functions Framework. When
|
212
|
+
you write functions, you should assume that multiple executions may be taking
|
213
|
+
place concurrently in different threads, and thus you should avoid operations
|
214
|
+
that can cause concurrency issues or race conditions. The easiest way to do
|
215
|
+
this is to make your functions self-contained and stateless. Avoid global
|
216
|
+
variables and don't share mutable data between different function executions.
|
217
|
+
|
218
|
+
Additionally, a serverless runtime may throttle the CPU whenever no actual
|
219
|
+
function executions are taking place. This lets it reduce the CPU resources
|
220
|
+
used (and therefore the cost to you), while keeping your application warmed up
|
221
|
+
and ready to respond to new requests quickly. An important implication, though,
|
222
|
+
is that you should avoid starting up background threads or processes. They may
|
223
|
+
not get any CPU time during periods when your Ruby application is not actually
|
224
|
+
executing a function.
|
225
|
+
|
226
|
+
In the sections below, we'll discuss a few techniques and features of the
|
227
|
+
Functions Framework to help you write Ruby code that fits well into a
|
228
|
+
serverless paradigm.
|
229
|
+
|
230
|
+
### Startup tasks
|
231
|
+
|
232
|
+
It is sometimes useful to perform one-time initialization that applies to many
|
233
|
+
function executions, for example to warm up caches, perform precomputation, or
|
234
|
+
establish shared remote connections. To run code during initialization, use
|
235
|
+
{FunctionsFramework.on_startup} to define a _startup task_.
|
236
|
+
|
237
|
+
```ruby
|
238
|
+
require "functions_framework"
|
239
|
+
|
240
|
+
FunctionsFramework.on_startup do |function|
|
241
|
+
# Perform initialization here.
|
242
|
+
require "my_cache"
|
243
|
+
MyCache.warmup
|
244
|
+
end
|
245
|
+
|
246
|
+
FunctionsFramework.http "hello" do |request|
|
247
|
+
# Initialization will be done by the time a normal function is called.
|
248
|
+
end
|
249
|
+
```
|
250
|
+
|
251
|
+
Startup tasks are run once per Ruby instance during cold start -- that is,
|
252
|
+
after the Ruby VM boots up but before the framework starts receiving requests
|
253
|
+
and executing functions. You can define multiple startup tasks, and they will
|
254
|
+
run in order, and are guaranteed to complete before any function is executed.
|
255
|
+
|
256
|
+
The block is optionally passed the {FunctionsFramework::Function} representing
|
257
|
+
the function that will be run. You code can, for example, perform different
|
258
|
+
initialization depending on the {FunctionsFramework::Function#name} or
|
259
|
+
{FunctionsFramework::Function#type}.
|
260
|
+
|
261
|
+
**In most cases, initialization code should live in an `on_startup` block
|
262
|
+
instead of at the "top level" of your Ruby file.** This is because some
|
263
|
+
serverless runtimes may load your Ruby code at build or deployment time (for
|
264
|
+
example, to verify that it properly defines the requested function), and this
|
265
|
+
will execute any code present at the top level of the Ruby file. If top-level
|
266
|
+
code is long-running or depends on runtime resources or environment variables,
|
267
|
+
this could cause the deployment to fail. By performing initialization in an
|
268
|
+
`on_startup` block instead, you ensure it will run only when an actual runtime
|
269
|
+
server is starting up, not at build/deployment time.
|
270
|
+
|
271
|
+
```ruby
|
272
|
+
require "functions_framework"
|
273
|
+
|
274
|
+
# DO NOT perform initialization here because this could get run at build time.
|
275
|
+
# require "my_cache"
|
276
|
+
# MyCache.warmup
|
277
|
+
|
278
|
+
# Instead initialize in an on_startup block, which is executed only when a
|
279
|
+
# runtime server is starting up.
|
280
|
+
FunctionsFramework.on_startup do
|
281
|
+
# Perform initialization here.
|
282
|
+
require "my_cache"
|
283
|
+
MyCache.warmup
|
284
|
+
end
|
285
|
+
|
286
|
+
# ...
|
287
|
+
```
|
288
|
+
|
289
|
+
Because startup tasks run during cold start, they could have an impact on your
|
290
|
+
function's startup latency. To mitigate this issue, it is possible to run parts
|
291
|
+
of your initialization lazily, as described below in the section below on
|
292
|
+
[lazy initialization](#Lazy_initialization).
|
293
|
+
|
294
|
+
### The execution context and global data
|
295
|
+
|
296
|
+
When your function block executes, the _object context_ (i.e. `self`) is set to
|
297
|
+
an instance of {FunctionsFramework::Function::Callable}. Each function
|
298
|
+
invocation (including functions that might be running concurrently in separate
|
299
|
+
threads) runs within a different instance, to help you avoid having functions
|
300
|
+
interfere with each other.
|
301
|
+
|
302
|
+
The object context also defines a few methods that may be useful when writing
|
303
|
+
your function.
|
304
|
+
|
305
|
+
First, you can obtain the logger by calling the
|
306
|
+
{FunctionsFramework::Function::Callable#logger} convenience method. This is
|
307
|
+
the same logger that is provided by the HTTP request object or by the
|
308
|
+
{FunctionsFramework.logger} global method.
|
309
|
+
|
310
|
+
Second, you can access global shared data by passing a key to
|
311
|
+
{FunctionsFramework::Function::Callable#global}. _Global shared data_ is a set
|
312
|
+
of key-value pairs that are available to every function invocation. By default,
|
313
|
+
two keys are available to all functions:
|
314
|
+
|
315
|
+
* `:function_name` whose String value is the name of the running function.
|
316
|
+
* `:function_type` whose value is either `:http` or `:cloud_event` depending
|
317
|
+
on the type of the running function.
|
318
|
+
|
319
|
+
Following is a simple example using the `logger` and `global` methods of the
|
320
|
+
context object:
|
321
|
+
|
322
|
+
```ruby
|
323
|
+
require "functions_framework"
|
324
|
+
|
325
|
+
FunctionsFramework.cloud_event "hello" do |event|
|
326
|
+
logger.info "Now running the function called #{global(:function_name)}"
|
327
|
+
end
|
328
|
+
```
|
329
|
+
|
330
|
+
To avoid concurrency issues, global shared data is immutable when executing a
|
331
|
+
function. You cannot add or delete keys or change the value of existing keys.
|
332
|
+
However, the global data is settable during startup tasks, because startup
|
333
|
+
tasks never run concurrently. You can use this feature to initialize shared
|
334
|
+
resources, as described below.
|
335
|
+
|
336
|
+
Using the global data mechanism is generally preferred over actual Ruby global
|
337
|
+
variables, because the Functions Framework can help you avoid concurrent edits.
|
338
|
+
Additionally, the framework will isolate the sets of global data associated
|
339
|
+
with different sets of functions, which lets you run functions in isolation
|
340
|
+
during unit tests. If you are testing multiple functions, they will not
|
341
|
+
interfere with each other as they might if they used global variables.
|
342
|
+
|
343
|
+
### Sharing resources
|
344
|
+
|
345
|
+
Although functions should generally be self-contained and stateless, it is
|
346
|
+
sometimes useful to share certain kinds of resources across multiple function
|
347
|
+
invocations that run on the same Ruby instance. For example, you might
|
348
|
+
establish a single connection to a remote database or other service, and share
|
349
|
+
it across function invocations to avoid incurring the overhead of
|
350
|
+
re-establishing it for every function invocation.
|
351
|
+
|
352
|
+
The best practice for sharing a resource across function invocations is to
|
353
|
+
initialize it in a {FunctionsFramework.on_startup} block, and reference it from
|
354
|
+
global shared data. (As discussed above, the best practice is to initialize
|
355
|
+
shared resources in a startup task rather than at the top level of a Ruby file,
|
356
|
+
and to use the Functions Framework's global data mechanism rather than Ruby's
|
357
|
+
global variables.)
|
358
|
+
|
359
|
+
Here is a simple example:
|
360
|
+
|
361
|
+
```ruby
|
362
|
+
require "functions_framework"
|
363
|
+
|
364
|
+
# Use an on_startup block to initialize a shared client and store it in
|
365
|
+
# the global shared data.
|
366
|
+
FunctionsFramework.on_startup do
|
367
|
+
require "google/cloud/storage"
|
368
|
+
set_global :storage_client, Google::Cloud::Storage.new
|
369
|
+
end
|
370
|
+
|
371
|
+
# The shared storage_client can be accessed by all function invocations
|
372
|
+
# via the global shared data.
|
373
|
+
FunctionsFramework.http "storage_example" do |request|
|
374
|
+
bucket = global(:storage_client).bucket "my-bucket"
|
375
|
+
file = bucket.file "path/to/my-file.txt"
|
376
|
+
file.download.to_s
|
377
|
+
end
|
378
|
+
```
|
379
|
+
|
380
|
+
Importantly, if you do share a resource across function invocations, make sure
|
381
|
+
the resource is thread-safe, so that separate functions running concurrently in
|
382
|
+
different threads can access them safely. The API clients provided by Google,
|
383
|
+
for example, are thread-safe and can be used concurrently.
|
384
|
+
|
385
|
+
Also of note: There is no guaranteed cleanup hook. The Functions Framework does
|
386
|
+
not provide a way to register a cleanup task, and we recommend against using
|
387
|
+
resources that require explicit "cleanup". This is because serverless runtimes
|
388
|
+
may perform CPU throttling, and therefore there may not be an opportunity for
|
389
|
+
cleanup tasks to run. (For example, you could register a `Kernel.at_exit` task,
|
390
|
+
but the Ruby VM may still terminate without calling it.)
|
391
|
+
|
392
|
+
### Lazy initialization
|
393
|
+
|
394
|
+
Because startup tasks run during cold start, they could have an impact on your
|
395
|
+
function's startup latency. You can mitigate this by initializing some globals
|
396
|
+
_lazily_. When setting a global, instead of computing and setting the value
|
397
|
+
directly (e.g. constructing a shared API client object directly), you can
|
398
|
+
provide a block that describes how to construct it on demand.
|
399
|
+
|
400
|
+
Here is an example using the storage client we saw above.
|
401
|
+
|
402
|
+
```ruby
|
403
|
+
require "functions_framework"
|
404
|
+
|
405
|
+
# This startup block describes _how_ to initialize a shared client, but
|
406
|
+
# does not construct it immediately.
|
407
|
+
FunctionsFramework.on_startup do
|
408
|
+
require "google/cloud/storage"
|
409
|
+
set_global :storage_client do
|
410
|
+
Google::Cloud::Storage.new
|
411
|
+
end
|
412
|
+
end
|
413
|
+
|
414
|
+
# The first time this function is invoked, it will call the above block
|
415
|
+
# to construct the storage client. Subsequent invocations will not need
|
416
|
+
# to construct it again, but will reuse the same shared object.
|
417
|
+
FunctionsFramework.http "storage_example" do |request|
|
418
|
+
bucket = global(:storage_client).bucket "my-bucket"
|
419
|
+
file = bucket.file "path/to/my-file.txt"
|
420
|
+
file.download.to_s
|
421
|
+
end
|
422
|
+
```
|
423
|
+
|
424
|
+
The block will not be called until a function actually attempts to access the
|
425
|
+
global. From that point, subsequent accesses of the global will return that
|
426
|
+
same shared value; the block will be called at most once. This is true even if
|
427
|
+
multiple functions are run concurrently in different threads.
|
428
|
+
|
429
|
+
Lazy initialization is particularly useful if you define several different
|
430
|
+
functions that may use different sets of shared resources. Instead of
|
431
|
+
initializing all resources eagerly up front, you could initialize them lazily
|
432
|
+
and run only the code needed by the function that is actually invoked.
|
433
|
+
|
200
434
|
## Structuring a project
|
201
435
|
|
202
436
|
A Functions Framework based "project" or "application" is a typical Ruby
|
@@ -209,12 +443,14 @@ and methods that assist in the function implementation.
|
|
209
443
|
By convention, the "main" Ruby file that defines functions should be called
|
210
444
|
`app.rb` and be located at the root of the project. The path to this file is
|
211
445
|
sometimes known as the **function source**. The Functions Framework allows you
|
212
|
-
to specify an arbitrary source, but
|
446
|
+
to specify an arbitrary source, but some hosting environments (such as Google
|
213
447
|
Cloud Functions) require it to be `./app.rb`.
|
214
448
|
|
215
449
|
A source file can define any number of functions (with distinct names). Each of
|
216
450
|
the names is known as a **function target**.
|
217
451
|
|
452
|
+
Following is a typical layout for a Functions Framework based project.
|
453
|
+
|
218
454
|
```
|
219
455
|
(project directory)
|
220
456
|
|
|
@@ -234,13 +470,16 @@ the names is known as a **function target**.
|
|
234
470
|
```ruby
|
235
471
|
# Gemfile
|
236
472
|
source "https://rubygems.org"
|
237
|
-
gem "functions_framework", "~> 0.
|
473
|
+
gem "functions_framework", "~> 0.9"
|
238
474
|
```
|
239
475
|
|
240
476
|
```ruby
|
241
477
|
# app.rb
|
242
478
|
require "functions_framework"
|
243
|
-
|
479
|
+
|
480
|
+
FunctionsFramework.on_startup do
|
481
|
+
require_relative "lib/hello"
|
482
|
+
end
|
244
483
|
|
245
484
|
FunctionsFramework.http "hello" do |request|
|
246
485
|
Hello.new(request).build_response
|
@@ -255,7 +494,7 @@ class Hello
|
|
255
494
|
end
|
256
495
|
|
257
496
|
def build_response
|
258
|
-
"Received request: #{request.
|
497
|
+
"Received request: #{@request.request_method} #{@request.url}\n"
|
259
498
|
end
|
260
499
|
end
|
261
500
|
```
|