95 - 实战之通用的接口幂等框架(实现篇)

2021-11-03  本文已影响0人  舍是境界

前面我们讲解了幂等框架的设计思路。在正常情况下,幂等框架的处理流程是比较简单的。调用方生成幂等号,传递给实现方,实现方记录幂等号或者用幂等号判重。但是,幂等框架要处理的异常情况很多,这也是设计的复杂之处和难点之处。比如,代码运行异常、业务系统宕机、幂等框架异常。

虽然幂等框架要处理的异常很多,但考虑到开发成本以及简单易用性,我们对某些异常的处理在工程上做了妥协,交由业务系统或者人工介入处理。这样就大大简化了幂等框架开发的复杂度和难度。

本文,我们针对幂等框架的设计思路,讲解如何编码实现。跟限流框架的讲解相同,对于幂等框架,我们也会还原它的整个开发过程,从 V1 版本需求、最小原型代码讲起,然后讲解如何 review 代码发现问题、重构代码解决问题,最终得到一份易读、易扩展、易维护、灵活、可测试的高质量代码实现

V1 版本功能需求

  1. 我们先来看,如何生成幂等号。
  1. 我们再来看,如何实现幂等号的存储、查询和删除。

最小原型代码实现

public class Idempotence {
  private JedisCluster jedisCluster;
  public Idempotence(String redisClusterAddress, GenericObjectPoolConfig config) {
    String[] addressArray= redisClusterAddress.split(";");
    Set<HostAndPort> redisNodes = new HashSet<>();
    for (String address : addressArray) {
      String[] hostAndPort = address.split(":");
      redisNodes.add(new HostAndPort(hostAndPort[0], Integer.valueOf(hostAndPort[1])));
    }
    this.jedisCluster = new JedisCluster(redisNodes, config);
  }
  
  public String genId() {
    return UUID.randomUUID().toString();
  }
  public boolean saveIfAbsent(String idempotenceId) {
    Long success = jedisCluster.setnx(idempotenceId, "1");
    return success == 1;
  }
  public void delete(String idempotenceId) {
    jedisCluster.del(idempotenceId);
  }
}

Review 最小原型代码

public class Idempotence {
  // comment-1: 如果要替换存储方式,是不是很麻烦呢?
  private JedisCluster jedisCluster;
  // comment-2: 如果幂等框架要跟业务系统复用jedisCluster连接呢?
  // comment-3: 是不是应该注释说明一下redisClusterAddress的格式,以及config是否可以传递进null呢?
  public Idempotence(String redisClusterAddress, GenericObjectPoolConfig config) {
    // comment-4: 这段逻辑放到构造函数里,不容易写单元测试呢
    String[] addressArray= redisClusterAddress.split(";");
    Set<HostAndPort> redisNodes = new HashSet<>();
    for (String address : addressArray) {
      String[] hostAndPort = address.split(":");
      redisNodes.add(new HostAndPort(hostAndPort[0], Integer.valueOf(hostAndPort[1])));
    }
    this.jedisCluster = new JedisCluster(redisNodes, config);
  }
  
  // comment-5: generateId()是不是比缩写要好点?
  // comment-6: 根据接口隔离原则,这个函数跟其他函数的使用场景完全不同,这个函数主要用在调用方,其他函数用在实现方,是不是应该分别放到两个类中?
  public String genId() {
    return UUID.randomUUID().toString();
  }
  // comment-7: 返回值的意义是不是应该注释说明一下?
  public boolean saveIfAbsent(String idempotenceId) {
    Long success = jedisCluster.setnx(idempotenceId, "1");
    return success == 1;
  }
  public void delete(String idempotenceId) {
    jedisCluster.del(idempotenceId);
  }
}

重构最小原型代码

// 代码目录结构
com.xzg.cd.idempotence
 --Idempotence
 --IdempotenceIdGenerator(幂等号生成类)
 --IdempotenceStorage(接口:用来读写幂等号)
 --RedisClusterIdempotenceStorage(IdempotenceStorage的实现类)
// 每个类的代码实现
public class Idempotence {
  private IdempotenceStorage storage;
  public Idempotence(IdempotenceStorage storage) {
    this.storage = storage;
  }
  public boolean saveIfAbsent(String idempotenceId) {
    return storage.saveIfAbsent(idempotenceId);
  }
  public void delete(String idempotenceId) {
    storage.delete(idempotenceId);
  }
}
public class IdempotenceIdGenerator {
  public String generateId() {
    return UUID.randomUUID().toString();
  }
}
public interface IdempotenceStorage {
  boolean saveIfAbsent(String idempotenceId);
  void delete(String idempotenceId);
}
public class RedisClusterIdempotenceStorage implements IdempotenceStorage {
  private JedisCluster jedisCluster;
  /**
   * Constructor
   * @param redisClusterAddress the format is 128.91.12.1:3455;128.91.12.2:3452;289.13.2.12:8978
   * @param config should not be null
   */
  public RedisIdempotenceStorage(String redisClusterAddress, GenericObjectPoolConfig config) {
    Set<HostAndPort> redisNodes = parseHostAndPorts(redisClusterAddress);
    this.jedisCluster = new JedisCluster(redisNodes, config);
  }
  public RedisIdempotenceStorage(JedisCluster jedisCluster) {
    this.jedisCluster = jedisCluster;
  }
  /**
   * Save {@idempotenceId} into storage if it does not exist.
   * @param idempotenceId the idempotence ID
   * @return true if the {@idempotenceId} is saved, otherwise return false
   */
  public boolean saveIfAbsent(String idempotenceId) {
    Long success = jedisCluster.setnx(idempotenceId, "1");
    return success == 1;
  }
  public void delete(String idempotenceId) {
    jedisCluster.del(idempotenceId);
  }
  @VisibleForTesting
  protected Set<HostAndPort> parseHostAndPorts(String redisClusterAddress) {
    String[] addressArray= redisClusterAddress.split(";");
    Set<HostAndPort> redisNodes = new HashSet<>();
    for (String address : addressArray) {
      String[] hostAndPort = address.split(":");
      redisNodes.add(new HostAndPort(hostAndPort[0], Integer.valueOf(hostAndPort[1])));
    }
    return redisNodes;
  }
}

小结

上一篇 下一篇

猜你喜欢

热点阅读