jasync icon indicating copy to clipboard operation
jasync copied to clipboard

[Opinion solicitation] How should the api converting from third part object to Promise look like?

Open vipcxj opened this issue 4 years ago • 3 comments
trafficstars

The implementation module which wrap the third part library such as Reactor or JxJava should be completely isolated from the core module. In other words, the implementation module should depend on core module, but the core module should not depend on the implementation module. This cause a problem, I don't know how should the api converting from third part object to Promise look like.

  • a: Use static method <T> Promise<T> JAsync.from(Object) in the core module. Obviously this is ugly and error-prone. But the fact that apis are aggregated together is an advantage.
  • b: Use static method <T> Promise<T> Promises.from(XXX<T> from) in the implementation module, where XXX may be Mono or Single or Future. This API makes perfect use of generics and looks more reliable but it doesn't make for a unified API.

vipcxj avatar Sep 15 '21 01:09 vipcxj

Every implementation module could expose "java service loader" services (https://docs.oracle.com/javase/9/docs/api/java/util/ServiceLoader.html). In the JAsync.from method you fetch all implementations of the converter interface and check if any of them supports the type of the passed object. This allows you to have a generic JAsync class in the core and n implementations in the modules. The core does not need to know about them. The incoming object's type is not that important here anyway. If someone passes anything unsupported, you just throw an exception.

mojo2012 avatar Sep 17 '21 19:09 mojo2012

@mojo2012 But an object argument is ugly and error-prone. And the error will be found until runtime.

vipcxj avatar Sep 18 '21 04:09 vipcxj

I’m just coming across this project out of curiosity, as I’m discovering WebFlux and I saw your comment on electronicarts/ea-async#54.

Just to add my 2 cents:

  1. Since this is a Maven project, you could have an actual dependency on those APIs but mark them as optional. This will allow you to declare type-safe methods in the core and properly compile it without forcing them on your users.
  2. I’d also suggest providing just a static <T> T await(Mono<T>) method like EA Async does, without the need for any annotations in the code.

This would considerably reduce the impact on the code, only needing to import that static method. Consider the first EA Async example from their homepage:

    public CompletableFuture<Boolean> buyItem(String itemTypeId, int cost)
    {
        if(!await(bank.decrement(cost))) {
            return completedFuture(false);
        }
        await(inventory.giveItem(itemTypeId));
        return completedFuture(true);
    }

vs your example:

    @Async()
    private JPromise<Double> _getEmployeeTotalSalaryByDepartment(String department) {
        double money = 0.0;
        Mono<List<Employee>> empsMono = employeeRepository.findEmployeeByDepartment(department);
        JPromise<List<Employee>> empsPromise = Promises.from(empsMono);
        for (Employee employee : empsPromise.await()) {
            Salary salary = Promises.from(salaryRepository.findSalaryByEmployee(employee.id)).await();
            money += salary.total;
        }
        return JAsync.just(money);
    }

with a static await() like EA Async, it could be rewritten simply as:

    private Mono<Double> _getEmployeeTotalSalaryByDepartment(String department) {
        double money = 0.0;
        List<Employee> emps = await(employeeRepository.findEmployeeByDepartment(department));
        for (Employee employee : emps ) {
            Salary salary = await(salaryRepository.findSalaryByEmployee(employee.id)));
            money += salary.total;
        }
        return Mono.just(money);
    }

which is much more readable and does not clutter the code with foreign types.

DidierLoiseau avatar Jul 27 '22 18:07 DidierLoiseau