diff --git a/.github/styles/config/vocabularies/Docs/accept.txt b/.github/styles/config/vocabularies/Docs/accept.txt index c6a3d66626..b2d7bd1cf5 100644 --- a/.github/styles/config/vocabularies/Docs/accept.txt +++ b/.github/styles/config/vocabularies/Docs/accept.txt @@ -28,6 +28,7 @@ bun [cC]acheable [cC]hatbot [cC]lasspath +[cC]olspan Codeium [cC]onfig(|uration|ure|urer|urator|map) [cC]onformant @@ -42,6 +43,7 @@ Datadog [dD]eclaratively DeltaSpike denylist +dev deregister(ed|ing) [dD]eserialization [dD]estructuring @@ -92,6 +94,7 @@ Hilla Initializr IntelliJ [iI]nterdependencies +iText [iI]terable Jakarta EE Jandex @@ -141,11 +144,13 @@ Minifinder [mM]ixin MobX [mM]odeless +[mM]odulith [mM]onospace [mM]ulticast [mM]ultiplatform [nN]amespace [nN]avbar +NET NetBeans npm npx @@ -161,6 +166,8 @@ OpenAPI [oO]verconsumption pageable [pP]asswordless +PDFBox +PDFs Payara performant [pP]ersister @@ -260,6 +267,7 @@ APIs CDN Cmd DOM +DTOs IDE JDK MVC @@ -267,6 +275,7 @@ LLMs PWA RPC SPI +SPIs SSDs SVG UI diff --git a/articles/building-apps/ai/index.adoc b/articles/building-apps/ai/index.adoc index 6ad780ba2a..92ce3bc221 100644 --- a/articles/building-apps/ai/index.adoc +++ b/articles/building-apps/ai/index.adoc @@ -9,9 +9,9 @@ order: 69 = AI in Vaadin Applications -In this section, you'll learn how to connect a Vaadin application to a Large Language Model (LLM) and integrate AI features into your workflows. Using common Vaadin application scenarios as examples, you'll explore typical UI patterns for AI integration. The focus is on simple, adaptable examples that you can quickly implement in your own projects. +In this section, you learn how to connect a Vaadin application to a Large Language Model (LLM) and integrate AI features into your workflows. Using common Vaadin application scenarios as examples, you explore typical UI patterns for AI integration. The focus is on simple, adaptable examples that you can quickly implement in your own projects. -You'll learn how to: +You learn how to: * connect your application to an AI client with popular Java libraries such as Spring AI and LangChain4j, * choose Vaadin components that create intuitive, AI-powered workflows -- such as `MessageInput`, `MessageList`, and `Scroller`, and diff --git a/articles/building-apps/ai/quickstart-guide.adoc b/articles/building-apps/ai/quickstart-guide.adoc index 3dd7a330b0..48ab77031e 100644 --- a/articles/building-apps/ai/quickstart-guide.adoc +++ b/articles/building-apps/ai/quickstart-guide.adoc @@ -8,9 +8,9 @@ section-nav: badge-flow --- -= Quick Start-Guide: Add an AI Chat Bot to a Vaadin + Spring Boot Application [badge-flow]#Flow# += Quick Start-Guide: Add an AI Chatbot to a Vaadin + Spring Boot Application [badge-flow]#Flow# -This guide shows how to connect a Large Language Model (LLM) into a Vaadin application using Spring AI and Spring Boot. You'll build a minimal chat UI with Vaadin provided components **MessageList** and **MessageInput**, stream responses token-by-token, and keep a conversational tone in the dialog with the AI. +This guide shows how to connect a Large Language Model (LLM) into a Vaadin application using Spring AI and Spring Boot. You build a minimal chat UI with Vaadin provided components **MessageList** and **MessageInput**, stream responses token-by-token, and keep a conversational tone in the dialog with the AI. image::images/chatbot-image.png[role=text-center] @@ -20,7 +20,7 @@ image::images/chatbot-image.png[role=text-center] == Prerequisites * Java 17+ -* Spring Boot 3.5+ (or newer) +* Spring Boot 3.5+ * Vaadin 24.8+ * An OpenAI API key (`OPENAI_API_KEY`) @@ -31,7 +31,7 @@ Download a Vaadin Spring starter from http://github.com/vaadin/skeleton-starter- **Pro Tip**: Starting the application with Hotswap Agent improves your development lifecycle. -Start with a cleaning and remove the default service `GreetService` and clear the existing UI content. You'll implement everything in `MainView`. +Start with a cleaning and remove the default service `GreetService` and clear the existing UI content. You implement everything in `MainView`. == 2. Add Spring AI dependencies @@ -72,7 +72,7 @@ Add the Spring AI BOM and the OpenAI starter to import the necessary dependencie == 3. Configure Your OpenAI Credentials -To access the API of OpenAI you need a license key. The preferred way to provide the key is through an environment variable, as this makes it available to other applications as well. After setting the environment variable on your system, refer to it from `application.properties` like this: +To access the API of OpenAI you need an API key. The preferred way to provide the key is through an environment variable, as this makes it available to other applications as well. After setting the environment variable on your system, refer to it from `application.properties` like this: [source,properties] ---- @@ -86,7 +86,7 @@ spring.ai.openai.api-key=${OPENAI_API_KEY} == 4. Enable Vaadin Push -To prevent end-users from sitting in front of a blank screen waiting for a response, you'll stream tokens asynchronously and update the UI live with response tokens. To do this, you need to enable server push: +To prevent end-users from sitting in front of a blank screen waiting for a response, you stream tokens asynchronously and update the UI live with response tokens. To do this, you need to enable server push: [source,java] ---- @@ -156,12 +156,12 @@ public class ChatService { ---- -Why a chat memory? **ChatMemory** keeps context of the conversations so users don't have to repeat themselves. The `chatId` keeps the context for a specific chat and doesn't share it with other chats and users. +Why a chat memory? **ChatMemory** keeps context of the conversations so users do not have to repeat themselves. The `chatId` keeps the context for a specific chat and does not share it with other chats and users. == 6. Build the Chat UI with Vaadin -Use `MessageList` to render the conversation as Markdown and `MessageInput` to handle the user prompts. Wrap the list in a `Scroller` so long chats don't grow the layout beyond the browser window: +Use `MessageList` to render the conversation as Markdown and `MessageInput` to handle the user prompts. Wrap the list in a `Scroller` so long chats do not grow the layout beyond the browser window: [source,java] ---- @@ -281,4 +281,4 @@ Start the application, open the browser, and try your first prompts. * `src/main/resources/application.properties` — OpenAI config * `pom.xml` — Vaadin + Spring AI dependencies -That's it — your Vaadin application now speaks AI. 🚀 +That is it -- your Vaadin application now speaks AI. diff --git a/articles/building-apps/ai/technical-setup/ide/index.adoc b/articles/building-apps/ai/technical-setup/ide/index.adoc index 16b0ad1e42..5ec54f0082 100644 --- a/articles/building-apps/ai/technical-setup/ide/index.adoc +++ b/articles/building-apps/ai/technical-setup/ide/index.adoc @@ -59,7 +59,7 @@ Never commit API keys to source control. Prefer environment variables or your CI == IDE Quick Guides -If you're unsure how to set environment variables in your specific IDE, see: +If you are unsure how to set environment variables in your specific IDE, see: * <> (Ultimate or Community) * <> diff --git a/articles/building-apps/ai/technical-setup/index.adoc b/articles/building-apps/ai/technical-setup/index.adoc index 88365b16dd..5559562c0c 100644 --- a/articles/building-apps/ai/technical-setup/index.adoc +++ b/articles/building-apps/ai/technical-setup/index.adoc @@ -55,7 +55,7 @@ Key characteristics: Popular tools: * https://ollama.com/[Ollama] - run many LLMs locally with a simple CLI and API. -* https://lmstudio.ai/[OpenLM Studio] - manage and run models locally with a graphical UI. +* https://lmstudio.ai/[LM Studio] - manage and run models locally with a graphical UI. === Quick Comparison diff --git a/articles/building-apps/architecture/api-spi.adoc b/articles/building-apps/architecture/api-spi.adoc index 9b6ed3af1c..77f30cedf2 100644 --- a/articles/building-apps/architecture/api-spi.adoc +++ b/articles/building-apps/architecture/api-spi.adoc @@ -12,18 +12,18 @@ order: 5 Whenever you design a building block for a Vaadin application - such as an application service or a UI component, you should think about how it interacts with the rest of the application. This typically happens through an *Application Programming Interface* (API), a *Service Provider Interface* (SPI), or a combination of both. -In this article, you'll learn what these mean, when to use them, and how to implement them in Java. +In this article, you learn what these mean, when to use them, and how to implement them in Java. == Application Programming Interface You typically design an API for either individual _classes_, or for entire _packages_. The API allows other parts of the application to _call_ your class or package. The other parts of the application depend on said class or package. -In this example, class B exposes an API that class A can call. Thus, class A depends on B and have to change if the API of class B changes: +In this example, class B exposes an API that class A can call. Thus, class A depends on B and has to change if the API of class B changes: image::images/api-dependency.png[A diagram of package A and package B, where package A depends on package B] -In Java, the API of a class is its _public_ methods. The API of a package are the _public_ classes and interfaces. All classes or methods that are not considered a part of the API should have a different visibility than public, such as package private. +In Java, the API of a class is its _public_ methods. The API of a package is the _public_ classes and interfaces. All classes or methods that are not considered a part of the API should have a different visibility than public, such as package private. In this example, `MyApplicationService` class is a part of the public API of the `com.example.application.service` package. The `publicApi()` method is a part of the public API of the class: @@ -105,7 +105,7 @@ public class MyApplicationService { === The API is Optional -Not all classes and packages require a public API. For instance, a UI view is typically only called by the web browser. Therefore, it doesn't need an API at all. +Not all classes and packages require a public API. For instance, a UI view is typically only called by the web browser. Therefore, it does not need an API at all. == Service Provider Interface @@ -129,7 +129,7 @@ The end result would have been the same -- package B calls package C -- but now As a rule of thumb, declare an SPI in the following cases: -1. *You don't yet know what the implementation is going to look like*. In this example, you can finish work on package B and use a mock implementation of the SPI until you start on the real implementation. +1. *You do not yet know what the implementation is going to look like*. In this example, you can finish work on package B and use a mock implementation of the SPI until you start on the real implementation. image:images/spi-unknown.png[A diagram of packages A and B, where a question mark points to an SPI of B] 2. *You need to support multiple implementations*. diff --git a/articles/building-apps/architecture/index.adoc b/articles/building-apps/architecture/index.adoc index f1d3130805..50917e42ca 100644 --- a/articles/building-apps/architecture/index.adoc +++ b/articles/building-apps/architecture/index.adoc @@ -11,7 +11,7 @@ section-nav: badge-deep-dive .Deep Dive - Recommended Approach [IMPORTANT] -This *opinionated* deep-dive explains core concepts (for example, architectural layers and monoliths) and gives a practical, recommended way to architect Vaadin applications. If you're new, this is a good place to start; if you're experienced, feel free to use whatever patterns work for you. +This *opinionated* deep-dive explains core concepts (for example, architectural layers and monoliths) and gives a practical, recommended way to architect Vaadin applications. If you are new, this is a good place to start; if you are experienced, feel free to use whatever patterns work for you. section_outline::[] diff --git a/articles/building-apps/architecture/layers.adoc b/articles/building-apps/architecture/layers.adoc index 6ead2f9644..db8c94777d 100644 --- a/articles/building-apps/architecture/layers.adoc +++ b/articles/building-apps/architecture/layers.adoc @@ -10,7 +10,7 @@ order: 1 = Conceptual Layers -If you have any previous experience with software architectures, you have probably heard about layers. You may have run into terms like “presentation layer”, “business logic layer”, “infrastructure layer”, etc. Layers can help you reason about the structure of the application, but they can also impose unnecessary restrictions. For instance, if you require that a layer can only depend on the layers below it, you can't use Service Provider Interfaces (SPI). Because of this, you should focus on system components with clear <> rather than layers in your Vaadin applications. +If you have any previous experience with software architectures, you have probably heard about layers. You may have run into terms like “presentation layer”, “business logic layer”, “infrastructure layer”, etc. Layers can help you reason about the structure of the application, but they can also impose unnecessary restrictions. For instance, if you require that a layer can only depend on the layers below it, you cannot use Service Provider Interfaces (SPI). Because of this, you should focus on system components with clear <> rather than layers in your Vaadin applications. That said, two layers make sense to use in Vaadin applications as well: the _UI layer_ (also known as the _presentation layer_) and the _application layer_. @@ -20,10 +20,10 @@ In traditional web applications, you have the _frontend_ and the _backend_. The [link=images/layers.png] image::images/layers.png[A diagram illustrating the UI layer and application layer of a Flow and a Hilla app, respectively] -When you are building your user interface with Flow, you write the user interface in Java and run it on the server - the backend. Unless you have created any web components of your own, all the code that runs in the browser -- the frontend -- is provided by Vaadin in one way or the other. The frontend and backend don't map directly onto the user interface and business logic. +When you are building your user interface with Flow, you write the user interface in Java and run it on the server - the backend. Unless you have created any web components of your own, all the code that runs in the browser -- the frontend -- is provided by Vaadin in one way or the other. The frontend and backend do not map directly onto the user interface and business logic. When you are building your user interface with Hilla, you write the user interface in React and run it in the browser. The rest of the application runs on the server. In this case, the frontend and backend correspond to the user interface and business logic. -It's also possible to write hybrid applications, where you write some parts of the user interface in Java and other parts in React. In this case, parts of the user interface run in the browser and parts on the server. +It is also possible to write hybrid applications, where you write some parts of the user interface in Java and other parts in React. In this case, parts of the user interface run in the browser and parts on the server. -Because of this, it makes more sense to talk about the UI layer and the application layer, as opposed to the frontend and the backend, or the user interface and the business logic. It's important to remember that these layers are _conceptual_ rather than physical. In a Flow or hybrid application, the UI layer covers both the browser and a part of the server. In a Hilla application, the UI layer is limited to the browser alone. In all cases, the application layer resides on the server. +Because of this, it makes more sense to talk about the UI layer and the application layer, as opposed to the frontend and the backend, or the user interface and the business logic. It is important to remember that these layers are _conceptual_ rather than physical. In a Flow or hybrid application, the UI layer covers both the browser and a part of the server. In a Hilla application, the UI layer is limited to the browser alone. In all cases, the application layer resides on the server. diff --git a/articles/building-apps/architecture/microservices.adoc b/articles/building-apps/architecture/microservices.adoc index 1260abd986..42bb87e18e 100644 --- a/articles/building-apps/architecture/microservices.adoc +++ b/articles/building-apps/architecture/microservices.adoc @@ -9,25 +9,24 @@ order: 3 = Microservices -Microservices is an architectural style that deconstructs a system into a collection of _loosely coupled_ and _independently deployable_ services. Each microservice is centered around a specific business capability and is developed, deployed and scaled independently, typically by one team. A microservice typically has its own database that's not shared with any other service. +Microservices is an architectural style that deconstructs a system into a collection of _loosely coupled_ and _independently deployable_ services. Each microservice is centered around a specific business capability and is developed, deployed, and scaled independently, typically by one team. A microservice typically has its own database that is not shared with any other service. Microservices interact over network calls and can therefore be built using different programming languages and frameworks, as long as the communication protocols (such as gRPC and JSON/REST/HTTP) are compatible. Because microservices can go down and back up at any time, they have to be resilient by design. A service calling another service should remain operational even if the other service is unavailable. -You can read more about microservices at, for example, https://martinfowler.com/microservices/[martinfowler.com], https://microservices.io/[microservices.io], or https://microservices.io/[Wikipedia]. +You can read more about microservices at, for example, https://martinfowler.com/microservices/[martinfowler.com], https://microservices.io/[microservices.io], or https://en.wikipedia.org/wiki/Microservices[Wikipedia]. -// TODO Add link to page about architectural styles once written == Microservices & Vaadin -Vaadin is not itself a platform for building microservices. However, you can build and deploy Vaadin applications for microservice environments. Since Vaadin applications are Spring Boot applications, they function well together with https://spring.io/projects/spring-cloud[Spring Cloud]. +Vaadin is not itself a platform for building microservices. However, you can build and deploy Vaadin applications for microservice environments. Since Vaadin applications are Spring Boot applications, they work well with https://spring.io/projects/spring-cloud[Spring Cloud]. -The two primary use cases for Vaadin in a microservice environment are building _dedicated_ user interfaces, and building _aggregating_ user interfaces. These are described next. If your use case doesn't fall in either of these categories, you may need to use something else for your user interface. +The two primary use cases for Vaadin in a microservice environment are building _dedicated_ user interfaces, and building _aggregating_ user interfaces. These are described next. If your use case does not fall in either of these categories, you may need to use something else for your user interface. === Dedicated User Interfaces -Some microservices can have their own user interfaces. For example, if a particular department within a larger organization has its own microservice, it makes sense that the user interface is also specific to that department. Since the microservice is designed for internal use, it's not likely to require as much scaling as, for example, a public-facing web shop. Therefore, it makes more sense to build the microservice as a Vaadin application, with its own Vaadin user interface. The service can still expose an API for other microservices to use. +Some microservices can have their own user interfaces. For example, if a particular department within a larger organization has its own microservice, it makes sense that the user interface is also specific to that department. Since the microservice is designed for internal use, it is not likely to require as much scaling as, for example, a public-facing web shop. Therefore, it makes more sense to build the microservice as a Vaadin application, with its own Vaadin user interface. The service can still expose an API for other microservices to use. Look at the following example: @@ -47,11 +46,10 @@ Look at the following example: [link=images/microservices-aggregating.png] image::images/microservices-aggregating.png[A diagram of a system with three separate microservices that share the same user interface] -In this fictional organization, the warehouse workers need access to both the product catalog, the inventory service and the shipping service. These microservices are all maintained by different teams, who use different frameworks (such as Spring Boot, Node.js and .NET) to build them. The microservices expose REST API:s for the user interface to use while communicating with each other through an event bus. +In this fictional organization, the warehouse workers need access to both the product catalog, the inventory service and the shipping service. These microservices are all maintained by different teams, who use different frameworks (such as Spring Boot, Node.js, and .NET) to build them. The microservices expose REST APIs for the user interface to use while communicating with each other through an event bus. -The warehouse UI is a Vaadin application that provides not only the user interface itself but also acts as its own _API gateway_. In other words, there's no need to set up a separate API gateway since the Vaadin application can communicate directly with the other microservices. This is effectively the Backend For Frontend (BFF) pattern. +The warehouse UI is a Vaadin application that provides not only the user interface itself but also acts as its own _API gateway_. In other words, there is no need to set up a separate API gateway since the Vaadin application can communicate directly with the other microservices. This is effectively the Backend For Frontend (BFF) pattern. -// TODO Is there a link to more information about BFF? == Advantages @@ -70,7 +68,7 @@ Since the microservices are decoupled and communicate over standardized network === Resilience -Well-designed microservices can handle failures gracefully, either by degrading functionality or by employing fallback mechanisms. Given the fact that they are also loosely coupled and independently deployable, the system as a whole becomes more resilient to disturbances. The failure of a single service won't take down the entire system. +Well-designed microservices can handle failures gracefully, either by degrading functionality or by employing fallback mechanisms. Because they are also loosely coupled and independently deployable, the system as a whole becomes more resilient to disturbances. The failure of a single service does not take down the entire system. === Continuous Deployment @@ -85,9 +83,9 @@ Microservices are useful for solving a certain class of problems. For other prob === Increased Complexity -As the number of microservices increases, so does the number of moving parts. Getting these parts to interact efficiently and consistently isn't easy. Data integrity is harder to achieve when multiple services are involved in implementing the same business process, especially when any of these services can go down at any time. +As the number of microservices increases, so does the number of moving parts. Getting these parts to interact efficiently and consistently is not easy. Data integrity is harder to achieve when multiple services are involved in implementing the same business process, especially when any of these services can go down at any time. -If you're not careful, your efforts may result in a distributed monolith that has none of the advantages of microservices, but all the disadvantages. In the worst case, you may end up with a _Distributed Ball of Mud_, which is even worse than a "Big Ball of Mud". +If you are not careful, your efforts may result in a distributed monolith that has none of the advantages of microservices, but all the disadvantages. In the worst case, you may end up with a _Distributed Ball of Mud_, which is even worse than a "Big Ball of Mud". === Technology Creep @@ -106,7 +104,7 @@ A distributed system consisting of multiple processes is more difficult to test == When to Use -Microservices have their advantages and disadvantages. It's important to know when it's a good choice. In summary, here are some factors that should exist for it to be the right choice: +Microservices have their advantages and disadvantages. It is important to know when it is a good choice. In summary, here are some factors that should exist for it to be the right choice: - You have more than one team working on the system. - Your system is providing more than one service to more than one group of users. diff --git a/articles/building-apps/architecture/monoliths.adoc b/articles/building-apps/architecture/monoliths.adoc index e38481cbec..ad898e4e6d 100644 --- a/articles/building-apps/architecture/monoliths.adoc +++ b/articles/building-apps/architecture/monoliths.adoc @@ -11,7 +11,7 @@ order: 2 In software engineering, a _monolithic application_ is a single, self-contained application. All system components run inside the same executable and communicate with each other over function calls. The application typically provides a single but complete service to its users. -Although a monolith often requires some supporting services like a database or an authentication provider, it's quite self-sufficient. In its simplest form, a monolithic application is a single executable that runs on a single server. +Although a monolith often requires some supporting services like a database or an authentication provider, it is quite self-sufficient. In its simplest form, a monolithic application is a single executable that runs on a single server. Even though monoliths are packaged as a single executable, they can still be modular. _Modular monoliths_ introduce clear boundaries between the different parts of the application and define how these parts can communicate with each other. Some parts may not know about the existence of other parts. A good way of building modular monoliths is to design them using <>. @@ -23,12 +23,12 @@ In the time of <>, monoliths have received a bit === Simplicity -Monoliths, when done well, are easy to develop, test, debug, and deploy. The components are contained inside a single codebase, and can often be contained inside a single project in your IDE. You need only to test and debug a single executable. You don't have to set up a lot of supporting services to deploy the monolith: often, the executable and a database are enough. +Monoliths, when done well, are easy to develop, test, debug, and deploy. The components are contained inside a single codebase, and can often be contained inside a single project in your IDE. You need only to test and debug a single executable. You do not have to set up a lot of supporting services to deploy the monolith: often, the executable and a database are enough. === Transactional Integrity -Since everything happens inside the same executable, you can rely on local transactions. You don't have to worry about things like distributed transactions or sagas. +Since everything happens inside the same executable, you can rely on local transactions. You do not have to worry about things like distributed transactions or sagas. === Less Overhead @@ -46,14 +46,14 @@ Despite the advantages mentioned before, some negative feelings about monoliths === Scalability Challenges -It is possible to scale a monolith. You can scale both up, by adding more hardware resources, and out, by deploying more instances of the monolith. However, you're always scaling the entire application. If only some parts of it require scaling, you may waste resources that cost money. +It is possible to scale a monolith. You can scale both up, by adding more hardware resources, and out, by deploying more instances of the monolith. However, you are always scaling the entire application. If only some parts of it require scaling, you may waste resources that cost money. You may also run into another type of scalability challenge: scaling the code. As the monolith grows and more features are added to it, increasing effort is needed to prevent the quality of the code from degrading. In the worst case, the monolith can degrade into an unmaintainable "Big Ball of Mud". === Continuous Deployment Challenges -Although you can use continuous deployment with a monolith, even small changes require rebuilding and deploying the entire application. If you haven't set up session replication and rolling updates, every redeployment means downtime for your users. +Although you can use continuous deployment with a monolith, even small changes require rebuilding and deploying the entire application. If you have not set up session replication and rolling updates, every redeployment means downtime for your users. === Team Collaboration Challenges diff --git a/articles/building-apps/architecture/packages.adoc b/articles/building-apps/architecture/packages.adoc index 491ecf9267..1b76ff906c 100644 --- a/articles/building-apps/architecture/packages.adoc +++ b/articles/building-apps/architecture/packages.adoc @@ -9,7 +9,7 @@ order: 6 = Package Structure :toclevels: 2 -When it comes to structuring the packages of a Java business application, there are two common paradigms: *package by layer* and *package by feature*. Both have their own pros and cons. In this article, you'll learn what they mean and how you can combine them to get the best of both worlds in your Vaadin applications. +When it comes to structuring the packages of a Java business application, there are two common paradigms: *package by layer* and *package by feature*. Both have their own pros and cons. In this article, you learn what they mean and how you can combine them to get the best of both worlds in your Vaadin applications. == Package by Layer @@ -34,11 +34,11 @@ When you package by layer, you put all classes that belong to the same architect └── Application ---- -This paradigm groups classes with similar responsibilities together. This leads to a *clear separation of concerns*. A class with too many responsibilities doesn't fit into any of the packages. This gives a natural inclination to split the class into smaller parts. +This paradigm groups classes with similar responsibilities together. This leads to a *clear separation of concerns*. A class with too many responsibilities does not fit into any of the packages. This gives a natural inclination to split the class into smaller parts. -One drawback of this approach is that most classes need to be public. This means classes across layers can directly access each other, potentially violating architectural boundaries. However, the architectural style often dictates a specific dependency flow, such as `ui` -> `service` -> `domain`. You'd have to use something like https://www.archunit.org/[ArchUnit] to ensure the dependencies between classes are according to your architectural style, or put each layer into a <>. Public visibility also makes it more difficult to separate <> from internal classes. +One drawback of this approach is that most classes need to be public. This means classes across layers can directly access each other, potentially violating architectural boundaries. However, the architectural style often dictates a specific dependency flow, such as `ui` -> `service` -> `domain`. You would have to use something such as https://www.archunit.org/[ArchUnit] to ensure the dependencies between classes are according to your architectural style, or put each layer into a <>. Public visibility also makes it more difficult to separate <> from internal classes. -Another drawback is that feature cohesion suffers. In the example above, all Customer-related code is spread across multiple packages, making it harder to understand the complete feature. This also has an impact on testing: you can't easily mock or isolate a complete feature. You often end up testing dependencies across multiple layers rather than testing a cohesive feature in isolation. +Another drawback is that feature cohesion suffers. In the example above, all Customer-related code is spread across multiple packages, making it harder to understand the complete feature. This also has an impact on testing: you cannot mock or isolate a complete feature. You often end up testing dependencies across multiple layers rather than testing a cohesive feature in isolation. From an evolutionary perspective, this approach makes it more difficult to split a <> into modules or <> if it grows too large. @@ -76,14 +76,14 @@ Furthermore, classes that constitute the <> of the feature The biggest issues with this approach are to decide _what a feature is_, and how to avoid making a mess _inside_ the feature package. -=== What's a Feature? +=== What Is a Feature? The term _feature_ is both inflated and vague in the software industry. Because of this the answer to the question depends on the nature and requirements of your application. If you are building a large application, it makes sense to package the application by _bounded context_. -.What's a Bounded Context? +.What is a Bounded Context? [NOTE] ==== -A _bounded context_ is a central pattern in Domain-Driven Design. It draws a clear boundary around a specific part of a software system. The concepts, rules, and language used inside that boundary are consistent and don't conflict with other parts of the system. +A _bounded context_ is a central pattern in Domain-Driven Design. It draws a clear boundary around a specific part of a software system. The concepts, rules, and language used inside that boundary are consistent and do not conflict with other parts of the system. Think of it as a "context bubble" where terms have a specific meaning. For example, the word _order_ might mean a customer's purchase in the *Sales* context but represent a stock replenishment request in the *Inventory* context. Because of this, the relationships between bounded contexts are explicit. In practice, this means explicitly defined APIs and SPIs. ==== @@ -103,7 +103,7 @@ com.example.application === Layers Inside Features -Features can grow quite big, which introduces the risk of the code inside the feature turning into a mess. To address this, you can package some of your classes by layer _inside_ the feature. In Vaadin applications -- and Flow applications in particular -- a first step is to split the <> into separate packages, like this: +Features can grow big, which introduces the risk of the code inside the feature turning into a mess. To address this, you can package some of your classes by layer _inside_ the feature. In Vaadin applications -- and Flow applications in particular -- a first step is to split the <> into separate packages, like this: [source] ---- @@ -120,9 +120,9 @@ Features can grow quite big, which introduces the risk of the code inside the fe └── + Application ---- -Now, the UI-related classes is in a separate `ui` package. The classes can have package visibility since they are only called by the web browser, not by other feature packages. They call the API of the root feature package, which has public visibility. +Now, the UI-related classes are in a separate `ui` package. The classes can have package visibility since they are only called by the web browser, not by other feature packages. They call the API of the root feature package, which has public visibility. -You may want to introduce other layers as well, such as `service` and `domain`, but then you'll again run into the problem of forced public visibility and unintended coupling. To address that, you can use ArchUnit or https://spring.io/projects/spring-modulith[Spring Modulith]. +You may want to introduce other layers as well, such as `service` and `domain`, but then you again run into the problem of forced public visibility and unintended coupling. To address that, you can use ArchUnit or https://spring.io/projects/spring-modulith[Spring Modulith]. === Beware of Database Coupling diff --git a/articles/building-apps/architecture/project-structure/index.adoc b/articles/building-apps/architecture/project-structure/index.adoc index 3e8b7cf8e6..1724b9e31c 100644 --- a/articles/building-apps/architecture/project-structure/index.adoc +++ b/articles/building-apps/architecture/project-structure/index.adoc @@ -9,9 +9,9 @@ order: 7 = Project Structure -A Vaadin project is essentially a https://spring.io/projects/spring-boot[Spring Boot] project, managed with https://maven.apache.org/[Maven]. As a result, if you're familiar with and already use Spring Boot, probably how you structure projects can work well with Vaadin. +A Vaadin project is essentially a https://spring.io/projects/spring-boot[Spring Boot] project, managed with https://maven.apache.org/[Maven]. As a result, if you are familiar with and already use Spring Boot, probably how you structure projects can work well with Vaadin. -If Spring Boot or Maven are new to you, though, you should first familiarize yourself with them before proceeding. Maven has a https://maven.apache.org/guides/getting-started/index.html[Getting Started Guide] that you'll find helpful. Spring Boot has an extensive https://docs.spring.io/spring-boot/index.html[reference manual] that includes a https://docs.spring.io/spring-boot/tutorial/first-application/index.html[tutorial]. +If Spring Boot or Maven are new to you, though, you should first familiarize yourself with them before proceeding. Maven has a https://maven.apache.org/guides/getting-started/index.html[Getting Started Guide] that is helpful. Spring Boot has an extensive https://docs.spring.io/spring-boot/index.html[reference manual] that includes a helpful https://docs.spring.io/spring-boot/tutorial/first-application/index.html[tutorial]. // TODO Mention Gradle @@ -37,7 +37,7 @@ This makes the deployment simple and with a few options. To deploy an applicatio // TODO Mention GraalVM -Another advantage to packaging an application as a JAR file is that it'll run inside its own Java Virtual Machine, and therefore its own operating system process. This allow you to configure and restart it without affecting other applications. Plus, if the application is compromised or crashes, other applications running on the same physical server are better protected since they run in their own processes. This feature, though, comes with a cost. +Another advantage to packaging an application as a JAR file is that it runs inside its own Java Virtual Machine, and therefore its own operating system process. This allows you to configure and restart it without affecting other applications. Plus, if the application is compromised or crashes, other applications running on the same physical server are better protected since they run in their own processes. This feature, though, comes with a cost. Since every application runs an embedded web server, they consume more memory and disk space than a traditional Java web application. However, since memory and disk space are quite cheap, the cost is usually well worth the benefits. @@ -46,7 +46,7 @@ Since every application runs an embedded web server, they consume more memory an Vaadin applications can also be packaged as https://docs.spring.io/spring-boot/how-to/deployment/traditional-deployment.html[traditional Java web applications]. The resulting WAR file must be deployed, though, to an external web application server. You can use an open source server (e.g., https://tomcat.apache.org/[Tomcat], https://jetty.org/index.html[Jetty], or https://undertow.io/[Undertow]). Or you can use a commercial server (e.g., JBoss or WebLogic). -A WAR file contains only the application and any dependencies not provided by the web application server. It's therefore smaller than a self-contained JAR file. Multiple WAR files can be deployed to the same web application server. Since the applications run inside the same JVM, the memory overhead is smaller compared to running each inside its own JVM. The drawback to this approach is that if something happens to the JVM process (e.g., a memory or thread leak), all of the applications are affected. +A WAR file contains only the application and any dependencies not provided by the web application server. It is therefore smaller than a self-contained JAR file. Multiple WAR files can be deployed to the same web application server. Since the applications run inside the same JVM, the memory overhead is smaller compared to running each inside its own JVM. The drawback to this approach is that if something happens to the JVM process (e.g., a memory or thread leak), all of the applications are affected. You should only consider WAR packaging if you already have a web application server, or if you need advanced features that are available only from a specific web application server. Otherwise, packaging as a JAR file is better. diff --git a/articles/building-apps/architecture/project-structure/multi-module.adoc b/articles/building-apps/architecture/project-structure/multi-module.adoc index 9eace2c710..85e8f43244 100644 --- a/articles/building-apps/architecture/project-structure/multi-module.adoc +++ b/articles/building-apps/architecture/project-structure/multi-module.adoc @@ -21,14 +21,14 @@ Multi-module projects are more complex than < ---- -<1> You can check for the latest version on the https://central.sonatype.com/artifact/org.springframework.boot/spring-boot-starter-parent[Maven Central Repository]. +<1> You can check for the latest version on the https://central.sonatype.com/artifact/org.springframework.boot/spring-boot-starter-parent[Maven Central Repository]. Declare all other dependency versions as project properties, like so: @@ -65,7 +65,7 @@ Declare all other dependency versions as project properties, like so: ---- <1> This property is used by `spring-boot-starter-parent` to configure the Java compiler plugin. -<2> You can check for the latest version on the https://central.sonatype.com/artifact/com.vaadin/vaadin-bom[Maven Central Repository]. +<2> You can check for the latest version on the https://central.sonatype.com/artifact/com.vaadin/vaadin-bom[Maven Central Repository]. Then import the Vaadin BOM, like this: @@ -96,7 +96,7 @@ To be able to use basic Spring features such as dependency injection, all module ---- -Unlike the single-module project, this POM file doesn't contain any plugins. Instead, it contains a section that lists all of the modules that should be included in the project build. Since at this point you won't have created any modules, add an empty section: +Unlike the single-module project, this POM file does not contain any plugins. Instead, it contains a section that lists all of the modules that should be included in the project build. Since at this point you have not created any modules, add an empty section: [source,xml] ---- @@ -159,7 +159,7 @@ Below is how a fully configured POM file for an empty multi-module Vaadin applic == Packages & Modules -The way you structure your modules depends on how you <<../packages#,structure your packages>>. If you are using *package by layer*, you'd typically have *one Maven module for each layer*. The end result would be a project structure that looks like this: +The way you structure your modules depends on how you <<../packages#,structure your packages>>. If you are using *package by layer*, you would typically have *one Maven module for each layer*. The end result would be a project structure that looks like this: [source] ---- @@ -187,7 +187,7 @@ The way you structure your modules depends on how you <<../packages#,structure y └── pom.xml ---- -If you are using *package by feature*, you'd typically have *one Maven module for each feature*. The end result would be a project structure that looks like this: +If you are using *package by feature*, you would typically have *one Maven module for each feature*. The end result would be a project structure that looks like this: [source] ---- @@ -235,7 +235,7 @@ Since all the modules are part of the same application, they should have the sam Each module still needs an `artifactId`. Use the same name for both the directory of a module and its `artifactId`. -After this, you'll need to declare the dependencies of the modules. For example, if you have a `service` module that depends on the `domain` service, you'd declare it like this: +After this, you need to declare the dependencies of the modules. For example, if you have a `service` module that depends on the `domain` service, you would declare it like this: .Service pom.xml [source,xml] @@ -284,7 +284,7 @@ Whenever you add a new module to a project, remember also to declare it in the p == UI Module -You'll need to add the Vaadin Maven plugin somewhere in your multi-module project. If you are using *package by layer*, the Vaadin dependencies and plugin go into the `ui` module. The complete POM file could look something like this: +You need to add the Vaadin Maven plugin somewhere in your multi-module project. If you are using *package by layer*, the Vaadin dependencies and plugin go into the `ui` module. The complete POM file could look something like this: .UI pom.xml [source,xml] @@ -343,7 +343,7 @@ Store frontend sources, such as CSS files and JavaScript, in the `src/main/front === Multiple UI Modules -If you are using *package by feature*, you'll have more than one UI module. In this case, you should treat the UI modules as Vaadin *add-ons*. This means you have to store module-specific CSS files, JavaScript, and images in the `src/main/resources/META-INF/resources` directory. See <> for more information. +If you are using *package by feature*, you have more than one UI module. In this case, you should treat the UI modules as Vaadin *add-ons*. This means you have to store module-specific CSS files, JavaScript, and images in the `src/main/resources/META-INF/resources` directory. See <> for more information. Add the theme files and the Vaadin Maven plugin to the _application module_, which is covered in the next section. @@ -404,7 +404,7 @@ The application module is an ordinary Maven module that contains at least the `A ---- -If you're using package by feature, the POM-file would also include the Vaadin Maven plugin: +If you are using package by feature, the POM-file would also include the Vaadin Maven plugin: .Package by Feature Application pom.xml [source,xml] @@ -475,7 +475,7 @@ If you're using package by feature, the POM-file would also include the Vaadin M == Priming Build -The first-ever build of a Maven project is called the _priming build_. During this build, all of the dependencies are downloaded and the plugins are executed for the first time. When you work with a Vaadin multi-module project, it's important to run the priming build either before, or directly after importing the project into your IDE. To perform a priming build, run this command in the root of the project: +The first-ever build of a Maven project is called the _priming build_. During this build, all of the dependencies are downloaded and the plugins are executed for the first time. When you work with a Vaadin multi-module project, it is important to run the priming build either before, or directly after importing the project into your IDE. To perform a priming build, run this command in the root of the project: [source,terminal] ---- @@ -484,5 +484,5 @@ mvn package During the priming build, the Vaadin Maven plugin generates several frontend files that are needed when the application runs. These files are generated into the module that configures the Vaadin Maven plugin. When you start the application, Vaadin finds these files and loads them, and any other frontend files you may have created, from the correct module. -Without the priming build, Vaadin would generate the missing files when the application starts for the first time. However, these files would end up in the module containing the `Application` class. If this is not the module that contains the Vaadin Maven plugin and your frontend files, the application won't work properly. +Without the priming build, Vaadin would generate the missing files when the application starts for the first time. However, these files would end up in the module containing the `Application` class. If this is not the module that contains the Vaadin Maven plugin and your frontend files, the application does not work properly. diff --git a/articles/building-apps/architecture/project-structure/single-module.adoc b/articles/building-apps/architecture/project-structure/single-module.adoc index 149023639f..752a14faaa 100644 --- a/articles/building-apps/architecture/project-structure/single-module.adoc +++ b/articles/building-apps/architecture/project-structure/single-module.adoc @@ -13,10 +13,10 @@ include::{articles}/_vaadin-version.adoc[] A single-module project consists of a single directory, with a single POM file and a single source directory. During the build, Maven packages the module either into a self-contained executable JAR file, or into a WAR file. -Single-module projects are simple and easy to understand. They're best suited for smaller applications, and ones that are maintained by small development teams. They're also suitable for new projects, since it's easy to migrate from a single-module project to a multi-module one -- if the need arises. +Single-module projects are simple and easy to understand. They are best suited for smaller applications, and ones that are maintained by small development teams. They are also suitable for new projects, since it is easy to migrate from a single-module project to a multi-module one -- if the need arises. [NOTE] -Your architecture imposes constraints on how parts of your code can interact. In a single-module project, all of the code is in the same place. There are no technical safeguards to prevent breaking these constraints -- intentionally or otherwise. The risk of this happening grows with the codebase or as the number of people involved increases. This can lead to the code becoming unmaintainable: known as a "Big Ball of Mud". When it seems this may be approaching, consider adding https://www.archunit.org/[ArchUnit] tests to your project, or converting to a <> project. +Your architecture imposes constraints on how parts of your code can interact. In a single-module project, all of the code is in the same place. There are no technical safeguards to prevent breaking these constraints -- intentionally or otherwise. The risk of this happening grows with the codebase or as the number of people involved increases. This can lead to the code becoming unmaintainable: known as a "Big Ball of Mud". When it seems this may be approaching, consider adding https://www.archunit.org/[ArchUnit] tests to your project, or converting to a <> project. // TODO Add link to page about ArchUnit, once written @@ -77,7 +77,7 @@ Like Spring Boot, Vaadin comes with its own set of dependencies. These are impor ---- -Importing the BOM makes the dependencies known to your project. When you use them, you don't have to declare their versions. To use the dependencies, add them to your project like so: +Importing the BOM makes the dependencies known to your project. When you use them, you do not have to declare their versions. To use the dependencies, add them to your project like so: [source,xml] ---- @@ -100,7 +100,7 @@ The only dependency you need for building a simple Vaadin application is `vaadin == Maven Plugins -Next, to build your project, you'll need to add two more Maven plugins: +Next, to build your project, you need to add two more Maven plugins: [source,xml] ---- diff --git a/articles/building-apps/business-logic/add-service.adoc b/articles/building-apps/business-logic/add-service.adoc index 85bf12138c..a3124db971 100644 --- a/articles/building-apps/business-logic/add-service.adoc +++ b/articles/building-apps/business-logic/add-service.adoc @@ -87,12 +87,12 @@ Vaadin does not enforce a specific naming convention for application services. W == Input & Output -Application services often need to communicate with <<../forms-data/persistence#,repositories or data access objects>> to fetch and store data. They also need to pass this data to the UI layer. For this, there are two options: pass the entities directly; or pass Data Transfer Objects (DTO:s). Both have advantages and disadvantages. +Application services often need to communicate with <<../forms-data/persistence#,repositories or data access objects>> to fetch and store data. They also need to pass this data to the UI layer. For this, there are two options: pass the entities directly; or pass Data Transfer Objects (DTOs). Both have advantages and disadvantages. === Entities -When the application service passes the entities directly to the UI layer, they become part of the application layer API. Many service methods delegate to the corresponding repository methods. Here's an example of this: +When the application service passes the entities directly to the UI layer, they become part of the application layer API. Many service methods delegate to the corresponding repository methods. Here is an example of this: [source,java] ---- @@ -119,22 +119,22 @@ Using entities in your application service is a good idea when your user interfa Your entities should be _anemic_, which means that they only contain data and little to no business logic. -In both cases, the user interface and the entities are likely to change at the same time, for the same reason. For example, if you need to add a field, you'll add it to both the user interface and the entity. +In both cases, the user interface and the entities are likely to change at the same time, for the same reason. For example, if you need to add a field, you add it to both the user interface and the entity. === Data Transfer Objects -Sometimes, application services shouldn't return the entities themselves. For instance, the domain model may contain business logic that must be called within some context that isn't available in the UI layer. It might require access to other services, or run inside a transaction. +Sometimes, application services should not return the entities themselves. For instance, the domain model may contain business logic that must be called within some context that is not available in the UI layer. It might require access to other services, or run inside a transaction. In other cases, the user interface may need only a subset of the data stored inside a single entity, or a combination of data from multiple entities. Fetching and returning the full entities would be a waste of resources. You may also have a situation where the domain model and user interface are changing independently of each other. For example, the domain model may have to be adjusted every year due to government regulations while the user interface remains about the same. -In this case, the application services should accept DTO:s as input, and return DTO:s as output. The entities should no longer be part of the application layer API. +In this case, the application services should accept DTOs as input, and return DTOs as output. The entities should no longer be part of the application layer API. -This adds another responsibility to the application service: mapping between entities and DTO:s. +This adds another responsibility to the application service: mapping between entities and DTOs. -When using query classes, you can do the mapping in them by returning their DTO:s, directly. The query DTO:s become part of the application layer API. +When using query classes, you can do the mapping in them by returning their DTOs, directly. The query DTOs become part of the application layer API. For storing data, services typically have to copy data from the DTO to the entity. For example, like this: @@ -167,12 +167,12 @@ public class CustomerCrudService { } ---- -When using DTO:s, you have more code to maintain. Some changes, like adding a new field to the application, requires more work. However, your user interface and domain model are isolated from each other, and can evolve independently. +When using DTOs, you have more code to maintain. Some changes, such as adding a new field to the application, require more work. However, your user interface and domain model are isolated from each other, and can evolve independently. === Domain Payload Objects -When using <<../forms-data/consistency/domain-primitives#,domain primitives>>, you should use them in your DTO:s, as well. In this case, the DTO:s are called _Domain Payload Objects_ (DPO). They're used in the exact same way as DTO:s. +When using <<../forms-data/consistency/domain-primitives#,domain primitives>>, you should use them in your DTOs, as well. In this case, the DTOs are called _Domain Payload Objects_ (DPO). They are used in the exact same way as DTOs. === Validation @@ -355,4 +355,4 @@ public class MyView extends Main { .Components Can Be Attached and Detached Multiple Times [IMPORTANT] -When adding a detach listener inside [methodname]`onAttach()`, always remove it when the component is detached. Otherwise, if the component is reattached later, multiple detach listeners will accumulate, leading to potential memory leaks. +When adding a detach listener inside [methodname]`onAttach()`, always remove it when the component is detached. Otherwise, if the component is reattached later, multiple detach listeners accumulate, leading to potential memory leaks. diff --git a/articles/building-apps/business-logic/background-jobs/index.adoc b/articles/building-apps/business-logic/background-jobs/index.adoc index 98aabe1bf1..71d69c9b31 100644 --- a/articles/building-apps/business-logic/background-jobs/index.adoc +++ b/articles/building-apps/business-logic/background-jobs/index.adoc @@ -1,6 +1,6 @@ --- title: Background Jobs -page-title: How handle background jobs in your Vaadin applications +page-title: How to Handle Background Jobs in Your Vaadin Applications description: How to handle background jobs. meta-description: There are different ways of implementing background jobs. To reduce the risk, you should learn one way, and then apply it consistently in your Vaadin apps. section-nav: badge-deep-dive @@ -10,13 +10,13 @@ order: 35 = Background Jobs [badge-deep-dive]#Deep Dive# -Many business applications need to perform in background threads. These tasks might be long-running operations triggered by the user, or scheduled jobs that run at specific times or intervals. +Many business applications need to perform tasks in background threads. These tasks might be long-running operations triggered by the user, or scheduled jobs that run at specific times or 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 of your Vaadin applications. .Deep Dive - Recommended Approach [IMPORTANT] -This *opinionated* deep-dive explains core concepts (for example, task executors in Spring) and gives a practical, recommended way to handle background jobs in Vaadin. If you're new, this is a good place to start; if you're experienced, feel free to use whatever patterns work for you. +This *opinionated* deep-dive explains core concepts (for example, task executors in Spring) and gives a practical, recommended way to handle background jobs in Vaadin. If you are new, this is a good place to start; if you are experienced, feel free to use whatever patterns work for you. == Threads @@ -27,14 +27,14 @@ Instead of creating threads manually, you should use either a thread pool, or vi 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're cheaper to start and run, which means you can have a much higher number of concurrent virtual threads than ordinary threads. If your virtual machine supports virtual threads, you should use them. +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. If your virtual machine supports virtual threads, you should use them. For more information on virtual threads, see the https://docs.oracle.com/en/java/javase/21/core/virtual-threads.html[Java Documentation]. == Task Execution -The background jobs themselves shouldn't 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. +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. @@ -68,11 +68,11 @@ public class MyWorker { ---- [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 doesn't match the name of the bean, Spring doesn't know which instance to inject. +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 first. Do this by adding the `@EnableAsync` annotation to your main application class, or any other `@Configuration` class. -Here's an example that adds the annotation to the main application class: +Here is an example that adds the annotation to the main application class: [source,java] ---- @@ -80,7 +80,7 @@ import org.springframework.scheduling.annotation.EnableAsync; @SpringBootApplication @EnableAsync -public class Application{ +public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); @@ -113,7 +113,7 @@ For more information about task execution, see the https://docs.spring.io/spring Using annotations makes the code more concise. However, they come with some caveats. -Should you forget to add `@EnableAsync` to your application, your `@Async` methods run synchronously in the calling thread instead of in a background thread. Also, you can't 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. +Should you forget to add `@EnableAsync` to your application, your `@Async` methods run synchronously in the calling thread instead of in a background thread. Also, 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: @@ -133,7 +133,7 @@ public class MyWorker { } ---- -If you interact directly with `TaskExecutor`, you'll avoid this problem. In the following example, both `performTask()` and `performAnotherTask()` execute in a background thread: +If you interact directly with `TaskExecutor`, you avoid this problem. In the following example, both `performTask()` and `performAnotherTask()` execute in a background thread: [source,java] ---- @@ -161,7 +161,7 @@ public class MyWorker { == Task Scheduling -Spring also has built in support for scheduling tasks through a `TaskScheduler`. You can interact with it either directly, or through annotations. With both, you have to enable it by adding the `@EnableScheduling` annotation to your main application class, or any other `@Configuration` class. +Spring also has built-in support for scheduling tasks through a `TaskScheduler`. You can interact with it either directly, or through annotations. With both, you have to enable it by adding the `@EnableScheduling` annotation to your main application class, or any other `@Configuration` class. Below is an example that adds the annotation to the main application class: @@ -179,7 +179,7 @@ public class Application{ } ---- -When interacting directly with the `TaskScheduler`, you'd inject it into your code, and schedule work with it. +When interacting directly with the `TaskScheduler`, you would inject it into your code, and schedule work with it. This is an example that uses the `TaskScheduler` to execute the `performTask()` method every five minutes: @@ -230,7 +230,7 @@ For more information about task scheduling, see the https://docs.spring.io/sprin === Task Scheduling Caveats -Spring uses a separate thread pool for task scheduling. The tasks themselves are also executed in this thread pool. That's fine if you have a small number of short tasks. However, if you have many tasks, or long-running tasks, you may have problems. For instance, your scheduled jobs might stop running because the thread pool has become exhausted. +Spring uses a separate thread pool for task scheduling. The tasks themselves are also executed in this thread pool. That is fine if you have a small number of short tasks. However, if you have many tasks, or long-running tasks, you may have problems. For instance, your scheduled jobs might stop running because the thread pool has become exhausted. To avoid trouble, you should use the scheduling thread pool to schedule jobs. Then give them to the task execution thread pool to execute. You can combine the `@Async` and `@Scheduled` annotations, like this: diff --git a/articles/building-apps/business-logic/background-jobs/interaction/callbacks.adoc b/articles/building-apps/business-logic/background-jobs/interaction/callbacks.adoc index 6bf0148094..44394fdceb 100644 --- a/articles/building-apps/business-logic/background-jobs/interaction/callbacks.adoc +++ b/articles/building-apps/business-logic/background-jobs/interaction/callbacks.adoc @@ -47,7 +47,7 @@ public void startBackgroundJob(Consumer onComplete, var result = doSomethingThatTakesALongTime(); onComplete.accept(result); } catch (Exception ex) { - onError.accept(result); + onError.accept(ex); } } ---- @@ -131,7 +131,7 @@ public void startBackgroundJob(Consumer onComplete, } ---- -All callbacks have to be thread-safe since they're called from the background thread, but owned and created by the user interface. For more information about how to implement these callbacks, see the <> documentation page. +All callbacks have to be thread-safe since they are called from the background thread, but owned and created by the user interface. For more information about how to implement these callbacks, see the <> documentation page. === Improving Cancel API @@ -184,13 +184,13 @@ public CancellableJob startBackgroundJob(Consumer onComplete, onComplete.accept(result); } catch (Exception ex) { - onError.accept(result); + onError.accept(ex); } }); return () -> cancelled.set(true); } ---- -The user interface would have to store the handle while the job is running, and call the `cancel()` method to cancel it. However, you can't use the `@Async` annotation in this case. It's because `@Async` methods can only return `void` or future-like types. In this case, you may want to return neither. +The user interface would have to store the handle while the job is running, and call the `cancel()` method to cancel it. However, you cannot use the `@Async` annotation in this case. It is because `@Async` methods can only return `void` or future-like types. In this case, you may want to return neither. -The handle itself is thread safe because you're using an `AtomicBoolean`. You don't need to take any special precautions to call it from the user interface. +The handle itself is thread safe because you are using an `AtomicBoolean`. You do not need to take any special precautions to call it from the user interface. diff --git a/articles/building-apps/business-logic/background-jobs/interaction/futures.adoc b/articles/building-apps/business-logic/background-jobs/interaction/futures.adoc index c95abed95a..09399c87b6 100644 --- a/articles/building-apps/business-logic/background-jobs/interaction/futures.adoc +++ b/articles/building-apps/business-logic/background-jobs/interaction/futures.adoc @@ -12,7 +12,7 @@ section-nav: badge-flow When using a Flow user interface, you can use a standard Java `CompletableFuture` to report results and errors to it, and to cancel the job. For reporting progress, however, you still need to use a callback. -Compared to <>, this approach is easier to implement in the application layer, but more difficult to implement in the UI layer. You should only use it if you've previously used `CompletableFuture`, and you need other features that it offers, like chaining completion stages together. +Compared to <>, this approach is easier to implement in the application layer, but more difficult to implement in the UI layer. You should only use it if you have previously used `CompletableFuture`, and you need other features that it offers, like chaining completion stages together. == Returning a Result @@ -34,11 +34,11 @@ To update the user interface, you have to add special completion stages that exe == Cancelling -You can cancel a Java `Future` by calling its `cancel()` method. The method has a `boolean` parameter that indicates whether the thread should be interrupted or not. However, `CompletableFuture`, which implements `Future`, doesn't use this parameter. It doesn't therefore make a difference whether you pass `true` or `false`. +You can cancel a Java `Future` by calling its `cancel()` method. The method has a `boolean` parameter that indicates whether the thread should be interrupted or not. However, `CompletableFuture`, which implements `Future`, does not use this parameter. Therefore, it does not make a difference whether you pass `true` or `false`. -When you cancel a `CompletableFuture`, it completes with a `CompletionException` caused by a `CancellationException`. However, the job continues to run silently in the background until it's finished. If you want to notify the job itself that it has been cancelled, and it should stop running at the next suitable moment, you'll have to make some changes. +When you cancel a `CompletableFuture`, it completes with a `CompletionException` caused by a `CancellationException`. However, the job continues to run silently in the background until it is finished. If you want to notify the job itself that it has been cancelled, and it should stop running at the next suitable moment, you have to make some changes. -`CompletableFuture` has an `isCancelled()` method that you can use to query whether the job has been cancelled or not. Therefore, you can no longer use the `@Async` annotation. Instead, you have to execute manually the job with the `TaskExecutor`, and manage the state of the returned `CompletableFuture`. The principle is the same as when using callbacks or handles. +`CompletableFuture` has an `isCancelled()` method that you can use to query whether the job has been cancelled or not. Therefore, you can no longer use the `@Async` annotation. Instead, you have to manually execute the job with the `TaskExecutor`, and manage the state of the returned `CompletableFuture`. The principle is the same as when using callbacks or handles. The earlier example would look like this when implemented using a `CompletableFuture`: @@ -73,4 +73,4 @@ public CompletableFuture startBackgroundJob() { } ---- -You don't need to do anything with the `future` after it has been cancelled, as it has already been completed. Returning is enough. +You do not need to do anything with the `future` after it has been cancelled, as it has already been completed. Returning is enough. diff --git a/articles/building-apps/business-logic/background-jobs/interaction/index.adoc b/articles/building-apps/business-logic/background-jobs/interaction/index.adoc index 4d363b779b..1d1cd430f3 100644 --- a/articles/building-apps/business-logic/background-jobs/interaction/index.adoc +++ b/articles/building-apps/business-logic/background-jobs/interaction/index.adoc @@ -9,9 +9,9 @@ order: 25 = User Interface Interaction -Some background jobs execute business processes in the background. The end user may see the result of the job, but doesn't have to interact directly with it. Scheduled and event triggered jobs are typically in this category. +Some background jobs execute business processes in the background. The end user may see the result of the job, but does not have to interact directly with it. Scheduled and event triggered jobs are typically in this category. -Then there are jobs that need to interact with the user interface. For instance, a job may want to update a progress indicator while running, and notify the user when it's finished or an error has occurred. Furthermore, the user may want to cancel a running job before it has completed. This page explains different options for allowing a user to interact with a background job, and vice versa. +Then there are jobs that need to interact with the user interface. For instance, a job may want to update a progress indicator while running, and notify the user when it is finished or an error has occurred. Furthermore, the user may want to cancel a running job before it has completed. This page explains different options for allowing a user to interact with a background job, and vice versa. == Options diff --git a/articles/building-apps/business-logic/background-jobs/interaction/reactive.adoc b/articles/building-apps/business-logic/background-jobs/interaction/reactive.adoc index 3889274d26..d30cb79c4c 100644 --- a/articles/building-apps/business-logic/background-jobs/interaction/reactive.adoc +++ b/articles/building-apps/business-logic/background-jobs/interaction/reactive.adoc @@ -13,12 +13,12 @@ order: 30 When using Flow or Hilla to build your user interface, you can use `Flux` or `Mono` from https://projectreactor.io/[Reactor] to allow your background jobs to interact with them. Reactor has an extensive API, which means you can do many things with it. This also means that it can be more difficult to learn than using callbacks or `CompletableFuture`. -If you're new to reactive programming, you should read Reactor's https://projectreactor.io/docs/core/release/reference/#intro-reactive[Introduction to Reactive Programming] before continuing. +If you are new to reactive programming, you should read Reactor's https://projectreactor.io/docs/core/release/reference/#intro-reactive[Introduction to Reactive Programming] before continuing. == Returning a Result -When you're using Reactor, you can't use the `@Async` annotation. Instead, you have to instruct your `Mono` or `Flux` to execute using the Spring `TaskExecutor`. Otherwise, your job executes in the thread that subscribes to the `Mono` or `Flux`. +When you are using Reactor, you cannot use the `@Async` annotation. Instead, you have to instruct your `Mono` or `Flux` to execute using the Spring `TaskExecutor`. Otherwise, your job executes in the thread that subscribes to the `Mono` or `Flux`. For example, a background job that returns a string or an exception could be implemented like this: @@ -62,10 +62,10 @@ public record BackgroundJobOutput( } ---- -The two built-in methods, `progressUpdate()` and `finished()` make the code look better when it's time to create instances of `BackgroundJobOutput`. +The two built-in methods, `progressUpdate()` and `finished()` make the code look better when it is time to create instances of `BackgroundJobOutput`. [NOTE] -If you've worked with sealed classes, you may be tempted to create a sealed interface called `BackgroundJobOutput`, and then create two records that implement that interface: one for progress updates; and another for the result. However, Hilla doesn't support this at the moment. +If you have worked with sealed classes, you may be tempted to create a sealed interface called `BackgroundJobOutput`, and then create two records that implement that interface: one for progress updates; and another for the result. However, Hilla does not support this at the moment. Next, you have to implement the background job like this: @@ -88,7 +88,7 @@ public Flux startBackgroundJob() { .subscribeOn(Schedulers.fromExecutor(taskExecutor)); return Flux.merge( // <3> - progress.asFlux().map(BackgroundJobOutput::progressUpdate), + progressUpdates.asFlux().map(BackgroundJobOutput::progressUpdate), result.map(BackgroundJobOutput::finished) ); } @@ -102,7 +102,7 @@ When your user interface subscribes to this `Flux`, it needs to check the state == Cancelling -You can cancel a subscription to a `Flux` or `Mono` at any time. However, as with `CompletableFuture`, cancelling the subscription doesn't stop the background job itself. To fix this, you need to tell the background job when it has been cancelled, so that it can stop. Continuing on the earlier example, adding support for cancelling could look like this: +You can cancel a subscription to a `Flux` or `Mono` at any time. However, as with `CompletableFuture`, cancelling the subscription does not stop the background job itself. To fix this, you need to tell the background job when it has been cancelled, so that it can stop. Continuing on the earlier example, adding support for cancelling could look like this: [source,java] ---- @@ -127,7 +127,7 @@ public Flux startBackgroundJob() { .subscribeOn(Schedulers.fromExecutor(taskExecutor)); return Flux.merge( - progress.asFlux().map(BackgroundJobOutput::progressUpdate), + progressUpdates.asFlux().map(BackgroundJobOutput::progressUpdate), result.map(BackgroundJobOutput::finished) ); } diff --git a/articles/building-apps/business-logic/background-jobs/jobs.adoc b/articles/building-apps/business-logic/background-jobs/jobs.adoc index 264e9847e2..c4b672623d 100644 --- a/articles/building-apps/business-logic/background-jobs/jobs.adoc +++ b/articles/building-apps/business-logic/background-jobs/jobs.adoc @@ -9,7 +9,7 @@ order: 10 = Implementing Jobs -When implementing a background job, it's important to decouple its logic from how and where it's triggered. This ensures flexibility in triggering the job from different sources. +When implementing a background job, it is important to decouple its logic from how and where it is triggered. This ensures flexibility in triggering the job from different sources. For instance, you may want to run a job every time an application starts. 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 once a day -- perhaps at midnight, or whenever a certain application event is published. @@ -17,7 +17,7 @@ Here is a visual example of a job with three different triggers: image::images/job-and-triggers.png[Job with Three Triggers] -In code, a job is a Spring bean, annotated with the `@Service` annotation. It contains one or more methods that execute when called the job in the calling thread. +In code, a job is a Spring bean, annotated with the `@Service` annotation. It contains one or more methods that execute the job in the calling thread when called. Below is an example of a Spring bean that implements a single background job: @@ -64,7 +64,7 @@ For more information about transaction management, see the <>, background jobs should not rely on method-level 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's not available in a background thread. Therefore, whenever the job is executed by a background thread, Spring would deny access. +Unlike <<../add-service#,application services>>, background jobs should not rely on method-level 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. When the background job needs information about the current user, this information should be passed to it by the <>, as an immutable method parameter. @@ -94,14 +94,14 @@ public class InvoiceCreationJob { In this example, the first method creates invoices for the orders whose IDs have been passed as parameters. The second generates invoices for all orders that have been shipped, but not yet invoiced. -Implementing batch jobs like this doesn't 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 causes some orders to have bad data in the database. As a result, the batch job won't be able to generate invoices for them. +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 causes some orders to have bad data in the database. As a result, the batch job cannot generate invoices for them. -Fixing the bug is easy, but users won't want to wait for the next batch run to occur. Instead, you can add a button to the user interface that allows them to trigger invoice generation for an individual order. +Fixing the bug is easy, but users do not want to wait for the next batch run to occur. Instead, you can add a button to the user interface that allows them 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's been executed on the same input. +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 receive multiple invoices because of an error somewhere. diff --git a/articles/building-apps/business-logic/background-jobs/triggers.adoc b/articles/building-apps/business-logic/background-jobs/triggers.adoc index 849189c929..4a616b5e1c 100644 --- a/articles/building-apps/business-logic/background-jobs/triggers.adoc +++ b/articles/building-apps/business-logic/background-jobs/triggers.adoc @@ -17,7 +17,7 @@ Below is a visual example of a job with three different triggers: image::images/job-and-triggers.png[Job with Three Triggers] -Spring has support for plugging in an `AsyncUncaughtExceptionHandler` that's called whenever an `@Async` method throws an exception. However, this moves the error handling outside of the method where the error occurred. To increase code readability, you should handle the error explicitly in each trigger. +Spring has support for plugging in an `AsyncUncaughtExceptionHandler` that is called whenever an `@Async` method throws an exception. However, this moves the error handling outside of the method where the error occurred. To increase code readability, you should handle the error explicitly in each trigger. Some triggers, like event listeners and schedulers, are not intended to be invoked by other objects. You should make them package-private to limit their visibility. @@ -101,7 +101,7 @@ This example uses the `@Async` annotation, but you can also execute the job, <<. 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. It's important not to use the scheduling thread pool for executing jobs, directly. Instead, schedule tasks using Spring’s `TaskScheduler` and then delegate the actual job execution to the `TaskExecutor`. +Spring uses a separate thread pool for scheduled tasks. It is important not to use the scheduling thread pool for executing jobs, directly. Instead, schedule tasks using Spring’s `TaskScheduler` and then delegate the actual job execution to the `TaskExecutor`. This is an example of a scheduler that schedules a job to execute every five minutes in a background thread: @@ -133,7 +133,7 @@ class MyBackgroundJobScheduler { The example here 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're easier to debug. Therefore, you should start with annotations when you implement schedulers. If you later need more control over scheduling, or run into problems that are difficult to debug, you should switch to a programmatic approach. +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 scheduling, or run into problems that are difficult to debug, you should switch to a programmatic approach. == Startup Jobs @@ -142,7 +142,7 @@ For startup jobs, you should create a startup trigger that executes the job when If you need to block the application initialization until the job is completed, you can execute it in the main thread. For non-blocking execution, consider using a listener for the `ApplicationReadyEvent` to trigger the job once the application is fully initialized. -Here's an example of a trigger that blocks initialization until the job is finished: +Here is an example of a trigger that blocks initialization until the job is finished: [source,java] ---- diff --git a/articles/building-apps/forms-data/add-form/dialogs-and-drawers.adoc b/articles/building-apps/forms-data/add-form/dialogs-and-drawers.adoc index fff0c06d56..204b0b2890 100644 --- a/articles/building-apps/forms-data/add-form/dialogs-and-drawers.adoc +++ b/articles/building-apps/forms-data/add-form/dialogs-and-drawers.adoc @@ -20,7 +20,7 @@ By implementing the form as a standalone component, you can reuse it in both dia == Displaying Forms in Dialogs -When working with dialogs in Vaadin, it's important to remember they do not alter the application's control flow. For example, in Swing, you can pass control to a dialog and wait until it closes. After that, control returns to the calling code and you can proceed, depending on what option the user selected. In Vaadin, opening a dialog is another way of displaying a UI element -- it doesn't block execution or pause logic. Instead, user interactions are handled through event listeners or callbacks. +When working with dialogs in Vaadin, it is important to remember they do not alter the application's control flow. For example, in Swing, you can pass control to a dialog and wait until it closes. After that, control returns to the calling code and you can proceed, depending on what option the user selected. In Vaadin, opening a dialog is another way of displaying a UI element -- it does not block execution or pause logic. Instead, user interactions are handled through event listeners or callbacks. The following example shows *a dialog used to create new project proposals*. The caller supplies an `onSaveCallback`, which is triggered when the user clicks the [guibutton]*Create Proposal* button: @@ -59,7 +59,7 @@ public class ProposalDialog extends Dialog { <1> Writes the form data to a new `Proposal` FDO (Form Data Object). <2> Uses a callback to let the caller decide how to save the FDO. -Here's how you might use the dialog in your application: +Here is how you might use the dialog in your application: [source,java] ---- @@ -81,8 +81,6 @@ var createProposalButton = new Button("Create Proposal", event -> { == Displaying Forms in Drawers -// TODO Write about the new master-detail layout that is coming in the next Vaadin version! - The following example shows *a drawer that reuses the same form component from the dialog example to edit project proposals*. The caller provides two callbacks: an `onSaveCallback` for handling save logic, and an `onCloseCallback` that runs when the drawer is closed: [source,java] @@ -127,7 +125,7 @@ public class ProposalDrawer extends Section { } private void save() { - form.getFormDataObject.ifPresent(proposal -> { + form.getFormDataObject().ifPresent(proposal -> { var savedProposal = onSaveCallback.apply(proposal); form.setFormDataObject(savedProposal); }); diff --git a/articles/building-apps/forms-data/add-form/fields-and-binding.adoc b/articles/building-apps/forms-data/add-form/fields-and-binding.adoc index 93ade7e389..e03ba367f2 100644 --- a/articles/building-apps/forms-data/add-form/fields-and-binding.adoc +++ b/articles/building-apps/forms-data/add-form/fields-and-binding.adoc @@ -16,7 +16,7 @@ FDOs are often fetched from and submitted to an <> documentation. == Common Field Components -Vaadin provides a set of *build-in field components* that you can use in your forms: +Vaadin provides a set of *built-in field components* that you can use in your forms: * *Text Input Components* - <> -- For standard single-line text input @@ -102,7 +102,7 @@ Vaadin provides a set of *build-in field components* that you can use in your fo == Introducing Binder -Vaadin offers a [classname]`Binder` class that binds fields to FDO properties, ensuring that changes made in the form update the FDO. For each field-property pair, `Binder` creates a new `Binding` object. However, since Binder is one-directional, *updates to the FDO outside of the `Binding` won't automatically reflect in the form fields* unless manually refreshed. +Vaadin offers a [classname]`Binder` class that binds fields to FDO properties, ensuring that changes made in the form update the FDO. For each field-property pair, `Binder` creates a new `Binding` object. However, since Binder is one-directional, *updates to the FDO outside of the `Binding` do not automatically reflect in the form fields* unless manually refreshed. When reading an FDO, `Binder` populates fields with corresponding property values. When writing, it updates the FDO with the modified field values. @@ -120,13 +120,13 @@ When implementing FDOs as JavaBeans, *each form field maps to a corresponding ge .JavaBean Conventions Are Optional [NOTE] -You can name your getters and setters any way you like, as long as their method descriptors match what's expected for property access methods. A getter should take no parameters and return a value, while a setter should accept a single parameter and return `void`. +You can name your getters and setters any way you like, as long as their method descriptors match what is expected for property access methods. A getter should take no parameters and return a value, while a setter should accept a single parameter and return `void`. You can add business logic to the setters. For instance, you could perform validation, or maintain a change log inside the bean. When an FDO is read, `Binder` populates each field with data from the corresponding getter. When the FDO is written, `Binder` calls the setters to update the bean with data in the form. You can then send the bean to an application service for processing. -Beans give you more flexibility for complex forms but require more boilerplate code than records, due to the need for explicit getters and setters. Since they are mutable, there's also a higher risk of bugs, especially when handling form cancellations or undo actions. +Beans give you more flexibility for complex forms but require more boilerplate code than records, due to the need for explicit getters and setters. Since they are mutable, there is also a higher risk of bugs, especially when handling form cancellations or undo actions. .Beware of Stale Data [IMPORTANT] @@ -264,7 +264,7 @@ public class ProposalForm extends Composite { // end::snippet[] } ---- -<1> Store the FDO for future reference. You'll need it when you implement the write functionality. +<1> Store the FDO for future reference. You need it when you implement the write functionality. <2> Clears the form if there is no FDO. For *write-through mode*, use `Binder.setBean()`: @@ -366,7 +366,7 @@ public class ProposalForm extends Composite { // end::snippet[] } ---- -<1> Throws an exception if `setFormDataObject()` was never called. If you called `setBean()` here with an empty FDO, you'd reset the form. +<1> Throws an exception if `setFormDataObject()` was never called. If you called `setBean()` here with an empty FDO, you would reset the form. <2> Returns the updated `Proposal` if validation succeeds. <3> Returns an empty `Optional` if validation fails. @@ -388,7 +388,7 @@ public record ProposalRecord( } ---- -Unlike JavaBeans, records do not have setters. Instead, `Binder` uses *string-based mapping* to bind form fields to record components. You also need to specify the record class when creating the binder: +Unlike JavaBeans, records do not have setters. Instead, `Binder` uses *string-based mapping* to bind form fields to record components. You need to specify the record class when creating the binder: .ProposalForm.java [source,java] diff --git a/articles/building-apps/forms-data/add-form/loading-and-saving.adoc b/articles/building-apps/forms-data/add-form/loading-and-saving.adoc index 24c88d848c..0f08886524 100644 --- a/articles/building-apps/forms-data/add-form/loading-and-saving.adoc +++ b/articles/building-apps/forms-data/add-form/loading-and-saving.adoc @@ -37,7 +37,7 @@ As there are two loading strategies, there are also two saving strategies, each The application service's API depends on several aspects: * the loading and saving strategy -* whether you're using entities or dedicated classes (or records) as FDOs +* whether you are using entities or dedicated classes (or records) as FDOs * what your UI does after it has saved an FDO This section covers the basics of designing an application service for loading and saving an FDO. The implementation of the service is not covered in this guide. @@ -45,7 +45,7 @@ This section covers the basics of designing an application service for loading a === Load from Selection -You don't need a separate `findById` method when using *Load from Selection*. All the information you need is returned by the list function. Here is an example of a list function that can be used to <>: +You do not need a separate `findById` method when using *Load from Selection*. All the information you need is returned by the list function. Here is an example of a list function that can be used to <>: [source,java] ---- @@ -62,12 +62,11 @@ public class ProposalService { <1> Always use transactions when saving and loading data. <2> Always secure your application services. See the <<../../security/protect-services#,Protect Services>> guide for details. -// TODO Link to the article about grids once it's written. === Fetch and Load -If you're using *Fetch and Load*, you need a separate method for fetching the FDO based on its ID, like this: +If you are using *Fetch and Load*, you need a separate method for fetching the FDO based on its ID, like this: [source,java] ---- @@ -103,7 +102,7 @@ public class Proposal { } ---- -Here's an example of an application service that saves and retrieves a `Proposal` FDO: +Here is an example of an application service that saves and retrieves a `Proposal` FDO: [source,java] ---- @@ -129,7 +128,7 @@ This approach works well when you use the entity itself as an FDO. === Insert/Update -When you're using *Insert/Update*, you typically store the ID outside of the FDO. A convenient way of doing this is to introduce a wrapper class that includes the ID: +When you are using *Insert/Update*, you typically store the ID outside of the FDO. A convenient way of doing this is to introduce a wrapper class that includes the ID: [source,java] ---- @@ -180,12 +179,12 @@ Each method returns a new instance of `PersistentProposal`, making it easy to pa == Loading a Form -You've now seen two common ways to design application services. Next, learn how these services integrate with Vaadin forms and views. +You have now seen two common ways to design application services. Next, learn how these services integrate with Vaadin forms and views. === Load from Selection -This approach is straightforward and requires no extra service call. However, this can result in “stale data,” where the grid *displays values that appear saved but haven't been persisted* to the backend. Stale data can occur when the following conditions are met: +This approach is straightforward and requires no extra service call. However, this can result in “stale data,” where the grid *displays values that appear saved but have not been persisted* to the backend. Stale data can occur when the following conditions are met: * The FDOs are mutable, * Your form uses *write-through mode*, and @@ -198,7 +197,7 @@ The stale FDO remains in memory until you refresh the grid. To avoid this, you s * Copy the FDO before passing it to the form. Copying ensures that the original object shown in the grid remains unchanged if the user cancels or validation fails. * Refresh the grid both after saving and canceling. This replaces any stale objects with fresh ones. -Here's an example of the Fetch and Load approach in practice: +Here is an example of the Fetch and Load approach in practice: [source,java] ---- @@ -231,7 +230,6 @@ public class ProposalView extends Main { If you need a refresher on form binding, buffered mode and write-through mode, see the <> guide. -// TODO Links to guides about grids and selection via URL parameter === Fetch and Load @@ -287,7 +285,7 @@ public class ProposalView extends Main { <2> Extracts the ID from the selected `ProposalListEntry` to fetch the corresponding `Proposal`. <3> Populates the form if the proposal exists. <4> Shows an error message if no proposal was found. -<5> The `Proposal` object is not used anywhere else so there's no need to copy it. +<5> The `Proposal` object is not used anywhere else so there is no need to copy it. == Saving a Form @@ -338,7 +336,7 @@ public class ProposalView extends Main { ==== Records and Single Save -When using records as FDO, `Binder` requires all record components to be bound to fields -- including the ID. Because you don't typically bind the ID to a UI component, you can create a dummy binding using [classname]`ReadOnlyHasValue`: +When using records as FDO, `Binder` requires all record components to be bound to fields -- including the ID. Because you do not typically bind the ID to a UI component, you can create a dummy binding using [classname]`ReadOnlyHasValue`: [source,java] ---- @@ -376,7 +374,7 @@ private void editProposal(PersistentProposal existingProposal) { } private void saveProposal() { - form.getFormDataObject().ifPresent(fdo => { + form.getFormDataObject().ifPresent(fdo -> { if (existingProposal == null) { editProposal(service.insert(fdo)); } else { diff --git a/articles/building-apps/forms-data/add-form/validation.adoc b/articles/building-apps/forms-data/add-form/validation.adoc index d9a04e039a..0c20709803 100644 --- a/articles/building-apps/forms-data/add-form/validation.adoc +++ b/articles/building-apps/forms-data/add-form/validation.adoc @@ -10,7 +10,7 @@ order: 10 = Form Validation :toclevels: 2 -Form validation is a fundamental aspect of building robust and user-friendly applications. It ensures that the data entered by users meets the expected format and business rules before it's processed or stored. Effective validation helps safeguard the *security* and *integrity* of your application. It also enhances the *user experience* by providing immediate feedback and preventing common input errors. +Form validation is a fundamental aspect of building robust and user-friendly applications. It ensures that the data entered by users meets the expected format and business rules before it is processed or stored. Effective validation helps safeguard the *security* and *integrity* of your application. It also enhances the *user experience* by providing immediate feedback and preventing common input errors. To learn more about validation from a broader architectural perspective, see the <<../consistency#,Data Consistency>> deep dive. @@ -98,7 +98,7 @@ public class PositiveIntegerValidator implements Validator { @Override public ValidationResult apply(Integer num, ValueContext context) { // <1> - return (num >= 0) + return (num > 0) ? ValidationResult.ok() : ValidationResult.error("number must be positive"); } @@ -118,7 +118,7 @@ binder.forField(myNumberField) === Converters -If you're using value objects or domain primitives in your FDO, you can create a converter by implementing [interfacename]`Converter`. The following example converts between an [classname]`EmailAddress` and a [classname]`String`: +If you are using value objects or domain primitives in your FDO, you can create a converter by implementing [interfacename]`Converter`. The following example converts between an [classname]`EmailAddress` and a [classname]`String`: .EmailAddressConverter.java [source,java] @@ -145,7 +145,7 @@ public class EmailAddressConverter implements Converter { } ---- -You'd use it with `Binder` like this: +You would use it with `Binder` like this: [source,java] ---- diff --git a/articles/building-apps/forms-data/add-grid/buffered-data.adoc b/articles/building-apps/forms-data/add-grid/buffered-data.adoc index cb750a41cb..22103e4f48 100644 --- a/articles/building-apps/forms-data/add-grid/buffered-data.adoc +++ b/articles/building-apps/forms-data/add-grid/buffered-data.adoc @@ -12,7 +12,7 @@ Buffered data refers to data that is *loaded from an application service where t As with <>, *buffered data is loaded into the grid at once and sorted in memory*. However, *the filtering happens in the application service*. -This guide starts with a complete example that you can copy-paste into your project if you want. It then breaks down the example into sections that explain how to populate, filter, and sort the grid. The guides does not cover setting up the Grid component itself; for that, see the <> component documentation. +This guide starts with a complete example that you can copy-paste into your project if you want. It then breaks down the example into sections that explain how to populate, filter, and sort the grid. The guide does not cover setting up the Grid component itself; for that, see the <> component documentation. == Copy-Paste into Your Project @@ -24,7 +24,7 @@ If you want to quickly try out a buffered grid in your Vaadin application, copy- include::{root}/src/main/java/com/vaadin/demo/buildingapps/grid/BufferedGridView.java[tags=full,indent=0] ---- -For more detailed walk-through of the example code, continue reading below. +For a more detailed walk-through of the example code, continue reading below. == Getting the Data @@ -36,12 +36,12 @@ In this example, the data is coming from a simple service class that _simulates_ include::{root}/src/main/java/com/vaadin/demo/buildingapps/grid/BufferedGridView.java[tags=data,indent=0] ---- -In a real world application, the service would be in its own file. The data would be loaded from a database +In a real-world application, the service would be in its own file. The data would be loaded from a database and the result size limited to avoid flooding the UI with too many items. For the same reason an empty filter string returns no items in this example. .Can you call a repository or Data Access Object (DAO) directly from the UI? [NOTE] -Yes, if you're not using <<../../security/protect-services#,method security>> or any other service layer logic. +Yes, if you are not using <<../../security/protect-services#,method security>> or any other service layer logic. == Populating and Filtering the Grid @@ -53,7 +53,7 @@ Since the filtering happens in the service, you need to call the service wheneve include::{root}/src/main/java/com/vaadin/demo/buildingapps/grid/BufferedGridView.java[tags=filtering,indent=0] ---- -The reason you're calling `ListDataView.setItems()` and not `Grid.setItems()` is that `Grid.setItems()` always creates a new data provider. This in turns clears the selection of the grid. If you want to preserve the selection, you need to update the existing data provider and that is what `ListDataView.setItems()` does. +The reason you are calling `ListDataView.setItems()` and not `Grid.setItems()` is that `Grid.setItems()` always creates a new data provider. This in turn clears the selection of the grid. If you want to preserve the selection, you need to update the existing data provider and that is what `ListDataView.setItems()` does. == Sorting diff --git a/articles/building-apps/forms-data/add-grid/paginated-data.adoc b/articles/building-apps/forms-data/add-grid/paginated-data.adoc index cdb38d4e38..2ce972208b 100644 --- a/articles/building-apps/forms-data/add-grid/paginated-data.adoc +++ b/articles/building-apps/forms-data/add-grid/paginated-data.adoc @@ -25,7 +25,7 @@ If you want to quickly try out a paginated grid in your Vaadin application, copy include::{root}/src/main/java/com/vaadin/demo/buildingapps/grid/PaginatedGridView.java[tags=full,indent=0] ---- -For more detailed walk-through of the example code, continue reading below. +For a more detailed walk-through of the example code, continue reading below. == Getting the Data @@ -39,7 +39,7 @@ This example uses offset-based pagination and uses Vaadin's `QuerySortOrder` API include::{root}/src/main/java/com/vaadin/demo/buildingapps/grid/PaginatedGridView.java[tags=data,indent=0] ---- -In a real world application, the service would be in its own file and the data would be queried from a database. +In a real-world application, the service would be in its own file and the data would be queried from a database. == Populating the Grid @@ -59,7 +59,7 @@ If you are using Spring Data, use the `setItemsPageable()` method. It passes a ` [source,java] ---- // Spring Data Repository: -public interface ItemReposiory extends PagingAndSortingRepository { +public interface ItemRepository extends PagingAndSortingRepository { Slice findByNameContainingIgnoreCase(String name, Pageable pageable); } @@ -72,8 +72,8 @@ grid.setItemsPageable(pageable -> repository .Can you call a repository directly from the UI? [NOTE] -Yes, if you're not using <<../../security/protect-services#,method security>> or any other service layer logic. -Otherwise, it's better to have an <<../../business-logic/add-service#,application service>> between the UI and the repository. +Yes, if you are not using <<../../security/protect-services#,method security>> or any other service layer logic. +Otherwise, it is better to have an <<../../business-logic/add-service#,application service>> between the UI and the repository. == Filtering diff --git a/articles/building-apps/forms-data/add-grid/static-data.adoc b/articles/building-apps/forms-data/add-grid/static-data.adoc index b493b4052e..864b4c0ace 100644 --- a/articles/building-apps/forms-data/add-grid/static-data.adoc +++ b/articles/building-apps/forms-data/add-grid/static-data.adoc @@ -24,7 +24,7 @@ If you want to quickly try out a static grid in your Vaadin application, copy-pa include::{root}/src/main/java/com/vaadin/demo/buildingapps/grid/StaticGridView.java[tags=full,indent=0] ---- -For more detailed walk-through of the example code, continue reading below. +For a more detailed walk-through of the example code, continue reading below. == Getting the Data @@ -36,7 +36,7 @@ In this example, the static data is a list of record objects returned by a simpl include::{root}/src/main/java/com/vaadin/demo/buildingapps/grid/StaticGridView.java[tags=data,indent=0] ---- -In a real world application, the service would be in its own file. The data could be statically defined as in this example, or loaded from a file or a database during application startup. +In a real-world application, the service would be in its own file. The data could be statically defined as in this example, or loaded from a file or a database during application startup. == Populating the Grid diff --git a/articles/building-apps/forms-data/consistency/domain-primitives.adoc b/articles/building-apps/forms-data/consistency/domain-primitives.adoc index 6c05e2be03..64e9004b14 100644 --- a/articles/building-apps/forms-data/consistency/domain-primitives.adoc +++ b/articles/building-apps/forms-data/consistency/domain-primitives.adoc @@ -11,7 +11,7 @@ order: 16 In programming, _primitive data types_ are the basis from which all other data types are constructed. In Java, the primitive types are the integer types (i.e., `byte`, `short`, `int`, `long`, and `char`); the floating-point number types (i.e., `float` and `double`); and `boolean`. Java provides several other data types that are also useful, such as `String`, `BigDecimal`, and date-time types. -Primitive data types have constraints on what you can store in them. For example, an `int` can hold integers between -2147483648 and 2147483647. When used for attributes in a domain model, more constraints are imposed. Even though the "item quantity" attribute, for instance, in an order for a store's merchandise may be an integer, not all integers are valid quantities. It shouldn't be possible for a customer to order a negative number of items. There might also be an upper limit on how many items a user can order, without having to contact sales. +Primitive data types have constraints on what you can store in them. For example, an `int` can hold integers between -2147483648 and 2147483647. When used for attributes in a domain model, more constraints are imposed. Even though the "item quantity" attribute, for instance, in an order for a store's merchandise may be an integer, not all integers are valid quantities. It should not be possible for a customer to order a negative number of items. There might also be an upper limit on how many items a user can order, without having to contact sales. == Constraint Enforcement Problem @@ -72,26 +72,26 @@ public class OrderItem { In this case, you have to remember to call the `Validator` at some point. -Both of these examples work, but they have the same problem: the attributes don't carry any domain meaning by themselves. A string is no different whether it contains a person's first name, or it's an SQL query. An integer can contain the quantity of an item ordered, or the primary key of a database record. +Both of these examples work, but they have the same problem: the attributes do not carry any domain meaning by themselves. A string is no different whether it contains a person's first name or it is an SQL query. An integer can contain the quantity of an item ordered, or the primary key of a database record. You have to validate the attribute values wherever you use them. If not, you might get unexpected errors during runtime. For instance, trying to store a 101-character string in a `VARCHAR` database column with a 100-character limit would throw an exception. Or worse, the database stores bad data (i.e., truncated data). Data integrity problems could spread to other parts of the system and have unintended consequences. For example, if a customer is able to enter negative item quantities, they may be able to give themselves a hefty discount: they could add items they want, then enter negative quantities of other items until the net total is zero -- or less. Suppose further the system issues refunds when an order with a negative net cost is detected. A customer could be paid to order items. -Strings are notorious for being used as attack vectors, because they can contain code. String input that hasn't been validated or escaped is the root cause of all injection attacks. Injection attacks are third on the https://owasp.org/www-project-top-ten/[OWASP Top Ten 2021] list of critical security risks to web applications. +Strings are notorious for being used as attack vectors, because they can contain code. String input that has not been validated or escaped is the root cause of all injection attacks. Injection attacks are third on the https://owasp.org/www-project-top-ten/[OWASP Top Ten 2021] list of critical security risks to web applications. == Introducing Domain Primitives All data structures in the domain model have domain meaning. For example, a `Customer` class corresponds to an actual customer in reality. However, instead of only attaching domain meaning to the top-level types, you should also attach domain meaning to the individual _attributes_. For example, instead of using a string for the username, and an integer for the quantity, you should create a `Username` class and a `Quantity` class. Then, wherever you need a username or a quantity, you can use these _domain primitives_.footnote:[The concept of _domain primitives_ was introduced in https://www.manning.com/books/secure-by-design[Secure by Design].] -A domain primitive is a Java class that has some specific qualities. First, it's a _value object_, meaning that it's immutable. Also, two objects with the same value are considered interchangeable. +A domain primitive is a Java class that has some specific qualities. First, it is a _value object_, meaning that it is immutable. Also, two objects with the same value are considered interchangeable. Second, it wraps one or more objects of other types. For example, a `Quantity` domain primitive could wrap an integer; a `Username` domain primitive could wrap a string. Domain primitives can wrap more than one object, and can also wrap other domain primitives. To represent a monetary amount, you need both the currency and the numeric amount. You may end up with a `CurrencyUnit` domain primitive, and also a `MonetaryAmount` domain primitive that wraps a `BigDecimal` and a `CurrencyUnit`. -Third, a domain primitive validates all of its input data in the constructor. Because it's also immutable, this means that it's guaranteed always to be valid. For more information about validation, see the <> documentation page. +Third, a domain primitive validates all of its input data in the constructor. Because it is also immutable, this means that it is guaranteed always to be valid. For more information about validation, see the <> documentation page. -Here's how the quantity domain primitive from the example above could look: +Here is how the quantity domain primitive from the example above could look: [source,java] ---- @@ -177,14 +177,14 @@ public record StreetAddress( ) {} ---- -When creating a new instance of this object, a developer now has to write `new StreetAddress(StreetNumber.of("123-4"), StreetName.of("Main Street"))`. It's a bit more verbose, but with this the compiler would complain if you tried to swap the parameters. +When creating a new instance of this object, a developer now has to write `new StreetAddress(StreetNumber.of("123-4"), StreetName.of("Main Street"))`. It is a bit more verbose, but with this the compiler would complain if you tried to swap the parameters. == Behavior Domain primitives are not only about containing and validating data. They can also contain behavior, such as calculation methods, transformation methods, or even business logic. This is because the constraints that control which values are valid also constrain what operations you can perform on them. -For example, you can't divide or multiply two amounts of money. You can add and subtract amounts of money, but only if they have the same currency. You can make these constraints explicit by declaring `add` and `subtract` methods on the `MonetaryAmount` domain primitive, like this: +For example, you cannot divide or multiply two amounts of money. You can add and subtract amounts of money, but only if they have the same currency. You can make these constraints explicit by declaring `add` and `subtract` methods on the `MonetaryAmount` domain primitive, like this: [source,java] ---- @@ -231,7 +231,7 @@ Whenever you fetch the wrapped value from a domain primitive, you should ask why == Usage in Flow -To use a single-value domain primitive in a Vaadin Flow user interface, you have to create a custom `Converter` for it. Because conversion errors are treated as validation errors by the `Binder`, there's no need to create a separate `Validator` to validate the input. For example, the converter of an `EmailAddress` domain primitive could look like this: +To use a single-value domain primitive in a Vaadin Flow user interface, you have to create a custom `Converter` for it. Because conversion errors are treated as validation errors by the `Binder`, there is no need to create a separate `Validator` to validate the input. For example, the converter of an `EmailAddress` domain primitive could look like this: [source,java] ---- @@ -356,7 +356,7 @@ To use domain primitives in Hilla, you have to make sure that they can be serial public final class Quantity { ... - @JsonCreate + @JsonCreator public Quantity(int value) { ... } @@ -368,7 +368,7 @@ public final class Quantity { } ---- -If you now use the `Quantity` domain primitive in a <<{articles}/hilla/guides/endpoints#,Hilla endpoint>>, it's treated as a `number` in TypeScript. No `Quantity` type is created in TypeScript. +If you now use the `Quantity` domain primitive in a <<{articles}/hilla/guides/endpoints#,Hilla endpoint>>, it is treated as a `number` in TypeScript. No `Quantity` type is created in TypeScript. Multi-value domain primitives are converted into their own TypeScript types, as long as they meet the requirements of <<{articles}/hilla/guides/endpoints#objects,Hilla endpoint objects>>. diff --git a/articles/building-apps/forms-data/consistency/eventual.adoc b/articles/building-apps/forms-data/consistency/eventual.adoc index 9d33c6db78..810a43d868 100644 --- a/articles/building-apps/forms-data/consistency/eventual.adoc +++ b/articles/building-apps/forms-data/consistency/eventual.adoc @@ -11,8 +11,6 @@ order: 50 Eventual consistency means that the effects of an update are not immediately observable across the entire system. Instead, it takes some time for changes to propagate. This time can be from a few milliseconds to several days, depending on the business requirements. -// TODO Microservices, Spring Modulith, domain events, sagas. - .Work in progress [IMPORTANT] -This page is a stub. It'll be expanded later. +This page is a stub. It is to be expanded later. diff --git a/articles/building-apps/forms-data/consistency/index.adoc b/articles/building-apps/forms-data/consistency/index.adoc index e82187d637..c9fe77b139 100644 --- a/articles/building-apps/forms-data/consistency/index.adoc +++ b/articles/building-apps/forms-data/consistency/index.adoc @@ -12,15 +12,15 @@ section-nav: badge-deep-dive Businesses rely on their data to make decisions, serve customers, or comply with regulations. Because of this, a key requirement of business applications is that the data is consistent. -In practice, this means that a user sees a consistent view of the data, regardless of how it's presented. For instance, the user could look at the same data in a summary view, in a grid view, and in a details view. +In practice, this means that a user sees a consistent view of the data, regardless of how it is presented. For instance, the user could look at the same data in a summary view, in a grid view, and in a details view. Furthermore, the data should always maintain its invariants. An invariant is a condition or property that remains true throughout the execution of a program, or within a particular scope. For instance, one invariant could be that the balance of a bank account may never be negative. Another invariant could be that the total of an invoice is always the sum of the individual invoice items. -Data consistency also means that the data should be correct and complete. The same type of data should be entered in the same way; there shouldn't be any duplicates; and required data should be present. +Data consistency also means that the data should be correct and complete. The same type of data should be entered in the same way; there should not be any duplicates; and required data should be present. .Deep Dive - Recommended Approach [IMPORTANT] -This *opinionated* deep-dive explains core concepts (for example, ACID transactions) and gives a practical, recommended way to handle data consistency in Vaadin. If you're new, this is a good place to start; if you're experienced, feel free to use whatever patterns work for you. +This *opinionated* deep-dive explains core concepts (for example, ACID transactions) and gives a practical, recommended way to handle data consistency in Vaadin. If you are new, this is a good place to start; if you are experienced, feel free to use whatever patterns work for you. == Topics diff --git a/articles/building-apps/forms-data/consistency/optimistic-locking.adoc b/articles/building-apps/forms-data/consistency/optimistic-locking.adoc index 9bb850c986..420b8377b1 100644 --- a/articles/building-apps/forms-data/consistency/optimistic-locking.adoc +++ b/articles/building-apps/forms-data/consistency/optimistic-locking.adoc @@ -9,11 +9,11 @@ order: 20 = Optimistic Locking -In all multi-user applications that change data, there is always a risk that two users might try to update the same data at the same time. To avoid data consistency conflicts, it's important that the application detects when this happens -- and handles it. One way of doing this is through optimistic locking. +In all multi-user applications that change data, there is always a risk that two users might try to update the same data at the same time. To avoid data consistency conflicts, it is important that the application detects when this happens -- and handles it. One way of doing this is through optimistic locking. -Optimistic locking assumes that no conflict is going to happen. Therefore, the application doesn't initially perform any data locking, as opposed to <>. However, if a conflict does occur, the application detects it and throws an exception, often rolling back the transaction. Spring has an `OptimisticLockingFailureException` that you can use for this. +Optimistic locking assumes that no conflict is going to happen. Therefore, the application does not initially perform any data locking, as opposed to <>. However, if a conflict does occur, the application detects it and throws an exception, often rolling back the transaction. Spring has an `OptimisticLockingFailureException` that you can use for this. -Optimistic locking failures are always detected by the application, not by the database. To detect conflicts, optimistic locking uses a version number. Every record that's to be updated should contain a version column with an integer. For every update, this version is incremented by one. Update operations compare the current version number with the last known version number. If these numbers are different, it means another user has updated the record after it was retrieved from the database. This is illustrated in the following pseudo-SQL example: +Optimistic locking failures are always detected by the application, not by the database. To detect conflicts, optimistic locking uses a version number. Every record that is to be updated should contain a version column with an integer. For every update, this version is incremented by one. Update operations compare the current version number with the last known version number. If these numbers are different, it means another user has updated the record after it was retrieved from the database. This is illustrated in the following pseudo-SQL example: [source,sql] ---- @@ -23,15 +23,15 @@ UPDATE myTable WHERE id = :id AND _version = :oldVersion ---- -An application executing this SQL script would then have to check the number of records that were actually updated based on the `WHERE` clause. If this number was 0, it means that either the primary keys didn't matched in the database, or the version numbers didn't match. Both cases would be optimistic locking failures. +An application executing this SQL script would then have to check the number of records that were actually updated based on the `WHERE` clause. If this number was 0, it means that either the primary keys did not match in the database, or the version numbers did not match. Both cases would be optimistic locking failures. -Both <> and <> have built-in support for optimistic locking. It's easy to implement them. However, you'll need both the primary key and the version number whenever you make an update. +Both <> and <> have built-in support for optimistic locking. They are straightforward to implement. However, you need both the primary key and the version number whenever you make an update. -Generally, you should use optimistic locking for all updates that don't risk causing the Time-Of-Check to Time-Of-Use (TOCTOU) problem. For that, you should use <> instead. +Generally, you should use optimistic locking for all updates that do not risk causing the Time-Of-Check to Time-Of-Use (TOCTOU) problem. For that, you should use <> instead. == Resolving Conflicts -When an optimistic locking failure occurs, it means that another user has either updated or deleted the data you're trying to update. The easiest way of resolving this is to inform the user of the conflict, ask them to refresh and try again. In applications where conflicts are rare, this solution is often good enough. +When an optimistic locking failure occurs, it means that another user has either updated or deleted the data you are trying to update. The simplest way of resolving this is to inform the user of the conflict, ask them to refresh and try again. In applications where conflicts are rare, this solution is often good enough. -If update conflicts are more frequent, you'll need to implement a mechanism for automatically merging changes, or use a Conflict-free Replicated Data Type (CRDT). However, this is outside the scope of this documentation page. +If update conflicts are more frequent, you need to implement a mechanism for automatically merging changes, or use a Conflict-free Replicated Data Type (CRDT). However, this is outside the scope of this documentation page. diff --git a/articles/building-apps/forms-data/consistency/pessimistic-locking.adoc b/articles/building-apps/forms-data/consistency/pessimistic-locking.adoc index 0bbce9143c..bbc417a2bf 100644 --- a/articles/building-apps/forms-data/consistency/pessimistic-locking.adoc +++ b/articles/building-apps/forms-data/consistency/pessimistic-locking.adoc @@ -11,7 +11,7 @@ order: 30 Pessimistic locking assumes that an update is going to fail because of a conflict. To prevent this, the application locks the record before it starts to write. Once the transaction is committed, the lock is released. If the record is already locked, the transaction waits until the lock is released and then proceeds with the update. You have to be careful, though, to avoid <>. -Converse to <>, pessimistic locking doesn't require a version number to be stored in every table. It uses the database to perform the lock. This requires the database to have programmer controllable write locks, which modern relational databases have. +Unlike <>, pessimistic locking does not require a version number to be stored in every table. It uses the database to perform the lock. This requires the database to have programmer controllable write locks, which modern relational databases have. The following SQL example illustrates how to use pessimistic locking. It locks a record, updates it, and then commits the transaction: @@ -24,7 +24,7 @@ COMMIT Both <> and <> have built-in support for pessimistic locking. -Whether pessimistic locking is faster than optimistic locking depends on the convention of the system. When you have many conflicts and use optimistic locking, you'll discard many transactions. This could be avoided with pessimistic locking, resulting in better throughput. When the contention is low, though, optimistic locking is faster. +Whether pessimistic locking is faster than optimistic locking depends on the contention of the system. When you have many conflicts and use optimistic locking, you discard many transactions. This could be avoided with pessimistic locking, resulting in better throughput. When the contention is low, though, optimistic locking is faster. Furthermore, different databases may implement pessimistic locking in different ways. You should check how your database handles pessimistic locking, so that you can make informed decisions about when and how to use it. @@ -33,9 +33,9 @@ Generally, you should use pessimistic locking in situations where optimistic loc == Resolving Conflicts -When you use pessimistic locking, you're avoiding conflicts rather than detecting them. However, there are still situations where Spring may throw a `PessimisticLockingFailureException`. The most typical ones are timeouts and deadlocks. +When you use pessimistic locking, you avoid conflicts rather than detecting them. However, there are still situations where Spring may throw a `PessimisticLockingFailureException`. The most typical ones are timeouts and deadlocks. -A timeout occurs if a transaction cannot acquire a lock within a certain amount of time. This happens because another transaction already holds the lock, and is not finished with it. +A timeout occurs if a transaction cannot acquire a lock within a certain amount of time. This happens because another transaction already holds the lock and has not finished with it. A deadlock occurs when one transaction is waiting for a lock held by another, or vice versa. When the database detects this, it designates one of the transactions as the victim, and rolls it back. @@ -61,7 +61,7 @@ public class MyApplicationService { } ---- -Pessimistic locking doesn't prevent one user from overwriting another's data in two consecutive transactions. For this, you should use <>. Incidentally, it's possible to combine both mechanisms, since optimistic locking happens in the application and pessimistic locking in the database. +Pessimistic locking does not prevent one user from overwriting another's data in two consecutive transactions. For this, you should use <>. Incidentally, it is possible to combine both mechanisms, since optimistic locking happens in the application and pessimistic locking in the database. == TOCTOU @@ -86,7 +86,7 @@ Time-of-Check to Time-of-Use (TOCTOU) is a problem that occurs when a critical p |=== -Suppose the business rules state that you're not allowed to overdraw the account. Therefore, you check the balance before any withdrawal and refuse the transaction if there aren't enough funds in the account. However, if you perform two withdrawals almost simultaneously, you may run into a TOCTOU problem. Although both transactions check the balance before inserting the withdrawals into the transactions table, you may inadvertently overdraw the account. This is illustrated in the following table: +Suppose the business rules state that you are not allowed to overdraw the account. Therefore, you check the balance before any withdrawal and refuse the transaction if there are not enough funds in the account. However, if you perform two withdrawals almost simultaneously, you may run into a TOCTOU problem. Although both transactions check the balance before inserting the withdrawals into the transactions table, you may inadvertently overdraw the account. This is illustrated in the following table: [cols="2,2,>1"] |=== @@ -150,4 +150,4 @@ To solve this problem, you have to ensure that all transactions involving an ind |=== -You don't have to update a row to lock it. In this example, the application locked a row in the accounts table even though it was inserting records into the transactions table. +You do not have to update a row to lock it. In this example, the application locked a row in the accounts table even though it was inserting records into the transactions table. diff --git a/articles/building-apps/forms-data/consistency/strong.adoc b/articles/building-apps/forms-data/consistency/strong.adoc index 6f54adc959..c0ec6c5505 100644 --- a/articles/building-apps/forms-data/consistency/strong.adoc +++ b/articles/building-apps/forms-data/consistency/strong.adoc @@ -13,8 +13,6 @@ Strong consistency, or immediate consistency, means that the effects of an updat One way of achieving strong consistency is to use transactions. Modern databases support transactions. As a general rule, you should use them whenever your business application reads or writes data. For more information about this, see the <> documentation page. -// TODO Write something about monolits and self-contained systems here as well. - .Work in progress [IMPORTANT] -This page is a stub. It'll be expanded later. \ No newline at end of file +This page is a stub. It is to be expanded later. \ No newline at end of file diff --git a/articles/building-apps/forms-data/consistency/transactions/declarative.adoc b/articles/building-apps/forms-data/consistency/transactions/declarative.adoc index 92bef4c329..1149305833 100644 --- a/articles/building-apps/forms-data/consistency/transactions/declarative.adoc +++ b/articles/building-apps/forms-data/consistency/transactions/declarative.adoc @@ -9,7 +9,7 @@ order: 10 = Declarative Transactions -The easiest way to manage transactions in a Spring application is by using the `@Transactional` annotation. You can place it directly on your class, or on individual methods. Don't use the annotation on interfaces, except for <>. +The easiest way to manage transactions in a Spring application is by using the `@Transactional` annotation. You can place it directly on your class, or on individual methods. Do not use the annotation on interfaces, except for <>. The following example instructs Spring to run all methods of the application service inside a new transaction: @@ -45,19 +45,19 @@ public class MyApplicationService { ---- [IMPORTANT] -In earlier versions of Spring, you could only use `@Transactional` on `public` methods. As of Spring version 6.0, you can also use it on `protected` and package-visible methods if you're using class-based proxies. For interface-based proxies, the methods must always be `public`, and defined in the proxied interface. Always make your transactional methods `public`. +In earlier versions of Spring, you could only use `@Transactional` on `public` methods. As of Spring version 6.0, you can also use it on `protected` and package-visible methods if you are using class-based proxies. For interface-based proxies, the methods must always be `public`, and defined in the proxied interface. Always make your transactional methods `public`. For more information about declarative transaction management, see the https://docs.spring.io/spring-framework/reference/data-access/transaction/declarative.html[Spring Documentation]. == Committing -You don't have to do anything special to commit a transaction. Spring commits the transaction after the method returns, unless it has been marked for rollback. This is described in the following section. +You do not have to do anything special to commit a transaction. Spring commits the transaction after the method returns, unless it has been marked for rollback. This is described in the following section. == Rolling Back -You mark the transaction for rollback by throwing an unchecked exception. The following example causes Spring to rollback the transaction if validation fails: +Mark the transaction for rollback by throwing an unchecked exception. The following example causes Spring to rollback the transaction if validation fails: [source,java] ---- @@ -75,7 +75,7 @@ public class MyApplicationService { } ---- -A checked exception doesn't cause the transaction to rollback by default. You can override this, though, by using the `rollbackFor` annotation attribute. The following example instructs Spring to rollback the transaction if `myMethod` throws a `MyCheckedException`: +A checked exception does not cause the transaction to rollback by default. You can override this, though, by using the `rollbackFor` annotation attribute. The following example instructs Spring to rollback the transaction if `myMethod` throws a `MyCheckedException`: [source,java] ---- @@ -90,8 +90,6 @@ public class MyApplicationService { } ---- -// TODO Write something about read-only transactions? - == Isolation Level @@ -116,13 +114,13 @@ public class MyApplicationService { == Caveats -When working with declarative transactions, it's important to remember that the annotations themselves don't manage any transactions. They're merely instructions for how Spring should manage the transactions. +When working with declarative transactions, it is important to remember that the annotations themselves do not manage any transactions. They are merely instructions for how Spring should manage the transactions. During application startup, Spring detects the `@Transactional` annotation and turns the service into a proxy. When a client calls the proxy, the call is routed through a _method interceptor_. The interceptor starts the transaction, calls the actual method, and then commits the transaction when the method returns, as illustrated in this diagram: image::images/declarative-transactions.png[Diagram of Client Calling a Service through a Proxy] -The `@Transactional` annotation is ignored if a service calls itself. This happens because the call doesn't go via the proxy, as illustrated in the following diagram: +The `@Transactional` annotation is ignored if a service calls itself. This happens because the call does not go via the proxy, as illustrated in the following diagram: image::images/declarative-transactions-self-call.png[Diagram of a Service Calling Itself, Bypassing the Proxy] @@ -148,5 +146,3 @@ public class MyApplicationService { You can fix this by managing the transactions, <>. -// Actually, you can fix it by using AspectJ proxies as well, but I don't want to go there. - diff --git a/articles/building-apps/forms-data/consistency/transactions/index.adoc b/articles/building-apps/forms-data/consistency/transactions/index.adoc index 36501f49bd..43a7bf2234 100644 --- a/articles/building-apps/forms-data/consistency/transactions/index.adoc +++ b/articles/building-apps/forms-data/consistency/transactions/index.adoc @@ -2,7 +2,7 @@ title: Transactions page-title: How to use ACID transactions in your Vaadin applications description: Learn about ACID transactions and how to use them in Vaadin applications. -meta-description: Learn about ACID transactions from things like deadlocks to overall management. +meta-description: Learn about ACID transactions from things such as deadlocks to overall management. order: 10 --- @@ -13,9 +13,9 @@ Database transactions ensure Atomicity, Consistency, Isolation, and Durability ( Atomicity means that the transaction is treated as a single unit. Either all updates succeed, or none of them. Even if only a single update fails, the transaction is rolled back, and all other updates are undone. -Consistency means that the database is in a valid state after each committed transaction. All integrity constraints -- like unique keys, foreign keys, and check constraints -- are met after each transaction. +Consistency means that the database is in a valid state after each committed transaction. All integrity constraints -- such as unique keys, foreign keys, and check constraints -- are met after each transaction. -Isolation means that transactions are isolated from each other so that they don't interfere. The end result should be the same regardless of whether two transactions execute in parallel -- or one after the other. This is also useful for queries that only read data from the database, without making any changes to it. +Isolation means that transactions are isolated from each other so that they do not interfere. The end result should be the same regardless of whether two transactions execute in parallel -- or one after the other. This is also useful for queries that only read data from the database, without making any changes to it. Durability means that once a transaction is committed, the changes are permanent. They should even survive a system crash. In practice, this means writing the changes to a durable storage medium, such as a hard drive. @@ -25,17 +25,17 @@ Although transactions are not specific to relational databases, they are discuss == Transaction Isolation -Databases typically use locks to achieve isolation. A _read lock_, or _shared_ lock, allows multiple transactions to read the same data, concurrently. It prevents any transaction from modifying it while others are reading. +Databases typically use locks to achieve isolation. A _read lock_, or _shared_ lock, allows multiple transactions to read the same data concurrently. It prevents any transaction from modifying it while others are reading. A _write lock_, or _exclusive_ lock, is used when a transaction needs to modify data. It prevents other transactions from reading or writing the data until the lock is released. Depending on the implementation and configuration of the database, and the needs of a transaction, locks can be applied at different levels of granularity. For example, a database may be able to lock individual cells, rows, pages, tables, or even the entire database. -These locks have an impact on performance. Because of this, there are different levels of isolation. They allow you to relax the isolation requirements in favor of improved performance. To understand what these levels mean, you first need to understand some phenomena that can happen when you're reading and writing data at the same time. +These locks have an impact on performance. Because of this, there are different levels of isolation. They allow you to relax the isolation requirements in favor of improved performance. To understand what these levels mean, you first need to understand some phenomena that can happen when you are reading and writing data at the same time. Dirty Reads:: A dirty read happens when a transaction reads changes made by another, before that second transaction has been committed. The problem is that should the second transaction be rolled back, the first one has read data that no longer exists. This can happen when there are no locks at all. -Non-Repeatable Reads:: A non-repeatable read happens when a transaction reads the same data twice, but gets different results because a second transaction has updated or deleted the data between the reads. This can occur if the first transaction only holds the read lock while it's reading the data instead of keeping it for its entire duration. +Non-Repeatable Reads:: A non-repeatable read happens when a transaction reads the same data twice, but gets different results because a second transaction has updated or deleted the data between the reads. This can occur if the first transaction only holds the read lock while it is reading the data instead of keeping it for its entire duration. Phantom Reads:: A phantom read is a special kind of non-repeatable read. It happens when a transaction performs a query that should have included data from another transaction. However, at the time of the query, the second transaction had not yet written that data. If the first transaction was to run the same query later, the data would appear. This can happen if the first transaction holds the read lock for its entire duration, but only locks the rows it has read instead of locking the entire table. @@ -53,7 +53,7 @@ Database implementations may also define their own isolation levels, and choose Furthermore, their default isolation levels may be different. The database systems PostgreSQL, Microsoft SQL Server, H2, and Oracle use _read committed_ as the default isolation level, whereas MariaDB and MySQL use _repeatable reads_. -Check the documentation for your database if you're not familiar with how it handles transaction isolation. +Check the documentation for your database if you are not familiar with how it handles transaction isolation. == Deadlocks @@ -115,9 +115,9 @@ One way of avoiding deadlocks is to acquire and release locks in the same order. |=== -It's not always clear why transaction deadlocks occur. In some databases, even read-only queries can cause deadlocks. You may be able to fix some deadlocks by changing the isolation level of your transaction, but this may have other negative consequences on the data consistency. +It is not always clear why transaction deadlocks occur. In some databases, even read-only queries can cause deadlocks. You may be able to fix some deadlocks by changing the isolation level of your transaction, but this may have other negative consequences on the data consistency. -When you can't avoid deadlocks, you have to be prepared to deal with them. Most databases are able to detect when a deadlock occurs. When this happens, they pick a victim transaction and roll it back, allowing the other transaction to proceed. Your application then has to execute the victim transaction again. You should check your database documentation to see how it handles deadlocks. +When you cannot avoid deadlocks, you have to be prepared to deal with them. Most databases are able to detect when a deadlock occurs. When this happens, they pick a victim transaction and roll it back, allowing the other transaction to proceed. Your application then has to execute the victim transaction again. You should check your database documentation to see how it handles deadlocks. A deadlock exception is a form of pessimistic locking exception. For more information about handling those, see the <<../pessimistic-locking#resolving-conflicts,Pessimistic Locking>> documentation page. @@ -132,13 +132,13 @@ Spring supports the following propagation levels: `REQUIRES_NEW`:: When there is an active transaction, Spring suspends it and creates a new one. Once the new transaction has completed, Spring resumes the earlier one. -`MANDATORY`:: If there is an active transaction, Spring executes the method inside it. Otherwise, Spring throws an exception and doesn't execute the method. +`MANDATORY`:: If there is an active transaction, Spring executes the method inside it. Otherwise, Spring throws an exception and does not execute the method. `SUPPORTS`:: For an active transaction, Spring executes the method inside it. Otherwise, the method is executed without a transaction. -`NOT_SUPPORTED`:: When there's an active transaction, Spring suspends it. The method is then executed without a transaction. Once the method has completed, Spring resumes the earlier one. +`NOT_SUPPORTED`:: When there is an active transaction, Spring suspends it. The method is then executed without a transaction. Once the method has completed, Spring resumes the earlier one. -`NEVER`:: If there is an active transaction, Spring throws an exception and doesn't execute the method. +`NEVER`:: If there is an active transaction, Spring throws an exception and does not execute the method. Spring also has a `NESTED` propagation level, but it has some limitations. For more information on it, see the https://docs.spring.io/spring-framework/reference/data-access/transaction/declarative/tx-propagation.html[Spring Documentation]. diff --git a/articles/building-apps/forms-data/consistency/transactions/programmatic.adoc b/articles/building-apps/forms-data/consistency/transactions/programmatic.adoc index 435428de75..baee7b566e 100644 --- a/articles/building-apps/forms-data/consistency/transactions/programmatic.adoc +++ b/articles/building-apps/forms-data/consistency/transactions/programmatic.adoc @@ -48,7 +48,7 @@ public class MyApplicationService { Programmatic transaction management is more verbose than declarative transaction management, but provides better control. Instead of relying on a proxy and a method interceptor, the method controls its own transactions. This removes the limitations of declarative transaction management. -In the following example, `myFirstMethod()` executes inside its own transaction, regardless of whether it's called directly by a client, or by `mySecondMethod()`. +In the following example, `myFirstMethod()` executes inside its own transaction, regardless of whether it is called directly by a client or by `mySecondMethod()`. [source,java] ---- @@ -87,7 +87,7 @@ For more information about programmatic transaction management, see the https:// == Committing -You don't have to do anything special to commit a transaction. Spring commits the transaction after the callback returns, unless it has been marked for rollback. +You do not have to do anything special to commit a transaction. Spring commits the transaction after the callback returns, unless it has been marked for rollback. == Rolling Back @@ -114,7 +114,7 @@ public class MyApplicationService { } ---- -Unlike declarative transactions, you can't throw checked exceptions when using programmatic transactions. +Unlike declarative transactions, you cannot throw checked exceptions when using programmatic transactions. == Isolation Level diff --git a/articles/building-apps/forms-data/consistency/validation.adoc b/articles/building-apps/forms-data/consistency/validation.adoc index c6f3d930ed..7fb62b68e7 100644 --- a/articles/building-apps/forms-data/consistency/validation.adoc +++ b/articles/building-apps/forms-data/consistency/validation.adoc @@ -2,7 +2,7 @@ title: Validation page-title: How to validate your data in your Vaadin apps description: Learn how to validate your data. -meta-description: Learn about data validation - principles, semantics, sanitization and more. +meta-description: Learn about data validation - principles, semantics, sanitization, and more. order: 15 --- @@ -50,7 +50,7 @@ public class User { } ---- -The annotations themselves don't perform any validation. When you have an instance of a `User` object, you can't tell whether it's valid without running it through a validator. In a Spring application, you can do this declaratively or programmatically. +The annotations themselves do not perform any validation. When you have an instance of a `User` object, you cannot tell whether it is valid without running it through a validator. In a Spring application, you can do this declaratively or programmatically. To validate an input object declaratively, you first need to add the `@Validated` annotation to the service. Next, you need to add the `@Valid` annotation to the parameter that you want to validate. During runtime, Spring turns your service into a proxy, and validates the input for you inside a method interceptor. @@ -71,7 +71,7 @@ public class UserService { } ---- -Declarative validation has the same limitations as Spring's other declarative services. Therefore, you can also do it, programmatically. In this case, you'd inject an instance of `Validator` and directly invoke it. +Declarative validation has the same limitations as Spring's other declarative services. Therefore, you can also do it, programmatically. In this case, you would inject an instance of `Validator` and directly invoke it. The following example also makes sure that the `User` object is valid, but does so programmatically: @@ -104,7 +104,7 @@ For more information about Jakarta Bean Validation, visit the https://beanvalida == Domain Primitives -Whereas Bean Validation requires the data to be passed through a validator, domain primitives have validation built into their constructors. The fact that a domain primitive object exists, means that it's valid -- at least to some extent. Using domain primitives, the earlier `User` example could look like this: +Whereas Bean Validation requires the data to be passed through a validator, domain primitives have validation built into their constructors. The fact that a domain primitive object exists means that it is valid -- at least to some extent. Using domain primitives, the earlier `User` example could look like this: [source,java] ---- @@ -127,30 +127,30 @@ public class User { ---- <1> The `EmailAddress` class has validation built in. <2> Instead of `@NonNull`, the `User` class is built in such a way that the `email` field can never be null. -<3> It's still possible to change the email address, but not set it to `null`. +<3> It is still possible to change the email address, but not set it to `null`. -Semantic validation isn't always easy to build into a domain primitive if the validation requires access to an external resource. You may, for example, have to check that something exists in a database. Making a custom Bean Validation constraint, though, that does this is easy since Spring supports injecting services into your constraint validators. Therefore, you could let the domain primitive validate its own size, lexical content, and syntax, while handing semantic validation over to Bean Validation. +Semantic validation is not always easy to build into a domain primitive if the validation requires access to an external resource. You may, for example, have to check that something exists in a database. Making a custom Bean Validation constraint, though, that does this is easy since Spring supports injecting services into your constraint validators. Therefore, you could let the domain primitive validate its own size, lexical content, and syntax, while handing semantic validation over to Bean Validation. For more information about domain primitives, see the <> documentation page. == Validation Principles -Regardless of whether you're using Bean Validation or domain primitives, the validation should follow the same general principles. Data validation is a multi-step process that goes from the cheaper and faster steps, to the expensive and slower steps. If one step fails, the validation stops immediately, and the validated value is rejected. All steps aren't always needed. +Regardless of whether you are using Bean Validation or domain primitives, the validation should follow the same general principles. Data validation is a multi-step process that goes from the cheaper and faster steps, to the expensive and slower steps. If one step fails, the validation stops immediately, and the validated value is rejected. Not all steps are always needed. Allowing the validation to continue not only wastes computing resources, but can be a security risk. For instance, the semantic validation step might try to parse the value, or use it as a database query argument. In the worst case, this can turn your validation into a vector for injection attacks, or attacks like https://en.wikipedia.org/wiki/Billion_laughs_attack[a billion laughs]. === Origin -Whenever the source of the data is relevant, you should validate that it's legitimate. How you do this depends on both the data itself, and how it enters your application. For instance, you could require a valid API-key, or you could check the client's IP-address against an allowlist or a denylist, or maybe use digital signatures. +Whenever the source of the data is relevant, you should validate that it is legitimate. How you do this depends on both the data itself and how it enters your application. For instance, you could require a valid API-key, or you could check the client's IP-address against an allowlist or a denylist, or maybe use digital signatures. -You're probably not going to build this type of validation into a custom constraint validator, or a domain primitive constructor. Rather, this is something that is handled at the edges of your system, like by a servlet filter or a firewall. +You are probably not going to build this type of validation into a custom constraint validator, or a domain primitive constructor. Rather, this is something that is handled at the edges of your system, such as a servlet filter or a firewall. === Size -Whenever the size of the data is variable (e.g., strings and files), you should validate that it's within reasonable limits. When the data is too big or too small, there is no point in validating it further. You can save computing resources by rejecting it early and freeing the memory -- especially if the data is too large. +Whenever the size of the data is variable (e.g., strings and files), you should validate that it is within reasonable limits. When the data is too big or too small, there is no point in validating it further. You can save computing resources by rejecting it early and freeing the memory -- especially if the data is too large. Here are some examples of size constraints: @@ -165,7 +165,7 @@ Bean Validation has built-in annotations for this type of validation: `@Size`, ` === Lexical Content -Whenever the data is text, you should check its lexical content. This means checking that it's correctly encoded, and contains the correct characters. It's best to do such a check before would parse the string. When it contains illegal characters, there is no point in proceeding. +Whenever the data is text, you should check its lexical content. This means checking that it is correctly encoded and contains the correct characters. It is best to do such a check before you parse the string. When it contains illegal characters, there is no point in proceeding. Here are some examples of lexical content constraints: @@ -186,12 +186,12 @@ Here are some examples of syntax constraints: - A UUID has the form `xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx`, where some digit have extra meaning. - An ISO 8601 formatted date has the form `yyyy-mm-dd`, where the year has to be between 0000 and 9999, the month between 01 and 12, and the day between 01 and 31. -If you're using regular expressions to validate the input, you can merge the lexical content and the syntax validation into a single step. However, if a check digit is involved, you have to do some parsing on your own. +If you are using regular expressions to validate the input, you can merge the lexical content and the syntax validation into a single step. However, if a check digit is involved, you have to do some parsing on your own. === Semantics -The final validation step is semantic validation. This means making sure that the data makes sense, even though it's syntactically correct. This almost always involves comparing the input to something like a standard, another input, or even an external data source. +The final validation step is semantic validation. This means making sure that the data makes sense, even though it is syntactically correct. This almost always involves comparing the input to something like a standard, another input, or even an external data source. Here are some examples of semantic constraints: @@ -204,13 +204,13 @@ Here are some examples of semantic constraints: == Sanitization -Sometimes, it makes sense to sanitize input before you validate it. People tend to enter certain data, like telephone numbers and addresses, in different ways. Nagging them about this results in a bad user experience. It's unnecessary when your application can sanitize the input itself. +Sometimes, it makes sense to sanitize input before you validate it. People tend to enter certain data, such as telephone numbers and addresses, in different ways. Nagging them about this results in a bad user experience. It is unnecessary when your application can sanitize the input itself. Here are some examples of automatic sanitization: - Remove trailing and leading whitespace. - Remove whitespace, `-`, `.`, `(`, and `)` from telephone numbers. -- Allow users to enter decimals using both `.` and `,` -- be careful if they're also used as thousand dividers. +- Allow users to enter decimals using both `.` and `,` -- be careful if they are also used as thousand dividers. - Replace `<` and `>` with `&lt;` and `&gt;`. Sanitization, though, is never a substitute for validation. You should always run the sanitized value through the complete validation chain. A sanitized value can be safe in one context, and unsafe in another. For example, if you escape HTML formatting characters in a string, although you can safely print it on a webpage, it may still contain an SQL injection attack. diff --git a/articles/building-apps/forms-data/create-custom-field/index.adoc b/articles/building-apps/forms-data/create-custom-field/index.adoc index ce6f57ceb4..986133fe1e 100644 --- a/articles/building-apps/forms-data/create-custom-field/index.adoc +++ b/articles/building-apps/forms-data/create-custom-field/index.adoc @@ -17,7 +17,7 @@ along with a step-by-step example that you can follow to build your own. == Custom Fields in Vaadin A Custom Field is useful when you want multiple components to behave as a single form field, -or when you have a component that doesn't support integration with Binder, labels, or helper text. +or when you have a component that does not support integration with Binder, labels, or helper text. In these cases, you can encapsulate the components inside a Custom Field. Several approaches are available depending on the level of customization required. @@ -41,12 +41,12 @@ or on the server side (`setPresentationValue`). A Custom Field automatically tracks value changes from its internal input elements (whether attached directly or nested inside other components). -This means you *usually* don't need to add additional listeners to synchronize the field value. +This means you *usually* do not need to add additional listeners to synchronize the field value. You must ensure that `generateModelValue()` correctly gathers all relevant changes to compute the field's value. [WARNING] -Be mindful that automatic tracking of value changes doesn't apply to all components, for example, `Grid` only provides selection change events. +Be mindful that automatic tracking of value changes does not apply to all components, for example, `Grid` only provides selection change events. ==== Example: Dice Field @@ -97,7 +97,7 @@ This topic is outside the scope of this guide. See the article on <<{articles}/flow/binding-data/field#,Creating a Component that Has a Value>> for more details. [IMPORTANT] -If your `AbstractField` is not displaying some Vaadin component properly, you may need add `@Uses({component}.class)` annotation (for example `@Uses(ComboBox.class)`, `@Uses(CustomField.class)` ), to prevent <<{articles}/flow/production/production-build#bundle-component-loading-optimizations,Bundle Optimization>> from not including their resources. +If your `AbstractField` is not displaying some Vaadin component properly, you may need to add `@Uses({component}.class)` annotation (for example `@Uses(ComboBox.class)`, `@Uses(CustomField.class)` ), to ensure <<{articles}/flow/production/production-build#bundle-component-loading-optimizations,Bundle Optimization>> includes their resources. == Choosing Internal Components @@ -121,11 +121,11 @@ This ensures your custom field behaves consistently with other Vaadin form field === Using HTML Element Wrappers HTML element wrappers (like `Input` or `Div`) give you more flexibility, but they require more careful design. -Unlike Vaadin components, these elements don't automatically handle states or styles. +Unlike Vaadin components, these elements do not automatically handle states or styles. You may need to manually implement how your custom field supports states such as `invalid` or `readonly`. That said, depending on your application design, not all states need to be supported. -For example, if `invalid` state is irrelevant to your custom field, you don't need to add logic for handling that state. +For example, if `invalid` state is irrelevant to your custom field, you do not need to add logic for handling that state. == Data Binding @@ -181,7 +181,7 @@ If the change is from the client, the new value from the event is used to update === Using Binder -Binding a Custom Field works the same way as with built-in components like `TextField` or `ComboBox`. If you're familiar with <<{articles}/building-apps/forms-data/add-form/fields-and-binding#,binding>> and <<{articles}/building-apps/forms-data/add-form/validation#,form validation>>, the process should feel identical. +Binding a Custom Field works the same way as with built-in components such as `TextField` or `ComboBox`. If you are familiar with <<{articles}/building-apps/forms-data/add-form/fields-and-binding#,binding>> and <<{articles}/building-apps/forms-data/add-form/validation#,form validation>>, the process should feel identical. Example binding: ```java @@ -310,14 +310,14 @@ vaadin-custom-field::part(required-indicator) { ``` [IMPORTANT] -If you're using a binder, calling `binder.asRequired()` on your field automatically enables the required indicator. +If you are using a binder, calling `binder.asRequired()` on your field automatically enables the required indicator. == Validation When extending `CustomField`, you get built-in support for marking the field as invalid and displaying error messages. This ensures your Custom Field behaves consistently with other Vaadin field components in terms of styling and accessibility. [IMPORTANT] -If you're extending `AbstractField`, you must implement the `HasValidationProperties` interface and provide elements that use the `invalid` and `errorMessage` properties. +If you are extending `AbstractField`, you must implement the `HasValidationProperties` interface and provide elements that use the `invalid` and `errorMessage` properties. You can manually set an error message and invalid state: @@ -460,7 +460,7 @@ However, this can cause problems if not handled carefully: * Do not rely on the same `invalid` and `errorMessage` properties for internal validation. Otherwise, when bound to a Binder, external validation is likely to override or ignore the internal state. -* It's recommended that you limit internal validation to built-in validators in Vaadin components. +* It is recommended that you limit internal validation to built-in validators in Vaadin components. ** For example, use `field.setMax(Integer)` on an `IntegerField`. * For advanced cases, you may provide a method that allows external validation frameworks (like Binder) to query the internal validation state. @@ -471,7 +471,7 @@ This guide does not cover such advanced integrations. Styling a Custom Field works much like styling other Vaadin field components. However, since a Custom Field may include both built-in parts (such as the label and error message) -and your own internal elements, it's important to know how to target both effectively. +and your own internal elements, it is important to know how to target both effectively. Before proceeding, review: @@ -482,7 +482,7 @@ These explain available selectors and theming options in detail. .Lumo theme used in examples below [NOTE] -The code examples below use component style variants and style properties of the <<{articles}/styling/themes/lumo#,Lumo theme>>. If you're not using Lumo, you'll need to use other alternatives. +The code examples below use component style variants and style properties of the <<{articles}/styling/themes/lumo#,Lumo theme>>. If you are not using Lumo, you need to use other alternatives. === Styling Default Custom Field Elements @@ -635,7 +635,7 @@ public void setI18n(DateTimePickerI18n i18n) { === Step 3: Update Components When Localization Changes Implement a method that updates internal elements whenever a new localization object is applied. -The exact code for this in `DateTimePicker` is a bit too complex to show as an example, since it's based on a web-component. +The exact code for this in `DateTimePicker` is a bit too complex to show as an example, since it is based on a web-component. A simplified version would look like this: @@ -662,10 +662,10 @@ This section highlights the most common cases you may encounter. === Labels and Input Association A `CustomField` provides a built-in label. -For single-input cases, you typically don't need to create an additional label. +For single-input cases, you typically do not need to create an additional label. However, the built-in label should be associated with the input element. -By default, this association does not exist because `CustomField` doesn't know +By default, this association does not exist because `CustomField` does not know which input the label should point to, especially in cases with multiple inputs. Vaadin does not currently provide a built-in solution for this, @@ -722,7 +722,7 @@ public void onAttach(AttachEvent event) { [.collapsible-list] == Try It -This step-by-step example, shows the creation a duration field. +This step-by-step example shows the creation of a duration field. The field consists of two input fields: hours and minutes. It includes custom labels so that the full value can be read as, for example, “2 hours and 30 minutes”. @@ -853,7 +853,7 @@ public final class MainView extends Main { ``` -If you test the component by entering some valid values in the input fields, you'll find that it works functionally but does not look polished. +If you test the component by entering some valid values in the input fields, you find that it works functionally but does not look polished. There are some obvious spacing issues, which are addressed in the next step. ==== @@ -862,7 +862,7 @@ There are some obvious spacing issues, which are addressed in the next step. [%collapsible] ==== -This Custom Field doesn't require extensive custom styling. +This Custom Field does not require extensive custom styling. <<{articles}/styling/themes/lumo/utility-classes#,Lumo Utility Classes>> can be used to quickly address the spacing issues. For the "hours" and "minutes" labels, add some left padding: @@ -877,7 +877,7 @@ For the "and" span element, add both left and right padding: andSpan.addClassNames(LumoUtility.Padding.Left.SMALL, LumoUtility.Padding.Right.SMALL); ``` -Here's the updated version of the DurationField: +Here is the updated version of the DurationField: ```java package com.mydomain.myproject.ui.components; @@ -1009,7 +1009,7 @@ You can verify this by opening the view containing the `DurationField` in your b The Custom Field can be attached to a Binder to define additional validators. First, create a DTO class for binding. -DTOs classes usually are separately from the UI code. Place it under `src/main/java//data`, for example `src/main/java/com/mydomain/myproject/ui/component`. +DTO classes are usually separate from the UI code. Place it under `src/main/java//data`, for example `src/main/java/com/mydomain/myproject/ui/component`. ```java package com.mydomain.myproject.ui.components; @@ -1093,7 +1093,7 @@ protected Duration generateModelValue() { } ``` -Here's the updated DurationField with validation included: +Here is the updated DurationField with validation included: ```java import com.vaadin.flow.component.customfield.CustomField; @@ -1213,7 +1213,7 @@ If your application supports multiple languages, your Custom Field should also p A common approach used by other Vaadin components is to create a dedicated class that contains all translatable strings. Start by only including the texts for the components added -and which don't already have a public API for updating them +and which do not already have a public API for updating them (an alternative approach would be to expose setters directly). This class can be part of the existing `DurationField` class, or as a separate class. @@ -1313,7 +1313,7 @@ duration.setI18n(new DurationFieldI18n("stundas", "minūtes", "un")); // Localiz ``` -Here's the updated `DurationField` with localization support: +Here is the updated `DurationField` with localization support: ```java import com.vaadin.cf.components.DurationFieldI18n; @@ -1449,7 +1449,7 @@ public class DurationField extends CustomField { [%collapsible] ==== -Finally, you'll address the accessibility requirements of this Custom Field. +Finally, address the accessibility requirements of this Custom Field. The challenge is that there is a main label (“Duration”) and two inputs ("hours" and "minutes"), each with its own label. @@ -1467,8 +1467,8 @@ Since the `for` attribute can reference only one input, a single target must be This is acceptable since screen readers prioritize `aria-labelledby` when reading input labels. Such changes can be handled through JavaScript, avoiding the need to manually generate unique input IDs. -You'll create a method that does all that on JS side, since it helps to avoid generating separate unique ids for the inputs. -It's not pretty, but it takes care of everything needed. +The following method does all that on the JS side, since it helps to avoid generating separate unique IDs for the inputs. +It is not pretty, but it takes care of everything needed. ```java private void setFor(IntegerField field, NativeLabel label, String labelIdPostfix) { @@ -1549,7 +1549,7 @@ protected void onAttach(AttachEvent attachEvent) { This ensures that accessibility links between labels and inputs are restored each time the field is attached to the UI. -Here's the updated DurationField with accessibility support included: +Here is the updated DurationField with accessibility support included: ```java package com.vaadin.cf.components.tutorial; @@ -1747,7 +1747,7 @@ public class DurationField extends CustomField { In this tutorial, a fully functional `CustomField` was built through a guided tutorial, covering not only the basics but also important production-level considerations. -By following these steps, you've seen how to move from a minimal implementation to a robust, production-ready custom field. +By following these steps, you have seen how to move from a minimal implementation to a robust, production-ready custom field. The final component supports data binding, validation, localization, theming, and accessibility all while remaining consistent with Vaadin's design system and best practices. diff --git a/articles/building-apps/forms-data/handle-uploads.adoc b/articles/building-apps/forms-data/handle-uploads.adoc index e236d92fd4..cb6771ebd8 100644 --- a/articles/building-apps/forms-data/handle-uploads.adoc +++ b/articles/building-apps/forms-data/handle-uploads.adoc @@ -181,7 +181,7 @@ Because the callback runs on the UI thread, avoid long-running processing inside When using `UploadHandler.toTempFile()`, you are responsible for deleting the temporary file after processing. Two common patterns: -*Immediate cleanup* — process and delete in the handler. This is the preferred approach when you don't need to keep the file around: +*Immediate cleanup* — process and delete in the handler. This is the preferred approach when you do not need to keep the file around: [source,java] ---- diff --git a/articles/building-apps/forms-data/persistence/add-flyway.adoc b/articles/building-apps/forms-data/persistence/add-flyway.adoc index bdca42cb32..6f15c82b15 100644 --- a/articles/building-apps/forms-data/persistence/add-flyway.adoc +++ b/articles/building-apps/forms-data/persistence/add-flyway.adoc @@ -10,9 +10,9 @@ order: 5 Whenever you store data in a relational database, you have to manage the database schema in some way. When the application is first installed, you have to create the entire database schema. When new features are deployed, you have to update the database schema. You might need to add new tables, introduce new columns, or move data between tables and remove the obsolete ones. -Some object-relational mapping tools, like Hibernate, can generate the initial schema for you. They may also be able to perform trivial updates to the schema, like creating new tables. In more complex cases, however, they are at a loss. For this reason, https://www.red-gate.com/products/flyway/community/[Flyway] is the recommended tool for managing database schemas in Vaadin applications. +Some object-relational mapping tools, such as Hibernate, can generate the initial schema for you. They may also be able to perform trivial updates to the schema, such as creating new tables. In more complex cases, however, they are at a loss. For this reason, https://www.red-gate.com/products/flyway/community/[Flyway] is the recommended tool for managing database schemas in Vaadin applications. -In this guide, you'll learn enough about Flyway to get started using it in your Vaadin applications. Because Flyway has more features than presented here, you should also read the https://documentation.red-gate.com/flyway[Flyway Documentation]. +In this guide, you learn enough about Flyway to get started using it in your Vaadin applications. Because Flyway has more features than presented here, you should also read the https://documentation.red-gate.com/flyway[Flyway Documentation]. == Migrations @@ -25,7 +25,7 @@ Versioned migrations should not change after they have been applied. When Flyway In addition to versioned migrations, Flyway also supports repeatable migrations. These migrations can change after they have been applied, and are automatically re-applied after every change. Repeatable migrations are always applied after the versioned migrations. -.Why you shouldn't mix `data.sql` with Flyway +.Why you should not mix `data.sql` with Flyway [IMPORTANT] Spring Boot's `data.sql` runs after Hibernate initializes the schema. When using Flyway, schema initialization is handled by Flyway itself, and using `data.sql` in parallel may lead to inconsistent state or runtime errors. Convert `data.sql` contents into migration scripts instead. @@ -52,7 +52,7 @@ Unless you are using an in-memory database like H2, you have to add a database s [NOTE] See the https://documentation.red-gate.com/flyway/getting-started-with-flyway/system-requirements/supported-databases-and-versions[Flyway Documentation] for a complete list of supported databases. -Spring Boot declares the modules in its parent POM, so you don't have to look up their versions. To use them, add them to your project POM-file, like this: +Spring Boot declares the modules in its parent POM, so you do not have to look up their versions. To use them, add them to your project POM-file, like this: [source,xml] ---- @@ -85,7 +85,7 @@ To configure Flyway to use a separate data source from your main application, se For more information, see the https://docs.spring.io/spring-boot/how-to/data-initialization.html#howto.data-initialization.migration-tool.flyway[Spring Boot Documentation]. -.Don't defer data source initialization +.Do not defer data source initialization [CAUTION] When you are using Flyway, you have to make sure the `spring.jpa.defer-datasource-initialization` configuration property is `false`. Otherwise, *your application fails to start*. The purpose of having this flag set to `true` is to allow JPA to create the schema before populating the database with data from `data.sql`. However, now Flyway is taking care of both of these tasks. diff --git a/articles/building-apps/forms-data/persistence/add-jooq.adoc b/articles/building-apps/forms-data/persistence/add-jooq.adoc index a54b567fac..fd3458207e 100644 --- a/articles/building-apps/forms-data/persistence/add-jooq.adoc +++ b/articles/building-apps/forms-data/persistence/add-jooq.adoc @@ -80,7 +80,7 @@ The code generator runs automatically whenever you build the Maven module. You c $ mvn jooq-codegen:generate ---- -If you're using a <>, configure the plugin in the module that contains the Flyway migrations. +If you are using a <>, configure the plugin in the module that contains the Flyway migrations. == Spring Boot Configuration diff --git a/articles/building-apps/forms-data/persistence/add-spring-data.adoc b/articles/building-apps/forms-data/persistence/add-spring-data.adoc index f7840db42c..1140de605a 100644 --- a/articles/building-apps/forms-data/persistence/add-spring-data.adoc +++ b/articles/building-apps/forms-data/persistence/add-spring-data.adoc @@ -39,7 +39,7 @@ You also need a JDBC driver for your database. For example, for PostgreSQL: ---- -Spring Boot declares driver versions in its parent POM, so you don't need to specify them. +Spring Boot declares driver versions in its parent POM, so you do not need to specify them. == Static Metamodel Generation @@ -67,7 +67,7 @@ Add this to your `pom.xml`: The `hibernate.version` property is inherited from the Spring Boot starter parent. -If you're using a <>, configure this only in modules containing JPA entities. +If you are using a <>, configure this only in modules containing JPA entities. == Spring Boot Configuration @@ -81,14 +81,14 @@ spring.datasource.username=myuser spring.datasource.password=mypassword ---- -By default, Hibernate validates that entities match the database schema but doesn't modify it. If you're using <> for migrations (recommended), keep this default: +By default, Hibernate validates that entities match the database schema but does not modify it. If you are using <> for migrations (recommended), keep this default: [source,properties] ---- spring.jpa.hibernate.ddl-auto=validate ---- -.Don't use `ddl-auto` in production +.Do not use `ddl-auto` in production [CAUTION] Avoid `create`, `create-drop`, or `update` in production. Use Flyway to manage schema changes explicitly. diff --git a/articles/building-apps/forms-data/persistence/index.adoc b/articles/building-apps/forms-data/persistence/index.adoc index 1004851e02..7e050ac606 100644 --- a/articles/building-apps/forms-data/persistence/index.adoc +++ b/articles/building-apps/forms-data/persistence/index.adoc @@ -14,12 +14,12 @@ Most Vaadin applications need to store and retrieve data from a database. How yo This section covers the main approaches to persistence and helps you choose between them—or combine them, which is what most real applications end up doing. -If you're starting a new project, you'll also need to set up <> and add your chosen persistence technology (<>, <>, or both). +If you are starting a new project, you also need to set up <> and add your chosen persistence technology (<>, <>, or both). == Two Approaches -You can think about database access in Java applications in two ways. The difference isn't just technical—it reflects how you model your problem. +You can think about database access in Java applications in two ways. The difference is not just technical—it reflects how you model your problem. === Aggregate-Oriented @@ -40,7 +40,7 @@ This approach works well when: - Objects have complex relationships and invariants that must be enforced together - You benefit from encapsulating business logic inside the objects themselves -- The object graph isn't too large (loading a thousand line items every time would be wasteful) +- The object graph is not too large (loading a thousand line items every time would be wasteful) === Table-Oriented @@ -60,16 +60,16 @@ This approach works well when: - You need fine-grained control over queries for performance reasons - The operation is naturally expressed as a database operation (bulk updates, reports, aggregations) -- You're working with simple data that doesn't need complex object relationships +- You are working with simple data that does not need complex object relationships == Most Applications Use Both -In practice, you'll likely use both approaches in the same application. This isn't a compromise—it's pragmatic. +In practice, you likely use both approaches in the same application. This is not a compromise—it is pragmatic. Your core domain objects—the ones with important state transitions and business rules—benefit from the aggregate-oriented approach. An `Order` that enforces pricing rules, validates state transitions, and maintains consistency across its line items is easier to reason about as an object graph. -But not everything is a state machine. A dashboard showing order counts by region doesn't need to load `Order` aggregates. A bulk operation archiving old records shouldn't instantiate thousands of objects. A search feature returning paginated results is naturally a data transformation, not an aggregate retrieval. +But not everything is a state machine. A dashboard showing order counts by region does not need to load `Order` aggregates. A bulk operation archiving old records should not instantiate thousands of objects. A search feature returning paginated results is naturally a data transformation, not an aggregate retrieval. A typical Vaadin application might have: @@ -77,7 +77,7 @@ A typical Vaadin application might have: - *Table-oriented queries* for list views, reports, search results, dashboards - *Table-oriented commands* for bulk updates, data imports, archival operations -This separation sometimes goes by the name Command Query Responsibility Segregation (CQRS), though you don't need to adopt the full pattern to benefit from the idea. Both the <> and <> documentation pages show how to implement this with dedicated query classes. +This separation sometimes goes by the name Command Query Responsibility Segregation (CQRS), though you do not need to adopt the full pattern to benefit from the idea. Both the <> and <> documentation pages show how to implement this with dedicated query classes. == Technology Choices @@ -98,7 +98,7 @@ public interface OrderRepository extends JpaRepository { JPA works best when: -- You're working with entity graphs that map reasonably to your object model +- You are working with entity graphs that map reasonably to your object model - You want automatic change tracking and dirty checking - Your queries are straightforward enough that derived query methods or JPQL handle them @@ -139,7 +139,7 @@ jOOQ works best when: - You need complex queries that are hard to express in JPQL - You want explicit control over the SQL being executed - Your schema is the source of truth and you want your code to reflect it -- You're doing bulk operations or reporting +- You are doing bulk operations or reporting See <> for implementation details. @@ -151,7 +151,7 @@ A practical combination used in real Vaadin projects: - *JPA* for loading and saving aggregates — your core domain entities with their relationships and business logic - *jOOQ* for everything else — list views, search, reports, bulk operations, complex queries -Both can share the same database and even participate in the same transactions. They're not mutually exclusive. +Both can share the same database and even participate in the same transactions. They are not mutually exclusive. [source,java] ---- @@ -178,13 +178,13 @@ public class OrderService { Some guidelines that have worked well in Vaadin applications: -*Start simple.* If your application is mostly CRUD with simple validation, you don't need complex patterns. Spring Data repositories with JPA entities might be all you need. +*Start simple.* If your application is mostly CRUD with simple validation, you do not need complex patterns. Spring Data repositories with JPA entities might be all you need. -*Introduce complexity when you feel the pain.* When queries become awkward in JPQL, add jOOQ. When business logic becomes scattered, consider richer domain objects. Don't architect for problems you don't have. +*Introduce complexity when you feel the pain.* When queries become awkward in JPQL, add jOOQ. When business logic becomes scattered, consider richer domain objects. Do not architect for problems you do not have. *Keep list views and detail views separate.* The data you show in a Grid (a few columns, many rows, paginated) is different from what you need when editing a single entity (complete object graph, all fields). Different queries for different purposes. -*Don't load what you don't need.* Whether you're using JPA or jOOQ, fetching complete entities when you only need three fields for a drop-down is wasteful. Use projections. +*Do not load what you do not need.* Whether you are using JPA or jOOQ, fetching complete entities when you only need three fields for a drop-down is wasteful. Use projections. *Be consistent within a bounded context.* If your order management uses JPA entities with repositories, stick with that pattern for orders. Mixing approaches within the same concept creates confusion. diff --git a/articles/building-apps/forms-data/persistence/jooq.adoc b/articles/building-apps/forms-data/persistence/jooq.adoc index 00308a8217..956e056420 100644 --- a/articles/building-apps/forms-data/persistence/jooq.adoc +++ b/articles/building-apps/forms-data/persistence/jooq.adoc @@ -10,12 +10,12 @@ order: 30 = jOOQ :toclevels: 2 -https://www.jooq.org/[jOOQ] generates Java code from your database schema and lets you write type-safe SQL. It's database-first: your schema is the source of truth, and your code reflects it. +https://www.jooq.org/[jOOQ] generates Java code from your database schema and lets you write type-safe SQL. It is database-first: your schema is the source of truth, and your code reflects it. jOOQ supports both <>. It excels at table-oriented work—queries, bulk operations, reports—but can also support aggregate-oriented persistence if you need it. Many Vaadin projects combine jOOQ with <>: JPA for entity lifecycle management, jOOQ for everything else. [NOTE] -This page assumes you've already <> to your project and are familiar with jOOQ basics. If you're new to jOOQ, complete the https://www.jooq.org/learn/[jOOQ tutorial] first. +This page assumes you have already <> to your project and are familiar with jOOQ basics. If you are new to jOOQ, complete the https://www.jooq.org/learn/[jOOQ tutorial] first. == Table-Oriented Access @@ -48,7 +48,7 @@ record.delete(); The `store()` method issues an `INSERT` or `UPDATE` depending on whether the record was created with `newRecord()` or fetched from the database. -Active records are ideal for straightforward CRUD where you don't need rich domain logic. They map directly to table rows with no ceremony. +Active records are ideal for straightforward CRUD where you do not need rich domain logic. They map directly to table rows with no ceremony. === Generated POJOs @@ -158,7 +158,7 @@ Query classes keep complex SQL organized and testable. Create as many as you nee == Aggregate-Oriented Access -If you need richer domain objects with business logic, jOOQ can support that too. You'll write more code than with JPA, but gain full control over mapping. +If you need richer domain objects with business logic, jOOQ can support that too. You will write more code than with JPA, but gain full control over mapping. === Entities @@ -218,7 +218,7 @@ public Optional findById(OrderId id) { This executes as a single SQL statement with a correlated subquery. The result maps directly to your domain objects—an `Order` containing a `List`. -MULTISET works with multiple levels of nesting and multiple child collections. It's particularly valuable when your aggregates have complex structures that would be painful to load with JPA's eager/lazy fetching. +MULTISET works with multiple levels of nesting and multiple child collections. It is particularly valuable when your aggregates have complex structures that would be painful to load with JPA's eager/lazy fetching. [NOTE] MULTISET requires jOOQ's commercial editions for some databases, or jOOQ 3.15+ with databases that support it natively (PostgreSQL, MySQL 8+, etc.). Check the https://www.jooq.org/doc/latest/manual/sql-building/column-expressions/multiset-value-constructor/[jOOQ documentation] for details. @@ -279,7 +279,7 @@ public class OrderRepository { } ---- -This is more work than JPA. You're responsible for: +This is more work than JPA. You are responsible for: - Mapping between records and entities - Deciding when to insert vs update @@ -294,8 +294,8 @@ The payoff is complete control. No surprise lazy loading, no proxy magic, no won Use jOOQ for aggregate-oriented persistence when: - You need precise control over SQL and loading behavior -- Your aggregates don't fit JPA's assumptions well -- You're already using jOOQ heavily and want consistency +- Your aggregates do not fit JPA's assumptions well +- You are already using jOOQ heavily and want consistency - You prefer explicit code over framework conventions Otherwise, consider <> for aggregates and jOOQ for queries—a combination that works well in practice. diff --git a/articles/building-apps/forms-data/persistence/spring-data.adoc b/articles/building-apps/forms-data/persistence/spring-data.adoc index a156999fb2..35b34b76d5 100644 --- a/articles/building-apps/forms-data/persistence/spring-data.adoc +++ b/articles/building-apps/forms-data/persistence/spring-data.adoc @@ -6,7 +6,7 @@ meta-description: Integrate JPA and Spring Data in Vaadin applications for aggre order: 20 --- -// Used for documenation links +// Used for documentation links :hibernate-version: current @@ -20,7 +20,7 @@ https://spring.io/projects/spring-data-jpa[Spring Data JPA] builds on JPA to red JPA is well suited for <>—loading object graphs, working with entities that have lifecycles and business rules, and saving them back. If you need fine-grained control over SQL, complex reporting queries, or bulk operations, consider combining JPA with <> or using jOOQ alone. [NOTE] -This page assumes you've already <> to your project and are familiar with JPA concepts. If you're new to JPA, read the https://spring.io/guides/gs/accessing-data-jpa[Accessing Data with JPA] guide first. +This page assumes you have already <> to your project and are familiar with JPA concepts. If you are new to JPA, read the https://spring.io/guides/gs/accessing-data-jpa[Accessing Data with JPA] guide first. == Entities @@ -48,12 +48,12 @@ JPA deduces table and column names from class and field names. To make < Use `ProxyUtils.getUserClass` because Hibernate may return proxied entities whose classes don't match directly. +<1> Use `ProxyUtils.getUserClass` because Hibernate may return proxied entities whose classes do not match directly. == Domain Primitives @@ -181,9 +181,9 @@ Then apply it: private EmailAddress email; ---- -This is cleaner but makes non-equality queries harder—`LIKE` queries require strings, not domain primitives. Attribute converters also don't work with primary keys. +This is cleaner but makes non-equality queries harder—`LIKE` queries require strings, not domain primitives. Attribute converters also do not work with primary keys. -Use converters for domain primitives that are only queried by equality and aren't identifiers. +Use converters for domain primitives that are only queried by equality and are not identifiers. === Embeddable Records @@ -251,7 +251,7 @@ public class OrderService { JPA entities can be *managed* or *detached*. While managed (within a transaction), changes are automatically saved when the transaction commits—even without calling `save`. -Once the transaction completes, entities become detached. Further changes aren't persisted without an explicit `save`. +Once the transaction completes, entities become detached. Further changes are not persisted without an explicit `save`. [source,java] ---- @@ -272,7 +272,7 @@ customerRepository.save(customer); ---- [CAUTION] -Don't modify managed entities if you don't intend to save the changes. Roll back the transaction to discard modifications. +Do not modify managed entities if you do not intend to save the changes. Roll back the transaction to discard modifications. See <<../consistency/transactions#,Transactions>> for more on transaction management. diff --git a/articles/building-apps/forms-data/replace-h2.adoc b/articles/building-apps/forms-data/replace-h2.adoc index fcd3db1a50..38bb308eb3 100644 --- a/articles/building-apps/forms-data/replace-h2.adoc +++ b/articles/building-apps/forms-data/replace-h2.adoc @@ -10,7 +10,7 @@ order: 40 = Replace H2 :toclevels: 2 -Many Spring Boot applications start with H2 because it's lightweight and easy to configure. However, you typically don't run H2 in production. Switching to your production database early in development helps catch compatibility issues sooner and lets you leverage database-specific features for performance. +Many Spring Boot applications start with H2 because it is lightweight and easy to configure. However, you typically do not run H2 in production. Switching to your production database early in development helps catch compatibility issues sooner and lets you leverage database-specific features for performance. This guide teaches you how to replace H2 with PostgreSQL, although the same principle can be applied to other databases such as MySQL, Oracle, and Microsoft SQL Server. @@ -157,7 +157,7 @@ You can run the test application from your IDE, just like the main application c == Start a Development Database -After getting integration tests to pass, you'll likely want to run the application itself against a persistent local PostgreSQL instance. While Testcontainers can also be used to run the application, using a standalone database allows data to persist across restarts and more closely resembles a production environment. +After getting integration tests to pass, you likely want to run the application itself against a persistent local PostgreSQL instance. While Testcontainers can also be used to run the application, using a standalone database allows data to persist across restarts and more closely resembles a production environment. To start a local PostgreSQL database using Docker, run the following command: @@ -181,7 +181,7 @@ Then, recreate the container and restart your application. To run your application without Testcontainers, you need to configure it to connect to the local development database. You typically do this in the `src/main/resources/application.properties` file. Because `application.properties` is often committed to source control, *it should not contain sensitive credentials or any unsafe production settings*, such as enabling Hibernate to drop and recreate the schema. -The credentials of the local development database should never be used anywhere else than on the local machine. Therefore they can be checked into source control. Also, if the application accidentally starts up with them in production, it can't do any harm since the production database would use different credentials (and probably a different URL). +The credentials of the local development database should never be used anywhere else than on the local machine. Therefore they can be checked into source control. Also, if the application accidentally starts up with them in production, it cannot do any harm since the production database would use different credentials (and probably a different URL). In production, the real credentials would come from a different configuration file or a vault. Because of this, you can use `${..}` placeholders for the real credentials, and use the local development credentials as default values. For production, use Spring profiles or external configuration sources to override these default values: diff --git a/articles/building-apps/index.adoc b/articles/building-apps/index.adoc index 6e0fc70385..7425d0554e 100644 --- a/articles/building-apps/index.adoc +++ b/articles/building-apps/index.adoc @@ -31,14 +31,14 @@ Rather than covering every possible aspect of a topic, the guides focus on the e Many guides include *mini-tutorials* at the end, allowing you to test concepts before applying them to your own applications. -If you're looking for a fast and effective way to become productive with Vaadin, start with the *How-to Guides*. +If you are looking for a fast and effective way to become productive with Vaadin, start with the *How-to Guides*. == Deep Dives [badge-deep-dive]#Deep Dive# The Deep Dive articles take a broader and more conceptual approach to building business applications. Unlike the structured how-to guides, these articles explore the reasoning behind certain practices, provide in-depth knowledge, and offer insights, opinions, and alternative approaches. -Reading the Deep Dives isn't essential for getting started with Vaadin. If you're an experienced software developer, you may even disagree with some viewpoints -- and that's perfectly fine. However, if you want to master Vaadin and make the most of its capabilities, these articles are well worth exploring. +Reading the Deep Dives is not essential for getting started with Vaadin. If you are an experienced software developer, you may even disagree with some viewpoints -- and that is perfectly fine. However, if you want to master Vaadin and make the most of its capabilities, these articles are well worth exploring. == Topics diff --git a/articles/building-apps/integration/rest-api.adoc b/articles/building-apps/integration/rest-api.adoc index 5e06c86677..0f3cb61fcf 100644 --- a/articles/building-apps/integration/rest-api.adoc +++ b/articles/building-apps/integration/rest-api.adoc @@ -2,14 +2,14 @@ title: REST API page-title: Integrating REST API alongside Vaadin description: Integration of REST API within the Vaadin application. -meta-description: Learn how to implement REST API that's available through the same host/port as your Vaadin application. +meta-description: Learn how to implement REST API that is available through the same host/port as your Vaadin application. order: 7 --- = REST API Alongside Vaadin -Typically, when building a Vaadin application, you don’t need to expose any REST endpoints, since all required data can be exchanged directly through the view layer. However, if you need to share data with an external or third-party service, you’ll likely need to create a REST API to allow that service to access your application’s data. +Typically, when building a Vaadin application, you do not need to expose any REST endpoints, since all required data can be exchanged directly through the view layer. However, if you need to share data with an external or third-party service, you will likely need to create a REST API to allow that service to access your application’s data. In this article, two common approaches to implementing REST services in Vaadin applications are explored: @@ -34,7 +34,7 @@ While this approach keeps your Vaadin application simpler and more maintainable, == Exposing REST Endpoints from the Same Module -In small to medium-sized applications, or in "monolithic" systems, it can be tempting to include both the Vaadin UI and REST endpoints within the same module. This may seem convenient, especially if your REST API is small and straightforward, and doesn't feel substantial enough to justify a separate module. +In small to medium-sized applications, or in "monolithic" systems, it can be tempting to include both the Vaadin UI and REST endpoints within the same module. This may seem convenient, especially if your REST API is small and straightforward, and does not feel substantial enough to justify a separate module. This approach can be perfectly valid in certain cases, particularly when the data resides in JVM memory and is shared between the Vaadin UI and the REST endpoints. It also provides excellent performance when you need to display live or frequently updated data in the UI, since no inter-service communication is required. @@ -48,7 +48,7 @@ To mitigate these issues, you can assign a separate servlet mapping for your Vaa <<{articles}/flow/integrations/spring/configuration#spring-boot-properties,`vaadin.url-mapping` property>> to point to a dedicated path, such as `/ui/*`. -However, in many cases, it may be sufficient to use a custom base path for your REST API (for example, prefixed with `/api/`) and to define a separate security configuration specifically for your REST API paths. If you are using https://spring.io/projects/spring-data-rest[Spring Data REST], there's https://docs.spring.io/spring-boot/appendix/application-properties/index.html#application-properties.data.spring.data.rest.base-path[`spring.data.rest.base-path`] property that you can adjust. +However, in many cases, it may be sufficient to use a custom base path for your REST API (for example, prefixed with `/api/`) and to define a separate security configuration specifically for your REST API paths. If you are using https://spring.io/projects/spring-data-rest[Spring Data REST], there is a https://docs.spring.io/spring-boot/appendix/application-properties/index.html#application-properties.data.spring.data.rest.base-path[`spring.data.rest.base-path`] property that you can adjust. === Updating Security Configuration diff --git a/articles/building-apps/mcp/index.adoc b/articles/building-apps/mcp/index.adoc index 7feb6c6a9b..86bc5aca34 100644 --- a/articles/building-apps/mcp/index.adoc +++ b/articles/building-apps/mcp/index.adoc @@ -13,7 +13,7 @@ Model Context Protocol (MCP) is an open standard that enables AI assistants to s == Vaadin MCP Server -Vaadin provides an MCP server that gives AI assistants direct access to comprehensive Vaadin documentation. This enables AI assistants to provide accurate, context-aware responses based on latest Vaadin documentation. +Vaadin provides an MCP server that gives AI assistants direct access to comprehensive Vaadin documentation. This enables AI assistants to provide accurate, context-aware responses based on the latest Vaadin documentation. The Vaadin MCP server provides: diff --git a/articles/building-apps/mcp/supported-tools/claude-code.adoc b/articles/building-apps/mcp/supported-tools/claude-code.adoc index 4e59d78ae7..e4938edf94 100644 --- a/articles/building-apps/mcp/supported-tools/claude-code.adoc +++ b/articles/building-apps/mcp/supported-tools/claude-code.adoc @@ -15,7 +15,7 @@ Claude Code is Anthropic's official CLI tool for agentic coding. It provides nat == Configuration -Add the following to your `~/.config/claude/claude_desktop_config.json`: +Add the following to your `.claude/settings.json`: [source,json] ---- @@ -24,7 +24,7 @@ Add the following to your `~/.config/claude/claude_desktop_config.json`: "vaadin": { "url": "https://mcp.vaadin.com/docs" } - }, + } } ---- diff --git a/articles/building-apps/mcp/supported-tools/gemini-cli.adoc b/articles/building-apps/mcp/supported-tools/gemini-cli.adoc index 4fef16a049..4175a57884 100644 --- a/articles/building-apps/mcp/supported-tools/gemini-cli.adoc +++ b/articles/building-apps/mcp/supported-tools/gemini-cli.adoc @@ -41,7 +41,7 @@ The configuration file location varies by operating system: * *Linux:* `~/.gemini/settings.json` * *Windows:* `%USERPROFILE%\.gemini\settings.json` -If the directory doesn't exist, create it before adding the configuration file. +If the directory does not exist, create it before adding the configuration file. == Automatic Tool Invocation diff --git a/articles/building-apps/mcp/supported-tools/github-copilot.adoc b/articles/building-apps/mcp/supported-tools/github-copilot.adoc index a5514fd487..9eb5ace70f 100644 --- a/articles/building-apps/mcp/supported-tools/github-copilot.adoc +++ b/articles/building-apps/mcp/supported-tools/github-copilot.adoc @@ -49,7 +49,7 @@ Enterprise customers: Organizations with Copilot Business or Enterprise must ena [TIP] ==== -The `.vscode/mcp.json` file is project-specific. If you want to use the Vaadin MCP server across multiple projects, you'll need to add this configuration to each project. +The `.vscode/mcp.json` file is project-specific. If you want to use the Vaadin MCP server across multiple projects, you need to add this configuration to each project. ==== === Switching to Agent Mode @@ -111,7 +111,7 @@ For organizations using Copilot Business or Enterprise: . An administrator must enable the "MCP servers in Copilot" policy . This setting is found in the GitHub organization's Copilot policies . Individual developers can then configure MCP servers in their projects -. Contact your GitHub organization administrator if you don't see this option +. Contact your GitHub organization administrator if you do not see this option == Resources diff --git a/articles/building-apps/mcp/supported-tools/junie.adoc b/articles/building-apps/mcp/supported-tools/junie.adoc index 28af1d27be..0eb7815bd1 100644 --- a/articles/building-apps/mcp/supported-tools/junie.adoc +++ b/articles/building-apps/mcp/supported-tools/junie.adoc @@ -9,7 +9,7 @@ order: 40 = Junie -Junie is JetBrains AI assistant for IntelliJ-based IDEs. It uses stdio transport and requires an adapter to connect to HTTP-based MCP servers like the Vaadin MCP server. +Junie is JetBrains' AI assistant for IntelliJ-based IDEs. It uses stdio transport and requires an adapter to connect to HTTP-based MCP servers like the Vaadin MCP server. *Requirements:* diff --git a/articles/building-apps/mcp/supported-tools/other-tools.adoc b/articles/building-apps/mcp/supported-tools/other-tools.adoc index 94d88b6c0a..27e663ddea 100644 --- a/articles/building-apps/mcp/supported-tools/other-tools.adoc +++ b/articles/building-apps/mcp/supported-tools/other-tools.adoc @@ -86,7 +86,7 @@ Adapt the JSON structure to match your tool's configuration format. The adapter [IMPORTANT] ==== -The stdio adapter requires Node.js 18 or later. Install it if you haven't already: +The stdio adapter requires Node.js 18 or later. Install it if you have not already: [source,bash] ---- @@ -138,7 +138,7 @@ Common issues and solutions: |stdio adapter fails |Ensure Node.js 18+ is installed. Try installing the adapter globally. -|Tool doesn't query server +|Tool does not query server |Some tools require explicit tool invocation. Try asking directly about Vaadin documentation. |=== @@ -151,7 +151,7 @@ Common issues and solutions: == Contributing -If you've successfully configured a tool not listed in this documentation, consider contributing your configuration: +If you have successfully configured a tool not listed in this documentation, consider contributing your configuration: . Open an issue on the https://github.com/vaadin/vaadin-mcp[Vaadin MCP repository] . Share your configuration details diff --git a/articles/building-apps/security/add-login.adoc b/articles/building-apps/security/add-login.adoc index f2e78f4dca..c8c0621f96 100644 --- a/articles/building-apps/security/add-login.adoc +++ b/articles/building-apps/security/add-login.adoc @@ -1,6 +1,6 @@ --- title: Add Login -page-title: How to login a user to a Vaadin application +page-title: How to Log In a user to a Vaadin application description: Learn how to add user login to a Vaadin application using Spring Security. meta-description: Learn how to add user login to a Vaadin application using Spring Security. This guide covers setting up in-memory authentication for development and testing. order: 1 @@ -45,11 +45,11 @@ Since Vaadin applications are built on Spring Boot, adding the *Spring Security == Create a Security Configuration Class -Simply adding Spring Security to your project locks you out of your application unless you configure authentication. You must define a [interfacename]`UserDetailsService` and a login form to allow users to log in. +Adding Spring Security to your project locks you out of your application unless you configure authentication. You must define a [interfacename]`UserDetailsService` and a login form to allow users to log in. .Security Package [TIP] -It's best practice to create a dedicated package for security-related classes. If your root package is [packagename]`com.example.application`, place the security configuration inside: `com.example.application.security` +It is best practice to create a dedicated package for security-related classes. If your root package is [packagename]`com.example.application`, place the security configuration inside: `com.example.application.security` This is a minimal implementation of a security configuration class: @@ -86,7 +86,7 @@ class SecurityConfig { } ---- <1> Always call with `VaadinSecurityConfigurer.vaadin()` -- this ensures that the application is properly configured. -<2> *Tip:* Log a warning message whenever using a configuration that shouldn't end up in production. +<2> *Tip:* Log a warning message whenever using a configuration that should not end up in production. The [classname]`VaadinSecurityConfigurer` class provides essential security configurations out of the box, including: @@ -171,8 +171,8 @@ class SecurityConfig { } ---- -Now, when a user tries to access the application, they'll be redirected to the login page. +Now, when a user tries to access the application, they are redirected to the login page. .Access Denied by Default [IMPORTANT] -By default, Vaadin *restricts access to server-side views and router layouts*. Unless explicitly permitted, even authenticated users will be unable to access views. This is covered in more detail in the <> guide. +By default, Vaadin *restricts access to server-side views and router layouts*. Unless explicitly permitted, even authenticated users are unable to access views. This is covered in more detail in the <> guide. diff --git a/articles/building-apps/security/add-logout.adoc b/articles/building-apps/security/add-logout.adoc index 3258178e60..a3d01da4fb 100644 --- a/articles/building-apps/security/add-logout.adoc +++ b/articles/building-apps/security/add-logout.adoc @@ -2,27 +2,27 @@ title: Add Logout page-title: How to implement secure logout in your Vaadin application description: Learn how to securely logout users from a Vaadin application using Spring Security. -meta-description: Learn to implement secure logout in a your application using Spring Security. Learn about session invalidation and Vaadin's logout API. +meta-description: Learn to implement secure logout in your application using Spring Security. Learn about session invalidation and Vaadin's logout API. order: 2 --- = Add Logout -Logging out of an application is just as important as logging in. Leaving a session open for too long or failing to properly close it can lead to serious security risks. +Logging out of an application is as important as logging in. Leaving a session open for too long or failing to properly close it can lead to serious security risks. -Since Vaadin uses *Spring Security* for authentication, it also relies on it for *logging out and session invalidation*. +Since Vaadin uses *Spring Security* for authentication, it also relies on Spring Security for *logging out and session invalidation*. .Vaadin Logout vs. Spring Logout [IMPORTANT] -In a traditional Spring web application, logging out requires sending a `POST` request to `/logout`, which must include Spring's CSRF token. However, Vaadin applications use their own CSRF protection mechanism, making this approach difficult to implement. Additionally, since Vaadin views run on the server, they don't interact with HTTP requests directly. +In a traditional Spring web application, logging out requires sending a `POST` request to `/logout`, which must include Spring's CSRF token. However, Vaadin applications use their own CSRF protection mechanism, making this approach difficult to implement. Additionally, since Vaadin views run on the server, they do not interact with HTTP requests directly. == Logging Out Vaadin provides the class [classname]`AuthenticationContext`, which includes a [methodname]`logout()` method. Calling this method *logs out the user* and *redirects* them to a preconfigured *logout success URL*. -You typically call [methodname]`logout()` from a *button* or *menu item* click listener. Here's how to add a logout button to a view: +You typically call [methodname]`logout()` from a *button* or *menu item* click listener. Here is how to add a logout button to a view: [source,java] ---- @@ -38,7 +38,7 @@ public class LogoutView extends Main { } } ---- -<1> Grants access to _authenticated users_ -- otherwise, users wouldn't be able to log out. +<1> Grants access to _authenticated users_ -- otherwise, users would not be able to log out. <2> Injects [classname]`AuthenticationContext`, which is a Spring Bean. @@ -68,7 +68,7 @@ class SecurityConfig { ---- <1> Sets `/logged-out.html` as the *logout success URL*. -If your application runs at `\https://example.com`, users will be redirected to `\https://example.com/logged-out.html` after logging out. +If your application runs at `\https://example.com`, users are redirected to `\https://example.com/logged-out.html` after logging out. === Absolute vs. Relative URLs diff --git a/articles/building-apps/security/protect-services.adoc b/articles/building-apps/security/protect-services.adoc index 8c5437513b..56fe839aaf 100644 --- a/articles/building-apps/security/protect-services.adoc +++ b/articles/building-apps/security/protect-services.adoc @@ -9,7 +9,7 @@ order: 4 = Protect Services -A secure application relies on multiple layers of protection, including both views and application services. Even when your views are protected, you should also protect the application services. This is important if you have views that allow users with different roles to do different things. If you, for instance, forget to disable a button for users lacking a particular role, and don't protect your services, you have created a privilege escalation. +A secure application relies on multiple layers of protection, including both views and application services. Even when your views are protected, you should also protect the application services. This is important if you have views that allow users with different roles to do different things. If you, for instance, forget to disable a button for users lacking a particular role, and do not protect your services, you have created a privilege escalation. == Introducing Method Security @@ -20,7 +20,7 @@ Spring Security protects services by creating a proxy that intercepts method cal image::images/method-security.png[A diagram of a flow view, a proxy, a method interceptor, and a service.] -In this guide, you'll only learn the minimum to get started with Spring method security in a Vaadin application. For more in-depth information, see the https://docs.spring.io/spring-security/reference/servlet/authorization/method-security.html[Spring Security Reference Manual]. +In this guide, you learn the minimum to get started with Spring method security in a Vaadin application. For more in-depth information, see the https://docs.spring.io/spring-security/reference/servlet/authorization/method-security.html[Spring Security Reference Manual]. == Enabling Method Security @@ -50,16 +50,16 @@ class SecurityConfig { .Test the method security [CAUTION] -Without [annotationname]`@EnableMethodSecurity`, *all services remain unprotected* -- even if you annotate methods with security rules! Always verify that method security is enabled with automatic tests. _A guide showing you how to do this in a Vaadin application is planned, but not yet written. In the meantime, refer to the https://docs.spring.io/spring-security/reference/servlet/test/method.html[Spring Reference Manual]._ +Without [annotationname]`@EnableMethodSecurity`, *all services remain unprotected* -- even if you annotate methods with security rules. Always verify that method security is enabled with automatic tests. _A guide showing you how to do this in a Vaadin application is planned, but not yet written. In the meantime, refer to the https://docs.spring.io/spring-security/reference/servlet/test/method.html[Spring Reference Manual]._ == Securing the Services -Spring Security uses different annotations to secure your services. The most flexible ones, which are enabled by default, are [annotationname]`@PreAuthorize`, [annotationname]`@PostAuthorize`, [annotationname]`@PreFilter`, and [annotationname]`@PostFilter`. In this guide, you'll only learn how to use [annotationname]`@PreAuthorize`. +Spring Security uses different annotations to secure your services. The most flexible ones, which are enabled by default, are [annotationname]`@PreAuthorize`, [annotationname]`@PostAuthorize`, [annotationname]`@PreFilter`, and [annotationname]`@PostFilter`. In this guide, you learn how to use [annotationname]`@PreAuthorize`. You can annotate both *service classes* and individual *service methods*. An annotation placed on the class applies to *all public methods* of the class. An annotation placed on a method *overrides any annotation on the class*. -[annotationname]`@PreAuthorize` takes as its single argument a Spring Expression Language (SpEL) expression that must evaluate to `true` to grant access. Although you can do some quite advanced things with SpEL, the most common methods you'll want to use are: +[annotationname]`@PreAuthorize` takes as its single argument a Spring Expression Language (SpEL) expression that must evaluate to `true` to grant access. Although you can do some advanced things with SpEL, the most common methods you want to use are: * `permitAll` allows *anyone* to call the method. * `isAuthenticated` allows any *authenticated* user to call the method. diff --git a/articles/building-apps/security/protect-views.adoc b/articles/building-apps/security/protect-views.adoc index a9a257e143..8b067a44c2 100644 --- a/articles/building-apps/security/protect-views.adoc +++ b/articles/building-apps/security/protect-views.adoc @@ -74,12 +74,12 @@ public class AdminView extends Main { .Router layouts are also protected [IMPORTANT] -When protecting views, ensure the router layout also allows access. If a view is accessible but its parent layout is restricted, users will still be blocked. +When protecting views, ensure the router layout also allows access. If a view is accessible but its parent layout is restricted, users are still blocked. === Annotation Inheritance -Security annotations are inherited from the closest superclass that has them. Annotating a subclass overrides any inherited annotations. Interfaces aren't checked for annotations, only classes. +Security annotations are inherited from the closest superclass that has them. Annotating a subclass overrides any inherited annotations. Interfaces are not checked for annotations, only classes. In the following example, [classname]`UserListingView` requires the `ADMIN` role: @@ -105,7 +105,7 @@ While multiple security annotations can be applied to a single view, doing so ca == Programmatic View Security -Vaadin provides API:s for protecting views programmatically. It's more verbose than using the annotations, but gives you greater control. +Vaadin provides APIs for protecting views programmatically. It is more verbose than using the annotations, but gives you greater control. === Making a Custom Navigation Access Checker @@ -125,19 +125,19 @@ class CustomAccessChecker implements NavigationAccessChecker { ---- <1> Registers the navigation access checker as a singleton Spring bean. -The [classname]`NavigationContext` object contains information about where you're trying to navigate, such as the view class, route parameters, and query parameters. It also contains the principal of the current user. +The [classname]`NavigationContext` object contains information about the navigation target, such as the view class, route parameters, and query parameters. It also contains the principal of the current user. -Since the access checker is a Spring bean, you inject other beans into it. For example, you may want to lookup additional information in order to make the access decision. +Since the access checker is a Spring bean, you can inject other beans into it. For example, you may want to look up additional information to make the access decision. -Once you've made a decision, you have to return an [classname]`AccessCheckResult`. The [classname]`AccessCheckResult` determines whether navigation is allowed, denied, or deferred. There are four possible outcomes: +Once you have made a decision, you have to return an [classname]`AccessCheckResult`. The [classname]`AccessCheckResult` determines whether navigation is allowed, denied, or deferred. There are four possible outcomes: [methodname]`AccessCheckResult.allow()` :: Access is granted. -[methodname]`AccessCheckResult.neutral()` :: The access checker cannot make a decision based on the given navigation information. Another access checker have to make the decision, or access will be denied. +[methodname]`AccessCheckResult.neutral()` :: The access checker cannot make a decision based on the given navigation information. Another access checker has to make the decision, or access is denied. [methodname]`AccessCheckResult.deny()` :: Access is denied. [methodname]`AccessCheckResult.reject()` :: Access is denied because of a misconfiguration or critical development time error. [NOTE] -The security annotations are actually enforced by a built-in access checker. +The security annotations are enforced by a built-in access checker. === Enabling a Navigation Access Checker @@ -163,7 +163,7 @@ class SecurityConfig { <1> The [annotationname]`@Bean` method must be `static` to prevent bootstrap errors caused by circular dependencies in bean definitions. <2> [classname]`CustomAccessChecker` is now *the only enabled access checker*. -You can have multiple access checkers active at the same time. When you navigate to a view, they will all be consulted. +You can have multiple access checkers active at the same time. When you navigate to a view, they are all consulted. [NOTE] To enable the built-in annotated view access checker, call `NavigationAccessControlConfigurer.withAnnotatedViewAccessChecker()`. @@ -209,13 +209,11 @@ public class MyView extends Main { ... } ---- -<1> All authenticated user have access to the view. +<1> All authenticated users have access to the view. <2> Administrators can do more inside the view than normal users. [classname]`AuthenticationContext` has multiple methods for checking the roles and authorities of the current user. Refer to the Javadoc for more information. -// TODO Write about access denied error messages once the page on overall error handling has been written - == Role Constants diff --git a/articles/building-apps/server-push/callbacks.adoc b/articles/building-apps/server-push/callbacks.adoc index c4b909e7ce..203ec0df25 100644 --- a/articles/building-apps/server-push/callbacks.adoc +++ b/articles/building-apps/server-push/callbacks.adoc @@ -36,9 +36,9 @@ private void onJobFailed(Exception error) { } ---- -For reporting progress, you can use a <<{articles}/components/progress-bar#,progress bar>>. If the background jobs reports the progress as a floating point value between 0.0 and 1.0, you can pass it directly to the `setValue` method of the progress bar. +For reporting progress, you can use a <<{articles}/components/progress-bar#,progress bar>>. If the background job reports the progress as a floating-point value between 0.0 and 1.0, you can pass it directly to the `setValue` method of the progress bar. -Here is an example of a button click listener that starts a background job, and uses the private methods, and the progress bar, to update the user interface: +Here is an example of a button click listener that starts a background job and uses the private methods and the progress bar to update the user interface: [source,java] ---- diff --git a/articles/building-apps/server-push/futures.adoc b/articles/building-apps/server-push/futures.adoc index 9d463c49b7..4fa0a817d3 100644 --- a/articles/building-apps/server-push/futures.adoc +++ b/articles/building-apps/server-push/futures.adoc @@ -34,12 +34,12 @@ private void onJobFailed(Throwable error) { } ---- -Note, that the error handler must accept a `Throwable`, and not an `Exception` when you're working with `CompletableFuture`. +Note that the error handler must accept a `Throwable`, and not an `Exception` when you are working with `CompletableFuture`. == Successful Completion -If a `CompletableFuture` completes successfully, you can instruct it to perform a specific operation by calling the `thenAccept()` method on it. This method takes a `Consumer` as its input. When the `CompletableFuture` completes, it calls this consumer with the result. +If a `CompletableFuture` completes successfully, you can instruct it to perform a specific operation by calling the `thenAccept()` method on it. This method takes a `Consumer` as its input. When the `CompletableFuture` completes, it passes the result to this consumer. Here is an example of a button click listener that starts a background job, and updates the user interface when it has completed successfully: @@ -60,7 +60,7 @@ If a `CompletableFuture` is completed with an exception, you can instruct it to The `exceptionally()` method takes a `Function`, instead of a `Consumer` as input. The exception is passed to the function as input. The function output is used as the result of the `CompletableFuture` that is returned by `exceptionally()`. -Flow has no version of `UI.accessLater()` that works with `Function`. However, since you're probably not interested in returning a result, you can create a helper function that adapts a `Consumer` to a `Function`, like this: +Flow has no version of `UI.accessLater()` that works with `Function`. However, since you are probably not interested in returning a result, you can create a helper function that adapts a `Consumer` to a `Function`, such as this: [source,java] ---- diff --git a/articles/building-apps/server-push/index.adoc b/articles/building-apps/server-push/index.adoc index c7f8434427..7b9c3e7270 100644 --- a/articles/building-apps/server-push/index.adoc +++ b/articles/building-apps/server-push/index.adoc @@ -54,7 +54,7 @@ For details on how to use `UI.access()` and `UI.push()`, see <>. diff --git a/articles/building-apps/server-push/reactive-browser-callable-services.adoc b/articles/building-apps/server-push/reactive-browser-callable-services.adoc index b2bd320925..e56f953c11 100644 --- a/articles/building-apps/server-push/reactive-browser-callable-services.adoc +++ b/articles/building-apps/server-push/reactive-browser-callable-services.adoc @@ -29,7 +29,7 @@ public class TimeService { } ---- <1> Emit a new message every second. -<2> Drop any messages that for some reason can't be sent to the client in time. +<2> Drop any messages that for some reason cannot be sent to the client in time. <3> Output the current date and time as a string. Hilla generates the necessary TypeScript types to subscribe to this service from the browser. diff --git a/articles/building-apps/server-push/reactive.adoc b/articles/building-apps/server-push/reactive.adoc index 74d2152f2e..0d70ed6db2 100644 --- a/articles/building-apps/server-push/reactive.adoc +++ b/articles/building-apps/server-push/reactive.adoc @@ -18,7 +18,7 @@ Background threads typically use cold streams for output. A cold stream starts e Broadcasts typically use hot streams for output. A hot stream emits values regardless of whether a client is subscribed or not. A subscriber only receives the values that were emitted while it was subscribed. -In your user interfaces, you typically don't need to worry about unsubscribing from cold streams, as they're often short lived. However, if you subscribe to a hot stream, it's important that you remember to unsubscribe when no longer needed. +In your user interfaces, you typically do not need to worry about unsubscribing from cold streams, as they are often short lived. However, if you subscribe to a hot stream, it is important that you remember to unsubscribe when no longer needed. === Subscribing with Flow @@ -36,7 +36,7 @@ private void onJobCompleted(String result) { The `UI.accessLater()` method is explained in the <> documentation page. -In the following example, a background job returns a `Mono`. The stream is cold, so you don't need to unsubscribe explicitly from it, as this happens once the `Mono` has completed. The job is started by a button click listener. +In the following example, a background job returns a `Mono`. The stream is cold, so you do not need to unsubscribe explicitly from it, as this happens once the `Mono` has completed. The job is started by a button click listener. [source,java] ---- @@ -47,7 +47,7 @@ button.addClickListener(clickEvent -> { }); ---- -In the following example, a `Flux` is used to receive chat messages. The stream is hot, so you have to subscribe to it when the component is attached, and unsubscribe when it's detached: +In the following example, a `Flux` is used to receive chat messages. The stream is hot, so you have to subscribe to it when the component is attached, and unsubscribe when it is detached: [source,java] ---- @@ -70,7 +70,7 @@ protected void onAttach(AttachEvent attachEvent) { === Subscribing with Hilla -In Hilla, you can only use a `Flux` -- even if you're only emitting a single value. However, you can easily convert a `Mono` to a `Flux` by calling the `asFlux()` method. +In Hilla, you can only use a `Flux` -- even if you are only emitting a single value. However, you can convert a `Mono` to a `Flux` by calling the `asFlux()` method. This is an example of a reactive service that delegates to a worker to start a background job. The worker returns a `Mono`, which the service converts to a `Flux`: @@ -94,7 +94,7 @@ public class MyBackgroundJobService { You subscribe to a `Flux` by calling the generated TypeScript service method. You then use the returned `Subscription` object to register a function that gets called whenever the `Flux` emits a value. -The following client-side uses the `Flux` from the earlier example to receive a single output from a server-side background job. The stream is cold, so you don't need to unsubscribe from it: +The following client-side uses the `Flux` from the earlier example to receive a single output from a server-side background job. The stream is cold, so you do not need to unsubscribe from it: [source,typescript] ---- @@ -124,12 +124,12 @@ useEffect(() => { == Handling Errors -In a reactive stream, an error is a terminal event. This means that the subscription is cancelled and no more values are emitted. If you're dealing with a hot stream, you should therefore consider resubscribing to it as a part of error recovery. +In a reactive stream, an error is a terminal event. This means that the subscription is cancelled and no more values are emitted. If you are dealing with a hot stream, you should therefore consider resubscribing to it as a part of error recovery. === Errors with Flow -In Flow, you can use the `doOnError()` method to attach a <> that's called if an error occurs. As for successful completion, you should declare a private method and wrap it inside `UI.accessLater()` . +In Flow, you can use the `doOnError()` method to attach a <> that is called if an error occurs. As for successful completion, you should declare a private method and wrap it inside `UI.accessLater()`. For example, a method for handling errors could look like this: @@ -155,7 +155,7 @@ button.addClickListener(clickEvent -> { === Errors with Hilla -With Hilla, you can use the `onError()` method of the `Subscription` object to register a function that's called if an error occurs. +With Hilla, you can use the `onError()` method of the `Subscription` object to register a function that is called if an error occurs. If you add error handling to the earlier background job example, you get something like this: @@ -174,12 +174,12 @@ const startJob = () => { } ---- -Note, that the error callback function doesn't receive any information about the error itself. +Note that the error callback function does not receive any information about the error itself. == Buffering -You shouldn't push updates to the browser more than 2 to 4 times per second. If your `Flux` is emitting events faster than that, you should buffer them and update the user interface in batches. Buffering a `Flux` is easy, as it has built-in support for it through the `buffer()` method. +You should not push updates to the browser more than 2 to 4 times per second. If your `Flux` is emitting events faster than that, you should buffer them and update the user interface in batches. Buffering a `Flux` is easy, as it has built-in support for it through the `buffer()` method. In the following example, the buffered stream buffers events for 250 milliseconds before it emits them in batches. Because of this, the user interface is receiving a `List` instead of an `Event`: @@ -195,9 +195,9 @@ public Flux> bufferedEventStream() { ---- -If you're using Flow, you can do the buffering in your user interface, before you subscribe to the stream. +If you are using Flow, you can do the buffering in your user interface, before you subscribe to the stream. -In the following example, the user interface component subscribes to the buffered stream when it's attached, and unsubscribes when it's detached: +In the following example, the user interface component subscribes to the buffered stream when it is attached, and unsubscribes when it is detached: [source,java] ---- @@ -215,9 +215,9 @@ protected void onAttach(AttachEvent attachEvent) { } ---- -If you're using Hilla, you have to do the buffering inside the reactive service. +If you are using Hilla, you have to do the buffering inside the reactive service. -The following example shows a browser callable service that buffers the stream before it's returned. Because of this, the generated TypeScript service method emits arrays of `Event` objects: +The following example shows a browser callable service that buffers the stream before it is returned. Because of this, the generated TypeScript service method emits arrays of `Event` objects: [source,java] ---- diff --git a/articles/building-apps/server-push/threads.adoc b/articles/building-apps/server-push/threads.adoc index 6cd7fde267..3cdcf4e7d3 100644 --- a/articles/building-apps/server-push/threads.adoc +++ b/articles/building-apps/server-push/threads.adoc @@ -2,7 +2,7 @@ title: Threads page-title: How to use threads in a Vaadin Flow user interface description: How to use threads in a Vaadin Flow user interface. -meta-description: Learn how to use use virtual threads in a Vaadin Flow user interface by reading this guide. +meta-description: Learn how to use virtual threads in a Vaadin Flow user interface by reading this guide. order: 10 --- @@ -11,7 +11,7 @@ order: 10 Developers often use server push to update the user interface from background jobs (see <>). However, in Vaadin Flow, there are also cases where you may want to start a separate thread for use by the user interface itself. You might, for instance, want to show the server date and time in "real time". -If you have experience with Swing, you might be tempted to use a `Timer`, or to start a new `Thread`, manually. In Flow, this isn't a good idea. Flow applications are multi-user applications, with potentially thousands of concurrent users. If each user creates their own `Timer`, or starts their own `Thread`, you may run out of threads. If that happens, the application crashes. +If you have experience with Swing, you might be tempted to use a `Timer`, or to start a new `Thread`, manually. In Flow, this is not a good idea. Flow applications are multi-user applications, with potentially thousands of concurrent users. If each user creates their own `Timer`, or starts their own `Thread`, you may run out of threads. If that happens, the application crashes. As a better strategy, use virtual threads, or Spring's `TaskExecutor` and `TaskScheduler`. These are explained in the following sections, with some examples. @@ -34,7 +34,7 @@ button.addClickListener(clickEvent -> { }); ---- -This is the easiest way of starting a new user interface thread. If you're able to use virtual threads, they should be your first choice. If you run into problems, though, switch to the `TaskExecutor`. +This is the easiest way of starting a new user interface thread. If you are able to use virtual threads, they should be your first choice. If you run into problems, though, switch to the `TaskExecutor`. For scheduled tasks, you should still use the `TaskScheduler`. This is covered later on this page. @@ -43,7 +43,7 @@ For scheduled tasks, you should still use the `TaskScheduler`. This is covered l You can use Spring's `TaskExecutor` and `TaskScheduler` to start tasks directly from the user interface, as well. Configuring them is covered in the <> documentation page. -Before starting a task with `TaskExecutor` and `TaskScheduler`, you should make sure it's actually UI-related and not a background job. To use them properly, you would inject them into your view, and then call them when needed. +Before starting a task with `TaskExecutor` and `TaskScheduler`, you should make sure it is actually UI-related and not a background job. To use them properly, you would inject them into your view, and then call them when needed. Here is an example of a view that gets the `TaskExecutor` as a constructor parameter: @@ -75,12 +75,12 @@ button.addClickListener(clickEvent -> { Because of the call to `UI.accessLater()`, the user interface is updated through a server push when the task finishes. [CAUTION] -Don't use the `@Async` annotation in Flow views. It turns them into proxies that don't work with Vaadin. +Do not use the `@Async` annotation in Flow views. It turns them into proxies that do not work with Vaadin. == Task Scheduler -You can use the `TaskScheduler` to schedule tasks. With it, you have to schedule the task when the component is attached, and cancel it when it's detached. +You can use the `TaskScheduler` to schedule tasks. With it, you have to schedule the task when the component is attached, and cancel it when it is detached. The following example schedules a task to be executed every second. The task sets the text of `currentTimeLabel` to the current date and time of the server. When the component is detached, the task is cancelled: @@ -103,4 +103,4 @@ protected void onAttach(AttachEvent attachEvent) { The tasks that you execute in the task scheduler should be fast. If you need to schedule long-running tasks, you should give them to `TaskExecutor` for execution. [CAUTION] -Do not use the `@Scheduled` annotation in Flow views. It turns them into proxies that don't work with Vaadin. +Do not use the `@Scheduled` annotation in Flow views. It turns them into proxies that do not work with Vaadin. diff --git a/articles/building-apps/server-push/updates.adoc b/articles/building-apps/server-push/updates.adoc index a3106fadd7..e4ef3800ba 100644 --- a/articles/building-apps/server-push/updates.adoc +++ b/articles/building-apps/server-push/updates.adoc @@ -9,7 +9,7 @@ order: 1 = Pushing UI Updates -Whenever you're using server push in Vaadin Flow, you're triggering it from a thread other than the normal HTTP request thread. Making changes to a UI from another thread and pushing them to the browser requires locking the user session. Otherwise, the UI update performed from another thread could conflict with a regular event-driven update and cause either data corruption, race conditions or deadlocks. +Whenever you are using server push in Vaadin Flow, you are triggering it from a thread other than the normal HTTP request thread. Making changes to a UI from another thread and pushing them to the browser requires locking the user session. Otherwise, the UI update performed from another thread could conflict with a regular event-driven update and cause either data corruption, race conditions or deadlocks. Such errors are by nature hard to discover and fix, since they often occur randomly and under a heavy load. Because of this, you may only access a UI using the `UI.access()` method, which locks the session to prevent race conditions. You would use it like this: @@ -35,7 +35,7 @@ public class Application implements AppShellConfigurator { } ---- -Afterwards, you'll have to call the `UI.push()` method whenever you want to push your changes to the browser, like this: +Afterwards, you have to call the `UI.push()` method whenever you want to push your changes to the browser, like this: [source,java] ---- @@ -48,13 +48,13 @@ ui.access(() -> { == Getting the UI Instance -Before you can call `access()`, you need to get the `UI` instance. You'd typically use `Component.getUI()` or `UI.getCurrent()` for this. However, both are problematic when it comes to server push. +Before you can call `access()`, you need to get the `UI` instance. You would typically use `Component.getUI()` or `UI.getCurrent()` for this. However, both are problematic when it comes to server push. -`Component.getUI()` is not thread-safe, which means you should only call it while the user session is locked. Therefore, you can't use it to call `access()`. +`Component.getUI()` is not thread-safe, which means you should only call it while the user session is locked. Therefore, you cannot use it to call `access()`. -`UI.getCurrent()` only returns a non-`null` value when the current thread owns the session lock. When called from a background thread, it returns `null`. Therefore, you can't use it either to call `access()`. +`UI.getCurrent()` only returns a non-`null` value when the current thread owns the session lock. When called from a background thread, it returns `null`. Therefore, you cannot use it either to call `access()`. -Whenever you're planning to use server push, you have to get a hold of the `UI` instance while the user session is locked. This typically happens right before you start your background thread. +Whenever you are planning to use server push, you have to get a hold of the `UI` instance while the user session is locked. This typically happens right before you start your background thread. Below is an example of a button click listener that starts a background thread: @@ -127,9 +127,9 @@ var subscription = myEventBus.subscribe(UI.getCurrent().accessLater((message) -> == Avoiding Memory Leaks -When you're using server push to update the user interface when an event has occurred, you would typically subscribe a listener to some broadcaster or event bus. When you do this, be sure to unsubscribe when the UI is detached. Otherwise, you'll have a memory leak that prevents your UI from being garbage collected. This is because the listener holds a reference to the `UI` instance. +When you are using server push to update the user interface when an event has occurred, you would typically subscribe a listener to some broadcaster or event bus. When you do this, be sure to unsubscribe when the UI is detached. Otherwise, you have a memory leak that prevents your UI from being garbage collected. This is because the listener holds a reference to the `UI` instance. -Always subscribe when your view is attached to a UI, and unsubscribe when it's detached. You can do this by overriding the `Component.onAttach()` method, like so: +Always subscribe when your view is attached to a UI, and unsubscribe when it is detached. You can do this by overriding the `Component.onAttach()` method, like so: [source,java] ---- @@ -155,9 +155,9 @@ protected void onAttach(AttachEvent attachEvent) { // <1> Another risk you have to manage when updating the user interface in response to events is flooding the user interface with updates. As a rule of thumb, you should not push more than two to four times per second. Pushing more often than that can cause performance issues. Plus, there is a limit to how many updates the human brain is able to register per second. -When you know events are coming no faster than two to four events per second, you can push on every event. However, if they're more frequent, you have to buffer events and update the user interface in batches. This is quite easy to do if you're using a `Flux` from https://projectreactor.io/[Reactor]. See the <> documentation page for more information about this. +When you know events are coming no faster than two to four events per second, you can push on every event. However, if they are more frequent, you have to buffer events and update the user interface in batches. This is quite easy to do if you are using a `Flux` from https://projectreactor.io/[Reactor]. See the <> documentation page for more information about this. -The buffering duration depends on the size of the UI update, and the network latency. In some applications, you may need to use a longer buffer duration. In others, a shorter one might work. You should try various durations to see what's best for your application. +The buffering duration depends on the size of the UI update, and the network latency. In some applications, you may need to use a longer buffer duration. In others, a shorter one might work. You should try various durations to see what is best for your application. == Avoiding Unnecessary Pushes @@ -185,7 +185,7 @@ This
is added from an event listener This
is added from within a call to UI.access() ---- -In this particular case, the call to `UI.access()` would not have been needed. Sometimes, you can deduce this by looking at the code. However, there are situations in which this isn't obvious. You may have code that's executed sometimes by the HTTP request thread, and other times by another thread. For this situation, you can check whether the current thread has locked the user session, like this: +In this particular case, the call to `UI.access()` would not have been needed. Sometimes, you can deduce this by looking at the code. However, there are situations in which this is not obvious. You may have code that is executed sometimes by the HTTP request thread, and other times by another thread. For this situation, you can check whether the current thread has locked the user session, like this: [source,java] ---- diff --git a/articles/building-apps/views/add-master-detail.adoc b/articles/building-apps/views/add-master-detail.adoc index d6c2856360..cd2ddb3df1 100644 --- a/articles/building-apps/views/add-master-detail.adoc +++ b/articles/building-apps/views/add-master-detail.adoc @@ -25,7 +25,7 @@ include::{root}/src/main/java/com/vaadin/demo/buildingapps/masterdetail/MasterDe For more detailed instructions on how to build a master-detail view from scratch, continue reading below. The guide uses the same code as in the copy-paste example, but breaks it down into smaller parts with explanations. -== What is a Master-Detail View? +== What Is a Master-Detail View? In a master-detail view, the user selects an item from a list (the master), and the details of the selected item are shown in another area (the detail). When no item is selected, the detail area is either hidden or shows a placeholder message. @@ -44,7 +44,7 @@ The example code in this guide does not look like the mock-ups above. It uses si == Scaffolding the View -When creating a master-detail view, start with the general structure. You'll need: +When creating a master-detail view, start with the general structure. You need: * a layout to arrange the master and detail components side by side, * a master component (e.g., a <>), @@ -105,7 +105,7 @@ In a real application, the detail component would be a form. How to build such a == Creating the Placeholder -When no detail is selected, it's good practice to show a placeholder message. Here's a simple implementation that shows a message in a paragraph: +When no detail is selected, it is good practice to show a placeholder message. Here is a simple implementation that shows a message in a paragraph: [source,java] ---- @@ -129,16 +129,16 @@ The secondary area of the Split Layout should be updated based on the user's sel * If the user has selected an item, show the detail component. * If no item is selected, show the placeholder component. -Because you're using a route parameter to represent the selected item's ID, implement this logic in the `setParameter()` method - remove the old detail area, check whether the ID is present, then show either the detail or the placeholder accordingly: +Because you are using a route parameter to represent the selected item's ID, implement this logic in the `setParameter()` method - remove the old detail area, check whether the ID is present, then show either the detail or the placeholder accordingly: [source,java] ---- include::{root}/src/main/java/com/vaadin/demo/buildingapps/masterdetail/MasterDetailView.java[tags=setparameter,indent=0] ---- -In this simple example, the selection is not visible in the master component. In a real application, you'd want to reflect the selection state in the master component as well. +In this simple example, the selection is not visible in the master component. In a real application, you would want to reflect the selection state in the master component as well. -For example, if you're using a Grid, you could select the corresponding row when an item is selected, and clear the selection when no item is selected. All this logic would also go into the `setParameter()` method. +For example, if you are using a Grid, you could select the corresponding row when an item is selected, and clear the selection when no item is selected. All this logic would also go into the `setParameter()` method. diff --git a/articles/building-apps/views/add-navi-menu.adoc b/articles/building-apps/views/add-navi-menu.adoc index d1f3e36d28..7a0b8f7286 100644 --- a/articles/building-apps/views/add-navi-menu.adoc +++ b/articles/building-apps/views/add-navi-menu.adoc @@ -23,12 +23,12 @@ If you want to quickly try out a navigation menu, you can copy-paste the followi include::{root}/src/main/java/com/vaadin/demo/buildingapps/routerlayout/MainLayout.java[tags=navimenu,indent=0] ---- -Expand the code for a complete implementation of a router layout with a navigation menu. For more detailed instructions on how to use build a navigation menu, continue reading below. +Expand the code for a complete implementation of a router layout with a navigation menu. For more detailed instructions on how to build a navigation menu, continue reading below. == Creating a Navigation Menu -You typically build a navigation menu in Vaadin using the Side Navigation components, which provide a vertical list of navigation links with support for collapsible, nested sections. This guide only covers the very basics of building a navigation menu. For more detailed information, see the <> documentation. +You typically build a navigation menu in Vaadin using the Side Navigation components, which provide a vertical list of navigation links with support for collapsible, nested sections. This guide covers the basics of building a navigation menu. For more detailed information, see the <> documentation. Start by creating an instance of [classname]`SideNav` to act as the container for the navigation items: @@ -49,7 +49,7 @@ SideNavItem homeItem = new SideNavItem("Home", HomeView.class); sideNav.addItem(homeItem); ---- -Now, whenever the user clicks on the item, the router navigates to the specified view. Furthermore, the item will be highlighted when the user is on that view. +Now, whenever the user clicks on the item, the router navigates to the specified view. Furthermore, the item is highlighted when the user is on that view. === Highlighting Nested Views @@ -100,7 +100,7 @@ For technical details, see the <>, you can create an icon for a menu item like this: @@ -113,7 +113,7 @@ For example, if you use <>, you can create an i item.setPrefixComponent(new Icon(menuEntry.icon())); ---- -If you're using SVG icons, you can do this instead: +If you are using SVG icons, you can do this instead: [source,java] ---- diff --git a/articles/building-apps/views/add-router-layout.adoc b/articles/building-apps/views/add-router-layout.adoc index 085a635533..bd2614291e 100644 --- a/articles/building-apps/views/add-router-layout.adoc +++ b/articles/building-apps/views/add-router-layout.adoc @@ -2,7 +2,7 @@ title: Add a Router Layout page-title: How to add a router layout to a Vaadin application description: Learn how to add a router layout to a Vaadin application. -meta-description: Learn to create and apply router layouts in Vaadin, including automatic and explicit layouts, and route prefixes for structured navigation. +meta-description: Learn to create and apply router layouts in Vaadin, including automatic and explicit layouts, and route prefixes for structured navigation. order: 20 --- @@ -29,7 +29,7 @@ include::{root}/src/main/java/com/vaadin/demo/buildingapps/routerlayout/HomeView For more detailed instructions on how to use router layouts, continue reading below. -== What is a Router Layout? +== What Is a Router Layout? Most business applications have interface elements that remain visible across different views, such as a navigation menu, header, or footer. Instead of duplicating these elements on every view, you can use a _router layout_. @@ -43,9 +43,9 @@ image::images/main-layout.png[Example of a router layout] Router layouts are UI components that implement the [interfacename]`RouterLayout` interface, which provides two key methods: * [methodname]`showRouterLayoutContent(HasElement)` -- shows the given view in the router layout. -* [methodname]`removeRouterLayoutContent(HasElement)` -- removes the given view in the router layout. +* [methodname]`removeRouterLayoutContent(HasElement)` -- removes the given view from the router layout. -When you navigate to a view, the router first determines which layout to use -- if any. If you're navigating from one view to another inside the same router layout, the *existing router layout instance is reused*. Otherwise, a new instance is created. The router then calls [methodname]`showRouterLayoutContent()`, passing in the new view instance. +When you navigate to a view, the router first determines which layout to use -- if any. If you are navigating from one view to another inside the same router layout, the *existing router layout instance is reused*. Otherwise, a new instance is created. The router then calls [methodname]`showRouterLayoutContent()`, passing in the new view instance. [TIP] The router layout becomes the parent of the view in the component hierarchy. You can use `Component.getParent()` method to access the layout from the view after the view has been added to the layout. @@ -112,7 +112,7 @@ If a route matches multiple layouts, the layout with the longest matching path t Defining multiple layouts with the exact same path results in an exception. [NOTE] -The path specified in `@Layout` need a leading slash (`/`), unlike the path in [annotationname]`@Route`. +The path specified in `@Layout` needs a leading slash (`/`), unlike the path in [annotationname]`@Route`. == Explicit Layouts diff --git a/articles/building-apps/views/add-view.adoc b/articles/building-apps/views/add-view.adoc index e303ac950d..e476db5419 100644 --- a/articles/building-apps/views/add-view.adoc +++ b/articles/building-apps/views/add-view.adoc @@ -10,7 +10,7 @@ order: 10 = Add a View :toclevels: 2 -In this guide, you'll learn how to create and name views in Java, assign multiple routes to a single view, and organize views into Java packages. +In this guide, you learn how to create and name views in Java, assign multiple routes to a single view, and organize views into Java packages. == Copy-Paste into Your Project @@ -80,7 +80,7 @@ public class CustomerListView extends Main { Users would be able to access this view by navigating to `\https://example.com/customer/list`. [NOTE] -Don't include a leading `/` when you specify the path of a view. +Do not include a leading `/` when you specify the path of a view. Navigation between views is covered in more detail in the <> guide. @@ -171,7 +171,7 @@ public class HomeView extends Div implements HasDynamicTitle { } ---- -The title is determined when the router navigates to the view. Any changes made after navigation will not affect the title. +The title is determined when the router navigates to the view. Any changes made after navigation do not affect the title. [IMPORTANT] A view cannot use both the [annotationname]`@PageTitle` annotation and implement the [interfacename]`HasDynamicTitle` interface simultaneously. @@ -201,4 +201,3 @@ The [annotationname]`@Menu` annotation has the following attributes: `icon` :: Specifies the menu icon. This is a string, allowing flexibility in interpretation. It could be an <<{articles}/components/icons#,Icon>> name or an SVG source, depending on the menu implementation. For more information on building a navigation menu, see <>. -// TODO Do we need a separate guide on building a navigation menu? diff --git a/articles/building-apps/views/navigate.adoc b/articles/building-apps/views/navigate.adoc index 1dfc198382..a2e53eb137 100644 --- a/articles/building-apps/views/navigate.adoc +++ b/articles/building-apps/views/navigate.adoc @@ -10,7 +10,7 @@ order: 15 = Navigate to a View :toclevels: 2 -In this guide, you'll learn how to use [classname]`RouterLink` and [methodname]`UI.navigate()` to navigate between views. You'll also learn how to improve the readability of your code by encapsulating some of the navigation logic into your own API. +In this guide, you learn how to use [classname]`RouterLink` and [methodname]`UI.navigate()` to navigate between views. You also learn how to improve the readability of your code by encapsulating some of the navigation logic into your own API. You can navigate to a view either programmatically through an API, by clicking a link, or by changing the URL of the browser. @@ -52,7 +52,7 @@ myLayout.add(link); === Route Parameters in Links -If the view is accepting a single route parameter, you can pass the parameter value to the [classname]`RouterLink` constructor. +If the view accepts a single route parameter, you can pass the parameter value to the [classname]`RouterLink` constructor. In the following example, [classname]`CustomerDetailsView` implements the [interfacename]`HasUrlParameter` interface and takes a single string parameter - the customer's ID. The link navigates to the details of the customer with ID `"cu1234"`: @@ -62,8 +62,7 @@ var link = new RouterLink("Customer Details", CustomerDetailsView.class, "cu1234 myLayout.add(link); ---- -If the view is accepting multiple route parameters, you need to construct an instance of [classname]`RouteParameters` and pass it to the [classname]`RouterLink` constructor. You can construct it in different ways; see its https://vaadin.com/api/platform/{moduleMavenVersion:com.vaadin:vaadin}/com/vaadin/flow/router/RouteParameters.html[API documentation] for details. -// TODO Should the API link be versioned? +If the view accepts multiple route parameters, you need to construct an instance of [classname]`RouteParameters` and pass it to the [classname]`RouterLink` constructor. You can construct it in different ways; see its https://vaadin.com/api/platform/{moduleMavenVersion:com.vaadin:vaadin}/com/vaadin/flow/router/RouteParameters.html[API documentation] for details. The following example creates a link to the customer details view with two route parameters; `customerId` with the value of `"cu1234"`, and `mode` with the value of `"edit"`: @@ -80,7 +79,6 @@ For more information about route parameters, see the < === Route Parameters in Programmatic Navigation -If the view is accepting a single route parameter, you can pass the parameter value to [methodname]`UI.navigate()`, like this: +If the view accepts a single route parameter, you can pass the parameter value to [methodname]`UI.navigate()`: [source,java] ---- @@ -104,7 +102,7 @@ button.addClickListener(event -> ); ---- -If the view is accepting multiple route parameters, you need to construct an instance of [classname]`RouteParameters` and pass it to [methodname]`UI.navigate()`, like this: +If the view accepts multiple route parameters, you need to construct an instance of [classname]`RouteParameters` and pass it to [methodname]`UI.navigate()`: [source,java] ---- @@ -119,7 +117,7 @@ button.addClickListener(event -> == Your Own API -Instead of scattering [methodname]`UI.navigate()` calls throughout your codebase, it's a good practice to encapsulate navigation logic within dedicated methods. This makes the code more readable, maintainable, and easier to refactor. +Instead of scattering [methodname]`UI.navigate()` calls throughout your codebase, it is a good practice to encapsulate navigation logic within dedicated methods. This makes the code more readable, maintainable, and easier to refactor. In the following example, the [classname]`CustomerDetailsView` has a static method for navigating to the details of the customer with the given ID: @@ -150,7 +148,7 @@ button.addClickListener(event -> If you use multiple route parameters, or custom parameter types, this approach becomes even more useful. -In the following example, the [classname]`CustomerDetailsView` accepts two route parameters; a value object [classname]`CustomerId` and an enum [classname]`Mode`: +In the following example, the [classname]`CustomerDetailsView` accepts two route parameters: a value object [classname]`CustomerId` and an enum [classname]`Mode`: [source,java] ---- @@ -207,7 +205,7 @@ button.addClickListener(event -> == React Views -So far, all the examples have covered navigating from one Java view to another. However, you can also navigate from a Java view to a React view. Unlike Java views, which use class references for navigation, React views require string-based routes because they don't have a corresponding Java class. +So far, all the examples have covered navigating from one Java view to another. However, you can also navigate from a Java view to a React view. Unlike Java views, which use class references for navigation, React views require string-based routes because they do not have a corresponding Java class. You can use anchor elements for navigation, or trigger programmatic navigation using [methodname]`UI.navigate()`. @@ -220,7 +218,7 @@ myLayout.add(link); ---- [NOTE] -Vaadin sets the https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base[base URL] of the application to the path of the main view. All relative links are resolved against this URL. This means that you don't have to worry about the context path when you create `Anchor` objects. +Vaadin sets the https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base[base URL] of the application to the path of the main view. All relative links are resolved against this URL. This means that you do not have to worry about the context path when you create `Anchor` objects. You can also programmatically navigate to React views, like this: @@ -229,5 +227,3 @@ You can also programmatically navigate to React views, like this: var button = new Button("Go to React view"); button.addClickListener(event -> UI.getCurrent().navigate("path/to/react/view")); ---- - -// For more information about using React views in Vaadin, see the <> guides. diff --git a/articles/building-apps/views/pass-data/index.adoc b/articles/building-apps/views/pass-data/index.adoc index e266d8b13b..abc6262bb6 100644 --- a/articles/building-apps/views/pass-data/index.adoc +++ b/articles/building-apps/views/pass-data/index.adoc @@ -16,7 +16,7 @@ The recommended way to pass data between views in a Vaadin application is throug Vaadin supports two types of URL parameters: * *Route parameters* -- Part of the URL path, used to identify a specific view or content, such as a particular entity or tab. -* *Query parameters* -- Key-value pairs appended to the URL, ideal for storing UI state, like a grid's sort order or a search field's value. +* *Query parameters* -- Key-value pairs appended to the URL, ideal for storing UI state, such as a grid's sort order or a search field's value. [TIP] Use *route parameters* when navigation depends on the structure of the URL, such as `/customer/123` to view customer details. diff --git a/articles/building-apps/views/pass-data/query-parameters.adoc b/articles/building-apps/views/pass-data/query-parameters.adoc index 3836d54ed0..f3a71286da 100644 --- a/articles/building-apps/views/pass-data/query-parameters.adoc +++ b/articles/building-apps/views/pass-data/query-parameters.adoc @@ -10,7 +10,7 @@ order: 30 = Query Parameters :toclevels: 2 -In this guide, you'll learn how to access and set query parameters in a Vaadin view. +In this guide, you learn how to access and set query parameters in a Vaadin view. == Copy-Paste into Your Project @@ -22,7 +22,7 @@ If you want to quickly try out query parameters in your Vaadin application, copy include::{root}/src/main/java/com/vaadin/demo/buildingapps/passdata/QueryParameterView.java[tags=snippet,indent=0] ---- -For more detailed instructions on how to use route parameters, continue reading below. +For more detailed instructions on how to use query parameters, continue reading below. == What Are Query Parameters? @@ -70,7 +70,7 @@ public class OrdersView extends Main implements BeforeEnterObserver { ---- <1> *Tip:* To improve readability and maintainability, declare query parameter names as constants. -Now, if you navigate to `/orders?sort=date&filter=shipped`, the query parameter values will be: +Now, if you navigate to `/orders?sort=date&filter=shipped`, the query parameter values are: * `sort` -> `"date"` * `filter` -> `"shipped"` @@ -85,7 +85,7 @@ Since query parameters are always strings and optional, they may be empty or con == Setting Query Parameters -The [classname]`QueryParameters` class is immutable, meaning you can't modify existing query parameters. Instead, you must create a new [classname]`QueryParameters` object and pass it to [methodname]`UI.navigate()`. Query parameters are set when navigating to a view. +The [classname]`QueryParameters` class is immutable, meaning you cannot modify existing query parameters. Instead, you must create a new [classname]`QueryParameters` object and pass it to [methodname]`UI.navigate()`. Query parameters are set when navigating to a view. In the following example, [classname]`OrdersView` provides a <<../navigate#your-own-api,custom API>> for navigating to itself while setting the `filter` query parameter: diff --git a/articles/building-apps/views/pass-data/route-parameters.adoc b/articles/building-apps/views/pass-data/route-parameters.adoc index 7c05abf7d9..b1613aa4c7 100644 --- a/articles/building-apps/views/pass-data/route-parameters.adoc +++ b/articles/building-apps/views/pass-data/route-parameters.adoc @@ -10,7 +10,7 @@ order: 10 = Route Parameters :toclevels: 2 -In this guide, you'll learn how to create a view that accepts a single route parameter. You'll also explore the differences between optional and wildcard route parameters. +In this guide, you learn how to create a view that accepts a single route parameter. You also explore the differences between optional and wildcard route parameters. == Copy-Paste into Your Project @@ -29,7 +29,7 @@ For more detailed instructions on how to use route parameters, continue reading Route parameters are dynamic segments in a URL that allow extra information to be passed to a view. They are appended to the route path and can be used to personalize responses or modify application behavior. -For example, if an application has a `greet` route that accepts a string parameter, users can call it with URLs like: +For example, if an application has a `greet` route that accepts a string parameter, users can call it with URLs such as: * `/greet/John` * `/greet/Jane` @@ -101,7 +101,7 @@ public class CustomerView extends Main implements HasUrlParameter { } ---- -Now, if you now navigate to `/customers`, the router calls [methodname]`setParameter()` with `null` as the parameter value. +Now, if you navigate to `/customers`, the router calls [methodname]`setParameter()` with `null` as the parameter value. == Wildcard Parameters @@ -122,7 +122,7 @@ public class CustomerView extends Main implements HasUrlParameter { if (parameter.isEmpty()) { showCustomerList(); } else { - // Extract the and process the segments from the parameter + // Extract and process the segments from the parameter } } diff --git a/articles/building-apps/views/pass-data/route-templates.adoc b/articles/building-apps/views/pass-data/route-templates.adoc index 9cc550f662..d1a905ee71 100644 --- a/articles/building-apps/views/pass-data/route-templates.adoc +++ b/articles/building-apps/views/pass-data/route-templates.adoc @@ -10,7 +10,7 @@ order: 20 = Route Templates :toclevels: 2 -In this guide, you'll learn how to create a view that accepts multiple route parameters using route templates. You'll also learn how to use modifiers and regular expressions to tweak the behavior of the route parameters. +In this guide, you learn how to create a view that accepts multiple route parameters using route templates. You also learn how to use modifiers and regular expressions to tweak the behavior of the route parameters. Using a single <> is easier than using a route template. If you can get the job done using the [interfacename]`HasUrlParameter` interface, use that instead. @@ -108,7 +108,6 @@ public class CustomerView extends Main implements BeforeEnterObserver { <2> The route parameter value can now be empty. Now, if you navigate to `/customer/cu12345`, the `action` route parameter is empty. You can handle empty parameters by providing a default value, redirecting users, or displaying an error message. -// TODO Link to conditional routing guide When using multiple optional route parameters, values are assigned from left to right. If a parameter is missing, the next available value shifts left to fill its place. For instance, consider an application with the route `customer/:customerId?/:action?`: @@ -163,7 +162,7 @@ If a route parameter is missing, `getWildcard()` returns an empty list. == Constraining Route Parameter Values with Regular Expressions -In all the examples discussed, the route parameters accept any value. However, a specific value is often expected for a route parameter and the view should be shown only when that specific value is present in the URL. You can do this by defining a regular expression for the route parameter. This reduces the need for validation and sanitation of route parameter values in the [methodname]`beforeEnter()` method. +In all the examples discussed, the route parameters accept any value. However, a specific value is often expected for a route parameter and the view should be shown only when that specific value is present in the URL. You can do this by defining a regular expression for the route parameter. This reduces the need for validation and sanitization of route parameter values in the [methodname]`beforeEnter()` method. [NOTE] The syntax of the regular expressions is checked at application startup. If there is an error, the application fails to start. @@ -180,6 +179,6 @@ public class CustomerView extends Main { } ---- -If you navigate to a URL that doesn't meet these constraints, you'll receive a `404 Not Found` error. +If you navigate to a URL that does not meet these constraints, you receive a `404 Not Found` error. -When you specify constraints on wildcard route parameters, the regular expression is applied to every segment that would be captured by the route parameter. If any of the segments fails to match the expression, the whole route template fails to match the URL, and you'll get a `404 Not Found` error. +When you specify constraints on wildcard route parameters, the regular expression is applied to every segment that would be captured by the route parameter. If any of the segments fails to match the expression, the whole route template fails to match the URL, and you get a `404 Not Found` error.