Java 8中的规范设计模式


关于规范模式specification Pattern,在我以前的一个工作岗位上,我已经利用这种模式成功地设计并实现了电信领域的实时授权的解决方案,现在又一次出现了类似的问题我的团队要解决这个问题,然后我注意到我的大多数同事都没有听说过这种模式。

背景
从本质上讲,这次主要目标是通过WebSocket通道或REST API向客户(例如银行和合作伙伴)推送近实时通知。一目了然没什么大不了的,但是当我们有相关数量的原始数据流经管道时,事情会变得有趣,根据客户创建的某些标准,在发出通知之前,应首先匹配这些标准规则。
为了实现这一目标,Java,Kafka,Cloud Foundry,Istio / Envoy等技术齐上阵,以及最重要的是,软件设计需要协调一致,以便所有的零碎可以很好地融合在一起,提供可扩展,安全,可扩展和弹性解决方案,我们的客户无疑可以依赖。
遵循Eric Evans和Martin Fowler的规范文件,规范模式是一种软件设计模式,可用于封装定义所需对象状态的业务规则。这是一种非常强大的方法,可以减少耦合并提高可扩展性,以选择与特定条件匹配的对象子集。这些标准可以使用逻辑运算符组合,然后形成Eric和Martin称之为复合规范。
因此受这个简短定义的启发,现在想象以下场景与标准匹配外汇交易规则:

  1. sourceCurrency =“EUR”
  2. sourceCurrency =“EUR” 或 sourceAmount> 500
  3. sourceCurrency =“EUR” AND sourceAmount BETWEEN 500 和 1000
  4. (sourceCurrency =“EUR” AND sourceAmount BETWEEN 500 AND1000)或 sourceCurrency =“USD”

上面我们有四个逐渐复杂的有效场景,客户可以完美地创建,例如,使用方便的QueryBuilderJS插件构建一个漂亮的UI 。话虽如此,下面我将仅概述使用Hibernate存储规范所需的基础,然后,如何将它们转换为Java 8谓词,这样以时尚的方式从内存数据流中过滤对象。

设计Hibernate实体
我将首先抛出类图,显示实体看起来的大图,然后将介绍重要的事项。(图太大见原文)

创建规范实体及其FieldSpecification,应该足以涵盖所有先前描述的场景,而其他场景甚至更复杂。

请注意,规范实体本身具有子字段,这将允许嵌套规范使我们能够实现非常好的标准复杂度。

创建Java谓词
一旦我们有了存储规范的基础,我们就可以构建理解DB模型的组件,然后我们将创建谓词。

通过使用Java函数式编程作为这种技术的灵魂,我们的生活变得更加容易,只需要几个简单的类,我们就可以将动态创建的规范转换为谓词,如下所示:


代码如下:

public final class Predicates {

    public static Predicate<FxTransaction> asPredicate(Set<Specification> specifications) {
        Objects.requireNonNull(specifications);
        return specifications.stream()
                .map(Specification::flattened)
                .map(Predicates::toSpecificationPredicate)
                .reduce(SpecificationPredicate::combineWith)
                .orElseThrow(IllegalStateException::new);
    }

    private static SpecificationPredicate toSpecificationPredicate(Stream<Specification> specificationStream) {
        return specificationStream
                .map(Predicates::toSpecificationPredicate)
                .reduce(SpecificationPredicate::combineWith)
                .orElseThrow(IllegalStateException::new);
    }

    private static SpecificationPredicate toSpecificationPredicate(Specification specification) {
        if (specification.getType().equals(SpecificationType.FIELD)) {
            return new FieldSpecificationPredicate((FieldSpecification) specification, Optional.empty());
        }
        throw new IllegalStateException("SpecificationType[" + specification.getType() + "] not supported");
    }
}

可以在DataMatchingTest 类中找到前面提到场景的其他测试。

结论
初看起来,规格文件可能看起来很复杂,需要花费大量精力来设置所有内容。但是希望你能在这里看到它实际上非常简单,基础设施到位并开始匹配。此外,我希望你能注意到这种技术在内存数据匹配中的价值,以及它如何让你的生活更轻松。最后,我应该说给定规范不经常更改,然后我们应该将谓词存储在某种内存缓存中以减少延迟。

完整的示例代码可以在这里找到:specification-pattern