分模块/分层
现代软件往往是复杂而多变的,对于一个大型系统,一个最为显而易见且直接的办法是对这个大型系统进行拆分,一种最常见的拆分方式是:
- 由高到低按照逻辑层次进行拆分
- 同一个逻辑层次按照功能模块进行模块拆分 这种拆分会极大的降低系统的复杂度,也利于这个系统开发工作的分工。
分层有很多好处,以 TCP/IP 为例:
- 分层有利标准化工作,TCP/IP 就是关于他们各个层次如何工作的标准
- 层次依赖降低,某层的实现可以替换而不影响其他层的工作
- 降低整体的复杂度,TCP/IP 各个层次对行为、标准变得简单
Go 项目的分层
分层的方式多种多样,《企业应用架构模式》中主要采用这样的分层结构:
- 表现层:提供服务,处理、验证 Http 请求等
- 领域层:逻辑核心
- 数据源层:与数据库、消息队列以及其他软件通信
- 类似但是稍有不同的还有 其他划分办法 (Marinescu/Brown 等5层结构,Nilson 7层结构等),但是如同 tcp/ip 的7层/5层划分,尽管多层次让系统变得更为清晰,但是同时也带来了更多复杂度。
依赖注入与工厂模式
依赖对象新建一般来说常见有几种方法
- 直接新建:A 依赖 B,直接 new B
- 工厂:通过 B 工厂或者一个 通用工厂生产 B
- 依赖注入:自动新建注入 A 依赖的 B
依赖注入的研发模式在 JAVA 工程中应用非常广泛,而在 Go 项目中尚未普及,比如facebook inject、uber dig、google wire,这些框架使用起来并没有特别方便,这是和 golang 的动态 能力不够有关系。大部分的依赖注入框架使用 tag 标记或者代码生成的方式进行处理,往往并不像 JAVA 中那么 自动化。
参考依赖注入的实现方式,在 Golang 中实现一套手动注入的抽象工厂,把依赖对象的创建逻辑集中到一处,比如下面的这个例子,本质也是一套抽象工厂:
var st store.Interface
var storeOnce sync.Once
func ProvideStore() storeInterface {
storeOnce.Do(func() {
st = NewStore(config.DbUri)
})
return st
}
func ProvideRemoteAClient() remoteAInterface {
return NewRemoteAClient(config.remoteAUri)
}
func ProvideServiceA() serviceAInterface {
return NewServiceA(ProvideStore())
}
func ProvideServiceB() serviceBInterface {
return NewServiceB(ProvideStore(), ProvideRemoteAClient())
}