java - 如何从自己的 validator 验证每个流程对象?

标签 java validation oop design-patterns

我有两个进程,对于每个进程,我将获得不同的 Record 对象,我需要验证这些 Record 对象。这意味着我不能使用单个 validator ,因为我必须为两个过程验证不同的字段。

  • 对于 processA,我使用 ValidatorA 类来验证其 Record 对象。
  • 对于 processB,我使用 ValidatorB 类来验证其 Record 对象。

如果它们有效,那么我会继续前进,否则我不会前进。下面是我的 A 和 B 的过程代码。

public class ProcessConsumer implements Runnable {
  private static final Logger logger = Logger.getInstance(ProcessConsumer.class);
  private final String processName;
  private final Validator validator;
  private final RecordProcessor<byte[], byte[]> process;

  public ProcessConsumer(String processName, Validator validator) {
    this.processName = processName;
    this.validator = validator;
    this.process = new RecordProcessor<>();
  }

  @Override
  public void run() {
    try {
      process.subscribe(getTopicsBasedOnProcessName(processName));
      ....

      while (true) {
        ConsumerRecords<byte[], byte[]> crs = process.poll(2000);
        for (ConsumerRecord<byte[], byte[]> cr : crs) {
          // record object will be different for my both the processes.
          Record record = decoder.decode(cr.value());
          Optional<DataHolder> validatedHolder = validator.getDataHolder(processName, record);
          if (!validatedHolder.isPresent()) {
            logger.logError("records dropped. payload= ", record);
            continue;
          }
          // send validatedHolder to processor class
          Processor.getInstance().execute(validatedHolder);
        }
      }
    } catch (Exception ex) {
      logger.logError("error= ", ExceptionUtils.getStackTrace(ex));
    }
  }
}

下面是我的 ValidatorA 类,我在其中验证 record 对象上的几个字段,如果它有效,那么我将返回 DataHolder .

public class ValidatorA extends Validator {
  private static final Logger logger = Logger.getInstance(ValidatorA.class);

  @Override
  public static Optional<DataHolder> getDataHolder(String processName, Record record) {
    Optional<DataHolder> dataHolder = Optional.absent();
    if (isValid(processName, record))
      dataHolder = Optional.of(buildDataHolder(processName, record));
    return dataHolder;
  }

  private DataHolder isValid(String processName, Record record) {
    return isValidClientIdDeviceId(processName, record) && isValidPayId(processName, record)
        && isValidHolder(processName, record)
  }  

  private DataHolder buildDataHolder(String processName, Record record) {
    Map<String, String> holder = (Map<String, String>) DataUtils.extract(record, "holder");
    String deviceId = (String) DataUtils.extract(record, "deviceId");
    Integer payId = (Integer) DataUtils.extract(record, "payId");
    String clientId = (String) DataUtils.extract(record, "clientId");

    // add mandatory fields in the holder map after getting other fields
    holder.put("isClientId", (clientId == null) ? "false" : "true");
    holder.put("isDeviceId", (clientId == null) ? "true" : "false");
    holder.put("abc", (clientId == null) ? deviceId : clientId);

    return new DataHolder.Builder(record).setClientId(clientId).setDeviceId(deviceId)
        .setPayId(String.valueOf(payId)).setHolder(holder).build();
  }

  private boolean isValidHolder(String processName, Record record) {
    Map<String, String> holder = (Map<String, String>) DataUtils.extract(record, "holder");
    if (MapUtils.isEmpty(holder)) {
      logger.logError("invalid holder is coming.");
      return false;
    }
    return true;
  }

  private boolean isValidpayId(String processName, Record record) {
    Integer payId = (Integer) DataUtils.extract(record, "payId");
    if (payId == null) {
      logger.logError("invalid payId is coming.");
      return false;
    }
    return true;
  }

  private boolean isValidClientIdDeviceId(String processName, Record record) {
    String deviceId = (String) DataUtils.extract(record, "deviceId");
    String clientId = (String) DataUtils.extract(record, "clientId");
    if (Strings.isNullOrEmpty(clientId) && Strings.isNullOrEmpty(deviceId)) {
      logger.logError("invalid clientId and deviceId is coming.");
      return false;
    }
    return true;
  }
}

下面是我的 ValidatorB 类,与 record 对象上的 ValidatorA 相比,我在其中验证几个不同的字段以及它是否有效,然后我将返回 DataHolder

public class ValidatorB extends Validator {
  private static final Logger logger = Logger.getInstance(ValidatorB.class);

  @Override
  public static Optional<DataHolder> getDataHolder(String processName, Record record) {
    Optional<DataHolder> dataHolder = Optional.absent();
    if (isValid(processName, record))
      dataHolder = Optional.of(buildDataHolder(processName, record));
    return dataHolder;
  }

  private DataHolder isValid(String processName, Record record) {
    return isValidType(processName, record) && isValidDatumId(processName, record) && isValidItemId(processName, record);
  }  

  private DataHolder buildDataHolder(String processName, Record record) {
    String type = (String) DataUtils.extract(record, "type");
    String datumId = (String) DataUtils.extract(record, "datumId");
    String itemId = (String) DataUtils.extract(record, "itemId");

    return new DataHolder.Builder(record).setType(type).setDatumId(datumId)
        .setItemId(itemId).build();
  }


  private boolean isValidType(String processName, Record record) {
    String type = (String) DataUtils.extract(record, "type");
    if (Strings.isNullOrEmpty(type)) {
      logger.logError("invalid type is coming.");
      return false;
    }
    return true;
  }  

  private boolean isValidDatumId(String processName, Record record) {
    String datumId = (String) DataUtils.extract(record, "datumId");
    if (Strings.isNullOrEmpty(datumId)) {
      logger.logError("invalid datumId is coming.");
      return false;
    }
    return true;
  }   

  private boolean isValidItemId(String processName, Record record) {
    String itemId = (String) DataUtils.extract(record, "itemId");
    if (Strings.isNullOrEmpty(itemId)) {
      logger.logError("invalid itemId is coming.");
      return false;
    }
    return true;
  }
}

下面是我的抽象类:

public abstract class Validator {
  public abstract Optional<DataHolder> getDataHolder(String processName, Record record);
}

问题:

这就是我为我的两个流程调用的方式。如您所见,我在构造函数参数中传递了 processName 及其特定 validator 。

ProcessConsumer processA = new ProcessConsumer("processA", new ValidatorA());
ProcessConsumer processB = new ProcessConsumer("processB", new ValidatorB());
  • 这是一个很好的设计,我的每个流程都通过其验证程序吗?
  • 有什么办法可以避免通过它吗?并根据 processName 在内部找出要使用的 validator ?我已经有了一个包含所有 processName 的枚举。我需要让这个设计具有可扩展性,这样如果我将来添加新流程,它应该是可扩展的。
  • 我的抽象类 Validator 的方式是否正确?它看起来根本没有做任何有用的事情。

我的每个 validator 基本上都在尝试验证 record 对象是否有效。如果它们有效,则它们生成 DataHolder 构建器并返回它,否则返回 Optional.absent();

我看到了这个post他们讨论了使用 Decorator pattern 的地方,但我不确定在这种情况下这对我有什么帮助。

最佳答案

首先,当我看到它们的声明和实现时:

public abstract class Validator {
  public abstract Optional<DataHolder> getDataHolder(String processName, Record record);
}

我不认为“validator ”是最好的术语。你的验证者不仅仅是验证者。您所说的 validator 具有主要功能:为特定过程提取数据。提取需要验证,但它不是主要功能。
而 validator 的主要功能是验证。
所以我认为您可以将它们称为:ProcessDataExtractor。

ProcessConsumer processA = new ProcessConsumer("processA", new ValidatorA());
ProcessConsumer processB = new ProcessConsumer("processB", new ValidatorB());

Is this a good design where for each of my process, pass its validator along with? Is there any way we can avoid passing that? And internally figure out what validators to use basis on the processName? I already have an enum with all my processName. I need to make this design extensible so that if I add new process in future, it should be scalable.

可扩展性是另一回事。
具有可扩展的设计广义上是指一旦在应用程序的生命周期中出现新的“正常”需求,就具有不意味着重要和/或有风险的修改的设计。
如果添加了新的流程消费者,则必须根据需要添加 ProcessDataExtractor。客户端应该知道这个新的流程消费者。
如果客户端代码在编译时实例化其流程消费者和数据提取器,使用枚举和映射来表示流程消费者和数据提取器不会使您的设计不可扩展,因为它需要很少的修改并且它们是隔离的

如果您希望对代码进行更少的修改,您可以通过反射实例化提取器并使用命名约定来检索它们。
例如,将它们始终放在同一个包中,并用相同的前缀命名每个提取器,例如:ProcessDataExtractorXXXXXX 是可变部分。
此解决方案的问题是在编译时:客户不知道 ProcessDataExtractor 是否有必要可用。

如果您希望新进程消费者和提取器的添加是动态的,即在应用程序运行期间,并且客户端也可以在运行期间检索它们,我认为这是另一个主题。

在编译时,设计可能会更好,因为到目前为止 ProcessConsumerProcessDataExtractor 类的客户端可能会错误地使用它们(即使用 Process A with ProcessDataExtractor B).
为避免这种情况,您有多种方法。
但是您已经猜到了这个想法:在专用位置以 protected 方式进行 ProcessConsumerProcessDataExtractor 之间的初始化和映射。

为了实现它,我建议你为 ProcessConsumer 引入一个接口(interface),它只提供来自 Runnable 的功能方法:

public interface IProcessConsumer extends Runnable {

}

从现在开始,想要使用流程的客户应该只使用此接口(interface)来执行他们的任务。
我们不想提供给客户端方法或构造函数来选择它的数据提取器。
为此,具体的 ProcessConsumer 类应该是一个内部私有(private)类,以防止客户端直接实例化它。
他们将不得不使用工厂来满足这一需求。
通过这种方式,客户端能够通过请求流程工厂来创建具有所需数据提取器的特定流程消费者,该工厂负责确保数据提取器和流程之间的一致性,并保证新流程消费者的实例化每次调用(您的流程是有状态的,因此您必须为您启动的每个流程使用者创建一个新的流程使用者)。

这是 ProcessConsumerFactory 类:

import java.util.HashMap;
import java.util.Map;

public class ProcessConsumerFactory {

    public static enum ProcessType {

      A("processA"), B("processB");

      private String name;

      ProcessType(String name) {
        this.name = name;
      }

      public String getName() {
        return name;
      }
    }

    private class ProcessConsumer implements IProcessConsumer {

      private final ProcessType processType;
      private final ProcessDataExtractor extractor;
      private final RecordProcessor<byte[], byte[]> process;

      public ProcessConsumer(ProcessType processType, ProcessDataExtractor extractor) {
        this.processType = processType;
        this.extractor = extractor;
        this.process = new RecordProcessor<>();
      }

      @Override
      public void run() {
         // your implementation...
      }
    }

    private static ProcessConsumerFactory instance = new ProcessConsumerFactory();

    private Map<ProcessType, ProcessDataExtractor> extractorsByProcessName;

    private ProcessConsumerFactory() {
      extractorsByProcessName = new HashMap<>();
      extractorsByProcessName.put(ProcessType.A, new ProcessDataExtractorA());
      extractorsByProcessName.put(ProcessType.B, new ProcessDataExtractorB());
      // add a new element in the map to add a new mapping
    }

    public static ProcessConsumerFactory getInstance() {
      return instance;
    }

    public IProcessConsumer createNewProcessConsumer(ProcessType  processType) {
      ProcessDataExtractor extractor = extractorsByProcessName.get(processType);
      if (extractor == null) {
        throw new IllegalArgumentException("processType " + processType + " not recognized");
      }
      IProcessConsumer processConsumer = new ProcessConsumer(processType, extractor);
      return processConsumer;
    }

}

现在 Process 消费者类的客户端可以像这样实例化它们:

IProcessConsumer processConsumer = ProcessConsumerFactory.getInstance().createNewProcessConsumer(ProcessType.A);

Also the way I have my abstract class Validator is right? It is not doing any useful things at all looks like.

您为 validator 使用了一个抽象类,但目前您还没有在此抽象类中移动常见行为,因此您可以使用一个接口(interface):

public interface ProcessDataExtractor{
  Optional<DataHolder> getDataHolder(ProcessType processType, Record record);
}

如果合适,您可以稍后引入抽象类。

关于java - 如何从自己的 validator 验证每个流程对象?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41306469/

相关文章:

java - 如何在java中检查Long for null

javascript - 在 initComponent 中验证

javascript - 组合功能+禁用

javascript - 更改实时验证的错误消息

C# 友元类和 OOP 组合

java - 当 try block 中的多行抛出异常时,使用 Try-With-Resources 而不是 finally block

java - 限制 JTable 中的列宽,但保留扩展列的能力

java - 在没有 Eclipse 的情况下创建和使用 Java 包

javascript - ES6 类超出最大调用堆栈大小

java - o.s.web.servlet.PageNotFound - 没有映射 GET/WEB-INF/views/welcome.jsp