functions_framework 0.5.1 → 0.8.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 +34 -0
- data/README.md +6 -6
- data/bin/functions-framework +4 -1
- data/bin/functions-framework-ruby +1 -1
- data/docs/deploying-functions.md +27 -22
- data/docs/overview.md +5 -5
- data/docs/testing-functions.md +50 -0
- data/docs/writing-functions.md +251 -14
- data/lib/functions_framework.rb +31 -4
- data/lib/functions_framework/cli.rb +98 -23
- data/lib/functions_framework/function.rb +190 -48
- data/lib/functions_framework/legacy_event_converter.rb +8 -5
- data/lib/functions_framework/registry.rb +34 -11
- data/lib/functions_framework/server.rb +21 -16
- data/lib/functions_framework/testing.rb +106 -18
- data/lib/functions_framework/version.rb +1 -1
- metadata +10 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d313c253cf23573fe55628885064699cf1da8ca51545446cd3a6adf58ffaf3ff
|
4
|
+
data.tar.gz: 2b82761a46c066f2a32af713c85e559afe5fe892bc1c761ec765772dd2633db2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b58f6f2c3fbebf5cf6c5eb28031a4ec7a389c063b9f2b8edba6b7e346f20683a8a9339202e8075299c67c5a347313c37bd82aac47422981008858e301133cccf
|
7
|
+
data.tar.gz: fbd5515ed316ab193eec32efe17eb66cc937582d1af9b6196854bac8d2dee16de6d0b7429aac66ce287b97043b5dfadc35d989451e7bb3495d62dee512cd1681
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,39 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
### v0.8.0 / 2021-03-02
|
4
|
+
|
5
|
+
* ADDED: Support for lazily-initialized globals
|
6
|
+
|
7
|
+
### v0.7.1 / 2021-01-26
|
8
|
+
|
9
|
+
* DOCS: Fixed several errors in the writing-functions doc samples
|
10
|
+
* DOCS: Updated documentation to note public release of GCF support
|
11
|
+
|
12
|
+
### v0.7.0 / 2020-09-25
|
13
|
+
|
14
|
+
* Now requires Ruby 2.5 or later.
|
15
|
+
* BREAKING CHANGE: Renamed "context" hash to "globals" and made it read-only for normal functions.
|
16
|
+
* BREAKING CHANGE: Server config is no longer passed to startup blocks.
|
17
|
+
* ADDED: Provided a "logger" convenience method in the context object.
|
18
|
+
* ADDED: Globals can be set from startup blocks, which is useful for initializing shared resources.
|
19
|
+
* ADDED: Support for testing startup tasks in the Testing module.
|
20
|
+
* ADDED: Support for controlling logging in the Testing module.
|
21
|
+
* FIXED: Fixed crash introduced in 0.6.0 when a block didn't declare an expected argument.
|
22
|
+
* FIXED: Better support for running concurrent tests.
|
23
|
+
* DOCS: Expanded documentation on initialization, execution context, and shared resources.
|
24
|
+
* DEPRECATED: The functions-framework executable is deprecated. Use functions-framework-ruby instead.
|
25
|
+
|
26
|
+
### v0.6.0 / 2020-09-17
|
27
|
+
|
28
|
+
* ADDED: You can use the --version flag to print the framework version
|
29
|
+
* ADDED: You can use the --verify flag to verify that a given function is defined
|
30
|
+
* ADDED: You can now define blocks that are executed at server startup
|
31
|
+
|
32
|
+
### v0.5.2 / 2020-09-06
|
33
|
+
|
34
|
+
* FIXED: Use global $stderr rather than STDERR for logger
|
35
|
+
* DOCS: Fix instructions for deployment to Google Cloud Functions
|
36
|
+
|
3
37
|
### v0.5.1 / 2020-07-20
|
4
38
|
|
5
39
|
* Updated some documentation links. No functional changes.
|
data/README.md
CHANGED
@@ -1,11 +1,11 @@
|
|
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) *(
|
8
|
-
* [
|
7
|
+
* [Google Cloud Functions](https://cloud.google.com/functions) *(public preview)*
|
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
|
11
11
|
|
@@ -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.8"
|
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 preview.
|
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
|
|
@@ -46,30 +42,39 @@ is to `bundle install` or `bundle update` and run your local tests prior to
|
|
46
42
|
deploying. Cloud Functions will not accept your function unless an up-to-date
|
47
43
|
`Gemfile.lock` is present.
|
48
44
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
45
|
+
Also, make sure your source file (which defines your function) is called
|
46
|
+
`app.rb`. The Functions Framework lets you choose a function source file, but
|
47
|
+
Cloud Functions currently requires you to use `app.rb`.
|
48
|
+
|
49
|
+
Decide _which_ function in the source file to invoke, that is, the name that you
|
50
|
+
used when writing the function. This is called the **target**. (Note that if you
|
51
|
+
did not specify a name for the function, it defaults to the name `function`.)
|
52
|
+
|
53
|
+
Choose a Cloud Functions **name** for your function. The **name** identifies
|
54
|
+
this function deployment (e.g. in the cloud console) and is also part of the
|
55
|
+
function's default URL. (Note: the **name** and the **target** do not have to
|
56
|
+
be the same value.)
|
53
57
|
|
54
58
|
Then, issue the gcloud command to deploy:
|
55
59
|
|
56
60
|
```sh
|
57
|
-
gcloud functions deploy $YOUR_FUNCTION_NAME
|
58
|
-
|
59
|
-
|
61
|
+
gcloud functions deploy $YOUR_FUNCTION_NAME \
|
62
|
+
--project=$YOUR_PROJECT_ID \
|
63
|
+
--runtime=ruby27 \
|
64
|
+
--trigger-http \
|
65
|
+
--entry-point=$YOUR_FUNCTION_TARGET
|
60
66
|
```
|
61
67
|
|
62
|
-
The
|
63
|
-
|
64
|
-
|
65
|
-
`gcloud config set project`.
|
68
|
+
The `--entry-point=` flag can be omitted if the **target** has the same value
|
69
|
+
as the **name**. Additionally, the `--project` flag can be omitted if you've
|
70
|
+
set your default project using `gcloud config set project`.
|
66
71
|
|
67
72
|
If your function handles events rather than HTTP requests, you'll need to
|
68
73
|
replace `--trigger-http` with a different trigger. For details, see the
|
69
74
|
[reference documentation](https://cloud.google.com/sdk/gcloud/reference/functions/deploy)
|
70
75
|
for `gcloud functions deploy`.
|
71
76
|
|
72
|
-
To update your deployment, just redeploy using the same function name
|
77
|
+
To update your deployment, just redeploy using the same function **name**.
|
73
78
|
|
74
79
|
### Configuring Cloud Functions deployments
|
75
80
|
|
@@ -81,7 +86,7 @@ and above, set `FUNCTION_LOGGING_LEVEL` to `WARN` when deploying:
|
|
81
86
|
|
82
87
|
```sh
|
83
88
|
gcloud functions deploy $YOUR_FUNCTION_NAME --project=$YOUR_PROJECT_ID \
|
84
|
-
--runtime=
|
89
|
+
--runtime=ruby27 --trigger-http --source=$YOUR_FUNCTION_SOURCE \
|
85
90
|
--entry-point=$YOUR_FUNCTION_TARGET \
|
86
91
|
--set-env-vars=FUNCTION_LOGGING_LEVEL=WARN
|
87
92
|
```
|
@@ -116,7 +121,7 @@ Dockerfile that you can use as a starting point. Feel free to adjust it to the
|
|
116
121
|
needs of your project:
|
117
122
|
|
118
123
|
```
|
119
|
-
FROM ruby:2.
|
124
|
+
FROM ruby:2.7
|
120
125
|
WORKDIR /app
|
121
126
|
COPY . .
|
122
127
|
RUN gem install --no-document bundler \
|
@@ -142,8 +147,8 @@ command may ask you for permission to enable the Cloud Build API for the project
|
|
142
147
|
if it isn't already enabled.
|
143
148
|
|
144
149
|
Because you provide your own Docker image when deploying to Cloud Run, you can
|
145
|
-
use any version of Ruby supported by the Functions Framework, from 2.
|
146
|
-
|
150
|
+
use any version of Ruby supported by the Functions Framework, from 2.5 through
|
151
|
+
3.0.
|
147
152
|
|
148
153
|
### Deploying an image to Cloud Run
|
149
154
|
|
data/docs/overview.md
CHANGED
@@ -8,8 +8,8 @@ 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) *(
|
12
|
-
* [
|
11
|
+
* [Google Cloud Functions](https://cloud.google.com/functions) *(public preview)*
|
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
|
15
15
|
|
@@ -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.8"
|
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
|
@@ -110,9 +110,8 @@ It is easy to connect an HTTP function to a Sinatra app. First, declare the
|
|
110
110
|
dependency on Sinatra in your `Gemfile`:
|
111
111
|
|
112
112
|
```ruby
|
113
|
-
# Gemfile
|
114
113
|
source "https://rubygems.org"
|
115
|
-
gem "functions_framework", "~> 0.
|
114
|
+
gem "functions_framework", "~> 0.8"
|
116
115
|
gem "sinatra", "~> 2.0"
|
117
116
|
```
|
118
117
|
|
@@ -198,6 +197,240 @@ FunctionsFramework.http "error_reporter" do |request|
|
|
198
197
|
end
|
199
198
|
```
|
200
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
|
+
|
201
434
|
## Structuring a project
|
202
435
|
|
203
436
|
A Functions Framework based "project" or "application" is a typical Ruby
|
@@ -207,15 +440,16 @@ needed by the function. It must include at least one Ruby source file that
|
|
207
440
|
defines functions, and can also include additional Ruby files defining classes
|
208
441
|
and methods that assist in the function implementation.
|
209
442
|
|
210
|
-
|
211
|
-
|
212
|
-
|
443
|
+
By convention, the "main" Ruby file that defines functions should be called
|
444
|
+
`app.rb` and be located at the root of the project. The path to this file is
|
445
|
+
sometimes known as the **function source**. The Functions Framework allows you
|
446
|
+
to specify an arbitrary source, but some hosting environments (such as Google
|
447
|
+
Cloud Functions) require it to be `./app.rb`.
|
213
448
|
|
214
|
-
|
215
|
-
|
216
|
-
cases.
|
449
|
+
A source file can define any number of functions (with distinct names). Each of
|
450
|
+
the names is known as a **function target**.
|
217
451
|
|
218
|
-
|
452
|
+
Following is a typical layout for a Functions Framework based project.
|
219
453
|
|
220
454
|
```
|
221
455
|
(project directory)
|
@@ -236,13 +470,16 @@ A simple project might look like this:
|
|
236
470
|
```ruby
|
237
471
|
# Gemfile
|
238
472
|
source "https://rubygems.org"
|
239
|
-
gem "functions_framework", "~> 0.
|
473
|
+
gem "functions_framework", "~> 0.8"
|
240
474
|
```
|
241
475
|
|
242
476
|
```ruby
|
243
477
|
# app.rb
|
244
478
|
require "functions_framework"
|
245
|
-
|
479
|
+
|
480
|
+
FunctionsFramework.on_startup do
|
481
|
+
require_relative "lib/hello"
|
482
|
+
end
|
246
483
|
|
247
484
|
FunctionsFramework.http "hello" do |request|
|
248
485
|
Hello.new(request).build_response
|
@@ -257,7 +494,7 @@ class Hello
|
|
257
494
|
end
|
258
495
|
|
259
496
|
def build_response
|
260
|
-
"Received request: #{request.
|
497
|
+
"Received request: #{@request.request_method} #{@request.url}\n"
|
261
498
|
end
|
262
499
|
end
|
263
500
|
```
|