Skip to content
Open
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
Original file line number Diff line number Diff line change
@@ -1,92 +1,16 @@
# Scheduling
# Overview
Copy link
Collaborator

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 Authentication and each Provider under 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.


With Serverpod you can schedule future work with the `future call` feature. Future calls are calls that will be invoked at a later time. An example is if you want to send a drip-email campaign after a user signs up. You can schedule a future call for a day, a week, or a month. The calls are stored in the database, so they will persist even if the server is restarted.

A future call is guaranteed to only execute once across all your instances that are running, but execution failures are not handled automatically. It is your responsibility to schedule a new future call if the work was not able to complete.

## Future calls

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)).

:::info
The future call feature is not enabled when running Serverpod in serverless mode.
:::

```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');
```

## Configuration

Future calls can be configured using options defined in the configuration files or environment variables. For a detailed list of configuration options, refer to the [Configuration](07-configuration.md) page.
Future calls can be configured using options defined in the configuration files or environment variables. For a detailed list of configuration options, refer to the [Configuration](../configuration) page.

Below is an example of how you can configure future calls in a YAML file:

Expand Down
82 changes: 82 additions & 0 deletions docs/06-concepts/14-scheduling/02-setup.md
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
Copy link
Collaborator

Choose a reason for hiding this comment

The 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 callAtTime and the other with callWithDelay below.

}
```

:::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.
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 no longer true: methods can now have an arbitrary number of parameters, including only the session, right? We should also mention here that Streaming parameters are not supported.

Copy link
Author

Choose a reason for hiding this comment

The 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.
They must have the Session parameter and then an arbitrary number of serializable parameters.

Copy link
Collaborator

Choose a reason for hiding this comment

The 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 FutureCall to be compliant with the invoke interface.

:::

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
Copy link
Collaborator

Choose a reason for hiding this comment

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

It is no longer necessary to cd into the server to run generate.

Suggested change
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.
Next, you need to generate the code for your future calls:
\```bash
$ serverpod generate
\```
Running 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 through the `pod.futureCalls` getter.

Ignore the backslash before the ``` markers. This is just to avoid breaking the suggestion.


The future calls you create are registered by `Serverpod` after the server starts.
Copy link
Collaborator

Choose a reason for hiding this comment

The 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
Copy link
Collaborator

Choose a reason for hiding this comment

The 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:

```dart
import 'package:serverpod/serverpod.dart';
import 'package:serverpod_auth_idp_server/core.dart';
import 'src/generated/protocol.dart';
import 'src/generated/endpoints.dart';
void run(List<String> args) async {
final pod = Serverpod(
args,
Protocol(),
Endpoints(),
);
// Set up authentication services
// The `pod.getPassword()` will get the value from `config/passwords.yaml`.
pod.initializeAuthServices(
tokenManagerBuilders: [
JwtConfig(
// Pepper used to hash the refresh token secret.
refreshTokenHashPepper: pod.getPassword('jwtRefreshTokenHashPepper')!,
// Algorithm used to sign the tokens (`hmacSha512` or `ecdsaSha512`).
algorithm: JwtAlgorithm.hmacSha512(
// Private key to sign the tokens. Must be a valid HMAC SHA-512 key.
SecretKey(pod.getPassword('jwtHmacSha512PrivateKey')!),
)
),
],
);
await pod.start();
}
```


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');
```
75 changes: 75 additions & 0 deletions docs/06-concepts/14-scheduling/03-legacy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Legacy
Copy link
Collaborator

Choose a reason for hiding this comment

The 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');
```
4 changes: 4 additions & 0 deletions docs/06-concepts/14-scheduling/_category_.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"label": "Scheduling",
"collapsed": true
}
2 changes: 2 additions & 0 deletions docs/06-concepts/17-backward-compatibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Collaborator

Choose a reason for hiding this comment

The 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
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.
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 or have a default value.
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.

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

Expand Down