Skip to content

Commit

Permalink
simplify crud example, add more complex list service example
Browse files Browse the repository at this point in the history
  • Loading branch information
sissbruecker committed Dec 4, 2023
1 parent 0d65e38 commit 8237007
Show file tree
Hide file tree
Showing 7 changed files with 222 additions and 102 deletions.
78 changes: 0 additions & 78 deletions articles/react/components/auto-crud/_shared.adoc

This file was deleted.

46 changes: 39 additions & 7 deletions articles/react/components/auto-crud/index.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -135,17 +135,49 @@ For more detailed examples of customizing the underlying form, please refer to t

== Working with Plain Java Beans

Auto CRUD supports working with plain, non-JPA beans. This can be useful, for example if you want to use the Data Transfer Object (DTO) pattern to decouple your domain from your presentation logic, or want to avoid serializing your JPA entities to the client-side due to performance concerns.
Auto CRUD supports working with plain, non-JPA beans. This can be useful, for example if you want to use the Data Transfer Object (DTO) pattern to decouple your domain from your presentation logic, or want to avoid serializing your JPA entities to the client due to performance concerns.

=== Implementing a Custom Service
:auto-crud:

include::./_shared.adoc[tag=custom-crud-service]
// tag::custom-crud-service[]
To use plain Java classes, you need to provide a custom implementation of the `CrudService<T>` interface, as the `CrudRepositoryService<T>` base class only works with JPA entities. The following methods need to be implemented:

==== Advanced Implementations
* `List<T> list(Pageable pageable, Filter filter)`: Returns a list of paginated, sorted and filtered items.
* `T save(T value)`: Either creates a new item or updates an existing one, depending on whether the item has an ID or not. Returns the saved item.
* `void delete(ID id)`: Deletes the item with the given ID.

Implementing a service that is not based on JPA requires more work than the example above.
ifdef::auto-form[]
[NOTE]
`CrudService` is a common interface shared with other components such as <<../auto-crud#,Auto CRUD>> and as such requires implementing a `list` method for fetching items. If you intend to use the service only with the Auto Form component, you can create a fake implementation of that method, for example by throwing an exception.

The `get`, `save` and `delete` method implementations should be straight-forward to adapt.
endif::[]

include::../auto-crud/_shared.adoc[tag=advanced-list-service]
The following example shows a basic implementation of a custom `CrudService` that wraps a JPA repository. The service only exposes DTO instances to the client and maps between the DTOs and JPA entities internally.

Check failure on line 155 in articles/react/components/auto-crud/index.asciidoc

View workflow job for this annotation

GitHub Actions / vale

[vale] articles/react/components/auto-crud/index.asciidoc#L155

[Vale.Spelling] Did you really mean 'DTOs'?
Raw output
{"message": "[Vale.Spelling] Did you really mean 'DTOs'?", "location": {"path": "articles/react/components/auto-crud/index.asciidoc", "range": {"start": {"line": 155, "column": 181}}}, "severity": "ERROR"}

[.example]
--
.ProductDtoCrudService.java
[source,java]
----
include::{root}/src/main/java/com/vaadin/demo/fusion/crud/ProductDtoCrudService.java[tags=snippet,indent=0]
----
.ProductDto.java
[source,java]
----
include::{root}/src/main/java/com/vaadin/demo/fusion/crud/ProductDto.java[tags=snippet,indent=0]
----
.Product.java
[source,java]
----
include::{root}/src/main/java/com/vaadin/demo/fusion/crud/Product.java[tags=snippet,indent=0]
----
.ProductRepository.java
[source,java]
----
include::{root}/src/main/java/com/vaadin/demo/fusion/crud/ProductRepository.java[tags=snippet,indent=0]
----
--

// end::custom-crud-service[]

The example above only has a very basic implementation of the `list` method that does not support sorting and filtering. For a proper implementation of that method, see the corresponding section in the <<../auto-grid#implementing-a-custom-service,Auto Grid>> documentation. It shows how to implement a `ListService`, in which the `list` method has the same signature as for `CrudService`.
7 changes: 3 additions & 4 deletions articles/react/components/auto-form/index.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -230,10 +230,9 @@ Note that `onSubmitError` callback is not be called for form validation errors,

== Working with Plain Java Beans

Auto Form supports working with plain, non-JPA beans. This can be useful, for example if you want to use the Data Transfer Object (DTO) pattern to decouple your domain from your presentation logic, or want to avoid serializing your JPA entities to the client-side due to performance concerns.
Auto Form supports working with plain, non-JPA beans. This can be useful, for example if you want to use the Data Transfer Object (DTO) pattern to decouple your domain from your presentation logic, or want to avoid serializing your JPA entities to the client due to performance concerns.

=== Implementing a Custom Service
:auto-form:

include::../auto-crud/_shared.adoc[tag=custom-crud-service]

:auto-form:
include::../auto-crud/index.asciidoc[tag=custom-crud-service]
47 changes: 43 additions & 4 deletions articles/react/components/auto-grid/index.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -165,15 +165,15 @@ You can read more on the properties, and use cases supported, on the <<../grid#,

== Working with Plain Java Beans

Auto Grid supports working with plain, non-JPA beans. This can be useful, for example if you want to use the Data Transfer Object (DTO) pattern to decouple your domain from your presentation logic, or want to avoid serializing your JPA entities to the client-side due to performance concerns.
Auto Grid supports working with plain, non-JPA beans. This can be useful, for example if you want to use the Data Transfer Object (DTO) pattern to decouple your domain from your presentation logic, or want to avoid serializing your JPA entities to the client due to performance concerns.

=== Implementing a Custom Service

To use plain Java classes, you need to provide a custom implementation of the `ListService<T>` interface, as the `ListRepositoryService<T>` base class only works with JPA entities. The following method needs to be implemented:

* `List<T> list(Pageable pageable, Filter filter)`: Returns a list of paginated, sorted and filtered items.

The following example shows a basic implementation of a custom `ListService` that wraps a JPA repository. The service only exposes DTO instances to the client and maps between the DTOs and JPA entities internally. It also reuses the Hilla `JpaFilterConverter` for the filter implementation.
The following example shows a basic implementation of a custom `ListService` that wraps a JPA repository. The service only exposes DTO instances to the client and maps between the DTOs and JPA entities internally. It also reuses the Hilla `JpaFilterConverter` for the filter implementation. In order for sorting and filtering to work, the DTO class and the JPA entity class have the same structure, which means all properties that are sorted and filtered by have the same name and are at the same path in the data structure.

Check failure on line 176 in articles/react/components/auto-grid/index.asciidoc

View workflow job for this annotation

GitHub Actions / vale

[vale] articles/react/components/auto-grid/index.asciidoc#L176

[Vale.Spelling] Did you really mean 'DTOs'?
Raw output
{"message": "[Vale.Spelling] Did you really mean 'DTOs'?", "location": {"path": "articles/react/components/auto-grid/index.asciidoc", "range": {"start": {"line": 176, "column": 181}}}, "severity": "ERROR"}

[.example]
--
Expand Down Expand Up @@ -201,10 +201,49 @@ include::{root}/src/main/java/com/vaadin/demo/fusion/crud/ProductRepository.java

==== Advanced Implementations

Implementing a service that is not based on JPA requires more work than the example above.
The example above works for an ideal case, where the DTO class structure maps one-to-one to the JPA entity class structure. In practice, however, this is often not the case. For example, you might want to flatten nested properties, use different property names or create computed properties that are not present in the JPA entity. Alternatively, you might want to use a different data source than JPA.

include::../auto-crud/_shared.adoc[tag=advanced-list-service]
For these cases you need to implement pagination, sorting and filtering manually using the parameters provided to the `list` method. The method receives a `org.springframework.data.domain.Pageable` and `dev.hilla.crud.filter.Filter` instances as parameters, which can be used to extract the following information:

* `Pageable.getOffset`: The offset of the first item to fetch
* `Pageable.getPageSize`: The maximum number of items to fetch
* `Pageable.getSort`: The sort order of the items. Each sort order contains the property to sort by (`Sort.Order.getProperty()`), and the direction (`Sort.Order.getDirection()`)
* `Filter`: The filter to apply to the items. The filter is a tree structure of `Filter` instances, where each `Filter` instance is either an `AndFilter`, an `OrFilter`, or a `PropertyStringFilter`:
** `AndFilter` contains a list of nested filters (`AndFilter.getChildren()`) that should all match.
** `OrFilter` contains a list of nested filters (`OrFilter.getChildren()`) of which at least one should match.
** `PropertyStringFilter` filters an individual property against a value. It contains the name of the property to filter (`PropertyStringFilter.getPropertyId()`), the value to filter against (`PropertyStringFilter.getFilterValue()`), and the matcher to use (`PropertyStringFilter.getMatcher()`). The filter value is always a string and needs to be converted into a respective type or format that works with the type of the property. The matcher specifies the type of filter operation, such as `EQUALS`, `CONTAINS`, `GREATER_THAN`, etc.

When using the default header filters, the filter is always an `AndFilter` with a list of `PropertyStringFilter` instances. The `PropertyStringFilter` instances are created based on the filter values entered by the user in the header filters. When using an external filter, the filter can be of any structure.

The following example shows a more complex use-case:

* The DTO class uses different property names than the JPA entity class and flattens nested properties. It also adds a computed property that is not present in the JPA entity.
* The sort implementation maps the DTO property names to the JPA entity class structure.
* The filter implementation creates JPA filter specifications based on the filter values entered by the user in the header filters. As part of that it demonstrates how to handle different types of matchers, and how to filter for computed properties.

Check failure on line 222 in articles/react/components/auto-grid/index.asciidoc

View workflow job for this annotation

GitHub Actions / vale

[vale] articles/react/components/auto-grid/index.asciidoc#L222

[Vale.Spelling] Did you really mean 'matchers'?
Raw output
{"message": "[Vale.Spelling] Did you really mean 'matchers'?", "location": {"path": "articles/react/components/auto-grid/index.asciidoc", "range": {"start": {"line": 222, "column": 198}}}, "severity": "ERROR"}

[.example]
--
.ProductAdvancedDtoListService.java
[source,java]
----
include::{root}/src/main/java/com/vaadin/demo/fusion/crud/ProductAdvancedDtoListService.java[tags=snippet,indent=0]
----
.ProductAdvancedDto.java
[source,java]
----
include::{root}/src/main/java/com/vaadin/demo/fusion/crud/ProductAdvancedDto.java[tags=snippet,indent=0]
----
.Product.java
[source,java]
----
include::{root}/src/main/java/com/vaadin/demo/fusion/crud/Product.java[tags=snippet,indent=0]
----
.ProductRepository.java
[source,java]
----
include::{root}/src/main/java/com/vaadin/demo/fusion/crud/ProductRepository.java[tags=snippet,indent=0]
----
--

++++
<style>
Expand Down
26 changes: 26 additions & 0 deletions src/main/java/com/vaadin/demo/fusion/crud/ProductAdvancedDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.vaadin.demo.fusion.crud;

//tag::snippet[]
public record ProductAdvancedDto(Long productId,
String productName,
String productCategory,
double productPrice,
Long supplierId,
String supplierInfo) {
public static ProductAdvancedDto fromEntity(Product product) {
// Compute a custom property that includes the supplier name and city
String supplierInfo = product.getSupplier() != null
? String.format("%s (%s)", product.getSupplier().getSupplierName(), product.getSupplier().getHeadquarterCity())
: "";

return new ProductAdvancedDto(
product.getId(),
product.getName(),
product.getCategory(),
product.getPrice(),
product.getSupplier().getId(),
supplierInfo
);
}
}
//end::snippet[]
Loading

0 comments on commit 8237007

Please sign in to comment.