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

Prepare or Initialise hook? #240

Open
jimmyff opened this issue May 28, 2021 · 4 comments
Open

Prepare or Initialise hook? #240

jimmyff opened this issue May 28, 2021 · 4 comments
Labels
kind/enhancement New feature or request

Comments

@jimmyff
Copy link

jimmyff commented May 28, 2021

I'm using GetIt for dependency injection. Is there a prepare/initialise method I could hook in to configure the DI or should I do this in the function body of each @CloudFunction()?

Thanks

@mtwichel
Copy link

mtwichel commented Jun 8, 2021

I think this could be super cool! I almost wonder if supporting middleware type functions could solve this usecause - ie functions that run before each function call. That way you could do thinks like initializing without writing to each method, but you could also verify authentication tokens, log requests to a custom server, ect.

I'm not sure how it would be defined, maybe add an @Middleware annotation to a function; then, when we build server.dart, we call these before calling the actual cloud function.

Or, they could be defined in the @CloudFunction() annotation itself? I'm thinking something like

Response middleware(Request request) {
    // do middleware stuff
}

@CloudFunction(
    middleware: [
        // functions here run before the function
        middleware
    ],
)
Response function(Request request) {
    // do function
}

Just ideas :) Happy to discuss further!

@mtwichel
Copy link

mtwichel commented Jun 22, 2021

Hey @jimmyff!

I just wanted to share a partial solution to Di in Dart Functions Framework using GetIt. We wrote an extension on the GetIt instance to register a singleton if not already set:

/// Extension on GetIt to register a singleton if not alreay set
extension RegisterIfNotSet on GetIt {
  /// Registers a singleton for type T if it doesn't already exist.
  /// Creates singleton with `create` function.
  Future<T> registerIfNotSet<T extends Object>({
    required FutureOr<T> Function() create,
  }) async {
    if (!GetIt.I.isRegistered<T>()) {
      GetIt.I.registerSingleton<T>(await create());
    }

    return GetIt.I.get<T>();
  }
}

You still have to create a new instance in your function code, but you do so like this:

final secretClient = await GetIt.I.registerIfNotSet<SecretClient>(
    create: () => SecretClient(),
  );

This is super nice for testing because you can mock a dependency using something like mockito (although I prefer mocktail these days), then in your test code register it as a singleton.

final SecretClient secretClient = MockSecretClient();
when(...).thenAnswer(...); // stubing methods as needed
GetIt.I.registerSingleton<SecretClient>(secretClient);

Then when your test code runs the actual function, it won't create a new dependency and instead will use the mocked one! Super handy!

I'd love to know if you have any feedback or similar ideas to this. I think it'd be cool if as a community we could build some best practices together around using this package as we're all doing some testing and as it inevitably grows. 😄

@jimmyff
Copy link
Author

jimmyff commented Jul 2, 2021

Wow this is great, thanks @mtwichel. I will definetly gibe your solution a try. I also use GetIt for the same reasons - it makes testing much easier. I'll have a go implementing this and let you now how I get on.

It would be quite nice to have something in the functions_framework to do this kind of thing, something along the lines of:

@Prepare()
void prepare(RequestContext context) { ... }

I imagine most people would not use it but if you do include it then it gets called right before your @CloudFunction()

@nickmeinhold
Copy link
Contributor

Just to throw in my 2c - I think the service locator pattern works really well here where we often want global state to cache expensive objects across function invocations, and lazy initialization to maximise cold start performance.

Personally I prefer to just add a simple Locator class rather than add an extra dependency.

There are of course good reasons not to use global state and I think that should probably be part of the conversation, but I'll just link to a good resource that discusses that:
https://gameprogrammingpatterns.com/service-locator.html#when-to-use-it

Thanks!

@kevmoo kevmoo added the kind/enhancement New feature or request label Dec 29, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind/enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

4 participants