A Little Architecture 【译】

by Robert C. Martin (Uncle Bob)
https://blog.cleancoder.com/uncle-bob/2016/01/04/ALittleArchitecture.html
@04 January 2016

译者注:这是很久前看到的一篇关于架构师的文章,出自 Bob 大叔,这篇文章曾被我分享给了好多人,好多不知什么是架构师的人,我不确定他们有看。但我确定我很认可这其中的道理:什么是架构师要关注的,不是数据库,不是库,也不是服务器,是业务及一些顶级的设计原则。
在当下,顶级的架构变成了云设计师专属,而中低层则是数据库直连的时代,再次把这篇文章翻译出来,给那些有志的开发者,关注哪些方能成就自己,勿做井底之蛙。



我想成为一名软件架构师。

这对于一个年轻的开发者来说是一个很好的目标。

我想要带团队做重要决策,包括数据库,框架和服务器以及其它一切。

恩,这样听起来,你并不是想成为一名架构师。

我当然想成为架构师,要在团队里做重大决策的那种。

但你刚才并没有指哪任何重要决策,你说的都是不想干的事项。

什么意思?数据库难道不是重要决策?你不知道我们花了多少钱在这上面?

可能很多,但,数据库不属于重要决策。

怎么能这么讲?数据库简直就是整个系统的内核了!它是整个数据的归集,排序,索引和访问的地方。没有它就没有整个系统。

数据库只是一个 IO 设施。它是提供了一些功能来做数据的排序、查询及报表,但这都只是系统架构的辅助。

辅助??这种解释太不可理喻了。

没错,就只是辅助。系统的业务规则可能会使用到这样那样的一些工具,但工具本身不是业务所固有的。也就是说,如果需要,你可以替换这些工具,但业务规则不会变。

恩,但我依然需要数据库来记录这些数据。

那是你的问题!

什么意思?

你认为业务规则依赖于你的数据库工具。但不是的,最少好的架构不应该有这样的依赖关系。

搞笑,如果不用这些必须的工具, 那我如何创建业务规则。

我并没有说不用这些工具,我是说不应该依赖于它们。业务规则应该不知道你用的是哪种具体的数据库。

如何让业务规则使用工具但不去了解工具?

你搞反了依赖。你的数据库依赖于业务。你需要明确业务不依赖于数据库。

胡扯。

正相反,我在讲计算机架构语言。这是一种依赖反转原则。低级策略应该依赖于高级策略。

更胡扯!高级策略(我想你是指业务)调用低级策略(这里指数据库)。所以是高级依赖于低级,就像呼叫者依赖于被呼者。路人皆知。

在运行时,这样讲是对的。但在编译时,我们希望的是依赖反转。高层级代码不应该直接标明低层级代码。

得了吧!不可能调用一个方法但又不提及它。

当然可以,这就是面向对象技术在做的事。

面向对象是指创建直接世界的对象。它是把数据和方法放在一起的内聚对象。它是把代码按直观的结构进行的一种组织。

谁给你讲的?

每个人都是这样理解的,绝对正确。

恩恩。使用面象对象原则你可以在不提及一个方法时来调用它。

你知道如何在一个面向对象设计中,对象间如何发送消息吗?

当然。

那你应该知道消息的发送者不知道接收者的类型吧。

这个取决于语言,在 Java 中,发送者要知道接收者的 Base type,在 Ruby 中,也要确定接收者能处理发送者所发送的消息。

是的。但这两种示例中,发送者都不知道具体的接收都类型。

恩,是的。

因此,发送方可以执行接收方的函数,而不需要指明接收方的确切类型。

我知道,但发送方依然依赖于接收方。

在运行时,是的。但在编译时,发送方的代码没有指明,或依赖于接收方的相关代码。事实上,代码层面,接收方依赖于发送方。

什么!发送方依然依赖于发送的 class。

来点代码吧。先看看 sender

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package sender;

public class Sender {
private Receiver receiver;

public Sender(Receiver r) {
receiver = r;
}

public void doSomething() {
receiver.receiveThis();
}

public interface Receiver {
void receiveThis();
}
}

这是 receiver

1
2
3
4
5
6
7
8
9
package receiver;

import sender.Sender;

public class SpecificReceiver implements Sender.Receiver {
public void receiveThis() {
//do something interesting.
}
}

注意了!receiver 依赖于 sender。还要注意  SpecificReceiver 依赖于 Sender。另外还要注意 senderreceiver 一无所知。

你这作弊了。你在 sender 的类里引用了 receiver 的接口。

你开始懂了。

懂什么?

架构原则。当然发送者有它的接口,接收者必须要实现该接口。

这表示我必须使用嵌入对象,然后。。。

嵌套对象只是一种达到目的的方法,这里是其它的。

等下。这些东西怎么使用数据库,回到讨论的起点。

那看更多的代码。首先看个简单的业务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package businessRules;

import entities.Something;

public class BusinessRule {
private BusinessRuleGateway gateway;

public BusinessRule(BusinessRuleGateway gateway) {
this.gateway = gateway;
}

public void execute(String id) {
gateway.startTransaction();
Something thing = gateway.getSomething(id);
thing.makeChanges();
gateway.saveSomething(thing);
gateway.endTransaction();
}
}

That business rule doesn’t do much.
这个业务倒是很简单。

It’s just an example. You’d likely have many classes like this, implementing lots of different business rules.
这只是个示例,你可以多弄点类,实现更多的业务。

OK, so what’s that Gateway thingy?
恩,Gateway 是个啥?

It supplies all the data access methods used by the business rule. It’s implemented as follows:
它提供了业务所需的数据访问方法。它的实现如下:

1
2
3
4
5
6
7
8
9
10
package businessRules;

import entities.Something;

public interface BusinessRuleGateway {
Something getSomething(String id);
void startTransaction();
void saveSomething(Something thing);
void endTransaction();
}

注意哦,这个包名是  businessRules

恩,那 Something 呢?

这里描述了一个简单的业务对象。我把它放到  entities 的包里。

1
2
3
4
5
6
7
package entities;

public class Something {
public void makeChanges() {
//...
}
}

这里是 BusinessRuleGateway 的最终实现。这个类知道真实的数据库。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package database;

import businessRules.BusinessRuleGateway;
import entities.Something;

public class MySqlBusinessRuleGateway implements BusinessRuleGateway {
public Something getSomething(String id) {
// use MySql to get a thing.
}

public void startTransaction() {
// start MySql transaction
}

public void saveSomething(Something thing) {
// save thing in MySql
}

public void endTransaction() {
// end MySql transaction
}
}

再次注意,在运行时业务方法调用数据库,但在编译时,database 包提及并依赖于 businessRules 包。

恩恩,我想我有点懂了。你在用一个多态来对业务隐藏数据库实现。但你依然对业务提供了数据库的接口。

不,不完全是。我们并没有尝试为业务提供所有的数据库。相反,我们只为业务创建了所需要的接口。这些接口的实现方来调用相应的数据库。

恩,但如果所有的业务需要所有的工具,然后你就不得不把所有的工具写在 gateway 的接口里。

看来你还是不大明白。

明白什么?这对我来说已经很清除了。

每个业务都为自己的数据访问定义单独的接口。

等等?

这叫接口分离原则。每个个业务类将只使用数据库数据的一部分。所以每个业务都提供一个接口去访问该部分数据。

这不就意味着要有一堆的接口,并且有一堆的类来调用其它的数据库类。

这才算开窍吗。

但这不乱吗,又浪费时间!为啥这样干啊?

你想又清晰,又省时间。

这简直是为了代码而代码。

相反,相对于那些不相关的决策,这才是最后要的架构。

这啥意思?

还记得你开始时说要成为一名软件架构师?你想做那些真正的重要的决策?

是啊,我是想这样啊。

在这些决策间,你想的是确定使用哪些数据库,哪些服务器,哪些框架。

恩,然后你说这不是重要的决策,这是不相关的。

是的,对架构师而言,重要决策,不包括使用什么样的数据库,服务器和框架。

但你总要先决定这些吧!

不,你不用。事实上,你更希望在开发周期更迟些时再做这些决定,这样你可以有更多信息。
有时,架构师早早的决定上个数据库,最后发现搞个文件写写就够了。
有时,架构师早早的上了个 Web 服务器,最后发现团队想要的只是一个 socker 接口。
有时,架构师早早的选定了一个框架,最后发现框架提供了一堆没用的功能,还增加了一堆有的没的限制。
可幸的是,团队架构师有一种方法论,可以将这些决策推迟到有足够的信息时再来做。
可幸的是,架构师将团队从速度慢、资源匮乏的 IO 设备和框架中隔离出来,使得团队能够创建快速、轻量级的测试环境
祝福那些架构师关心真正重要的事情的团队,并推迟那些不重要的事情。

胡扯。不知所云。

如果你还没有进入管理层,也许十年后你可以的。