Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Convert automatically into a centralized bean for multiple domain objects

I am creating a project which will respond to collect multiple bean object, save it to the database and return the status of the transaction. There can be multiple objects that can be sent from the client. For each object, they are having separate database thus separate controller.

So I planned to create a framework that can accept multiple objects from multiple controllers and send only one centralized object. But I am not sure how to use a centralized object as a return type in the controller(currently I returned them as Object). Below is my code:

Controller:

@RestController
@RequestMapping("/stat/player")
public class PlayerController {

    @Autowired
    private StatService<PlayerValue> statPlayer;

    @RequestMapping("/number/{number}")
    public Object findByNumber(@PathVariable String number) { // Here returning Object seem odd
        return statPlayer.findByNumber(number);
    }
}

Service:

@Service
@Transactional(isolation = Isolation.READ_COMMITTED)
public class PlayerServiceImpl implements StatService<PlayerValue> {

    @Autowired
    private PlayerRepository repository;

    @Override
    public PlayerValue findByNumber(String number) {
        Optional<PlayerEntity> numberValue = repository.findByNumber(number);
        return numberValue.map(PlayerEntity::toValue).orElse(null);
    }
}

In service I returned the PlayerValue object but I want to wrap this object into a centralized bean ResponseValue. I created an aspect for that

@Aspect
@Component
public class Converter {
    private static final Logger LOG = LoggerFactory.getLogger(Converter.class);

    @Pointcut("within(@org.springframework.web.bind.annotation.RestController *)")
    public void restControllerClassMethod() {}

    private <T> ResponseValue<T> convert(List<T> results) {
        String message = results.isEmpty() ? "No result found" : ResponseValueStatus.OK.toString();

        return new ResponseValue<>(ResponseValueStatus.OK, message, results);
    }

    @Around("restControllerClassMethod()")
    @SuppressWarnings("unchecked")
    public <T> ResponseValue<T> convert(ProceedingJoinPoint joinPoint) {
        ResponseValue value;
        try {
            Object findObject = joinPoint.proceed();
            List<Object> objects = toList(findObject);
            value = convert(objects);
        } catch (NullPointerException e) {
            throw new StatException(String.format("Exception thrown from %s from %s method with parameter %s", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName(), joinPoint.getArgs()[0].toString()));
            //this exception will go in a controller advice and create a response value with this message
        } catch (Throwable e) {
            LOG.error("Exception occurred while converting the object", e);
            throw new StatException(String.format("Exception thrown from %s from %s method with parameter %s with exception message %s", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName(), joinPoint.getArgs()[0].toString(), e.getMessage()));
        }
        return value;
    }

    private List<Object> toList(Object findObject) {
        List<Object> objects = new ArrayList<>();
        if (findObject instanceof List) {
            ((List) findObject).forEach(item -> objects.add(findObject));
        } else {
            objects.add(findObject);
        }
        return objects;
    }
}

To sum up, There could be multiple entity similar to PlayerValue. I need a way to return the result in a centralized bean. Above process work, BUT for this I have to give return type as Object in Controller. Does anybody has an idea how can I use return type as List or T in controller. Also I know it can be done by implementing a ValueConverter interface, but this conversion is straightforward. So it would be nice if any other developer don't have to implement the ValueConverter everytime he want to add a different controller.

Also feel free to review the implementation and let me know if anyone has some alternative idea or some comments.

Note: I reduce a lot of code in the question so that it can be easier to understandable without understanding the actual requirement context. Please do let me know if anyone need more info.

like image 285
Deb Avatar asked Jul 17 '18 12:07

Deb


1 Answers

After some research I came across to a better design solution for the framework (but of course with flaws) to achieve conversion to a centralized bean for multiple domain objects is to use a marker interface.

Marker interface can provide a centralized type for all the bean. The main rule need to be followed by the client is to implement that marker interface. So the basic solution is

Marker interface:

public interface StateResponseValue<T> {
}

Implement the interface in all the bean.

public class PlayerValue implements StateResponseValue<PlayerValue> {
}

public class ResponseValue<T> implements StateResponseValue<T> {

    //fields and their getter and setter
}

Change the return type in service and controller.

public interface StatService<T> {
    StateResponseValue<T> findByNumber(String number);
}

Change the return type in controller

@RestController
@RequestMapping("/stat/player")
public class PlayerController {

    @Autowired
    private StatService<PlayerValue> statPlayer;

    @RequestMapping("/number/{number}")
    public StateResponseValue<T> findByNumber(@PathVariable String number) { // Here returning Object seem odd
        return statPlayer.findByNumber(number);
    }
}

Note: The main drawback I feel is that whenever we want to access the field client need to explicitly cast the object to ResponseValue which is still pretty ugly.

like image 120
Deb Avatar answered Oct 23 '22 06:10

Deb