diff --git a/articles/building-apps/application-layer/background-jobs/concurrency.adoc b/articles/building-apps/application-layer/background-jobs/concurrency.adoc new file mode 100644 index 0000000000..eb97fd98ff --- /dev/null +++ b/articles/building-apps/application-layer/background-jobs/concurrency.adoc @@ -0,0 +1,9 @@ +--- +title: Concurrent Jobs +description: How to handle concurrent job executions. +order: 30 +--- + += Concurrent Jobs + +// TODO Write about running the same job concurrently inside the same VM and on different VMs (but do this after you have written about server push in the presentation layer) diff --git a/articles/building-apps/application-layer/background-jobs/images/job-and-triggers.png b/articles/building-apps/application-layer/background-jobs/images/job-and-triggers.png new file mode 100644 index 0000000000..a4448abc82 Binary files /dev/null and b/articles/building-apps/application-layer/background-jobs/images/job-and-triggers.png differ diff --git a/articles/building-apps/application-layer/background-jobs/index.adoc b/articles/building-apps/application-layer/background-jobs/index.adoc new file mode 100644 index 0000000000..504b93cdf0 --- /dev/null +++ b/articles/building-apps/application-layer/background-jobs/index.adoc @@ -0,0 +1,264 @@ +--- +title: Background Jobs +description: How to handle background jobs in Vaadin applications. +order: 11 +--- + += Background Jobs + +Many business applications need to perform in background threads. These tasks could be long-running tasks triggered by the user, or scheduled jobs that run automatically at a specific time of day, or at specific intervals. + +Working with more than one thread increases the risk of bugs. Furthermore, there are many different ways of implementing background jobs. To reduce the risk, you should learn one way, and then apply it consistently in all your Vaadin applications. + +== Threads + +Whenever you work with background threads in a Vaadin application, you should never create new `Thread` objects directly. First, new threads are expensive to start. Second, the number of concurrent threads in a Java application is limited. An exact number is impossible to give, but typically it is measured in thousands. + +Instead, you should use thread pools, or virtual threads. + +A thread pool consists of a queue, and a pool of running threads. The threads pick tasks from the queue and execute them. When the thread pool receives a new job, it adds it to the queue. +The queue has an upper size limit. If the queue is full, the thread pool rejects the job, and throws an exception. + +Virtual threads were added in Java 21. Whereas ordinary threads are managed by the operating system, virtual threads are managed by the Java virtual machine. They are cheaper to start and run, which means you can have a much higher number of concurrent virtual threads than ordinary threads. + +See the https://docs.oracle.com/en/java/javase/21/core/virtual-threads.html[Java Documentation] for more information about virtual threads. + +== Task Execution + +The background jobs themselves should not need to manage their own thread pools, or virtual threads. Instead, they should use _executors_. An executor is an object that takes a `Runnable`, and executes it at some point in the future. Spring provides a `TaskExecutor`, that you should use in your background jobs. + +By default, Spring Boot sets up a `ThreadPoolTaskExecutor` in your application context. You can tweak the parameters of this executor through the `spring.task.executor.*` configuration properties. + +If you want to use virtual threads, you can enable them by setting the `spring.threads.virtual.enabled` configuration property to `true`. In this case, Spring Boot sets up a `SimpleAsyncTaskExecutor`, and creates a new virtual thread for every task. + +You can interact with the `TaskExecutor` either directly, or declaratively through annotations. + +When interacting with it directly, you inject an instance of `TaskExecutor` into your code, and submit work to it. Here is an example of a class that uses the `TaskExecutor`: + +[source,java] +---- +import org.springframework.core.task.TaskExecutor; + +@Component +public class MyWorker { + + private final TaskExecutor taskExecutor; + + MyWorker(TaskExecutor taskExecutor) { + this.taskExecutor = taskExecutor; + } + + public void performTask() { + taskExecutor.execute(() -> { + System.out.println("Hello, I'm running inside thread " + Thread.currentThread()); + }); + } +} +---- + +[IMPORTANT] +When you inject the `TaskExecutor`, you have to name the parameter `taskExecutor`. The application context may contain more than one bean that implements the `TaskExecutor` interface. If the parameter name does not match the name of the bean, Spring does not know which instance to inject. + +If you want to use annotations, you have to enable them before you can use them. Do this by adding the `@EnableAsync` annotation to your main application class, or any other `@Configuration` class: + +[source,java] +---- +import org.springframework.scheduling.annotation.EnableAsync; + +@SpringBootApplication +@EnableAsync +public class Application{ + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} +---- + +You can now use the `@Async` annotation to tell Spring to execute your code in a background thread: + +[source,java] +---- +import org.springframework.scheduling.annotation.Async; + +@Component +public class MyWorker { + + @Async + public void performTask() { + System.out.println("Hello, I'm running inside thread " + Thread.currentThread()); + } +} +---- + +See the https://docs.spring.io/spring-framework/reference/integration/scheduling.html[Spring Documentation] for more information about task execution. + +=== Caveats + +Using annotations makes the code more concise. However, they come with some caveats you need to be aware of. + +First, if you forget to add `@EnableAsync` to your application, and you call an `@Async` method, it executes in the calling thread, not in a background thread. + +Second, you cannot call an `@Async` method from within the bean itself. This is because Spring by default uses proxies to process `@Async` annotations, and local method calls bypass the proxy. In the following example, `performTask()` is executed in a background thread, and `performAnotherTask()` in the calling thread: + +[source,java] +---- +@Component +public class MyWorker { + + @Async + public void performTask() { + System.out.println("Hello, I'm running inside thread " + Thread.currentThread()); + } + + public void performAnotherTask() { + performTask(); // This call runs in the calling thread + } +} +---- + +If you interact with `TaskExecutor` directly, you avoid this problem: + +[source,java] +---- +@Component +public class MyWorker { + + private final TaskExecutor taskExecutor; + + MyWorker(TaskExecutor taskExecutor) { + this.taskExecutor = taskExecutor; + } + + public void performTask() { + taskExecutor.execute(() -> { + System.out.println("Hello, I'm running inside thread " + Thread.currentThread()); + }); + } + + public void performAnotherTask() { + performTask(); // This call runs in a background thread + } +} +---- + +In this case, both `performTask()` and `performAnotherTask()` execute in a background thread. + +== Task Scheduling + +Spring also has built in support for scheduling tasks through a `TaskScheduler`. You can interact with it either directly, or through annotations. In both cases, you have to enable it by adding the `@EnableScheduling` annotation to your main application class, or any other `@Configuration` class: + +[source,java] +---- +import org.springframework.scheduling.annotation.EnableScheduling; + +@SpringBootApplication +@EnableScheduling +public class Application{ + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} +---- + +When interacting with the `TaskScheduler` directly, you inject it into your code, and schedule wok with it. Here is an example class that uses the `TaskScheduler`: + +[source,java] +---- +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.scheduling.TaskScheduler; + +@Component +class MyScheduler implements ApplicationListener { + + private final TaskScheduler taskScheduler; + + MyScheduler(TaskScheduler taskScheduler) { + this.taskScheduler = taskScheduler; + } + + @Override + public void onApplicationEvent(ApplicationReadyEvent event) { + taskScheduler.scheduleAtFixedRate(this::performTask, Duration.ofMinutes(5)); + } + + private void performTask() { + System.out.println("Hello, I'm running inside thread " + Thread.currentThread()); + } +} +---- + +This example starts to call `performTask()` every 5 minutes after the application has started up. + +You can achieve the same using the `@Scheduled` annotation, like this: + +[source,java] +---- +import org.springframework.scheduling.annotation.Scheduled; + +@Component +class MyScheduler { + + @Scheduled(fixedRate = 5, timeUnit = TimeUnit.MINUTES) + public void performTask() { + System.out.println("Hello, I'm running inside thread " + Thread.currentThread()); + } +} +---- + +See the https://docs.spring.io/spring-framework/reference/integration/scheduling.html[Spring Documentation] for more information about task scheduling. + +=== Caveats + +Spring uses a separate thread pool for task scheduling. The tasks themselves are also executed in this thread pool. If you have a small number of short tasks, this is not a problem. However, if you have many tasks, or long-running tasks, you may run into problems. For instance, your scheduled jobs may stop running because the thread pool has become exhausted. + +To avoid problems, you should use the scheduling thread pool to schedule jobs, and then hand them over to the task execution thread pool for execution. You can combine the `@Async` and `@Scheduled` annotations, like this: + +[source,java] +---- +@Component +class MyScheduler { + + @Scheduled(fixedRate = 5, timeUnit = TimeUnit.MINUTES) + @Async + public void performTask() { + System.out.println("Hello, I'm running inside thread " + Thread.currentThread()); + } +} +---- + +You can also interact with the `TaskScheduler` and `TaskExecutor` directly, like this: + +[source,java] +---- +@Component +class MyScheduler implements ApplicationListener { + + private final TaskScheduler taskScheduler; + private final TaskExecutor taskExecutor; + + MyScheduler(TaskScheduler taskScheduler, TaskExecutor taskExecutor) { + this.taskScheduler = taskScheduler; + this.taskExecutor = taskExecutor; + } + + @Override + public void onApplicationEvent(ApplicationReadyEvent event) { + taskScheduler.scheduleAtFixedRate(this::performTask, Duration.ofMinutes(5)); + } + + private void performTask() { + taskExecutor.execute(() -> { + System.out.println("Hello, I'm running inside thread " + Thread.currentThread()); + }); + } +} +---- + +== Building + +// TODO Come up with a better heading, and maybe a short intro to this section. + +section_outline::[] diff --git a/articles/building-apps/application-layer/background-jobs/jobs.adoc b/articles/building-apps/application-layer/background-jobs/jobs.adoc new file mode 100644 index 0000000000..388f235775 --- /dev/null +++ b/articles/building-apps/application-layer/background-jobs/jobs.adoc @@ -0,0 +1,91 @@ +--- +title: Implementing Jobs +description: How to implement backgorund jobs. +order: 10 +--- + += Implementing Jobs + +When you implement a background job, you should decouple its implementation from how it is triggered, and where it is executed. This makes it possible to trigger the job in multiple ways. + +For instance, you may want to run the job every time the application starts up. In this case, you may want to run it in the main thread, blocking the initialization of the rest of the application until the job is finished. You may also want to run the job in a background thread every day at midnight, or whenever a certain application event is published. + +image::images/job-and-triggers.png[A job with three triggers] + +In code, a job is a Spring bean, annotated with the `@Component` or `@Service` annotation. It contains one or more methods, that when called, execute the job in the calling thread, like this: + +[source,java] +---- +import org.springframework.stereotype.Component; + +@Component +public class MyBackgroundJob { + + public void performBackgroundJob() { + ... + } +} +---- + +If the job is <> from within the same package, the class should be package private. Otherwise, it has to be public. + +== Transactions + +If the job works on the database, it should manage its own transactions. Because a job is a Spring bean, you can use either declarative, or programmatic transaction management. Here is the earlier example, with declarative transactions: + +[source,java] +---- +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Propagation; +import org.springframework.transaction.annotation.Transactional; + +@Component +public class MyBackgroundJob { + + @Transactional(propagation = Propagation.REQUIRES_NEW) + public void performBackgroundJob() { + ... + } +} +---- + +This guarantees that the job runs inside a new transaction, regardless of how it is triggered. + +== Security + +Unlike <<../application-services#,application services>>, background jobs should _not_ use method security. The reason is that Spring Security uses the `SecurityContext` to access information about the current user. This context is typically thread local, which means it is not available in a background thread. Therefore, whenever the job is executed by a background thread, Spring would deny access. + +If the background job needs information about the current user, this information should be passed to it by the <>, as an immutable method parameter. + +== Batch Jobs + +If you are writing a batch job that processes multiple inputs, you should consider implementing two versions of it: one that processes all applicable inputs, and another that processes a given set of inputs. For example, a batch job that generates invoices for shipped orders could look like this: + +[source,java] +---- +@Component +public class InvoiceCreationJob { + + @Transactional(propagation = Propagation.REQUIRES_NEW) + public void createInvoicesForOrders(Collection orders) { + ... + } + + @Transactional(propagation = Propagation.REQUIRES_NEW) + public void createInvoicesForAllApplicableOrders() { + ... + } +} +---- + +In this example, the first method creates invoices for the orders whose ID:s have been passed as parameters. The second method creates invoices for all orders that have been shipped and not yet invoiced. + +Implementing batch jobs like this does not require much effort if done from the start, but allows for flexibility that may be useful. Continuing on the invoice generation example, you may discover a bug in production. This bug has caused some orders to have bad data in the database. As a result, the batch job has not been able to generate invoices for them. Fixing the bug is easy, but your users do not want to wait for the next batch run to occur. Instead, as a part of the fix, you can add a button to the user interface that allows a user to trigger invoice generation for an individual order. + +== Idempotent Jobs + +Whenever you build a background job that updates, or generates data, you should consider making the job _idempotent_. An idempotent job leaves the database in the same state regardless of how many times it has been executed on the same input. + +For example, a job that generates invoices for shipped orders should always check that no invoice already exists before it generates a new one. Otherwise, some customers may end up getting multiple invoices because of an error somewhere. + +How to make a job idempotent depends on the job itself. It is therefore outside the scope of this documentation page. diff --git a/articles/building-apps/application-layer/background-jobs/triggers.adoc b/articles/building-apps/application-layer/background-jobs/triggers.adoc new file mode 100644 index 0000000000..8aacc65b8c --- /dev/null +++ b/articles/building-apps/application-layer/background-jobs/triggers.adoc @@ -0,0 +1,196 @@ +--- +title: Triggering Jobs +description: How to trigger backgorund jobs. +order: 20 +--- + += Triggering Jobs + +A trigger is an object that starts a job. It decides which thread the job should execute in, executes it, and handles any exceptions that occurred. + +image::images/job-and-triggers.png[A job with three triggers] + +Business applications have many different triggers. You may want to trigger some jobs on application startup. Other jobs may run once a week, every day at midnight, or every five minutes. Some jobs may run in response to application events, and others in response to user input. You might even have some jobs that can be triggered through a REST API, or through Java Management Extensions (JMX). You can even create multiple triggers for the same job. + +== User Triggered Jobs + +For user triggered jobs, an <<../application-services#,application service>> acts as the trigger. You can create a dedicated service class for this, or add a method to a suitable, existing application service. Like all other application service methods, it should be protected using method security. + +However, unlike a normal application service method, the service should let the job handle its own transactions. + +Here is an example of an application service that executes a job in a background thread when called: + +[source,java] +---- +@Service +public class MyApplicationService { + private static final Logger log = LoggerFactory.getLogger(MyApplicationService.class); + private final MyBackgroundJob job; + + MyApplicationService(MyBackgroundJob job) { + this.job = job; + } + + @PreAuthorize("hasAuthority('permission:startjob')") // <1> + @Async // <2> + public void startJob(MyJobParameters params) { + try { + job.executeJob(params); // <3> + } catch (Exception ex) { + log.error("Error executing background job", ex); // <4> + } + } +} +---- +<1> Spring ensures the current user has permission to start the job. +<2> Spring executes the method using its task executor thread pool. +<3> The application service delegates to the job, and passes data from the client. +<4> The application service logs any exceptions that may occur. + +This example uses the `@Async` annotation, but you can also execute the job <<../background-jobs#task-execution,programmatically>>. + +Sometimes, the background job needs to interact with the user interface. You may want to update a progress bar, do something with the results, or show an error message. To do that, you have to use server push, and design your service method in a particular way. You can find more information about this on the <<{articles}/building-apps/presentation-layer/server-push#,Server Push>> documentation page. + +== Event Triggered Jobs + +For event triggered jobs, you should create an event listener that receives events from Spring's event publisher. By default, the event publisher calls each listener in the same thread that published the event. You should therefore hand over the job to the `TaskExecutor`. + +Since the listener is not intended to be called by other objects, you should make it package private. + +Here is an example of a listener that executes a job in a background thread whenever a `MyEvent` is published: + +[source,java] +---- +@Component +class PerformBackgroundJobOnMyEventTrigger { // <1> + private static final Logger log = LoggerFactory.getLogger(PerformBackgroundJobOnMyEventTrigger.class); + private final MyBackgroundJob job; + + PerformBackgroundJobOnMyEventTrigger(MyBackgroundJob job) { + this.job = job; + } + + @EventListener // <2> + @Async // <3> + public void onMyEvent(MyEvent event) { + try { + job.executeJob(event.someDataOfInterestToTheJob()); // <4> + } catch (Exception ex) { + log.error("Error executing background job", ex); // <5> + } + } +} +---- +<1> Trigger is package private. +<2> Spring calls the trigger when the `MyEvent` is published. +<3> Spring executes the method using its task executor thread pool. +<4> The trigger delegates to the job, and passes data from the event. +<5> The trigger logs any exceptions that may occur. + +This example uses the `@Async` annotation, but you can also execute the job <<../background-jobs#task-execution,programmatically>>. + +== Scheduled Jobs + +For scheduled jobs, you should create a scheduler that uses Spring's scheduling mechanism to trigger the job. + +Spring uses a separate thread pool for scheduled tasks. You should not use this thread pool to execute the jobs. Instead, your schedulers should hand over the jobs to the `TaskExecutor`. + +Since the scheduler is not intended to be called by other objects, you should make it package private. + +Here is an example of a scheduler that schedules a job to execute every five minutes in a background thread: + +[source,java] +---- +@Component +class MyBackgroundJobScheduler { // <1> + + private static final Logger log = LoggerFactory.getLogger(MyBackgroundJobScheduler.class); + private final MyBackgroundJob job; + + MyBackgroundJobScheduler(MyBackgroundJob job) { + this.job = job; + } + + @Scheduled(fixedRate = 5, timeUnit = TimeUnit.MINUTES) // <2> + @Async // <3> + public void executeJob() { + try { + job.executeJob(); // <4> + } catch (Exception ex) { + log.error("Error executing scheduled job", ex); // <5> + } + } +} +---- +<1> Scheduler is package private. +<2> Spring calls the trigger every 5 minutes. +<3> Spring executes the method using its task executor thread pool. +<4> The scheduler delegates to the job. +<5> The scheduler logs any exceptions that may occur. + +This example uses the `@Scheduled` and `@Async` annotations, but you can also execute the job using the task scheduler and task executor <<../background-jobs#task-scheduling,programmatically>>. + +Programmatic schedulers are more verbose, but they are easier to debug. Therefore, you should start with annotations when you implement schedulers. If you later need more control over the scheduling, or run into problems that are difficult to debug, you should switch to a programmatic approach. + +== Startup Jobs + +For startup jobs, you should create a startup trigger that executes the job when the application starts. + +Since the trigger is not intended to be called by other objects, you should make it package private. + +If you want the initialization of the application to block until the job is finished, you should start the job inside the constructor of your trigger. Furthermore, you should execute the job in the calling thread, which in this case is Spring's main thread. If an error occurs during a job like this, you probably want the application to exit. Therefore, you can leave any exceptions unhandled. + +Here is an example of a trigger that blocks initialization until the job is finished: + +[source,java] +---- +@Component +class MyStartupTrigger { // <1> + + MyStartupTrigger(MyBackgroundJob job) { + job.executeJob(); // <2> + } +} +---- +<1> Trigger is package private. +<2> The trigger delegates to the job, and executes in the calling thread. + +[IMPORTANT] +Whenever you implement a startup trigger like this, you have to remember that the application is still being initialized. That means that not all services may be available for your job to use. + +If you want to trigger a job after the application has started, you should start the job in response to the `ApplicationReadyEvent` event. This event is published by Spring Boot when the application has started up and is ready to serve requests. Here is an example of a trigger that executes a job in a background thread after the application has started up: + +[source,java] +---- +import org.springframework.boot.context.event.ApplicationReadyEvent; + +@Component +class MyStartupTrigger { // <1> + + private static final Logger log = LoggerFactory.getLogger(MyStartupTrigger.class); + private final MyBackgroundJob job; + + MyStartupTrigger(MyBackgroundJob job) { + this.job = job; + } + + @EventListener // <2> + @Async // <3> + public void onApplicationReady(ApplicationReadyEvent event) { + try { + job.executeJob(); // <4> + } catch (Exception ex) { // <5> + log.error("Error executing job on startup", ex); + } + } +} +---- +<1> Trigger is package private. +<2> Spring calls the trigger when the `ApplicationReadyEvent` is published. +<3> Spring executes the method using its task executor thread pool. +<4> The trigger delegates to the job. +<5> The trigger logs any exceptions that may occur. + +This example uses the `@Async` annotation, but you can also execute the job <<../background-jobs#task-execution,programmatically>>. + +// TODO How to trigger jobs using Control Center? diff --git a/articles/building-apps/application-layer/domain-primitives.adoc b/articles/building-apps/application-layer/domain-primitives.adoc index 65781791c6..cb6a662fc0 100644 --- a/articles/building-apps/application-layer/domain-primitives.adoc +++ b/articles/building-apps/application-layer/domain-primitives.adoc @@ -1,7 +1,7 @@ --- title: Domain Primitives description: Learn what domain primitives are and how to use them in your applications. -order: 50 +order: 28 --- diff --git a/articles/building-apps/application-layer/persistence/index.adoc b/articles/building-apps/application-layer/persistence/index.adoc index 312aa07d25..4456d50968 100644 --- a/articles/building-apps/application-layer/persistence/index.adoc +++ b/articles/building-apps/application-layer/persistence/index.adoc @@ -1,7 +1,7 @@ --- title: Persistence description: How do handle persistence in Vaadin applications. -order: 20 +order: 40 --- = Persistence diff --git a/articles/building-apps/index.adoc b/articles/building-apps/index.adoc index 3fdf237b02..2a57673a26 100644 --- a/articles/building-apps/index.adoc +++ b/articles/building-apps/index.adoc @@ -7,7 +7,7 @@ section-nav: flat expanded // TODO Change order once there is more material -= Building Apps The Vaadin Way += Building Apps the Vaadin Way .Work in progress [IMPORTANT] diff --git a/articles/building-apps/presentation-layer/index.adoc b/articles/building-apps/presentation-layer/index.adoc new file mode 100644 index 0000000000..0ae0b36896 --- /dev/null +++ b/articles/building-apps/presentation-layer/index.adoc @@ -0,0 +1,5 @@ +--- +title: Presentation Layer +description: How to build the presentation layer of Vaadin applications. +order: 30 +--- \ No newline at end of file diff --git a/articles/building-apps/presentation-layer/server-push/index.adoc b/articles/building-apps/presentation-layer/server-push/index.adoc new file mode 100644 index 0000000000..4a0c87881e --- /dev/null +++ b/articles/building-apps/presentation-layer/server-push/index.adoc @@ -0,0 +1,9 @@ +--- +title: Server Push +description: How to use server push in your user interfaces. +order: 50 +--- + += Server Push + +// TODO Write about server push (and move some of the other documentation here as well)