-
Notifications
You must be signed in to change notification settings - Fork 78
docs: Type-safe code generated interface for future calls #390
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,82 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Setup | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| To create future calls, extend the `FutureCall` class and define the methods you wish to invoke at a later time. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ```dart | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import 'package:serverpod/serverpod.dart'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| class ExampleFutureCall extends FutureCall { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Future<void> doWork(Session session, String data) async { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Do something interesting in the future here. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+9
to
+11
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just to broaden the examples, it could be good to have two methods here and show one with |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| :::info | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| For a method to be recognized by Serverpod as a future call, it must return a `Future<void>` and take at least two parameters. The first parameter must be a [`Session`](../sessions) object. You can pass any serializable types as other parameters, and even use `List`, `Map`, `Set` or Dart records as long as they are typed. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is no longer true: methods can now have an arbitrary number of parameters, including only the session, right? We should also mention here that
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Methods with only the Session parameter are not supported for future call code generation.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm... I think this slipped by me on the revision. Can we patch the implementation to make them supported? This has a lot of valid use cases - like running routines that depend only on the database - and lacking this support deviates from the endpoint experience. I wouldn't oppose implementing this by adding a no-op parameter to the generated |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ::: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Next, you need to generate the code for your future calls. You do this by running `serverpod generate` in the server directory of your project: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ```bash | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $ cd your_server | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| $ serverpod generate | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| `serverpod generate` will create a type-safe interface for invoking the future calls in the server's `generated/future_calls.dart` file. This interface can be accessed from the Serverpod object. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+19
to
+26
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is no longer necessary to
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| The future calls you create are registered by `Serverpod` after the server starts. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is worth enforcing with a warning that trying to schedule future calls before server start will lead to exceptions. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ```dart | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| void run(List<String> args) async { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| final pod = Serverpod( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| args, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Protocol(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Endpoints(), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ... | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await pod.start(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ... | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+30
to
+44
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is best that the first examples are fully complete snippets that can be copy/pasted and work (or nearly due to the name of the project that goes in the imports). See the example below from authentication: serverpod_docs/docs/06-concepts/11-authentication/01-setup.md Lines 29 to 61 in 4367b7e
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| You are now able to register a future call to be invoked in the future by calling either `callWithDelay` or `callAtTime` depending on your needs. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Invoke the future call 1 hour from now by calling `callWithDelay`. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ```dart | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await pod.futureCalls | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .callWithDelay(const Duration(hours: 1)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .example | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .doWork('1'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Invoke the future call at a specific time and/or date in the future by calling `callAtTime`. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ```dart | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await pod.futureCalls | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .callAtTime(DateTime(2026, 1, 1)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .example | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .doWork('2'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| When registering a future call, it is also possible to give it an `identifier` so that it can be referenced later. The same identifier can be applied to multiple future calls. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ```dart | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await pod.futureCalls | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .callWithDelay( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const Duration(hours: 1), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| identifier: 'an-identifying-string', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .example | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .doWork('1'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| This identifier can then be used to cancel all future calls registered with said identifier. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ```dart | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await pod.futureCalls.cancel('an-identifying-string'); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| # Legacy | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is worth explaining why is this format legacy and in favor of what has it is been discontinued - citing the need for manual registration and the error-prone management of future calls by string names. It is good to have a warning pointing to the overview as the new recommended experience to highlight this. |
||
|
|
||
| Creating a future call is simple, extend the `FutureCall` class and override the `invoke` method. The method takes two params the first being the [`Session`](../sessions) object and the second being an optional SerializableModel ([See models](../models)). | ||
|
|
||
| ```dart | ||
| import 'package:serverpod/serverpod.dart'; | ||
| class ExampleFutureCall extends FutureCall<MyModelEntity> { | ||
| @override | ||
| Future<void> invoke(Session session, MyModelEntity? object) async { | ||
| // Do something interesting in the future here. | ||
| } | ||
| } | ||
| ``` | ||
|
|
||
| To let your Server get access to the future call you have to register it in the main run method in your `server.dart` file. You register the future call by calling `registerFutureCall` on the Serverpod object and giving it an instance of the future call together with a string that gives the future call a name. The name has to be globally unique and is used to later invoke the future call. | ||
|
|
||
| ```dart | ||
| void run(List<String> args) async { | ||
| final pod = Serverpod( | ||
| args, | ||
| Protocol(), | ||
| Endpoints(), | ||
| ); | ||
| ... | ||
| pod.registerFutureCall(ExampleFutureCall(), 'exampleFutureCall'); | ||
| ... | ||
| } | ||
| ``` | ||
|
|
||
| You are now able to register a future call to be invoked in the future by calling either `futureCallWithDelay` or `futureCallAtTime` depending on your needs. | ||
|
|
||
| Invoke the future call 1 hour from now by calling `futureCallWithDelay`. | ||
|
|
||
| ```dart | ||
| await session.serverpod.futureCallWithDelay( | ||
| 'exampleFutureCall', | ||
| data, | ||
| const Duration(hours: 1), | ||
| ); | ||
| ``` | ||
|
|
||
| Invoke the future call at a specific time and/or date in the future by calling `futureCallAtTime`. | ||
|
|
||
| ```dart | ||
| await session.serverpod.futureCallAtTime( | ||
| 'exampleFutureCall', | ||
| data, | ||
| DateTime(2025, 1, 1), | ||
| ); | ||
| ``` | ||
|
|
||
| :::note | ||
| `data` is an object created from a class defined in one of your yaml files and has to be the same as the one you expect to receive in the future call. in the `model` folder, `data` may also be null if you don't need it. | ||
| ::: | ||
|
|
||
| When registering a future call, it is also possible to give it an `identifier` so that it can be referenced later. The same identifier can be applied to multiple future calls. | ||
|
|
||
| ```dart | ||
| await session.serverpod.futureCallWithDelay( | ||
| 'exampleFutureCall', | ||
| data, | ||
| const Duration(hours: 1), | ||
| identifier: 'an-identifying-string', | ||
| ); | ||
| ``` | ||
|
|
||
| This identifier can then be used to cancel all future calls registered with said identifier. | ||
|
|
||
| ```dart | ||
| await session.serverpod.cancelFutureCall('an-identifying-string'); | ||
| ``` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| { | ||
| "label": "Scheduling", | ||
| "collapsed": true | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -7,6 +7,8 @@ Following a simple set of rules, your server will stay compatible with older app | |||||||||
| 1. __Avoid changing parameter names in endpoint methods.__ In the REST API Serverpod generates, the parameters are passed by name. This means that changing the parameter names of the endpoint methods will break backward compatibility. | ||||||||||
| 2. __Do not delete endpoint methods or change their signature.__ Instead, add new methods if you must pass another set of parameters. Technically, you can add new named parameters if they are not required, but creating a new method may still feel cleaner. | ||||||||||
| 3. __Avoid changing or removing fields and types in the serialized classes.__ However, you are free to add new fields as long as they are nullable. | ||||||||||
| 4. __Avoid changing parameter names in future call methods.__ Changing the parameter names of the future call methods will break backward compatibility since parameters are passed by name. | ||||||||||
|
Comment on lines
9
to
+10
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggestion: Unrelated change, but it is worth for future calls (and only valid now with a recent PR).
Suggested change
|
||||||||||
| 5. __Do not delete future call methods or change their signature.__ Instead, add new methods if you must pass another set of parameters. | ||||||||||
|
|
||||||||||
| ## Managing breaking changes with endpoint inheritance | ||||||||||
|
|
||||||||||
|
|
||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The experience we want to provide with the docs is for users to receive as little context as they need to get started with a given feature, then be redirected to dedicated pages as needed to get deeper in details or more advanced scenarios. The "Working with Endpoints" and "Working with Models" pages are not examples here, because they are presenting everything at once and have a refactor pending. Take an inspiration on how we did the
Authenticationand eachProviderunder it.Future calls are different from authentication, though, because they require no setup to work - as with models and endpoints. Although there is the possibility to configure, this is optional and should be shown separately.
This overview page should start with the general context (as it has now) and proceed guiding the user on how to create a simple
FutureCall. Think of this first page more like a guide than a documentation. Then, all details that could be mentioned along the way can be noted in infos/tips/notes that point to the more advanced page.It is worth mentioning in the feature presentation as it being the same experience as endpoints, but for scheduling future work.