Skip to content

Use Cloud Events for all events #66

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 11, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 46 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,43 +59,58 @@ dotnet new gcf-event
```

That will create the same set of files as before, but the `Function`
class now implements `ICloudEventFunction`. This is a function that
responds to [CNCF Cloud Events](https://cloudevents.io/). The procedure
for running a Cloud Event Function is exactly the same as for an
HTTP Function.

## Legacy Event Functions

Google Cloud Functions support for events predates the CNCF Cloud
Events initiative. "Legacy" event functions handle these events via
the `ILegacyEventFunction<T>` type. The type parameter `T`
represents the payload type of the event. Any class type that can be
deserialized via `System.Text.Json` can be used, but the Functions
Framework for .NET comes with the following payload types built-in,
all within the `Google.Cloud.Functions.LegacyEvents` namespace:

- `StorageObject` for Google Cloud Storage events
- `FirestoreEvent` for Cloud Firestore events
- `PubSubMessage` for Cloud Pub/Sub messages

See the Google Cloud Functions ["Events and Triggers"
documentation](https://cloud.google.com/functions/docs/concepts/events-triggers)
for more information about the triggers available.
class now implements `ICloudEventFunction<StorageObject>`. This is a
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels a little mixed now in that the line between "true" cloud events and converted legacy functions is blury. I believe it would be clearer if:

  • The explanation focuses on (typed) Cloud Events with a note "there are no pure cloud events right now, will be adding more information here when there are".
  • A ### title with something like Legacy events as Cloud Events, that starts with the paragraph marked as Note:, and includes the StorageObject example.
  • The untyped Cloud Event section as it is now.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The explanation is focused on the sample, which has to be for a concrete event type.
I actually like it being blurry between "true" cloud events and converted ones - because that should be regarded as an implementation detail, ideally. The note is really just in case anyone looks at the underlying HTTP request and gets confused.

function that responds to [CNCF Cloud
Events](https://cloudevents.io/), expecting data corresponding to a
Google Cloud Storage object. If you deploy the function with a
trigger of `google.storage.object.finalize` and then upload a new
object to Google Cloud Storage, the sample event will log the
details of the new event, including some properties of the storage
object.

The procedure for running a Cloud Event Function is exactly the same
as for an HTTP Function.

The type argument to the generic `ICloudEventFunction<TData>` interface
expresses the type of data your function expects within the
CloudEvent. This is deserialized using System.Text.Json
deserialization: you can specify any type, so long as
it can be deserialized appropriately from the Cloud Event data
attribute.

> **Note:**
> Google Cloud Functions support for events predates the CNCF Cloud
> Events initiative. The types in the `Google.Cloud.Functions.Framework.GcfEvents`
> namespace provide payloads for these events. The Functions Framework
> converts the Google Cloud Functions representation into a Cloud Event
> representation transparently, so as a developer you only need to
> handle Cloud Events.

### Untyped Cloud Event Functions

If you are experimenting with Cloud Events and don't yet have a
payload data model you wish to commit to, or you want your function
to be able to handle *any* CloudEvent, you can implement the
non-generic `ICloudEventFunction` interface. Your function's method
will then just be passed a `CloudEvent`, with no separate data object.

After installing the template package described earlier, use the
`gcf-legacy-event` template:
`gcf-untyped-event` template:

```sh
mkdir HelloLegacyEvents
cd HelloLegacyEvents
dotnet new gcf-legacy-event
mkdir HelloUntypedEvents
cd HelloUntypedEvents
dotnet new gcf-untyped-event
```

This will create a legacy event function consuming Google Cloud
Storage events. If you deploy the function with a trigger of
`google.storage.object.finalize` and then upload a new object to
Google Cloud Storage, the sample event will log the details of the
new event, including some properties of the storage object.
This will create a function that simply logs the information about
any Cloud Event it receives.

> **Note:**
> The automatic conversion of Google Cloud Functions events to
> Cloud Events only happens for typed Cloud Event functions. Untyped
> Cloud Event functions only succeed when the underlying HTTP request
> contains a Cloud Event.

## VB and F# support

Expand Down
4 changes: 4 additions & 0 deletions build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ set -e

export ContinuousIntegrationBuild=true
export Configuration=Release
# When building examples, build against the version in this
# repo rather than against NuGet; this allows us to make breaking
# changes.
export LocalFunctionsFramework=true

echo Building...
dotnet build -nologo -clp:NoSummary -v quiet src
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,26 +14,40 @@

namespace Google.Cloud.Functions.Examples.FSharpEventFunction

open CloudNative.CloudEvents
open Google.Cloud.Functions.Framework
open Google.Cloud.Functions.Framework.GcfEvents
open System.Threading.Tasks

/// <summary>
/// A function that can be triggered in responses to changes in Google Cloud Storage.
/// The type argument (StorageObject in this case) determines how the event payload is deserialized.
/// The event must be deployed so that the trigger matches the expected payload type. (For example,
/// deploying a function expecting a StorageObject payload will not work for a trigger that provides
/// a FirestoreEvent.)
/// </summary>
type Function() =
interface ICloudEventFunction with
interface ICloudEventFunction<StorageObject> with
/// <summary>
/// Logic for your function goes here. Note that a Cloud Event function just consumes an event;
/// it doesn't provide any response.
/// </summary>
/// <param name="cloudEvent">The Cloud Event your function should respond to.</param>
/// <param name="cloudEvent">The Cloud Event your function should consume.</param>
/// <param name="data">The deserialized data within the Cloud Event.</param>
/// <param name="cancellationToken">A cancellation token that is notified if the request is aborted.</param>
/// <returns>A task representing the asynchronous operation.</returns>
member this.HandleAsync(cloudEvent, cancellationToken) =
member this.HandleAsync(cloudEvent, data, cancellationToken) =
printfn "Storage object information:"
printfn " Name: %s" data.Name
printfn " Bucket: %s" data.Bucket
printfn " Size: %A" data.Size
printfn " Content type: %s" data.ContentType
printfn "Cloud event information:"
printfn "ID: %s" cloudEvent.Id
printfn "Source: %A" cloudEvent.Source
printfn "Type: %s" cloudEvent.Type
printfn "Subject: %s" cloudEvent.Subject
printfn "DataSchema: %A" cloudEvent.DataSchema
printfn "DataContentType: %A" cloudEvent.DataContentType
printfn "SpecVersion: %A" cloudEvent.SpecVersion
printfn "Data: %A" cloudEvent.Data
printfn " ID: %s" cloudEvent.Id
printfn " Source: %A" cloudEvent.Source
printfn " Type: %s" cloudEvent.Type
printfn " Subject: %s" cloudEvent.Subject
printfn " DataSchema: %A" cloudEvent.DataSchema
printfn " DataContentType: %A" cloudEvent.DataContentType
printfn " SpecVersion: %A" cloudEvent.SpecVersion
Task.CompletedTask

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright 2020, Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

namespace Google.Cloud.Functions.Examples.FSharpUntypedEventFunction

open Google.Cloud.Functions.Framework
open System.Threading.Tasks

type Function() =
interface ICloudEventFunction with
/// <summary>
/// Logic for your function goes here. Note that a Cloud Event function just consumes an event;
/// it doesn't provide any response.
/// </summary>
/// <param name="cloudEvent">The Cloud Event your function should consume.</param>
/// <param name="cancellationToken">A cancellation token that is notified if the request is aborted.</param>
/// <returns>A task representing the asynchronous operation.</returns>
member this.HandleAsync(cloudEvent, cancellationToken) =
printfn "Cloud event information:"
printfn "ID: %s" cloudEvent.Id
printfn "Source: %A" cloudEvent.Source
printfn "Type: %s" cloudEvent.Type
printfn "Subject: %s" cloudEvent.Subject
printfn "DataSchema: %A" cloudEvent.DataSchema
printfn "DataContentType: %A" cloudEvent.DataContentType
printfn "SpecVersion: %A" cloudEvent.SpecVersion
printfn "Data: %A" cloudEvent.Data
Task.CompletedTask
Original file line number Diff line number Diff line change
Expand Up @@ -14,34 +14,47 @@

using CloudNative.CloudEvents;
using Google.Cloud.Functions.Framework;
using Google.Cloud.Functions.Framework.GcfEvents;
using System;
using System.Threading;
using System.Threading.Tasks;

namespace Google.Cloud.Functions.Examples.SimpleEventFunction
{
public class Function : ICloudEventFunction
/// <summary>
/// A function that can be triggered in responses to changes in Google Cloud Storage.
/// The type argument (StorageObject in this case) determines how the event payload is deserialized.
/// The event must be deployed so that the trigger matches the expected payload type. (For example,
/// deploying a function expecting a StorageObject payload will not work for a trigger that provides
/// a FirestoreEvent.)
/// </summary>
public class Function : ICloudEventFunction<StorageObject>
{
/// <summary>
/// Logic for your function goes here. Note that a Cloud Event function just consumes an event;
/// it doesn't provide any response.
/// </summary>
/// <param name="cloudEvent">The Cloud Event your function should respond to.</param>
/// <param name="cloudEvent">The Cloud Event your function should consume.</param>
/// <param name="data">The deserialized data within the Cloud Event.</param>
/// <param name="cancellationToken">A cancellation token that is notified if the request is aborted.</param>
/// <returns>A task representing the asynchronous operation.</returns>
public Task HandleAsync(CloudEvent cloudEvent, CancellationToken cancellationToken)
public Task HandleAsync(CloudEvent cloudEvent, StorageObject data, CancellationToken cancellationToken)
{
Console.WriteLine("Storage object information:");
Console.WriteLine($" Name: {data.Name}");
Console.WriteLine($" Bucket: {data.Bucket}");
Console.WriteLine($" Size: {data.Size}");
Console.WriteLine($" Content type: {data.ContentType}");
Console.WriteLine("Cloud event information:");
Console.WriteLine($"ID: {cloudEvent.Id}");
Console.WriteLine($"Source: {cloudEvent.Source}");
Console.WriteLine($"Type: {cloudEvent.Type}");
Console.WriteLine($"Subject: {cloudEvent.Subject}");
Console.WriteLine($"DataSchema: {cloudEvent.DataSchema}");
Console.WriteLine($"DataContentType: {cloudEvent.DataContentType}");
Console.WriteLine($"Time: {cloudEvent.Time?.ToUniversalTime():yyyy-MM-dd'T'HH:mm:ss.fff'Z'}");
Console.WriteLine($"SpecVersion: {cloudEvent.SpecVersion}");
Console.WriteLine($"Data: {cloudEvent.Data}");

Console.WriteLine($" ID: {cloudEvent.Id}");
Console.WriteLine($" Source: {cloudEvent.Source}");
Console.WriteLine($" Type: {cloudEvent.Type}");
Console.WriteLine($" Subject: {cloudEvent.Subject}");
Console.WriteLine($" DataSchema: {cloudEvent.DataSchema}");
Console.WriteLine($" DataContentType: {cloudEvent.DataContentType}");
Console.WriteLine($" Time: {cloudEvent.Time?.ToUniversalTime():yyyy-MM-dd'T'HH:mm:ss.fff'Z'}");
Console.WriteLine($" SpecVersion: {cloudEvent.SpecVersion}");

// In this example, we don't need to perform any asynchronous operations, so the
// method doesn't need to be declared async.
return Task.CompletedTask;
Expand Down

This file was deleted.

Loading