본문 바로가기
토비의봄

7. AsyncTemplate 의 콜백헬, 중복작업 문제

by 이석준석이 2021. 3. 8.

1. 콜백헬 예시와 해결

 

코드의 근본적인 문제

  • ListenableFuture 를 리턴하고 콜백을 추가하기 때문에 아래와 같은 콜백헬 문제가 생김
  • 에러를 처리하는 부분이 계속 중복이 된다.
@GetMapping("/rest")
public DeferredResult<String> rest(int idx) {
    DeferredResult<String> dr = new DeferredResult<>();
    
    ListenableFuture<ResponseEntity<String>> f1 = rt.getForEntity("http://localhost:8081/service?req={req}", String.class, "hello " + idx);
    f1.addCallback(s -> {
        ListenableFuture<ResponseEntity<String>> f2 = rt.getForEntity("http://localhost:8081/service2?req={req}", String.class, s.getBody());
        f2.addCallback(s2 -> {
            ListenableFuture<String> f3 = myService.work(s2.getBody());
            f3.addCallback(s3 -> {
                dr.setResult(s3);
            }, e3 -> {
                dr.setErrorResult(e3.getMessage());
            });
        }, e2 -> {
            dr.setErrorResult(e2.getMessage());
        });
    },
    e -> {
        dr.setErrorResult(e.getMessage());
    });

    return dr;
}

 

리팩토링 이후

@RestController
public static class MyController {
    @Autowired
    MyService myService;

    AsyncRestTemplate rt = new AsyncRestTemplate(new Netty4ClientHttpRequestFactory(new NioEventLoopGroup(1)));

    @GetMapping("/rest")
    public DeferredResult<String> rest(int idx) {
        DeferredResult<String> dr = new DeferredResult<>();

        Completion
                .from(rt.getForEntity(URL1, String.class, "hello " + idx))
                .andApply(s -> rt.getForEntity(URL2, String.class, s.getBody()))
                .andApply(s -> myService.work(s.getBody()))
                .andError(e -> dr.setErrorResult(e.toString()))
                .andAccept(s -> dr.setResult(s));

        return dr;
    }
}

@NoArgsConstructor
public static class AcceptCompletion<S> extends Completion<S, Void> {
    Consumer<S> con;
    public AcceptCompletion(Consumer<S> con) {
        this.con = con;
    }

    @Override
    public void run(S value) {
        con.accept(value);
    }
}

@NoArgsConstructor
public static class AsyncCompletion<S, T> extends Completion<S, T> {
    Function<S, ListenableFuture<T>> fn;
    public AsyncCompletion(Function<S, ListenableFuture<T>> fn) {
        this.fn = fn;
    }

    @Override
    public void run(S value) {
        ListenableFuture<T> lf = fn.apply(value);
        lf.addCallback(s -> complete(s), e -> error(e));
    }
}

@NoArgsConstructor
public static class ErrorCompletion<T> extends Completion<T, T> {
    Consumer<Throwable> econ;
    public ErrorCompletion(Consumer<Throwable> econ) {
        this.econ = econ;
    }

    @Override
    public void run(T value) {
        if(next != null) next.run(value);
    }

    @Override
    public void error(Throwable e) {
        econ.accept(e);
    }
}

@NoArgsConstructor
public static class Completion<S, T> {
    Completion next;

    // 일종의 팩토리메소드
    public static <S, T> Completion<S, T> from(ListenableFuture<T> lf) {
        Completion<S, T> c = new Completion<>();
        lf.addCallback(s -> c.complete(s), e -> c.error(e));
        return c;
    }

    public <V> Completion<T, V> andApply(Function<T, ListenableFuture<V>> fn) {
        Completion<T, V> c = new AsyncCompletion<>(fn);
        this.next = c;
        return c;
    }

    public void andAccept(Consumer<T> con) {
        Completion<T, Void> c = new AcceptCompletion<>(con);
        this.next = c;
    }

    public Completion<T, T> andError(Consumer<Throwable> econ) {
        Completion<T, T> c = new ErrorCompletion<>(econ);
        this.next = c;
        return c;
    }

    public void complete(T s) {
        if(next != null) next.run(s);
    }

    public void error(Throwable e) {
        if(next != null) next.error(e);
    }

    public void run(S value) {
    }
}

 

 

'토비의봄' 카테고리의 다른 글

9. Webflux  (0) 2021.03.11
8. CompletableFuture  (2) 2021.03.09
6. 비동기 RestTemplate, 비동기 MVC/Servlet  (0) 2021.03.06
5. 자바와 스프링의 비동기 개발기술  (0) 2021.03.05
4.3. Reactive Streams  (0) 2021.03.03