Skip to content

Commit e6c5789

Browse files
authored
Merge branch 'main' into docs/mscb-aura-styling
2 parents d851514 + 34fdf5b commit e6c5789

File tree

122 files changed

+3098
-6341
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

122 files changed

+3098
-6341
lines changed

articles/building-apps/business-logic/add-service/index.adoc renamed to articles/building-apps/business-logic/add-service.adoc

Lines changed: 171 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,19 @@
22
title: Add a Service
33
page-title: How to add an application service to a Vaadin application
44
description: Learn how to add an application service to a Vaadin application.
5-
meta-description: Learn how to design and implement application services in Vaadin. This guide covers best practices, security, naming conventions, and calling services from Flow and Hilla views.
6-
layout: tabbed-page
7-
tab-title: Overview
5+
meta-description: Learn how to design and implement application services in Vaadin. This guide covers best practices, security, naming conventions, and calling services from Vaadin views.
86
order: 5
97
---
108

119

1210
= Add an Application Service
11+
:toclevels: 2
1312

14-
In a Vaadin application, the _application layer_ contains the business, the data, and any integrations to external systems. The application layer exposes an <<../../architecture/api-spi#,API>> that the _UI layer_ (i.e., the views) can call:
13+
In a Vaadin application, the _application layer_ contains the business, the data, and any integrations to external systems. The application layer exposes an <<../architecture/api-spi#,API>> that the _UI layer_ (i.e., the views) can call:
1514

1615
image::images/application-layer-api.png[A diagram of the UI layer calling the application layer through an API]
1716

18-
This API is implemented by _application services_. In practice, application services are *Spring beans* that you can call from Vaadin Flow and Hilla views.
17+
This API is implemented by _application services_. In practice, application services are *Spring beans* that you can call from Vaadin views.
1918

2019

2120
== Design Guidelines
@@ -24,8 +23,8 @@ You can design application services according to your preferred architectural st
2423

2524
* The application services should have *high cohesion*. This means that all the methods in your service should relate to the same thing.
2625
* The application services should be *stateless*.
27-
* Application services should *initiate and complete <<../../forms-data/consistency/transactions#,database transactions>>* before returning results.
28-
* The application services should be *<<../../security/protect-services#,secure>>*.
26+
* Application services should *initiate and complete <<../forms-data/consistency/transactions#,database transactions>>* before returning results.
27+
* The application services should be *<<../security/protect-services#,secure>>*.
2928
* Views should invoke application services, but application services *should not have dependencies on views*.
3029

3130
[NOTE]
@@ -88,7 +87,7 @@ Vaadin does not enforce a specific naming convention for application services. W
8887

8988
== Input & Output
9089

91-
Application services often need to communicate with <<../../forms-data/repositories#,repositories>> 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.
90+
Application services often need to communicate with <<../forms-data/repositories#,repositories>> 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.
9291

9392

9493
=== Entities
@@ -116,9 +115,6 @@ public class CustomerCrudService {
116115
}
117116
----
118117

119-
[CAUTION]
120-
When most of your service methods delegate to a repository, it may be tempting to skip the service and have the UI layer communicate directly with the repository. However, this isn't a good idea because of the cross-cutting concerns that the application service has to handle. This is explained later on this page.
121-
122118
Using entities in your application service is a good idea when your user interface and entities match each other, closely. For example, you could have a form with fields that match the fields of the entity -- or a grid with columns that match them.
123119

124120
Your entities should be _anemic_, which means that they only contain data and little to no business logic.
@@ -138,7 +134,7 @@ In this case, the application services should accept DTO:s as input, and return
138134

139135
This adds another responsibility to the application service: mapping between entities and DTO:s.
140136

141-
When using <<../../forms-data/repositories#query-classes,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.
137+
When using <<../forms-data/repositories#query-classes,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.
142138

143139
For storing data, services typically have to copy data from the DTO to the entity. For example, like this:
144140

@@ -176,14 +172,14 @@ When using DTO:s, you have more code to maintain. Some changes, like adding a ne
176172

177173
=== Domain Payload Objects
178174

179-
When using <</building-apps/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.
175+
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.
180176

181177

182178
=== Validation
183179

184180
All input should be validated by the application services before they do anything else with it. This is important for security, integrity, and consistency. Even if you use input validation in your user interface, you should still validate the data in the application services.
185181

186-
You can validate the input in different ways. For more information, see the <</building-apps/forms-data/consistency/validation#,Validation>> documentation page.
182+
You can validate the input in different ways. For more information, see the <<../forms-data/consistency/validation#,Validation>> documentation page.
187183

188184

189185
== Package Naming
@@ -194,14 +190,169 @@ For example, services related to "customer relationship management" would be pla
194190

195191
This structure keeps services well-organized, easy to find, and clearly associated with their purpose.
196192

197-
See the <<../../architecture/packages#,Package Structure>> documentation page for more information.
193+
See the <<../architecture/packages#,Package Structure>> documentation page for more information.
194+
195+
196+
== Injecting a Service into a View
197+
198+
Since application services are Spring beans, you can inject them directly into your Vaadin views through constructor injection.
199+
200+
In the following example, [classname]`CustomerOnboardingService` is injected into [classname]`CustomerOnboardingView`:
201+
202+
[source,java]
203+
----
204+
@Route
205+
public class CustomerOnboardingView extends Main {
206+
207+
private final CustomerOnboardingService service; // <1>
208+
209+
// tag::snippet[]
210+
public CustomerOnboardingView(CustomerOnboardingService service) { // <2>
211+
// end::snippet[]
212+
this.service = service;
213+
// ...
214+
}
215+
...
216+
}
217+
----
218+
<1> Store the service in a `final` variable for future reference.
219+
<2> Inject the service as a constructor parameter.
220+
221+
Constructor injection is recommended because it ensures that dependencies are provided at object creation, making the class easier to test and avoiding potential issues with uninitialized fields. Additionally, since the service is stored in a final variable, it cannot be reassigned accidentally, ensuring safer code.
222+
223+
224+
== Calling a Service
225+
226+
Since Vaadin views are regular Java objects, calling a service is as simple as invoking a method.
227+
228+
In the following example, the view calls [classname]`CustomerOnboardingService` when the user clicks a button:
229+
230+
[source,java]
231+
----
232+
@Route
233+
public class CustomerOnboardingView extends Main {
234+
235+
private final CustomerOnboardingService service;
236+
private final Binder<CustomerOnboardingForm> binder;
237+
238+
public CustomerOnboardingView(CustomerOnboardingService service) {
239+
this.service = service;
240+
this.binder = new Binder<>(CustomerOnboardingForm.class);
241+
242+
// Fields omitted
243+
244+
var createCustomerBtn = new Button("Create");
245+
// tag::snippet[]
246+
createCustomerBtn.addClickListener(event -> createCustomer());
247+
// end::snippet[]
248+
add(createCustomerBtn);
249+
}
250+
251+
private void createCustomer() {
252+
// tag::snippet[]
253+
try {
254+
var formData = binder.writeRecord(); // <1>
255+
var customer = service.onboardCustomer(formData); // <2>
256+
CustomerView.navigateTo(customer.customerId()); // <3>
257+
} catch (ValidationException ex) {
258+
// Handle the exception
259+
}
260+
// end::snippet[]
261+
}
262+
}
263+
----
264+
<1> Retrieves a `CustomerOnboardingForm` record from the binder.
265+
<2> Calls the service to onboard the customer.
266+
<3> Navigates to the newly created customer's view.
267+
268+
For more information about forms and data binding, see the <<../forms-data/add-form#,Add a Form>> guide.
198269

199270

200-
== Calling from Views
271+
== Calling a Service on View Creation
201272

202-
You can call an application service both from Flow and Hilla. When calling an application service from a Hilla view, it must be *browser-callable*, which introduces certain design constraints. These constraints do not apply when calling the service from a Flow view.
273+
Sometimes, you may need to call a service immediately upon view creation—for example, to populate a combo box or grid with data. While it may be tempting to do this in the constructor, *this is not recommended*.
203274

204-
The following guides teach you how to call application services in Flow and Hilla:
275+
Vaadin may instantiate a view without actually displaying it. Because of this, you should *keep constructors free of side effects*.
276+
277+
.What is a side effect?
278+
[NOTE]
279+
A _side effect_ is any operation that modifies state outside the object's scope or interacts with external systems like databases, files, or network services during object construction.
280+
281+
282+
=== After Navigation
283+
284+
To call a service only after the user has navigated to a view, implement the [interfacename]`AfterNavigationObserver` interface and call the service in the [methodname]`afterNavigation()` method:
285+
286+
[source,java]
287+
----
288+
@Route
289+
// tag::snippet[]
290+
public class MyView extends Main implements AfterNavigationObserver {
291+
// end::snippet[]
292+
293+
private final CountryService countryService;
294+
private final ComboBox<Country> countries;
295+
296+
public MyView(CountryService countryService) {
297+
this.countryService = countryService;
298+
countries = new ComboBox<>();
299+
add(countries);
300+
}
301+
302+
// tag::snippet[]
303+
@Override
304+
public void afterNavigation(AfterNavigationEvent afterNavigationEvent) {
305+
countries.setItems(countryService.getCountries());
306+
}
307+
// end::snippet[]
308+
}
309+
----
310+
311+
This ensures that service calls happen only when the view is actually rendered.
312+
313+
314+
=== Cleaning Up
315+
316+
If a service call requires cleanup afterward -- such as unsubscribing from a stream -- use Vaadin's *attach and detach events*.
317+
318+
Every Vaadin component is notified when it is attached to or detached from the UI. You can handle these events in two ways:
319+
320+
1. Override the protected [methodname]`onAttach()` and [methodname]`onDetach()` methods.
321+
2. Register attach and detach listeners dynamically.
322+
323+
A common approach is to override [methodname]`onAttach()` and register a detach listener.
324+
325+
In the following example, the view subscribes to a reactive stream when attached and unsubscribes when detached:
326+
327+
[source,java]
328+
----
329+
public class MyView extends Main {
330+
331+
private final SubscriptionService subscriptionService;
332+
333+
public MyView(SubscriptionService subscriptionService) {
334+
this.subscriptionService = subscriptionService;
335+
// ...
336+
}
337+
338+
// tag::snippet[]
339+
@Override
340+
protected void onAttach(AttachEvent attachEvent) {
341+
var subscription = subscriptionService.myStream().subscribe(message -> { // <1>
342+
// Do something with the message
343+
});
344+
addDetachListener(detachEvent -> {
345+
detachEvent.unregisterListener(); // <2>
346+
subscription.dispose(); // <3>
347+
});
348+
}
349+
// end::snippet[]
350+
}
351+
----
352+
<1> Calls the service to subscribe to the stream when attached.
353+
<2> Removes the detach listener to prevent duplicate listeners.
354+
<3> Cancels the subscription to avoid memory leaks.
205355

206-
* <<flow#,Calling Application Services in Flow>>
207-
* <<hilla#,Calling Application Services in Hilla>>
356+
.Components Can Be Attached and Detached Multiple Times
357+
[IMPORTANT]
358+
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.

0 commit comments

Comments
 (0)