Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions articles/building-apps/business-logic/add-service.adoc
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: Add a Service
page-title: How to add an application service to a Vaadin application
page-title: How to add an application service to a Vaadin application
description: Learn how to add an application service to a Vaadin application.
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.
order: 5
Expand All @@ -14,13 +14,13 @@ In a Vaadin application, the _application layer_ contains the business, the data

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

This API is implemented by _application services_. In practice, application services are *Spring beans* that you can call from Vaadin views.
This API is implemented by _application services_. In practice, application services are *Spring beans* that you can call from Vaadin views.


== Design Guidelines

You can design application services according to your preferred architectural style, but following these best practices helps prevent common issues:

* The application services should have *high cohesion*. This means that all the methods in your service should relate to the same thing.
* The application services should be *stateless*.
* Application services should *initiate and complete <<../forms-data/consistency/transactions#,database transactions>>* before returning results.
Expand All @@ -43,8 +43,8 @@ public class OrderCreationService {
private final OrderRepository orderRepository;
private final ApplicationEventPublisher eventPublisher;

OrderCreationService(Validator validator,
OrderRepository orderRepository,
OrderCreationService(Validator validator,
OrderRepository orderRepository,
ApplicationEventPublisher eventPublisher) {
this.validator = validator;
this.orderRepository = orderRepository;
Expand All @@ -58,7 +58,7 @@ public class OrderCreationService {
throw new ConstraintViolationException(validationErrors);
}
var order = orderRepository.saveAndFlush(createOrderFromForm(orderForm));
eventPublisher.publishEvent(new OrderCreatedEvent(order)); // Notify other
eventPublisher.publishEvent(new OrderCreatedEvent(order)); // Notify other
// components of
// the new order
return order.orderId();
Expand Down Expand Up @@ -247,7 +247,7 @@ public class CustomerOnboardingView extends Main {
// end::snippet[]
add(createCustomerBtn);
}

private void createCustomer() {
// tag::snippet[]
try {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
---
title: Callbacks
page-title: How to use callbacks to interact with your UI | Vaadin
page-title: How to use callbacks to interact with your UI | Vaadin
description: How to use callbacks to interact with the user interface.
meta-description: When using a Flow user interface, the simplest way of allowing background jobs to interact with it is through callbacks. Learn more here.
order: 10
section-nav: badge-flow
---


= Callbacks [badge-flow]#Flow#
= Callbacks [badge-flow]#Flow#

When using a Flow user interface, the simplest way of allowing background jobs to interact with it is through callbacks. You can use `Consumer`, `Runnable`, and `Supplier` as callback interfaces, depending on how you want to interact with the background job.

Expand Down Expand Up @@ -41,7 +41,7 @@ A background job that returns a string or an exception could be implemented like
[source,java]
----
@Async
public void startBackgroundJob(Consumer<String> onComplete,
public void startBackgroundJob(Consumer<String> onComplete,
Consumer<Exception> onError) {
try {
var result = doSomethingThatTakesALongTime();
Expand All @@ -60,8 +60,8 @@ When the background job is also reporting its progress, for instance as a percen
[source,java]
----
@Async
public void startBackgroundJob(Consumer<String> onComplete,
Consumer<Double> onProgress,
public void startBackgroundJob(Consumer<String> onComplete,
Consumer<Double> onProgress,
Consumer<Exception> onError) {
try {
onProgress.apply(0.0);
Expand Down Expand Up @@ -93,8 +93,8 @@ A job can be cancelled. To do that, it would look like this:
[source,java]
----
@Async
public void startBackgroundJob(Consumer<String> onComplete,
Consumer<Double> onProgress,
public void startBackgroundJob(Consumer<String> onComplete,
Consumer<Double> onProgress,
Consumer<Exception> onError,
Supplier<Boolean> isCancelled) {
try {
Expand Down Expand Up @@ -150,7 +150,7 @@ Next, implement the service method like this:

[source,java]
----
public CancellableJob startBackgroundJob(Consumer<String> onComplete,
public CancellableJob startBackgroundJob(Consumer<String> onComplete,
Consumer<Double> onProgress
Consumer<Exception> onError) {
var cancelled = new AtomicBoolean(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ order: 30

= Producing Reactive Streams

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

Expand Down Expand Up @@ -49,7 +49,7 @@ You first need to create a data type that can contain both progress updates and
import com.vaadin.hilla.Nullable;

public record BackgroundJobOutput(
@Nullable Double progressUpdate,
@Nullable Double progressUpdate,
@Nullable String result
) {
public static BackgroundJobOutput progressUpdate(double progressUpdate) {
Expand Down Expand Up @@ -78,8 +78,8 @@ private String doSomethingThatTakesALongTime(Consumer<Double> onProgress) {
public Flux<BackgroundJobOutput> startBackgroundJob() {
Sinks.Many<Double> progressUpdates = Sinks // <1>
.many()
.unicast()
.onBackpressureError();
.unicast()
.onBackpressureError();

var result = Mono // <2>
.fromSupplier(() -> doSomethingThatTakesALongTime(
Expand Down Expand Up @@ -107,7 +107,7 @@ You can cancel a subscription to a `Flux` or `Mono` at any time. However, as wit
[source,java]
----
private String doSomethingThatTakesALongTime(
Consumer<Double> onProgress,
Consumer<Double> onProgress,
Supplier<Boolean> isCancelled) {
...
}
Expand All @@ -116,8 +116,8 @@ public Flux<BackgroundJobOutput> startBackgroundJob() {
var cancelled = new AtomicBoolean(false);
Sinks.Many<Double> progressUpdates = Sinks
.many()
.unicast()
.onBackpressureError();
.unicast()
.onBackpressureError();

var result = Mono
.fromSupplier(() -> doSomethingThatTakesALongTime(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ The following example shows *a dialog used to create new project proposals*. The
[source,java]
----
public class ProposalDialog extends Dialog {

private final SerializableConsumer<Proposal> onSaveCallback;
private final ProposalForm form;

Expand Down Expand Up @@ -93,7 +93,7 @@ public class ProposalDrawer extends Section {
private final SerializableRunnable onCloseCallback;
private final ProposalForm form;

public ProposalDrawer(SerializableFunction<Proposal, Proposal> onSaveCallback,
public ProposalDrawer(SerializableFunction<Proposal, Proposal> onSaveCallback,
SerializableRunnable onCloseCallback) {
this.onSaveCallback = onSaveCallback;
this.onCloseCallback = onCloseCallback;
Expand All @@ -111,11 +111,11 @@ public class ProposalDrawer extends Section {
var closeBtn = new Button("Close", event -> close());

var buttons = new HorizontalLayout(closeBtn, saveBtn);

// Configure the drawer
add(header, form, buttons);
addClassNames(LumoUtility.Display.FLEX, // <2>
LumoUtility.FlexDirection.COLUMN,
LumoUtility.FlexDirection.COLUMN,
LumoUtility.Border.ALL,
LumoUtility.Padding.MEDIUM);
setVisible(false); // <3>
Expand Down Expand Up @@ -158,10 +158,10 @@ public class ProposalView extends Main {
var savedProposal = proposalService.save(proposal); // <1>
grid.getDataProvider().refreshAll(); // <2>
return savedProposal;
},
},
grid::deselectAll // <3>
);
grid.addSelectionListener(event ->
grid.addSelectionListener(event ->
drawer.setProposal(event.getFirstSelectedItem().orElse(null)) // <4>
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ public class ProposalForm extends Composite<FormLayout> {
} else {
return Optional.empty(); // <3>
}
}
}
// end::snippet[]
}
----
Expand Down Expand Up @@ -379,10 +379,10 @@ The equivalent *Project Proposal* FDO using a *record* looks like this:
[source,java]
----
public record ProposalRecord(
String title,
ProposalType type,
String description,
LocalDate startDate,
String title,
ProposalType type,
String description,
LocalDate startDate,
LocalDate endDate
) {
}
Expand Down Expand Up @@ -444,10 +444,10 @@ To reduce this risk, you can also use constants for record component names inste
[source,java]
----
public record ProposalRecord(
String title,
ProposalType type,
String description,
LocalDate startDate,
String title,
ProposalType type,
String description,
LocalDate startDate,
LocalDate endDate
) {
public static final String PROP_TITLE = "title";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ public class ProposalView extends Main {
// tag::snippet[]
private void editProposal(Proposal proposal) {
form.setFormDataObject(new Proposal(proposal)); // <2>
// Perform other UI updates, such as making the form visible,
// Perform other UI updates, such as making the form visible,
// updating the title, etc.
}
// end::snippet[]
Expand Down Expand Up @@ -273,7 +273,7 @@ public class ProposalView extends Main {

private void editProposal(Proposal proposal) {
form.setFormDataObject(proposal); // <5>
// Perform other UI updates, such as making the form visible,
// Perform other UI updates, such as making the form visible,
// updating the title, etc.
}

Expand Down Expand Up @@ -317,7 +317,7 @@ public class ProposalView extends Main {
// (Constructor omitted for brevity.)

private void editProposal(Proposal proposal) {
form.setFormDataObject(new Proposal(proposal));
form.setFormDataObject(new Proposal(proposal));
// Perform other UI updates, such as making the form visible,
// updating the title, etc.
}
Expand All @@ -342,11 +342,11 @@ When using records as FDO, `Binder` requires all record components to be bound t

[source,java]
----
binder = new Binder<>(ProposalRecord.class);
binder = new Binder<>(ProposalRecord.class);
// tag::snippet[]
binder.forField(new ReadOnlyHasValue<Long>(ignore -> {})).bind("proposalId");
// end::snippet[]
binder.forField(titleField).bind("title");
binder.forField(titleField).bind("title");
binder.forField(proposalTypeField).bind("type");
// And so on...
----
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
title: Domain Primitives
page-title: How to use domain primitives in Vaadin applications
description: Learn what domain primitives are and how to use them in applications.
meta-description: Learn what domain primitives are and how you can use them in your Vaadin applications for better structure and logic.
meta-description: Learn what domain primitives are and how you can use them in your Vaadin applications for better structure and logic.
order: 16
---

Expand All @@ -15,7 +15,7 @@ Primitive data types have constraints on what you can store in them. For example


== Constraint Enforcement Problem

Traditionally, these domain constraints have been enforced in various ways. For example, a typical way to create a validator utility might look like this:

[source,java]
Expand Down Expand Up @@ -63,7 +63,7 @@ public class OrderItem {
@Max(QuantityUtils.MAX_QTY)
private int quantity;
...

public OrderItem(int quantity) {
this.quantity = quantity;
}
Expand All @@ -74,7 +74,7 @@ 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.

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

Expand Down Expand Up @@ -160,7 +160,7 @@ Domain primitives offer another benefit. They reduce the risk of mixing attribut
[source,java]
----
public record StreetAddress(
String number,
String number,
String name
) {}
----
Expand All @@ -172,7 +172,7 @@ With domain primitives, the `StreetAddress` object now looks like this:
[source,java]
----
public record StreetAddress(
StreetNumber number,
StreetNumber number,
StreetName streetName
) {}
----
Expand All @@ -182,7 +182,7 @@ When creating a new instance of this object, a developer now has to write `new S

== 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.
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:

Expand Down Expand Up @@ -220,7 +220,7 @@ public final class Discount {
...

public MonetaryAmount applyTo(MonetaryAmount regularPrice) {
return new MonetaryAmount(currency,
return new MonetaryAmount(currency,
discountFactor.multiply(regularPrice.value()));
}
}
Expand Down Expand Up @@ -284,7 +284,7 @@ public class MonetaryAmountConverter implements Converter<BigDecimal, MonetaryAm
}

@Override
public Result<MonetaryAmount> convertToModel(BigDecimal value,
public Result<MonetaryAmount> convertToModel(BigDecimal value,
ValueContext valueContext) {
if (value == null) {
return null;
Expand All @@ -297,7 +297,7 @@ public class MonetaryAmountConverter implements Converter<BigDecimal, MonetaryAm
}

@Override
public BigDecimal convertToPresentation(MonetaryAmount monetaryAmount,
public BigDecimal convertToPresentation(MonetaryAmount monetaryAmount,
ValueContext valueContext) {
return monetaryAmount == null ? null : monetaryAmount.amount();
}
Expand All @@ -319,7 +319,7 @@ public class MonetaryAmountField extends CustomField<MonetaryAmount> {
amountField = new BigDecimalField();
add(currencyField, amountField);
}

@Override
protected MonetaryAmount generateModelValue() {
var currency = currencyField.getValue();
Expand Down
Loading
Loading