From 82370074601d048c7c616871b14cea90fa481ad5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sascha=20I=C3=9Fbr=C3=BCcker?= Date: Mon, 4 Dec 2023 11:24:31 +0100 Subject: [PATCH] simplify crud example, add more complex list service example --- .../react/components/auto-crud/_shared.adoc | 78 -------------- .../react/components/auto-crud/index.asciidoc | 46 ++++++-- .../react/components/auto-form/index.asciidoc | 7 +- .../react/components/auto-grid/index.asciidoc | 47 +++++++- .../demo/fusion/crud/ProductAdvancedDto.java | 26 +++++ .../crud/ProductAdvancedDtoListService.java | 102 ++++++++++++++++++ .../fusion/crud/ProductDtoCrudService.java | 18 ++-- 7 files changed, 222 insertions(+), 102 deletions(-) delete mode 100644 articles/react/components/auto-crud/_shared.adoc create mode 100644 src/main/java/com/vaadin/demo/fusion/crud/ProductAdvancedDto.java create mode 100644 src/main/java/com/vaadin/demo/fusion/crud/ProductAdvancedDtoListService.java diff --git a/articles/react/components/auto-crud/_shared.adoc b/articles/react/components/auto-crud/_shared.adoc deleted file mode 100644 index d75355289f..0000000000 --- a/articles/react/components/auto-crud/_shared.adoc +++ /dev/null @@ -1,78 +0,0 @@ -// tag::custom-crud-service[] -To use plain Java classes, you need to provide a custom implementation of the `CrudService` interface, as the `CrudRepositoryService` base class only works with JPA entities. The following methods need to be implemented: - -* `List list(Pageable pageable, Filter filter)`: Returns a list of paginated, sorted and filtered items. -* `T get(ID id)`: Returns the item with the given ID. If the item does not exist, returns `null`. -* `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. - -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. - -endif::[] - -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. It also reuses the Hilla `JpaFilterConverter` for the filter implementation. - -[.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[] - -// tag::advanced-list-service[] -When implementing the `list` method using a custom data source you are responsible for converting the paging, sorting and filter parameters provided to the service into some data structure that your data source understands. The service 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 (`AndFilter.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 outlines a custom service implementation that fetches data from a fictional REST API. It extracts the paging, sorting and filtering parameters from the `Pageable` and `Filter` instances, and converts them into a custom data structure before passing them to an external API. - -ifdef::auto-crud[] -[NOTE] -The following example contains an implementation of the `ListService` interface. The `list` method between `ListService` and `CrudService` are the same, so the example is also applicable to Auto CRUD. Implementations of the `get`, `save` and `delete` methods are not shown. - -endif::[] - -[.example] --- -.ProductApiListService.java -[source,java] ----- -include::{root}/src/main/java/com/vaadin/demo/fusion/crud/ProductApiListService.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] ----- --- - -// end::advanced-list-service[] diff --git a/articles/react/components/auto-crud/index.asciidoc b/articles/react/components/auto-crud/index.asciidoc index ebb88f35e5..5c05931acf 100644 --- a/articles/react/components/auto-crud/index.asciidoc +++ b/articles/react/components/auto-crud/index.asciidoc @@ -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` interface, as the `CrudRepositoryService` base class only works with JPA entities. The following methods need to be implemented: -==== Advanced Implementations +* `List 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. + +[.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`. \ No newline at end of file diff --git a/articles/react/components/auto-form/index.asciidoc b/articles/react/components/auto-form/index.asciidoc index a62c114577..64c9ca605c 100644 --- a/articles/react/components/auto-form/index.asciidoc +++ b/articles/react/components/auto-form/index.asciidoc @@ -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] diff --git a/articles/react/components/auto-grid/index.asciidoc b/articles/react/components/auto-grid/index.asciidoc index 77660d9e40..5e432eeaf1 100644 --- a/articles/react/components/auto-grid/index.asciidoc +++ b/articles/react/components/auto-grid/index.asciidoc @@ -165,7 +165,7 @@ 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 @@ -173,7 +173,7 @@ To use plain Java classes, you need to provide a custom implementation of the `L * `List 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. [.example] -- @@ -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. + +[.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] +---- +-- ++++