I have an abstract base service that includes basic CRUD operations. However, there’s a situation where, for example, when saving a Post entity, I also need to save the User information who created the post, as Post and User are related entities.
Then, I thought it would make more sense to get the User information from SecurityContextHolder and save it. However, I’m not sure how to apply this to the save method in the base service for CRUD operations. In some entity save operations, I don’t need to deal with User information, so there’s no issue saving those. But for saving a Post, I want to retrieve the User from SecurityContextHolder instead of getting it from the requestDto.
How can I achieve this without breaking the base structure, avoiding code repetition, and adhering to SOLID principles in a way suitable for a corporate project?
@RequiredArgsConstructor
public abstract class BaseServiceImpl<
Entity extends BaseEntity,
DTO extends BaseDTO,
RequestDTO,
Mapper extends IBaseMapper<DTO, Entity, RequestDTO>,
Repository extends BaseRepository<Entity>>
implements BaseService<DTO,RequestDTO> {
private final Mapper getMapper;
private final Repository getRepository;
@Transactional
public DTO save(RequestDTO requestDTO) {
Entity entity = getMapper.requestDTOToEntity(requestDTO);
getRepository.save(entity);
return getMapper.entityToDTO(entity);
}
public List<DTO> getAll() {
return getMapper.entityListToDTOList(getRepository.findAll());
}
public DTO update(UUID uuid, RequestDTO requestDTO) {
Entity entity = getRepository.findByUuid(uuid).orElse(null);
if (entity != null) {
entity = getMapper.requestDtoToExistEntity(entity, requestDTO);
getRepository.save(entity);
return getMapper.entityToDTO(entity);
} else {
return null;
}
}
public DTO getByUUID(UUID uuid) {
Entity entity = getRepository.findByUuid(uuid).orElse(null);
if (entity != null) {
return getMapper.entityToDTO(entity);
} else {
return null;
}
}
@Transactional
public Boolean deleteByUUID(UUID uuid) {
Entity entity = getRepository.findByUuid(uuid).orElse(null);
if (entity != null) {
getRepository.delete(entity);
return Boolean.TRUE;
} else {
return Boolean.FALSE;
}
}}
Base Service Interface
public interface BaseService<
DTO extends BaseDTO,
RequestDTO> {
DTO save(RequestDTO requestDTO);
List<DTO> getAll();
DTO update(UUID uuid, RequestDTO requestDTO);
DTO getByUUID(UUID uuid);
Boolean deleteByUUID(UUID uuid);
}
PostServiceImpl Service
public class PostServiceImpl extends BaseServiceImpl<
PostEntity,
PostResponseDTO,
PostRequestDTO,
PostMapper,
PostRepository>
implements PostService {
// Other codes...
}
Post Service Interface
public interface PostService extends BaseService<PostResponseDTO, PostRequestDTO> {
//Other codes...
}
I came up with a solution, but I’m not sure if it’s logical or adheres to SOLID principles. Does anyone have a better solution ?
UserContextService
@Service
@RequiredArgsConstructor
public class UserContextServiceImpl implements UserContextService {
private final UserRepository userRepository;
@Override
public UserEntity getCurrentAuthenticatedUser() {
String currentPrincipalEmail = SecurityContextHolder.getContext().getAuthentication().getName();
return userRepository.findByEmail(currentPrincipalEmail)
.orElseThrow(() -> new UsernameNotFoundException("User not found!"));
}
}
Post mapper
@Mapper(componentModel = "spring", uses = {CategoryRepository.class})
public interface PostMapper extends IBaseMapper<PostResponseDTO, PostEntity, PostRequestDTO> {
@Mapping(target = "author", expression = "java(UserMappingUtils.getAuthenticatedUser(userContextService))")
PostEntity requestDTOToEntity(PostRequestDTO dto);
}