【问题标题】:WebClient: Waiting for blocking call in API to perform a new callWebClient:等待 API 中的阻塞调用以执行新调用
【发布时间】:2022-02-01 11:56:52
【问题描述】:

我目前正在制作一个微服务来为我们的自动化测试环境创建测试用户。数据库可以通过另一个 API 访问,所以为了创建一个测试用户,我需要调用这个 API。

应该创建测试用户,然后在执行测试时将其释放。测试用户的识别码是SSN(national identifier digit),对每个公民都是唯一的。我的 API/微服务使用生成的 SSN 生成一个新用户,并应通过 API 将其发布到数据库,然后发送到控制数据库的后端服务。此后端服务不是响应式的。

问题是在数据库中,已经有很多现有的用户被其他手动执行的测试使用。现有的测试用户不能被篡改,所以我需要验证生成的 SSN 是否已经存在于数据库中。

我的做法如下:

generate a new ssn
while(ssn exists in db){
    generate new ssn
}
post generated user to db

但是,当在检查用户是否存在时放置一个 .block() (不好的做法,我知道...),程序会在死锁中停止并且没有任何反应。

我的控制器:

@ResponseBody
@PostMapping("normal")
public Mono<User> createNormalUser() throws Exception {
    return userService.createNormalUser();
}

@ResponseBody
@GetMapping("{ssn}")
public Mono<User> getUserBySSN(@PathVariable String ssn){
    return userService.getUserBySsn(ssn);
}

我的服务:

public Mono<User> createNormalUser(){
   String ssn = generateNewSsnNotInDB();

   Mono<UserResource> newUserMono = Mono.just(
           UserResource.builder()
                   .ssn(ssn)
                   .email(ssn + "-test@somedomain.com")
                   .state("NORMAL")
                   .preferred2FaMethod("some2FAMethod")
                   .build()
   );
   return postUser(newUserMono)
           .then(updatePassword(ssn))
           .then(setState(ssn, "NORMAL"));
}

private String generateNewSsnNotInDB() {
   String ssn;
   boolean userExists = false;

   do {
       ssn = ssnGenerator.generateOneValidSsnOnDate(ssnGenerator.generateRandomSsnDate());
       userExists = checkIfUserExists(ssn);
   } while (userExists);
   return ssn;
}

private boolean checkIfUserExists(String ssn) {
   User user;
   try {
       user = getUserBySsn(ssn).share().block();
       return true;
   } catch (WebClientResponseException.NotFound exception) {
       return false;
   }
}

public Mono<User> getUserBySsn(String ssn) {
   return webClient.get()
           .uri(userBySsnURI(ssn))
           .retrieve()
           .bodyToMono(User.class);
}

public Mono<User> postUser(Mono<UserResource> userMono) {
   return webClient.post()
           .uri(setUserURI())
           .body(userMono, UserResource.class)
           .retrieve()
           .bodyToMono(User.class);
}

public Mono<User> postUser(User user) {
   user.setPid(generateNewSsnNotInDB());
   UserResource res = UserResource.builder()
           .ssn(user.getPid())
           .email(user.getEmail())
           .phoneNumber(user.getPhoneNumber())
           .state(user.getState())
           .preferred2FaMethod(user.getPreferred2FaMethod())
           .password(user.getPassword())
           .build();
   log.info("Resource generated in post-user is: " + res.toString());
   return postUser(Mono.just(res));
}

public Mono<User> updatePassword(String ssn) {
   Mono<User> user = Mono.just(User.builder()
           .pid(ssn)
           .password("password01")
           .build());
   return webClient.patch()
           .uri(setUpdatePasswordURI())
           .body(user, User.class)
           .retrieve()
           .bodyToMono(User.class);
}

private Mono<User> setState(String ssn, String state) {
   return webClient.put()
           .uri(updateStateURI(ssn, state))
           .retrieve()
           .bodyToMono(User.class);
}

我在 createNormalUser 函数中链接了调用,因为后端需要此序列才能为用户设置所需的属性。我不确定为什么这是必需的顺序,并且更改它不是我的范围的一部分。

我还省略了一些可能与这个问题无关的函数。

有人可以在正确的方向上帮助我如何使用 checkIfUsersExist 执行调用然后发布用户吗?一个星期以来,我一直在努力解决这个问题。

最奇怪的是,如果我首先使用有效的 ssn 调用 getUser,那么 postUser 就可以正常工作。如果我尝试在没有先调用 getUser 的情况下调用 postUser,它会在 .block() 上死锁。

【问题讨论】:

  • 为什么使用block()?只需将调用添加到您的反应链调用中。 Reactive 并不意味着你的逻辑不会被按顺序处理,它只是意味着没有线程会阻塞等待一些长时间运行的操作。
  • 我的问题是,如果测试用户当前是否存在于数据库中,我需要过滤响应。如果没有块(),我不确定如何做到这一点。你能指出我正确的方向吗?

标签: spring webclient


【解决方案1】:

避免block() 调用和用户链接调用,如下所示(createNormalUser()generateNewSsnNotInDB() 已更新,checkIfUserExists() 已删除):

public Mono<User> createNormalUser(){
    Mono<UserResource> newUserMono = generateNewSsnNotInDB().map( ssn ->
           UserResource.builder()
                   .ssn(ssn)
                   .email(ssn + "-test@somedomain.com")
                   .state("NORMAL")
                   .preferred2FaMethod("some2FAMethod")
                   .build()
   );
   return postUser(newUserMono)
           .then(updatePassword(ssn))
           .then(setState(ssn, "NORMAL"));
}

private Mono<String> generateNewSsnNotInDB() {
   return Mono.just(ssnGenerator.generateOneValidSsnOnDate(ssnGenerator.generateRandomSsnDate()))
      .flatMap(ssn -> getUserBySsn(ssn))
      .switchIfEmpty(Mono.defer(() -> generateNewSsnNotInDB()));
}

public Mono<User> getUserBySsn(String ssn) {
   return webClient.get()
           .uri(userBySsnURI(ssn))
           .retrieve()
           .bodyToMono(User.class);
}

public Mono<User> postUser(Mono<UserResource> userMono) {
   return webClient.post()
           .uri(setUserURI())
           .body(userMono, UserResource.class)
           .retrieve()
           .bodyToMono(User.class);
}

public Mono<User> postUser(User user) {
   user.setPid(generateNewSsnNotInDB());
   UserResource res = UserResource.builder()
           .ssn(user.getPid())
           .email(user.getEmail())
           .phoneNumber(user.getPhoneNumber())
           .state(user.getState())
           .preferred2FaMethod(user.getPreferred2FaMethod())
           .password(user.getPassword())
           .build();
   log.info("Resource generated in post-user is: " + res.toString());
   return postUser(Mono.just(res));
}

public Mono<User> updatePassword(String ssn) {
   Mono<User> user = Mono.just(User.builder()
           .pid(ssn)
           .password("password01")
           .build());
   return webClient.patch()
           .uri(setUpdatePasswordURI())
           .body(user, User.class)
           .retrieve()
           .bodyToMono(User.class);
}

private Mono<User> setState(String ssn, String state) {
   return webClient.put()
           .uri(updateStateURI(ssn, state))
           .retrieve()
           .bodyToMono(User.class);
}

【讨论】:

  • 感谢您的反馈。我在这里看到一个逻辑缺陷:我需要一个数据库中不存在的测试用户,而这似乎相反?每当我使用从我正在调用的 API 中给出 404 的 ssn 时,这个 ssn 就是我想用来生成新用户的那个。这种颠倒的逻辑是我在反应式世界中无法理解的。
  • 我已经更新了我的答案。请检查。我确实误解了你的要求。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-09-26
  • 2019-05-08
  • 2019-07-18
  • 1970-01-01
  • 2011-05-13
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多