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 |