Skip to content

Commit

Permalink
feat: Add interfaces for CRUD services (#20743)
Browse files Browse the repository at this point in the history
Similar interfaces were previously in Hilla but are equally useful in Flow applications

Fixes #20834
  • Loading branch information
Artur- authored Jan 13, 2025
1 parent 4688eb1 commit c96213d
Show file tree
Hide file tree
Showing 28 changed files with 2,231 additions and 7 deletions.
56 changes: 49 additions & 7 deletions vaadin-spring/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@
<artifactId>jakarta.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jspecify</groupId>
<artifactId>jspecify</artifactId>
<version>1.0.0</version>
</dependency>


<dependency>
<groupId>com.vaadin</groupId>
Expand Down Expand Up @@ -97,6 +103,26 @@
<artifactId>spring-data-commons</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<scope>provided</scope>
<exclusions>
<exclusion>
<artifactId>aspectjrt</artifactId>
<groupId>org.aspectj</groupId>
</exclusion>
<exclusion>
<artifactId>jcl-over-slf4j</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>jakarta.persistence</groupId>
<artifactId>jakarta.persistence-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.vaadin</groupId>
<artifactId>flow-data</artifactId>
Expand All @@ -120,6 +146,22 @@
<scope>test</scope>
</dependency>

<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-test-autoconfigure</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
Expand Down Expand Up @@ -189,13 +231,13 @@

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.vaadin.flow.spring.data;

import org.jspecify.annotations.Nullable;

import com.vaadin.flow.spring.data.filter.Filter;

/**
* A service that can count the number of items with a given filter.
*/
public interface CountService {

/**
* Counts the number of items that match the given filter.
*
* @param filter
* the filter, or {@code null} to use no filter
* @return the number of items in the service that match the filter
*/
public long count(@Nullable Filter filter);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.vaadin.flow.spring.data;

/**
* A service that can create, read, update, and delete a given type of object.
*
* @param <T>
* the type of object to manage
* @param <ID>
* the type of the object's identifier
*/
public interface CrudService<T, ID> extends ListService<T>, FormService<T, ID> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.vaadin.flow.spring.data;

/**
* A service that can update and delete a given type of object.
*
* @param <T>
* the type of object to manage
* @param <ID>
* the type of the object's identifier
*
*/
public interface FormService<T, ID> {

/**
* Saves the given object and returns the (potentially) updated object.
* <p>
* If you store the object in a SQL database, the returned object might have
* a new id or updated consistency version.
*
* @param value
* the object to save
* @return the fresh object; will never be {@literal null}.
*/
T save(T value);

/**
* Deletes the object with the given id.
*
* @param id
* the id of the object to delete
*/
void delete(ID id);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.vaadin.flow.spring.data;

import java.util.Optional;

/**
* A service that can fetch the given type of object.
*/
public interface GetService<T, ID> {

/**
* Gets the object with the given id.
*
* @param id
* the id of the object
* @return the object, or an empty optional if no object with the given id
*/
Optional<T> get(ID id);

/**
* Checks if an object with the given id exists.
*
* @param id
* the id of the object
* @return {@code true} if the object exists, {@code false} otherwise
*/
default boolean exists(ID id) {
return get(id).isPresent();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.vaadin.flow.spring.data;

import java.util.List;

import org.jspecify.annotations.Nullable;
import org.springframework.data.domain.Pageable;

import com.vaadin.flow.spring.data.filter.Filter;

/**
* A service that can list the given type of object.
*
* @param <T>
* the type of object to list
*/
public interface ListService<T> {
/**
* Lists objects of the given type using the paging, sorting and filtering
* options provided in the parameters.
*
* @param pageable
* contains information about paging and sorting
* @param filter
* the filter to apply or {@code null} to not filter
* @return a list of objects or an empty list if no objects were found
*/
List<T> list(Pageable pageable, @Nullable Filter filter);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.vaadin.flow.spring.data.filter;

import java.util.List;

/**
* A filter that requires all children to pass.
* <p>
* Custom filter implementations need to handle this filter by running all child
* filters and verifying that all of them pass.
*/
public class AndFilter extends Filter {

private List<Filter> children;

/**
* Create an empty filter.
*/
public AndFilter() {
// Empty constructor is needed for serialization
}

/**
* Create a filter with the given children.
*
* @param children
* the children of the filter
*/
public AndFilter(Filter... children) {
setChildren(List.of(children));
}

public List<Filter> getChildren() {
return children;
}

public void setChildren(List<Filter> children) {
this.children = children;
}

@Override
public String toString() {
return getClass().getSimpleName() + " [children=" + children + "]";
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.vaadin.flow.spring.data.filter;

import java.io.Serializable;

import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonSubTypes.Type;
import com.fasterxml.jackson.annotation.JsonTypeInfo;

/**
* Superclass for all filters to be used with CRUD services. This specific class
* is never used, instead a filter instance will be one of the following types:
* <ul>
* <li>{@link AndFilter} - Contains a list of nested filters, all of which need
* to pass.</li>
* <li>{@link OrFilter} - Contains a list of nested filters, of which at least
* one needs to pass.</li>
* <li>{@link PropertyStringFilter} - Matches a specific property, or nested
* property path, against a filter value, using a specific operator.</li>
* </ul>
*/
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY)
@JsonSubTypes({ @Type(value = OrFilter.class, name = "or"),
@Type(value = AndFilter.class, name = "and"),
@Type(value = PropertyStringFilter.class, name = "propertyString") })
public class Filter implements Serializable {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.vaadin.flow.spring.data.filter;

import java.util.List;

/**
* A filter that requires at least one of its children to pass.
* <p>
* Custom filter implementations need to handle this filter by running all child
* filters and verifying that at least one of them passes.
*/
public class OrFilter extends Filter {

private List<Filter> children;

/**
* Create an empty filter.
*/
public OrFilter() {
// Empty constructor is needed for serialization
}

/**
* Create a filter with the given children.
*
* @param children
* the children of the filter
*/
public OrFilter(Filter... children) {
setChildren(List.of(children));
}

public List<Filter> getChildren() {
return children;
}

public void setChildren(List<Filter> children) {
this.children = children;
}

@Override
public String toString() {
return getClass().getSimpleName() + " [children=" + children + "]";
}

}
Loading

0 comments on commit c96213d

Please sign in to comment.