Skip to content
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

feat: introduce declarative function signatures #114

Merged
merged 6 commits into from Nov 4, 2021

Conversation

anniefu
Copy link
Contributor

@anniefu anniefu commented Nov 2, 2021

Allow expressing the function signature type configuration through code
instead of through FUNCTION_SIGNATURE_TYPE env var. This means
the FUNCTION_SIGNATURE_TYPE var can be omitted when deploying
a function with a declarative function signature.

The new signature also changes to using the official CloudEventInterface from the CloudEvents SDK instead of the Function Framework's custom class.

The primary difference is $cloudevent->getTime() returns a DateTime instead of a string. This can be converted back to a string as follows:

$cloudevent->getTime()->format(DateTimeInterface::RFC3339_EXTENDED);

Define your Function

index.php

<?php

require 'vendor/autoload.php';

use Psr\Http\Message\ServerRequestInterface;
use Google\CloudFunctions\FunctionsFramework;
use CloudEvents\V1\CloudEventInterface;
 
FunctionsFramework::http("helloHttp", func(ServerRequestInterface $request)
{
   return "Hello World from a PHP HTTP function!" . PHP_EOL;
});
 
FunctionsFramework::cloudEvent("helloCloudEvents", func(CloudEventInterface $cloudevent)
{
   // Print the whole CloudEvent
   $stdout = fopen('php://stdout', 'wb');
   fwrite($stdout, $cloudevent);
});

Run your function

php -S localhost:8080 vendor/bin/router.php

Allow expressing the function signature type configuration through code
instead of through `FUNCTION_SIGNATURE_TYPE` env var.

$invoker = new Invoker($target, $signatureType);
Copy link
Member

Choose a reason for hiding this comment

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

Do we not need to pass in the $signatureType to preserve backwards compatibility here? ie GCFv1 customers today might be using non-declarative cloudevent functions in PHP

Copy link
Contributor Author

@anniefu anniefu Nov 3, 2021

Choose a reason for hiding this comment

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

It still respects the env var, I just moved it from being read here to being read within invoker.php so I could test it properly. router.php is hard to test because it's being used as script instead of a just declaring a function or class.

Copy link
Collaborator

Choose a reason for hiding this comment

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

There are a few reasons why this is not a good idea

  1. This breaks backwards compatibility
  2. The decision to inject the environment variable was intentional. This is a best practice (see dependency injection), as it keeps the logic of where the value comes from (e.g. the environment) separate from the class which uses the value (e.g. the Invoker)
  3. Having the value be injectable is more testable, as you can test the class without having to mess with environment variables.

.github/workflows/conformance.yml Outdated Show resolved Hide resolved
@anniefu anniefu merged commit 84d8ee9 into GoogleCloudPlatform:master Nov 4, 2021
Copy link
Collaborator

@bshaffer bshaffer left a comment

Choose a reason for hiding this comment

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

Please do not tag a release until we resolve the backwards compatibility breaking issues


$invoker = new Invoker($target, $signatureType);
Copy link
Collaborator

Choose a reason for hiding this comment

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

There are a few reasons why this is not a good idea

  1. This breaks backwards compatibility
  2. The decision to inject the environment variable was intentional. This is a best practice (see dependency injection), as it keeps the logic of where the value comes from (e.g. the environment) separate from the class which uses the value (e.g. the Invoker)
  3. Having the value be injectable is more testable, as you can test the class without having to mess with environment variables.

public function __construct(callable $function, bool $marshalToCloudEventInterface = false)
{
$this->marshalToCloudEventInterface = $marshalToCloudEventInterface;
parent::__construct($function);
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is not optimal because you're changing the constructor of FunctionsWrapper, which has a $signature parameter that is now unavailable.

This is breaking backwards compatibility and also making that parameter unavailable to users.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I can add back the $signature optional parameter.

{
$signatureType = getenv('FUNCTION_SIGNATURE_TYPE', true) ?: 'http';
Copy link
Collaborator

Choose a reason for hiding this comment

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

It was an intentional choice to have the environment variable passed in. Injecting the environment variables is a better practice, easier to test, more reliable than having the logic exist in the Invoker itself. Also, this breaks backwards compatibility, if users were calling the Invoker directly. I think we need to roll back this change.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The issue I ran into was that I couldn't test CloudEvent functions properly with this code in router.php, because it is just a script and I couldn't figure out how to pass it a CloudEvent request.

I can add back the signature type parameter here, and then only read the env var if it's missing. That way it is not a change to the constructor?

Can we mark these classes as internal? In general, I don't think we want people to be using these directly.


private function __construct()
{
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

For more idiomatic PHP, this should be removed

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sounds good!

return Registry::$fnRegistry[$name][0];
}

public static function getFunctionHandle(string $name)
Copy link
Collaborator

Choose a reason for hiding this comment

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

In general static methods are something we try to avoid. It would be better to make these methods instance methods, and instantiate the registry at runtime.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah ok, we can use a singleton pattern instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants