java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > java依赖混乱

java依赖混乱存在的问题与解决方案

作者:GuangHui

这篇文章主要为大家介绍了java依赖混乱存在的问题与解决方案,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

呈现的形态

存在的问题与解决方案

缺少防腐层,会让请求对象传导到业务代码中,造成了业务与外部接口的耦合,也就是业务依赖了一个外部通信协议。一般来说,业务的稳定性要比外部接口高,这种反向的依赖就会让业务一直无法稳定下来,继而在日后带来更多的问题。解决方案自然就是引入一个防腐层,将业务和接口隔离开来。

业务代码中出现具体的实现类,实际上是违反了依赖倒置原则。因为违反了依赖倒置原则,业务代码也就不可避免地受到具体实现的影响,也就造成了业务代码的不稳定。识别一段代码是否属于业务,我们不妨问一下,看把它换成其它的东西,是否影响业务。解决这种坏味道就是引入一个模型,将业务与具体的实现隔离开来。

编程规则

依赖倒置原则,即高层模块不应依赖于底层模块,二者应依赖于抽象。抽象不应该依赖细节,细节应该依赖于抽象。

记住一句话

代码应该向着稳定性的方向依赖。

缺少防腐层

@PostMapping("/books")
public NewBookResponse createBook(final NewBookRequest request) {
    boolean result = this.service.createBook(request);
    ...
}

按照通常的架构设计原则,service 层属于我们的核心业务,而 controller层属于接口。二者相较而言,核心业务的重要程度更高一些,所以,它的稳定程度也应该更高一些。同样的业务,我们可以用 REST 的方式对外提供,也可以用 RPC 的方式对外提供。

NewBookRequest穿越了两层,职责不清晰的同时,也存在破坏核心业务层稳定性的风险。为此,我们只要再引入一个模型就可以破解这个问题。

class NewBookParameter {
    ...
}
class NewBookRequest {
    public NewBookParameters toNewBookRequest() {
        ...
    }
}
@PostMapping("/books")
public NewBookResponse createBook(final NewBookRequest request) {
    boolean result = this.service.createBook(request.toNewBookParameter());
    ...
}

通过增加一个模型,我们就破解了依赖关系上的纠结。也许你会说,虽然它们成了两个类,但是,它们两个应该长得一模一样吧。这算不算是一种重复呢?但我的问题是,它们两个为什么要一样呢?有了两层不同的参数,我们就可以给不同层次上的模型以不同的约定了。

比如,对于 controller 层的请求对象,因为它的主要作用是传输,所以,一般来说,我们约定请求对象的字段主要是基本类型。而 service 的参数对象,因为它已经是核心业务的一部分,就需要全部转化为业务对象。举个例子,比如,同样表示价格,在请求对象中,我们可以是一个 double 类型,而在业务参数对象中,它应该是 Price 类型。

业务代码里的具体实现

@Task
public void sendBook() {
    try {
        this.service.sendBook();
    } catch (Throwable t) {
        this.feishuSender.send(new SendFailure(t)));
        throw t;
    }
}

上面的不符合设计原则,它违反了依赖倒置原则。高层模块不应依赖于低层模块,二者应依赖于抽象。抽象不应依赖于细节,细节应依赖于抽象。

上面这段代码,在一段业务处理中出现了一个具体的实现,也就是这里的 feishuSender。你需要知道,业务代码中任何与业务无关的东西都是潜在的坏味道。

在这里,飞书肯定不是业务的一部分,它只是当前选择的一个具体实现。换言之,是否选择飞书,与团队当前的状态是相关的,如果哪一天团队切换即时通信软件,这个实现就需要换掉。

识别一个东西是业务的一部分,还是一个可以替换的实现,我们不妨问问自己,如果不用它,是否还有其它的选择?就像这里,飞书是可以被其它即时通信软件替换的。

另外,常见的中间件,比如,Kafka、Redis、MongoDB 等等,通常也都是一个具体的实现,其它中间件都可以把它替换掉。所以,它们在业务代码里出现,那一定就是一个坏味道了。

既然我们已经知道了,这些具体的东西是一种坏味道,那该怎么解决呢?你可以引入一个模型,也就是这个具体实现所要扮演的角色,通过它,将业务和具体的实现隔离开来。

interface FailureSender {
void send(SendFailure failure);
}
class FeishuFailureSenderS implements FailureSender {
...
}

这里我们通过引入一个 FailureSender,业务层只依赖于这个 FailureSender 的接口就好,而具体的飞书实现可以通过依赖注入的方式注入进去。

依赖关系是软件开发中非常重要的一个东西,然而,很多程序员在写代码的时候,由于开发习惯的原因,常常会忽略掉依赖关系这件事本身。

现在已经有一些工具,可以保证我们在写代码的时候,不会出现严重破坏依赖关系的情况,比如,像前面那种 service 层调用 resource 层的代码。

在 Java 世界里,我们就可以用 ArchUnit 来保证这一切。看名字就不难发现,它是把这种架构层面的检查做成了单元测试,下面就是这样的一个单元测试:

@Test
public void should_follow_arch_rule() {
    JavaClasses clazz = new ClassFileImporter().importPackages("...");
    ArchRule rule = layeredArchitecture()
    .layer("Resource").definedBy("..resource..")
    .layer("Service").definedBy("..service..")
    .whereLayer("Resource").mayNotBeAccessedByAnyLayer()
    .whereLayer("Service").mayOnlyBeAccessedByLayers("Resource");
    rule.check(clazz);
}

在这里,我们定义了两个层,分别是 Resource 层和 Service 层,而且我们要求 Resource 层的代码不能被其它层访问,而 Service 层的代码只能由 Resource 层方法访问。这就是我们的架构规则,一旦代码里有违反这个架构规则的代码,这个测试就会失败,问题也就会暴露出来。

以上就是java依赖混乱存在的问题与解决方案的详细内容,更多关于java依赖混乱的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:
阅读全文