diff --git a/docs/06-concepts/14-scheduling.md b/docs/06-concepts/14-scheduling.md deleted file mode 100644 index 1206f994..00000000 --- a/docs/06-concepts/14-scheduling.md +++ /dev/null @@ -1,133 +0,0 @@ -# Scheduling - -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 { - @override - Future 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 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. - -Below is an example of how you can configure future calls in a YAML file: - -```yaml -futureCallExecutionEnabled: true - -futureCall: - concurrencyLimit: 5 - scanInterval: 2000 -``` - -### Enable or disable future call execution - -This option allows you to enable or disable the execution of future calls. By default, it is set to `true`. You might want to disable future call execution in environments where you don't want background tasks to run, such as during testing or in a staging environment where you want to focus on API behavior without triggering scheduled tasks. - -Example configuration: - -```yaml -futureCallExecutionEnabled: false -``` - -### Concurrency limit - -This option sets the maximum number of future calls that can run concurrently. By default, it is set to `1`. Configuring this is useful if you have resource-intensive tasks and want to avoid overloading your server. For example, in a production environment, you might want to tune this value to ensure that not all of the server's resources are allocated to future calls, leaving room for other critical tasks. - -Setting this value to a negative number or `null` removes the limitation, allowing an unlimited number of concurrent future calls. However, this should be used with caution as it can lead to resource exhaustion. - -Example configuration: - -```yaml -futureCall: - concurrencyLimit: 5 # Adjust this value based on your server's capacity -``` - -### Scan interval - -This option determines how often the system scans for future calls to execute, in milliseconds. The default value is `5000` (5 seconds). Adjusting this interval can help balance responsiveness and resource usage. For example, reducing the interval can make future calls execute closer to their scheduled time, while increasing it can reduce database load in environments with limited resources. - -Example configuration: - -```yaml -futureCall: - scanInterval: 2000 # Adjust this value based on your server's responsiveness needs -``` diff --git a/docs/06-concepts/14-scheduling/01-setup.md b/docs/06-concepts/14-scheduling/01-setup.md new file mode 100644 index 00000000..59a9642d --- /dev/null +++ b/docs/06-concepts/14-scheduling/01-setup.md @@ -0,0 +1,103 @@ +# Setup + +Serverpod supports scheduling 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, a month, or a [recurring interval](recurring-task). 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. + +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 doWork(Session session, String data) async { + // Do something interesting in the future here. + } + + Future doOtherWork(Session session, String data) async { + // Do something interesting in the future here. + } +} +``` + +:::info +For a method to be recognized by Serverpod as a future call, it must return a `Future` 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. `Streaming` parameters are not supported. +::: + +Next, you need to generate the code for your future calls: + +```bash +$ 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. + +The future calls you create are registered by `Serverpod` after the server starts. + +```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 args) async { + final pod = Serverpod( + args, + Protocol(), + Endpoints(), + ); + + await pod.start(); +} +``` + +You are now able to schedule future calls to be invoked in the future by calling either `callWithDelay` or `callAtTime` depending on your needs. + +:::warning +Scheduling a future call before the server starts will lead to exceptions. +::: + +Invoke a future call 1 hour from now by calling `callWithDelay`. + +```dart +await pod.futureCalls + .callWithDelay(const Duration(hours: 1)) + .example + .doWork('1'); +``` + +Invoke a 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 + .doOtherWork('2'); +``` + +:::info +Scheduling a future call at a specific time/date will always resolve the `DateTime` to UTC. +::: + +When scheduling 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 scheduled with said identifier. + +```dart +await pod.futureCalls.cancel('an-identifying-string'); +``` + +:::info +The future call feature is not enabled when running Serverpod in serverless mode. +::: diff --git a/docs/06-concepts/14-scheduling/02-recurring-task.md b/docs/06-concepts/14-scheduling/02-recurring-task.md new file mode 100644 index 00000000..0ae25963 --- /dev/null +++ b/docs/06-concepts/14-scheduling/02-recurring-task.md @@ -0,0 +1,71 @@ +# Recurring Task + +The recommended way to achieve cron-like scheduling is by scheduling a future call inside another. +To set this up, extend the `FutureCall` class and define two methods. + +```dart +import 'package:serverpod/serverpod.dart'; + +class ExampleFutureCall extends FutureCall { + Future doWork(Session session, int input) async { + await _doWork(session, input); + } + + Future _doWork(Session session, int input) async { + session.log('Working with input $input'); + } +} +``` + +Next, generate the code for your future call: + +```bash +$ serverpod generate +``` + +:::info +Code is only generated for the public method while the private method contains the logic to be invoked as a recurring task. +::: + +Next, import the generated `endpoints.dart` file and schedule the recurring future call using the generated code: + +```dart +import 'package:serverpod/serverpod.dart'; +import 'generated/endpoints.dart'; + +class ExampleFutureCall extends FutureCall { + Future doWork(Session session, int input) async { + await session.serverpod.futureCalls + .callWithDelay(const Duration(minutes: 20)) + .example + .doWork(input + 1); + + await _doWork(session, input); + } + + Future _doWork(Session session, int input) async { + session.log('Working with input $input'); + } +} +``` + +Now when you schedule the `doWork` future call, it will continously invoke `_doWork` at an interval of 20 minutes. + +```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 args) async { + final pod = Serverpod( + args, + Protocol(), + Endpoints(), + ); + + await pod.start(); + await pod.futureCalls.callWithDelay(Duration(minutes: 20)).example.doWork(2); +} +``` diff --git a/docs/06-concepts/14-scheduling/03-inheritance.md b/docs/06-concepts/14-scheduling/03-inheritance.md new file mode 100644 index 00000000..9202385b --- /dev/null +++ b/docs/06-concepts/14-scheduling/03-inheritance.md @@ -0,0 +1,91 @@ +# Inheritance + +Inheritance gives you the possibility to modify the behavior of `FutureCall` classes defined in other Serverpod modules. If the parent `FutureCall` class was marked as `abstract`, no code is generated for it. + +Currently, there are the following possibilities to extend another `FutureCall` class: + +## Inheriting from a `FutureCall` class + +Given an existing `FutureCall` class, it is possible to extend or modify its behavior while retaining the already exposed methods. + +```dart +import 'package:serverpod/serverpod.dart'; + +class Greeter extends FutureCall { + Future hello(Session session, String name) async { + session.log('Hello $name'); + } +} + +class MyGreeter extends Greeter { + Future bye(Session session, String name) async { + session.log('Bye $name'); + } +} +``` + +The generated server code will now be able to access both `Greeter` and `MyGreeter`. +Whereas the `Greeter` only exposes the original `hello` method, `MyGreeter` now exposes both the inherited `hello` and its own `bye` methods. + +## Inheriting from a `FutureCall` class marked `abstract` + +Future calls marked as `abstract` are not added to the server. But if they are subclassed, their methods will be exposed through the subclass. + +```dart +import 'package:serverpod/serverpod.dart'; + +abstract class Greeter extends FutureCall { + Future hello(Session session, String name) async { + session.log('Hello $name'); + } +} + +class MyGreeter extends Greeter {} +``` + +Since `Greeter` is `abstract`, it will not be added to the server. However, `MyGreeter` will expose a single `hello` method. + +:::info +Serverpod modules can expose future calls to users with `abstract` `FutureCall`. Code is only generated on the current project that extends the abstract future call. +::: + +### Extending an `abstract` `FutureCall` class + +In the above example, the `MyGreeter` only exposed the inherited `hello` method. It can be further extended with custom methods like this: + +```dart +import 'package:serverpod/serverpod.dart'; + +class MyGreeter extends Greeter { + Future bye(Session session, String name) async { + session.log('Bye $name'); + } +} +``` + +In this case, it will expose both a `hello` and a `bye` method. + +### Overriding future call methods + +It is possible to override methods of the superclass. This can be useful when you want to modify the behavior of specific methods but preserve the rest. + +```dart +import 'package:serverpod/serverpod.dart'; + +abstract class Greeter extends FutureCall { + Future hello(Session session, String name) async { + session.log('Hello $name'); + } +} + +class ExcitedGreeter extends Greeter { + @override + Future hello(Session session, String name) async { + session.log('Hello $name!!!'); + } +} +``` + +Since `Greeter` is `abstract`, it will not be exposed on the server. The `ExcitedGreeter` will expose a single `hello` method, and its implementation will augment the superclass's one by adding `!!!` to the output. + +This way, you can modify the behavior of future call methods while still sharing the implementation through calls to `super`. Be aware that the method signature has to be compatible with the base class per Dart's rules, meaning you can add optional parameters, but can not add required parameters or change the return type. diff --git a/docs/06-concepts/14-scheduling/04-configuration.md b/docs/06-concepts/14-scheduling/04-configuration.md new file mode 100644 index 00000000..afb7d137 --- /dev/null +++ b/docs/06-concepts/14-scheduling/04-configuration.md @@ -0,0 +1,47 @@ +# 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](../configuration) page. + +Below is an example of how you can configure future calls in a YAML file: + +```yaml +futureCallExecutionEnabled: true + +futureCall: + concurrencyLimit: 5 + scanInterval: 2000 +``` + +### Enable or disable future call execution + +This option allows you to enable or disable the execution of future calls. By default, it is set to `true`. You might want to disable future call execution in environments where you don't want background tasks to run, such as during testing or in a staging environment where you want to focus on API behavior without triggering scheduled tasks. + +Example configuration: + +```yaml +futureCallExecutionEnabled: false +``` + +### Concurrency limit + +This option sets the maximum number of future calls that can run concurrently. By default, it is set to `1`. Configuring this is useful if you have resource-intensive tasks and want to avoid overloading your server. For example, in a production environment, you might want to tune this value to ensure that not all of the server's resources are allocated to future calls, leaving room for other critical tasks. + +Setting this value to a negative number or `null` removes the limitation, allowing an unlimited number of concurrent future calls. However, this should be used with caution as it can lead to resource exhaustion. + +Example configuration: + +```yaml +futureCall: + concurrencyLimit: 5 # Adjust this value based on your server's capacity +``` + +### Scan interval + +This option determines how often the system scans for future calls to execute, in milliseconds. The default value is `5000` (5 seconds). Adjusting this interval can help balance responsiveness and resource usage. For example, reducing the interval can make future calls execute closer to their scheduled time, while increasing it can reduce database load in environments with limited resources. + +Example configuration: + +```yaml +futureCall: + scanInterval: 2000 # Adjust this value based on your server's responsiveness needs +``` diff --git a/docs/06-concepts/14-scheduling/05-legacy.md b/docs/06-concepts/14-scheduling/05-legacy.md new file mode 100644 index 00000000..0540884c --- /dev/null +++ b/docs/06-concepts/14-scheduling/05-legacy.md @@ -0,0 +1,79 @@ +# Legacy + +:::warning +This approach is error prone since it involves manually registering and scheduling future calls using string identifiers. The recommended way to interact with the future calls feature is through the [type-safe API](setup). +::: + +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 { + @override + Future 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 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'); +``` diff --git a/docs/06-concepts/14-scheduling/_category_.json b/docs/06-concepts/14-scheduling/_category_.json new file mode 100644 index 00000000..dc0af035 --- /dev/null +++ b/docs/06-concepts/14-scheduling/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Scheduling", + "collapsed": true +} \ No newline at end of file diff --git a/docs/06-concepts/17-backward-compatibility.md b/docs/06-concepts/17-backward-compatibility.md index 8e1f3597..55eaf519 100644 --- a/docs/06-concepts/17-backward-compatibility.md +++ b/docs/06-concepts/17-backward-compatibility.md @@ -6,7 +6,9 @@ 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. +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 diff --git a/versioned_docs/version-3.2.0/06-concepts/14-scheduling.md b/versioned_docs/version-3.2.0/06-concepts/14-scheduling.md deleted file mode 100644 index 1206f994..00000000 --- a/versioned_docs/version-3.2.0/06-concepts/14-scheduling.md +++ /dev/null @@ -1,133 +0,0 @@ -# Scheduling - -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 { - @override - Future 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 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. - -Below is an example of how you can configure future calls in a YAML file: - -```yaml -futureCallExecutionEnabled: true - -futureCall: - concurrencyLimit: 5 - scanInterval: 2000 -``` - -### Enable or disable future call execution - -This option allows you to enable or disable the execution of future calls. By default, it is set to `true`. You might want to disable future call execution in environments where you don't want background tasks to run, such as during testing or in a staging environment where you want to focus on API behavior without triggering scheduled tasks. - -Example configuration: - -```yaml -futureCallExecutionEnabled: false -``` - -### Concurrency limit - -This option sets the maximum number of future calls that can run concurrently. By default, it is set to `1`. Configuring this is useful if you have resource-intensive tasks and want to avoid overloading your server. For example, in a production environment, you might want to tune this value to ensure that not all of the server's resources are allocated to future calls, leaving room for other critical tasks. - -Setting this value to a negative number or `null` removes the limitation, allowing an unlimited number of concurrent future calls. However, this should be used with caution as it can lead to resource exhaustion. - -Example configuration: - -```yaml -futureCall: - concurrencyLimit: 5 # Adjust this value based on your server's capacity -``` - -### Scan interval - -This option determines how often the system scans for future calls to execute, in milliseconds. The default value is `5000` (5 seconds). Adjusting this interval can help balance responsiveness and resource usage. For example, reducing the interval can make future calls execute closer to their scheduled time, while increasing it can reduce database load in environments with limited resources. - -Example configuration: - -```yaml -futureCall: - scanInterval: 2000 # Adjust this value based on your server's responsiveness needs -``` diff --git a/versioned_docs/version-3.2.0/06-concepts/14-scheduling/01-setup.md b/versioned_docs/version-3.2.0/06-concepts/14-scheduling/01-setup.md new file mode 100644 index 00000000..59a9642d --- /dev/null +++ b/versioned_docs/version-3.2.0/06-concepts/14-scheduling/01-setup.md @@ -0,0 +1,103 @@ +# Setup + +Serverpod supports scheduling 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, a month, or a [recurring interval](recurring-task). 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. + +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 doWork(Session session, String data) async { + // Do something interesting in the future here. + } + + Future doOtherWork(Session session, String data) async { + // Do something interesting in the future here. + } +} +``` + +:::info +For a method to be recognized by Serverpod as a future call, it must return a `Future` 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. `Streaming` parameters are not supported. +::: + +Next, you need to generate the code for your future calls: + +```bash +$ 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. + +The future calls you create are registered by `Serverpod` after the server starts. + +```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 args) async { + final pod = Serverpod( + args, + Protocol(), + Endpoints(), + ); + + await pod.start(); +} +``` + +You are now able to schedule future calls to be invoked in the future by calling either `callWithDelay` or `callAtTime` depending on your needs. + +:::warning +Scheduling a future call before the server starts will lead to exceptions. +::: + +Invoke a future call 1 hour from now by calling `callWithDelay`. + +```dart +await pod.futureCalls + .callWithDelay(const Duration(hours: 1)) + .example + .doWork('1'); +``` + +Invoke a 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 + .doOtherWork('2'); +``` + +:::info +Scheduling a future call at a specific time/date will always resolve the `DateTime` to UTC. +::: + +When scheduling 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 scheduled with said identifier. + +```dart +await pod.futureCalls.cancel('an-identifying-string'); +``` + +:::info +The future call feature is not enabled when running Serverpod in serverless mode. +::: diff --git a/versioned_docs/version-3.2.0/06-concepts/14-scheduling/02-recurring-task.md b/versioned_docs/version-3.2.0/06-concepts/14-scheduling/02-recurring-task.md new file mode 100644 index 00000000..0ae25963 --- /dev/null +++ b/versioned_docs/version-3.2.0/06-concepts/14-scheduling/02-recurring-task.md @@ -0,0 +1,71 @@ +# Recurring Task + +The recommended way to achieve cron-like scheduling is by scheduling a future call inside another. +To set this up, extend the `FutureCall` class and define two methods. + +```dart +import 'package:serverpod/serverpod.dart'; + +class ExampleFutureCall extends FutureCall { + Future doWork(Session session, int input) async { + await _doWork(session, input); + } + + Future _doWork(Session session, int input) async { + session.log('Working with input $input'); + } +} +``` + +Next, generate the code for your future call: + +```bash +$ serverpod generate +``` + +:::info +Code is only generated for the public method while the private method contains the logic to be invoked as a recurring task. +::: + +Next, import the generated `endpoints.dart` file and schedule the recurring future call using the generated code: + +```dart +import 'package:serverpod/serverpod.dart'; +import 'generated/endpoints.dart'; + +class ExampleFutureCall extends FutureCall { + Future doWork(Session session, int input) async { + await session.serverpod.futureCalls + .callWithDelay(const Duration(minutes: 20)) + .example + .doWork(input + 1); + + await _doWork(session, input); + } + + Future _doWork(Session session, int input) async { + session.log('Working with input $input'); + } +} +``` + +Now when you schedule the `doWork` future call, it will continously invoke `_doWork` at an interval of 20 minutes. + +```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 args) async { + final pod = Serverpod( + args, + Protocol(), + Endpoints(), + ); + + await pod.start(); + await pod.futureCalls.callWithDelay(Duration(minutes: 20)).example.doWork(2); +} +``` diff --git a/versioned_docs/version-3.2.0/06-concepts/14-scheduling/03-inheritance.md b/versioned_docs/version-3.2.0/06-concepts/14-scheduling/03-inheritance.md new file mode 100644 index 00000000..9202385b --- /dev/null +++ b/versioned_docs/version-3.2.0/06-concepts/14-scheduling/03-inheritance.md @@ -0,0 +1,91 @@ +# Inheritance + +Inheritance gives you the possibility to modify the behavior of `FutureCall` classes defined in other Serverpod modules. If the parent `FutureCall` class was marked as `abstract`, no code is generated for it. + +Currently, there are the following possibilities to extend another `FutureCall` class: + +## Inheriting from a `FutureCall` class + +Given an existing `FutureCall` class, it is possible to extend or modify its behavior while retaining the already exposed methods. + +```dart +import 'package:serverpod/serverpod.dart'; + +class Greeter extends FutureCall { + Future hello(Session session, String name) async { + session.log('Hello $name'); + } +} + +class MyGreeter extends Greeter { + Future bye(Session session, String name) async { + session.log('Bye $name'); + } +} +``` + +The generated server code will now be able to access both `Greeter` and `MyGreeter`. +Whereas the `Greeter` only exposes the original `hello` method, `MyGreeter` now exposes both the inherited `hello` and its own `bye` methods. + +## Inheriting from a `FutureCall` class marked `abstract` + +Future calls marked as `abstract` are not added to the server. But if they are subclassed, their methods will be exposed through the subclass. + +```dart +import 'package:serverpod/serverpod.dart'; + +abstract class Greeter extends FutureCall { + Future hello(Session session, String name) async { + session.log('Hello $name'); + } +} + +class MyGreeter extends Greeter {} +``` + +Since `Greeter` is `abstract`, it will not be added to the server. However, `MyGreeter` will expose a single `hello` method. + +:::info +Serverpod modules can expose future calls to users with `abstract` `FutureCall`. Code is only generated on the current project that extends the abstract future call. +::: + +### Extending an `abstract` `FutureCall` class + +In the above example, the `MyGreeter` only exposed the inherited `hello` method. It can be further extended with custom methods like this: + +```dart +import 'package:serverpod/serverpod.dart'; + +class MyGreeter extends Greeter { + Future bye(Session session, String name) async { + session.log('Bye $name'); + } +} +``` + +In this case, it will expose both a `hello` and a `bye` method. + +### Overriding future call methods + +It is possible to override methods of the superclass. This can be useful when you want to modify the behavior of specific methods but preserve the rest. + +```dart +import 'package:serverpod/serverpod.dart'; + +abstract class Greeter extends FutureCall { + Future hello(Session session, String name) async { + session.log('Hello $name'); + } +} + +class ExcitedGreeter extends Greeter { + @override + Future hello(Session session, String name) async { + session.log('Hello $name!!!'); + } +} +``` + +Since `Greeter` is `abstract`, it will not be exposed on the server. The `ExcitedGreeter` will expose a single `hello` method, and its implementation will augment the superclass's one by adding `!!!` to the output. + +This way, you can modify the behavior of future call methods while still sharing the implementation through calls to `super`. Be aware that the method signature has to be compatible with the base class per Dart's rules, meaning you can add optional parameters, but can not add required parameters or change the return type. diff --git a/versioned_docs/version-3.2.0/06-concepts/14-scheduling/04-configuration.md b/versioned_docs/version-3.2.0/06-concepts/14-scheduling/04-configuration.md new file mode 100644 index 00000000..afb7d137 --- /dev/null +++ b/versioned_docs/version-3.2.0/06-concepts/14-scheduling/04-configuration.md @@ -0,0 +1,47 @@ +# 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](../configuration) page. + +Below is an example of how you can configure future calls in a YAML file: + +```yaml +futureCallExecutionEnabled: true + +futureCall: + concurrencyLimit: 5 + scanInterval: 2000 +``` + +### Enable or disable future call execution + +This option allows you to enable or disable the execution of future calls. By default, it is set to `true`. You might want to disable future call execution in environments where you don't want background tasks to run, such as during testing or in a staging environment where you want to focus on API behavior without triggering scheduled tasks. + +Example configuration: + +```yaml +futureCallExecutionEnabled: false +``` + +### Concurrency limit + +This option sets the maximum number of future calls that can run concurrently. By default, it is set to `1`. Configuring this is useful if you have resource-intensive tasks and want to avoid overloading your server. For example, in a production environment, you might want to tune this value to ensure that not all of the server's resources are allocated to future calls, leaving room for other critical tasks. + +Setting this value to a negative number or `null` removes the limitation, allowing an unlimited number of concurrent future calls. However, this should be used with caution as it can lead to resource exhaustion. + +Example configuration: + +```yaml +futureCall: + concurrencyLimit: 5 # Adjust this value based on your server's capacity +``` + +### Scan interval + +This option determines how often the system scans for future calls to execute, in milliseconds. The default value is `5000` (5 seconds). Adjusting this interval can help balance responsiveness and resource usage. For example, reducing the interval can make future calls execute closer to their scheduled time, while increasing it can reduce database load in environments with limited resources. + +Example configuration: + +```yaml +futureCall: + scanInterval: 2000 # Adjust this value based on your server's responsiveness needs +``` diff --git a/versioned_docs/version-3.2.0/06-concepts/14-scheduling/05-legacy.md b/versioned_docs/version-3.2.0/06-concepts/14-scheduling/05-legacy.md new file mode 100644 index 00000000..0540884c --- /dev/null +++ b/versioned_docs/version-3.2.0/06-concepts/14-scheduling/05-legacy.md @@ -0,0 +1,79 @@ +# Legacy + +:::warning +This approach is error prone since it involves manually registering and scheduling future calls using string identifiers. The recommended way to interact with the future calls feature is through the [type-safe API](setup). +::: + +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 { + @override + Future 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 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'); +``` diff --git a/versioned_docs/version-3.2.0/06-concepts/14-scheduling/_category_.json b/versioned_docs/version-3.2.0/06-concepts/14-scheduling/_category_.json new file mode 100644 index 00000000..dc0af035 --- /dev/null +++ b/versioned_docs/version-3.2.0/06-concepts/14-scheduling/_category_.json @@ -0,0 +1,4 @@ +{ + "label": "Scheduling", + "collapsed": true +} \ No newline at end of file diff --git a/versioned_docs/version-3.2.0/06-concepts/17-backward-compatibility.md b/versioned_docs/version-3.2.0/06-concepts/17-backward-compatibility.md index 8e1f3597..76309a3a 100644 --- a/versioned_docs/version-3.2.0/06-concepts/17-backward-compatibility.md +++ b/versioned_docs/version-3.2.0/06-concepts/17-backward-compatibility.md @@ -6,7 +6,9 @@ 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. +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 @@ -22,7 +24,7 @@ class TeamEndpoint extends Endpoint { Future join(Session session) async { // … } - + // many more methods, like `leave`, etc. }