MapStruct, in Java, does not know how to map an id to an entity. This problem is commonly seen when an id field in the DTO, needs to mapped to an Java object field in the Entity, since in most cases you don’t want to share the full entity field in the DTO.

E.g. UUID projectId in the DTO, Project project in the TimeRegistration Entity class.

To generalize this mapping of ids to entities, it was assumed to centralize this logic in a EntityResolverService could be a solution to reduce boilerplace in our mapping classes.

EntityResolverService.class:

package com.sensey.mdf.common.service;

import com.sensey.mdf.relation.entity.client.Client;
import com.sensey.mdf.relation.entity.employee.Employee;
import com.sensey.mdf.relation.entity.subcontractor.Subcontractor;
import com.sensey.mdf.relation.entity.team.Team;
import com.sensey.mdf.common.exception.common.EntityNotFoundException;
import com.sensey.mdf.common.exception.common.ExceptionError;
import com.sensey.mdf.project.entity.Dp;
import com.sensey.mdf.project.entity.Pop;
import com.sensey.mdf.project.entity.Project;
import com.sensey.mdf.relation.repository.client.ClientRepository;
import com.sensey.mdf.relation.repository.employee.EmployeeRepository;
import com.sensey.mdf.relation.repository.subcontractor.SubcontractorRepository;
import com.sensey.mdf.relation.repository.team.TeamRepository;
import com.sensey.mdf.project.repository.DpRepository;
import com.sensey.mdf.project.repository.PopRepository;
import com.sensey.mdf.project.repository.ProjectRepository;
import lombok.RequiredArgsConstructor;
import org.mapstruct.Named;
import org.springframework.stereotype.Service;

import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;

/**
 * This class needs to be split up for best practice. 
 * Currently, doing this through one class to make speed. 
 * - Merthan Tukus. 
 */
@Service
@RequiredArgsConstructor
public class EntityResolverService {

    private final EmployeeRepository employeeRepository;
    private final SubcontractorRepository subcontractorRepository;
    private final TeamRepository teamRepository;
    private final DpRepository dpRepository;
    private final PopRepository popRepository;
    private final ProjectRepository projectRepository;
    private final ClientRepository clientRepository;

    @Named("employeeFromId")
    public Employee employeeFromId(UUID id) {
        return id == null ? null : employeeRepository.findById(id)
            .orElseThrow(() -> new EntityNotFoundException(
                ExceptionError.EMPLOYEE_NOT_FOUND_ERROR,
                Map.of("id", id)
            ));
    }

    @Named("employeesFromIds")
    public Set<Employee> employeesFromIds(Set<UUID> ids) {
        if (ids == null || ids.isEmpty()) {
            return Set.of();
        }
        return employeeRepository.findAllById(ids).stream().collect(Collectors.toSet());
    }

    @Named("employeeIdsFromEntities")
    public Set<UUID> employeeIdsFromEntities(Set<Employee> employees) {
        return employees == null ? null : employees.stream().map(Employee::getId).collect(Collectors.toSet());
    }

    @Named("subcontractorFromId")
    public Subcontractor subcontractorFromId(UUID id) {
        return id == null ? null : subcontractorRepository.findById(id)
            .orElseThrow(() -> new EntityNotFoundException(
                ExceptionError.SUBCONTRACTOR_NOT_FOUND_ERROR,
                Map.of("id", id)
            ));
    }

    @Named("teamFromId")
    public Team teamFromId(UUID id) {
        return id == null ? null : teamRepository.findById(id)
            .orElseThrow(() -> new EntityNotFoundException(
                ExceptionError.TEAM_NOT_FOUND_ERROR,
                Map.of("id", id)
            ));
    }

    @Named("teamsFromIds")
    public Set<Team> teamsFromIds(Set<UUID> ids) {
        if (ids == null || ids.isEmpty()) {
            return Set.of();
        }
        return teamRepository.findAllById(ids).stream().collect(Collectors.toSet());
    }

    @Named("teamIdsFromEntities")
    public Set<UUID> teamIdsFromEntities(Set<Team> teams) {
        return teams == null ? null : teams.stream().map(Team::getId).collect(Collectors.toSet());
    }

    @Named("dpFromId")
    public Dp dpFromId(UUID id) {
        return id == null ? null : dpRepository.findById(id)
            .orElseThrow(() -> new EntityNotFoundException(
                ExceptionError.DP_NOT_FOUND_ERROR,
                Map.of("id", id)
            ));
    }

    @Named("popFromId")
    public Pop popFromId(UUID id) {
        return id == null ? null : popRepository.findById(id)
            .orElseThrow(() -> new EntityNotFoundException(
                ExceptionError.POP_NOT_FOUND_ERROR,
                Map.of("id", id)
            ));
    }

    @Named("projectFromId")
    public Project projectFromId(UUID id) {
        return id == null ? null : projectRepository.findById(id)
            .orElseThrow(() -> new EntityNotFoundException(
                ExceptionError.PROJECT_NOT_FOUND_ERROR,
                Map.of("id", id)
            ));
    }

    @Named("projectsFromIds")
    public Set<Project> projectsFromIds(Set<UUID> ids) {
        if (ids == null || ids.isEmpty()) {
            return Set.of();
        }
        return projectRepository.findAllById(ids).stream().collect(Collectors.toSet());
    }

    @Named("projectIdsFromEntities")
    public Set<UUID> projectIdsFromEntities(Set<Project> projects) {
        return projects == null ? null : projects.stream().map(Project::getId).collect(Collectors.toSet());
    }

    @Named("clientFromId")
    public Client clientFromId(UUID id) {
        return id == null ? null : clientRepository.findById(id)
            .orElseThrow(() -> new EntityNotFoundException(
                ExceptionError.CLIENT_NOT_FOUND_ERROR,
                Map.of("id", id)
            ));
    }

    @Named("clientsFromIds")
    public Set<Client> clientsFromIds(Set<UUID> ids) {
        if (ids == null || ids.isEmpty()) {
            return Set.of();
        }
        return clientRepository.findAllById(ids).stream().collect(Collectors.toSet());
    }

    @Named("clientIdsFromEntities")
    public Set<UUID> clientIdsFromEntities(Set<Client> clients) {
        return clients == null ? null : clients.stream().map(Client::getId).collect(Collectors.toSet());
    }

}

Code Review

Pros

Functionality Is Clear – The class is designed to resolve various entities from their IDs, making use of Spring Data JPA repositories.

Consistent Exception Handling – It properly throws EntityNotFoundException with specific error messages when an entity isn't found.

Use of @Named for MapStruct – The class provides methods that can be used as mappers in MapStruct.

Lombok for Boilerplate Reduction@RequiredArgsConstructor ensures dependency injection without explicit constructors.

Issues & Areas for Improvement

Single Responsibility Violation – This class is responsible for resolving multiple types of entities (Employees, Projects, Clients, etc.), making it hard to maintain.

Code Duplication – The pattern of fetching an entity from a repository and throwing an exception is repeated multiple times.

Lack of Generic Abstraction – Almost all methods follow the same CRUD pattern, which could be generalized with a generic resolver.

Poor Scalability – Adding a new entity requires modifying this bloated class, making the system rigid and non-extensible.


Refactored Solution