Skip to content

Commit

Permalink
add abstract service for typical operations
Browse files Browse the repository at this point in the history
  • Loading branch information
PaulBredl committed Apr 17, 2024
1 parent e63809d commit c615832
Show file tree
Hide file tree
Showing 4 changed files with 536 additions and 0 deletions.
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ dependencies {
implementation 'jakarta.validation:jakarta.validation-api'
implementation 'io.dapr:dapr-sdk:1.+' // Dapr's core SDK with all features, except Actors.
implementation 'io.dapr:dapr-sdk-springboot:1.+' // Dapr's SDK integration with SpringBoot
implementation 'org.modelmapper:modelmapper:3.+'
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
runtimeOnly 'org.postgresql:postgresql'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
package de.unistuttgart.iste.meitrex.common.service;

import de.unistuttgart.iste.meitrex.common.exception.MeitrexNotFoundException;
import de.unistuttgart.iste.meitrex.common.persistence.IWithId;
import de.unistuttgart.iste.meitrex.common.persistence.MeitrexRepository;
import org.modelmapper.ModelMapper;

import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Supplier;

/**
* Abstract service for CRUD operations.
* Services extending this class can utilize the helper methods to perform CRUD operations on entities.
*
* @param <I> the type of the entity id, e.g., UUID
* @param <E> the type of the entity, e.g., UserEntity
* @param <D> the type of the DTO, e.g., User
*/
public abstract class AbstractCrudService<I, E extends IWithId<I>, D> {

/**
* @return the class of the entity, e.g., UserEntity.class
*/
protected abstract Class<E> getEntityClass();

/**
* @return the class of the DTO, e.g., User.class
*/
protected abstract Class<D> getDtoClass();

/**
* Get the model mapper used to map between entities and DTOs.
* This method should return a pre-configured model mapper.
* The model mapper should be set up to map between the entity and DTO classes,
* and between input classes and the entity class.
*
* @return the model mapper used to map between entities and DTOs
*/
protected abstract ModelMapper getModelMapper();

/**
* @return the repository used to access the entities
*/
protected abstract MeitrexRepository<E, I> getRepository();

/**
* Get all entities from the repository and convert them to DTOs.
*
* @return a list of all entities as DTOs
*/
protected List<D> getAll() {
final List<E> entities = getRepository().findAll();
return convertToDtos(entities);
}

/**
* Find an entity by its id and convert it to a DTO.
*
* @param id the id of the entity to find
* @return the entity as a DTO, or an empty optional if no entity with the given id exists
*/
protected Optional<D> find(final I id) {
return getRepository()
.findById(id)
.map(this::convertToDto);
}

/**
* Gets an entity by its id and convert it to a DTO.
* If no entity with the given id exists, throws an exception.
*
* @param id the id of the entity to get
* @return the entity as a DTO
* @throws MeitrexNotFoundException if no entity with the given id exists
*/
protected D getOrThrow(final I id) {
return convertToDto(getRepository().findByIdOrThrow(id));
}

/**
* Creates an entity using the given entity creator and saves it to the repository.
*
* @param entityCreator supplier that returns the entity to create
* @return the created entity as an entity
*/
protected E createEntity(final Supplier<E> entityCreator) {
final E entity = entityCreator.get();
return getRepository().save(entity);
}

/**
* Creates an entity by mapping the given input to an entity and saves it to the repository.
* Note: This requires the model mapper to be set up correctly.
*
* @param createInput the input to map to an entity
* @return the created entity as an entity
*/
protected E createEntity(final Object createInput) {
return createEntity(() -> getModelMapper().map(createInput, getEntityClass()));
}

/**
* Creates an entity using the given entity creator, saves it to the repository
* and converts it to a DTO.
*
* @param entityCreator supplier that returns the entity to create
* @return the created entity as a DTO
*/
protected D create(final Supplier<E> entityCreator) {
final E entity = createEntity(entityCreator);

return convertToDto(entity);
}

/**
* Creates an entity by mapping the given input to an entity, saves it to the repository
* and converts it to a DTO.
* Note: This requires the model mapper to be set up correctly.
*
* @param createInput the input to map to an entity
* @return the created entity as a DTO
*/
protected D create(final Object createInput) {
return create(() -> getModelMapper().map(createInput, getEntityClass()));
}

/**
* Updates an entity by its id using the given entity updater and saves it to the repository.
*
* @param id the id of the entity to update
* @param entityUpdater consumer that updates the entity
* @return the updated entity as a DTO
*/
protected D update(final I id, final Consumer<E> entityUpdater) {
final E entity = getRepository().findByIdOrThrow(id);
entityUpdater.accept(entity);

final E savedEntity = getRepository().save(entity);

return convertToDto(savedEntity);
}

/**
* Updates an entity by its id by mapping the given input
* to the existing entity and saves it to the repository.
* This will not overwrite the entity, but update only the fields
* that are defined in the input.
*
* @param id the id of the entity to update
* @param updateInput the input to map to an entity
* @return the updated entity as a DTO
*/
protected D update(final I id, final Object updateInput) {
return update(id, entity -> getModelMapper().map(updateInput, entity));
}

/**
* Deletes an entity by its id.
*
* @param id the id of the entity to delete
* @return true if the entity was deleted, false if no entity with the given id exists
* @apiNote subclasses will likely require a more sophisticated deletion logic
*/
protected boolean delete(final I id) {
if (!getRepository().existsById(id)) {
return false;
}

getRepository().deleteById(id);
return true;
}

/**
* Converts an entity to a DTO.
*
* @param entity the entity to convert
* @return the entity as a DTO
*/
protected D convertToDto(final E entity) {
return getModelMapper().map(entity, getDtoClass());
}

/**
* Converts a list of entities to a list of DTOs.
*
* @param entities the entities to convert
* @return the entities as DTOs
*/
protected List<D> convertToDtos(final List<E> entities) {
return entities.stream()
.map(this::convertToDto)
.toList();
}
}
Loading

0 comments on commit c615832

Please sign in to comment.