I have a BaseEntity class in my Spring Boot application that includes common fields for all entities:
@Data
@MappedSuperclass
public class BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String creatorId;
private ZonedDateTime createTime;
private String updaterId;
private ZonedDateTime updateTime;
private boolean deleted;
}
Each entity in the application inherits from BaseEntity. When displaying the create-update information for any entity, I need to map the creatorId and updaterId to full names from the User table.
To achieve this, I created an API for each entity that retrieves the create-update info using a repository query specific to that entity. However, this leads to duplicated logic across multiple APIs and service methods.
To avoid duplication, I tried to create a single API, getCreateUpdateInfo()
, that accepts an entity ID and a type (to identify the entity). In a shared service, I used a switch statement to call the appropriate repository query based on the type.
Example of the shared service logic:
public CreateUpdateInfoDto getCreateUpdateInfo(IdTypeEnumDto dto) {
return switch (dto.getTypeEnum()) {
case USER -> userRepository.getCreateUpdateInfo(dto.getId());
case ORDER -> orderRepository.getCreateUpdateInfo(dto.getId());
case PROJECT -> projectRepository.getCreateUpdateInfo(dto.getId());
default -> throw new IllegalArgumentException("Unhandled entity type: " + dto.getTypeEnum());
};
}
While this reduces redundancy, I am not entirely satisfied with the switch
-based design...
Is it better to keep separate endpoints for retrieving create-update metadata for each entity, or is there a more efficient way to implement a shared logic without duplication?
To implement a strategy pattern where each entity type has its own handler for retrieving create-update metadata, strategy pattern can be considered. It allows you to delegate the responsibility of fetching the data to specific strategy implementations.
Here is the solution way shown below
1 ) Define an Interface for Handlers
public interface CreateUpdateInfoHandler {
boolean supports(IdTypeEnum type);
CreateUpdateInfoDto getCreateUpdateInfo(Long id);
}
2 ) Implement Handlers for Each Entity
@Service
public class UserCreateUpdateInfoHandler implements CreateUpdateInfoHandler {
private final UserRepository userRepository;
public UserCreateUpdateInfoHandler(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public boolean supports(IdTypeEnum type) {
return type == IdTypeEnum.USER;
}
@Override
public CreateUpdateInfoDto getCreateUpdateInfo(Long id) {
return userRepository.getCreateUpdateInfo(id);
}
}
@Service
public class OrderCreateUpdateInfoHandler implements CreateUpdateInfoHandler {
private final OrderRepository orderRepository;
public OrderCreateUpdateInfoHandler(OrderRepository orderRepository) {
this.orderRepository = orderRepository;
}
@Override
public boolean supports(IdTypeEnum type) {
return type == IdTypeEnum.ORDER;
}
@Override
public CreateUpdateInfoDto getCreateUpdateInfo(Long id) {
return orderRepository.getCreateUpdateInfo(id);
}
}
3 ) Register and Use Handlers in the Shared Service
@Service
public class CreateUpdateInfoService {
private final List<CreateUpdateInfoHandler> handlers;
public CreateUpdateInfoService(List<CreateUpdateInfoHandler> handlers) {
this.handlers = handlers;
}
public CreateUpdateInfoDto getCreateUpdateInfo(IdTypeEnumDto dto) {
return handlers.stream()
.filter(handler -> handler.supports(dto.getTypeEnum()))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("Unhandled entity type: " + dto.getTypeEnum()))
.getCreateUpdateInfo(dto.getId());
}
}
4 ) Endpoints in a controller to fetch create-update info for any entity
@RestController
@RequestMapping("/api/v1")
public class MetadataController {
private final CreateUpdateInfoService createUpdateInfoService;
public MetadataController(CreateUpdateInfoService createUpdateInfoService) {
this.createUpdateInfoService = createUpdateInfoService;
}
@GetMapping("/create-update-info")
public ResponseEntity<CreateUpdateInfoDto> getCreateUpdateInfo(@RequestParam Long id, @RequestParam IdTypeEnum type) {
IdTypeEnumDto dto = new IdTypeEnumDto(id, type);
return ResponseEntity.ok(createUpdateInfoService.getCreateUpdateInfo(dto));
}
}
Here is the sample request shown below
/api/v1/create-update-info?id=123&type=USER