searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

Kratos简介

2023-05-24 08:47:28
175
0

kratos简介

概述

kratos app

kratos app用法和设计理念

Kratos 是一套 Go 语言编写的微服务框架,它提供了一套基础的、易于使用的微服务开发框架和工具集。

Kratos 提供了一套命名为 app 的接口和工具,它设计用来帮助更方便地管理和运行服务。

在 Kratos 中,一个 app 代表一个可以运行的应用。app.App 是一个接口,它定义了一个应用需要实现的基本方法,包括 Name()Run()Stop()等。

app.App 的设计理念是将一个应用的启动和停止过程标准化,并将它们与应用的具体业务逻辑解耦。这使得你可以在不改变应用内部实现的情况下,更改应用的启动和停止方式,或者在多个应用之间复用相同的启动和停止逻辑。

具体来说,app.App 的各个方法的含义如下:

  • Name(): 返回应用的名称。这个名称通常在日志、指标等地方使用,用来标识是哪个应用生成的这些数据。
  • Run(): 运行应用。这个方法会阻塞,直到应用退出或者发生错误。
  • Stop(): 停止应用。这个方法会停止应用的运行,并做一些清理工作,例如关闭数据库连接、停止后台任务等。

通过这种方式,Kratos 提供了一种灵活而又统一的方式来管理应用的生命周期。无论你的应用是一个 HTTP 服务器、一个 gRPC 服务,还是一个消息队列消费者,都可以用相同的方式来启动和停止它们。这让你的代码更加模块化,也更容易测试和维护。

biz/data/service

在 Kratos 中,biz、data、service 是常用的三个概念:

  1. biz(Business Logic):业务逻辑层。

    biz 层主要负责处理业务逻辑,将复杂的业务逻辑封装为简单、可重用的业务接口,并暴露给上层的 service 层调用。biz 层通常包含多个 Use Case,每个 Use Case 对应一个具体的业务场景,可以通过调用 DAO 层来获取数据,处理数据并返回结果。

  2. data(Data Access Object):数据访问层。

    data 层主要负责与数据库交互,提供统一的访问接口和实现,屏蔽数据库的具体操作细节,以便于提高代码的可重用性和可维护性。data 层通常用来提供诸如增删改查等基本操作接口,以及对数据进行验证、格式化、转换等操作,确保数据的有效性和完整性。

  3. service:微服务层。

    service 层是微服务框架的核心部分,负责处理外部请求并调用相应的 biz 接口来处理业务逻辑。在 service 层中,通常会处理一些基本的服务功能,比如授权、身份验证、流量控制等,以保障整个服务的可靠性和安全性。

以上三层概念可以通过垂直拆分和水平拆分等方式进行组合和扩展,以构建出更复杂、更高效的微服务架构。在 Kratos 中,这些概念提供了一种通用的设计模式,旨在提高微服务的可维护性、可扩展性和可重用性,加速微服务的开发和部署流程。

biz

业务逻辑层(biz)是Kratos中最具有业务特性的一层,它主要负责业务逻辑的实现,包括业务规则、流程和算法等。在Kratos中,业务逻辑层通常包含了领域模型、业务逻辑和应用服务等组件。因此,理解biz的use case需要从具体的业务场景出发,考虑业务逻辑层需要实现哪些业务规则、流程和算法等,以及如何通过领域模型、业务逻辑和应用服务等组件来实现这些业务逻辑。具体来说,可以通过以下几个步骤来进一步理解biz的use case:

1. 确定业务场景:首先需要明确具体的业务场景,例如电商平台的订单管理、社交平台的好友关系管理等。

2. 分析业务规则:在确定业务场景后,需要分析业务规则,即业务逻辑层需要实现哪些业务规则,例如订单管理中的订单状态转换规则、好友关系管理中的好友关系验证规则等。

3. 设计领域模型:在分析业务规则后,需要设计领域模型,即业务逻辑层需要处理的业务对象及其属性和行为。例如订单管理中的订单对象、商品对象等。

4. 实现业务逻辑:在设计领域模型后,需要实现业务逻辑,即根据业务规则和领域模型来实现具体的业务逻辑。例如订单管理中的订单状态转换逻辑、商品库存检查逻辑等。

5. 提供应用服务:在实现业务逻辑后,需要提供应用服务,即将业务逻辑封装成可供其他组件调用的服务。例如订单管理中的订单服务、商品服务等。

通过以上步骤,可以进一步理解biz的use case,即业务逻辑层需要实现具体的业务规则、流程和算法等,以及如何通过领域模型、业务逻辑和应用服务等组件来实现这些业务逻辑。


biz/use case

在 Clean Architecture 中,Use Case 是一种特殊的对象,它代表了一个具体的业务操作或者说用户的一个用例。每个 Use Case 都会对应一个业务流程,并且包含了这个业务流程所有的业务逻辑。

在 Kratos 的架构中,biz 层就是用来定义这些 Use Case 的。具体来说,每个 Use Case 通常会被定义为一个 Go 接口,这个接口中的方法就对应了该 Use Case 可以执行的操作。然后,我们会为这个接口提供一个或多个实现,这些实现就是具体的业务逻辑。

以下是一个简单的例子:

package biz

type UserUseCase interface {
	CreateUser(ctx context.Context, name string) (User, error)
	GetUser(ctx context.Context, id int) (User, error)
}

type User struct {
	ID   int
	Name string
}

在这个例子中,我们定义了一个 UserUseCase,它有两个方法:CreateUserGetUser。这就代表了两个业务用例:创建用户和获取用户

通过这种方式,我们可以将业务逻辑从底层的数据存储和网络传输中解耦出来,这使得业务逻辑更容易编写和测试,并且也更加灵活。比如,我们可以根据需要为同一个 Use Case 提供多个实现,或者将一个 Use Case 的实现替换为另一个,而不需要修改其他的代码。


在 Kratos 架构中,biz 层定义的 Use Case 接口应当由 biz 层的具体实现类来实现,而不是在 data 层或 service 层。data 层和 service 层会与 biz 层进行交互,但它们本身不直接包含业务逻辑。

biz 层,会实现 Use Case 接口,这些实现类会依赖于底层的 data 层提供的数据存储和检索服务。实际的业务逻辑会在 biz 层实现类中完成。

以前面的例子为例,可以在 biz 层实现 UserUseCase 接口:


package biz

import "context"

type UserRepository interface {
	Create(ctx context.Context, user User) error
	Get(ctx context.Context, id int) (User, error)
}

type UserUseCaseImpl struct {
	repo UserRepository
}

func NewUserUseCase(repo UserRepository) UserUseCase {
	return &UserUseCaseImpl{repo: repo}
}

func (u *UserUseCaseImpl) CreateUser(ctx context.Context, name string) (User, error) {
	user := User{Name: name}
	err := u.repo.Create(ctx, user)
	return user, err
}

func (u *UserUseCaseImpl) GetUser(ctx context.Context, id int) (User, error) {
	return u.repo.Get(ctx, id)
}

在这个例子中,我们定义了一个 UserRepository 接口,它表示了数据存储和检索的抽象。然后,我们实现了 UserUseCaseImpl,它实现了 UserUseCase 接口,并依赖于 UserRepository 接口来完成数据存储和检索的操作。这样,业务逻辑就完全封装在 biz 层中了。

data 层会实现 UserRepository 接口,而 service 层会调用 biz 层的 UserUseCase 接口来执行业务逻辑。这样,我们就实现了业务逻辑与底层数据存储和网络传输的解耦。


service是否可以直接调data?

在Kratos架构中,推荐的方式是让service层通过biz层来访问data层。这样做的目的是为了保持架构的清晰和解耦,每一层都有明确的职责:

  • service层:负责处理网络请求,转发请求到biz层,并将biz层返回的结果格式化为网络响应。
  • biz层:包含核心的业务逻辑,负责处理业务请求。它会调用data层提供的服务来实现具体的数据操作。
  • data层:负责数据的存储和检索,为biz层提供数据服务。

service层直接访问data层可能会导致架构变得混乱,业务逻辑可能会分散在多个层次中,这使得代码更难理解和维护。此外,如果service层直接依赖于data层,那么当我们需要改变数据存储的方式时,可能需要修改service层的代码,这违反了单一职责原则和开闭原则。

当然,这只是一种推荐的方式,实际上在某些特定的情况下,service层直接访问data层可能是更合适的选择。比如,在某些简单的场景下,例如只是简单的数据获取并没有涉及复杂的业务逻辑,这时候service层直接访问data层可能会更加直接和高效。但是,需要清楚的知道这样做的潜在风险,这可能使得你的应用的业务逻辑分散在service层和biz层之间,这可能使得后续的维护变得更复杂。

另外,在一些需要性能优化的场景,例如批量数据操作,如果经过biz层会产生大量的不必要的数据转换,也可以考虑service层直接访问data层。

总的来说,这是一个权衡的问题,我们要在保持架构清晰和代码复杂性之间进行权衡。在大多数情况下,我们推荐保持架构的清晰,即让service层通过biz层来访问data层,这样可以更好的应对业务的变化和扩展。在有特殊需要的情况下,可以适当的让service层直接访问data层,但是需要清楚的知道这样做的潜在风险。


data变化对biz的影响

如果改变了data数据存储的方式,确实可能需要修改biz层的实现,但这正是biz层的职责之一:抽象和封装底层数据的操作,为service层提供一致的接口。这样,service层就可以避免直接依赖具体的数据存储方式,从而达到松耦合的效果。

当数据存储方式改变时,biz层可能需要修改,但service层并不需要知道这个变化,它依然调用biz层的接口就可以了。这就是所谓的开闭原则:对扩展开放,对修改封闭。在这种架构下,当我们需要添加新的数据存储方式时,我们只需要在biz层添加新的实现,而不需要修改service层的代码。

这种分层的设计可以帮助我们更好地管理代码的复杂性,每一层只需要关注自己的职责,不需要关心其他层的具体实现。这样,当系统需要扩展或者修改时,我们可以更容易地进行修改,而不会影响到整个系统。

在大多数应用程序架构中,包括 Kratos,service 层和 biz 层具有不同的职责:

  1. service 层:这一层主要处理网络协议和格式,比如 HTTP、gRPC、Thrift 等。它的职责是解析请求,调用适当的业务逻辑(在 biz 层),然后将结果格式化为适当的响应。service 层并不应该包含任何业务逻辑,它只是一个“传递者”或“翻译者”。
  2. biz 层:这是应用程序的核心,包含了所有的业务逻辑。biz 层负责实现用例,并调用 data 层以持久化数据。biz 层应该尽可能地独立于网络和数据存储,以便它们可以根据需要更改,而不会影响到业务逻辑。

如果你将数据访问逻辑直接放在 service 层,那么你就将业务逻辑(在 biz 层)和数据访问逻辑混合在了一起。这将使得你的代码更难维护和测试。此外,如果你将来需要更改数据存储方式,你可能需要更改 service 层的代码,这将导致更多的工作,并可能引入错误。

相反,如果你将数据访问逻辑放在 biz 层,那么 service 层就只需要关注网络协议和数据格式化,而不需要关心如何获取或存储数据。这使得你的代码更易于理解和维护。如果你需要更改数据存储方式,你只需要更改 biz 层的代码,而不需要更改 service 层的代码。

总的来说,将数据访问逻辑放在 biz 层而不是 service 层,可以使你的代码更易于维护,更具有灵活性,更易于测试,而且更符合关注点分离的原则。

开闭原则:对扩展开放,对修改封闭

"对扩展开放,对修改封闭" 是面向对象设计原则中的开闭原则(Open-Closed Principle, OCP)。它的核心理念是,软件实体(类、模块、函数等等)应该对扩展开放,对修改封闭。这意味着已有的代码是封闭的(不应该修改),新的功能是通过扩展来实现的(添加新的代码)。

以 Kratos 的 biz 层和 service 层为例。假设我们现在需要添加一个新的数据存储方式。如果我们在 service 层直接调用了 data 层,那么我们可能需要修改 service 层的代码来适配新的数据存储方式。这就违反了开闭原则,因为我们需要修改已有的 service 层代码。

然而,如果我们在 biz 层封装了数据操作的接口,那么 service 层就只需要调用这个接口,而不需要关心具体的数据存储方式。这样,当我们添加新的数据存储方式时,我们只需要在 biz 层添加新的接口实现,而不需要修改 service 层的代码。这就符合了开闭原则,因为我们通过扩展(添加新的 biz 层代码)来实现新的功能,而不是通过修改已有的代码。

总的来说,开闭原则鼓励我们使用接口和抽象类,使得我们的代码更具有灵活性和可维护性,更易于扩展和修改。

代码示例

简洁示例1

假设我们有一个用于绘制各种形状的系统,其中有一个 Draw 函数用于绘制形状:

type Shape struct {
	Type string
}

func Draw(s Shape) {
	if s.Type == "circle" {
		fmt.Println("Draw a circle")
	} else if s.Type == "rectangle" {
		fmt.Println("Draw a rectangle")
	}
}

现在,如果我们需要添加一个新的形状,比如三角形,那么我们需要修改 Draw 函数,这就违反了开闭原则。

我们可以通过引入接口来改进这个设计,使其符合开闭原则:

type Shape interface {
	Draw()
}

type Circle struct{}

func (c Circle) Draw() {
	fmt.Println("Draw a circle")
}

type Rectangle struct{}

func (r Rectangle) Draw() {
	fmt.Println("Draw a rectangle")
}

func Draw(s Shape) {
	s.Draw()
}

现在,如果我们需要添加一个新的形状,我们只需要创建一个实现了 Shape 接口的新类型,而不需要修改 Draw 函数:

type Triangle struct{}

func (t Triangle) Draw() {
	fmt.Println("Draw a triangle")
}

这就是开闭原则的一个简单例子:通过扩展(添加新的类型)来增加新的功能,而不是通过修改已有的代码。这样的设计使得代码更具有灵活性和可维护性。

新增data层的数据存储方式示例

如何在 Kratos 的架构中应用开闭原则。假设项目已经有了对 MySQL 的支持,现在需要添加对 Postgres 的支持。

首先,在 data 层,我们定义一个 DataRepo 接口,这个接口定义了所有的数据操作:

package data

type DataRepo interface {
	// 定义了所有需要的数据操作
	// ...
}

data 层,我们分别为 MySQL 和 Postgres 实现这个接口:

type MySQLRepo struct {
	// MySQL 的具体实现
	// ...
}

func (r *MySQLRepo) ... {
	// MySQL 的具体操作
	// ...
}

type PostgresRepo struct {
	// Postgres 的具体实现
	// ...
}

func (r *PostgresRepo) ... {
	// Postgres 的具体操作
	// ...
}

然后,在 biz 层,我们依赖 DataRepo 接口,而不是具体的实现:

package biz

type Biz struct {
	repo data.DataRepo
	// ...
}

func NewBiz(repo data.DataRepo) *Biz {
	return &Biz{repo: repo}
}

这样,当需要添加对新的数据存储方式的支持时,只需要在 data 层添加新的 DataRepo 接口的实现,而不需要修改 biz 层的代码。这就符合了开闭原则:对扩展开放,对修改封闭。

在应用启动时,根据配置或者其他逻辑,可以选择初始化哪种 DataRepo 的实现,并将其注入到 biz 层,这样 biz 层就可以透明地切换不同的数据存储方式。


var repo data.DataRepo
if useMySQL {
	repo = &data.MySQLRepo{...}
} else if usePostgres {
	repo = &data.PostgresRepo{...}
}
biz := biz.NewBiz(repo)

这样,就通过接口和依赖注入,实现了对开闭原则的遵守,保证了代码的可扩展性和可维护性。

0条评论
作者已关闭评论
李****成
1文章数
0粉丝数
李****成
1 文章 | 0 粉丝
Ta的热门文章查看更多
李****成
1文章数
0粉丝数
李****成
1 文章 | 0 粉丝
原创

Kratos简介

2023-05-24 08:47:28
175
0

kratos简介

概述

kratos app

kratos app用法和设计理念

Kratos 是一套 Go 语言编写的微服务框架,它提供了一套基础的、易于使用的微服务开发框架和工具集。

Kratos 提供了一套命名为 app 的接口和工具,它设计用来帮助更方便地管理和运行服务。

在 Kratos 中,一个 app 代表一个可以运行的应用。app.App 是一个接口,它定义了一个应用需要实现的基本方法,包括 Name()Run()Stop()等。

app.App 的设计理念是将一个应用的启动和停止过程标准化,并将它们与应用的具体业务逻辑解耦。这使得你可以在不改变应用内部实现的情况下,更改应用的启动和停止方式,或者在多个应用之间复用相同的启动和停止逻辑。

具体来说,app.App 的各个方法的含义如下:

  • Name(): 返回应用的名称。这个名称通常在日志、指标等地方使用,用来标识是哪个应用生成的这些数据。
  • Run(): 运行应用。这个方法会阻塞,直到应用退出或者发生错误。
  • Stop(): 停止应用。这个方法会停止应用的运行,并做一些清理工作,例如关闭数据库连接、停止后台任务等。

通过这种方式,Kratos 提供了一种灵活而又统一的方式来管理应用的生命周期。无论你的应用是一个 HTTP 服务器、一个 gRPC 服务,还是一个消息队列消费者,都可以用相同的方式来启动和停止它们。这让你的代码更加模块化,也更容易测试和维护。

biz/data/service

在 Kratos 中,biz、data、service 是常用的三个概念:

  1. biz(Business Logic):业务逻辑层。

    biz 层主要负责处理业务逻辑,将复杂的业务逻辑封装为简单、可重用的业务接口,并暴露给上层的 service 层调用。biz 层通常包含多个 Use Case,每个 Use Case 对应一个具体的业务场景,可以通过调用 DAO 层来获取数据,处理数据并返回结果。

  2. data(Data Access Object):数据访问层。

    data 层主要负责与数据库交互,提供统一的访问接口和实现,屏蔽数据库的具体操作细节,以便于提高代码的可重用性和可维护性。data 层通常用来提供诸如增删改查等基本操作接口,以及对数据进行验证、格式化、转换等操作,确保数据的有效性和完整性。

  3. service:微服务层。

    service 层是微服务框架的核心部分,负责处理外部请求并调用相应的 biz 接口来处理业务逻辑。在 service 层中,通常会处理一些基本的服务功能,比如授权、身份验证、流量控制等,以保障整个服务的可靠性和安全性。

以上三层概念可以通过垂直拆分和水平拆分等方式进行组合和扩展,以构建出更复杂、更高效的微服务架构。在 Kratos 中,这些概念提供了一种通用的设计模式,旨在提高微服务的可维护性、可扩展性和可重用性,加速微服务的开发和部署流程。

biz

业务逻辑层(biz)是Kratos中最具有业务特性的一层,它主要负责业务逻辑的实现,包括业务规则、流程和算法等。在Kratos中,业务逻辑层通常包含了领域模型、业务逻辑和应用服务等组件。因此,理解biz的use case需要从具体的业务场景出发,考虑业务逻辑层需要实现哪些业务规则、流程和算法等,以及如何通过领域模型、业务逻辑和应用服务等组件来实现这些业务逻辑。具体来说,可以通过以下几个步骤来进一步理解biz的use case:

1. 确定业务场景:首先需要明确具体的业务场景,例如电商平台的订单管理、社交平台的好友关系管理等。

2. 分析业务规则:在确定业务场景后,需要分析业务规则,即业务逻辑层需要实现哪些业务规则,例如订单管理中的订单状态转换规则、好友关系管理中的好友关系验证规则等。

3. 设计领域模型:在分析业务规则后,需要设计领域模型,即业务逻辑层需要处理的业务对象及其属性和行为。例如订单管理中的订单对象、商品对象等。

4. 实现业务逻辑:在设计领域模型后,需要实现业务逻辑,即根据业务规则和领域模型来实现具体的业务逻辑。例如订单管理中的订单状态转换逻辑、商品库存检查逻辑等。

5. 提供应用服务:在实现业务逻辑后,需要提供应用服务,即将业务逻辑封装成可供其他组件调用的服务。例如订单管理中的订单服务、商品服务等。

通过以上步骤,可以进一步理解biz的use case,即业务逻辑层需要实现具体的业务规则、流程和算法等,以及如何通过领域模型、业务逻辑和应用服务等组件来实现这些业务逻辑。


biz/use case

在 Clean Architecture 中,Use Case 是一种特殊的对象,它代表了一个具体的业务操作或者说用户的一个用例。每个 Use Case 都会对应一个业务流程,并且包含了这个业务流程所有的业务逻辑。

在 Kratos 的架构中,biz 层就是用来定义这些 Use Case 的。具体来说,每个 Use Case 通常会被定义为一个 Go 接口,这个接口中的方法就对应了该 Use Case 可以执行的操作。然后,我们会为这个接口提供一个或多个实现,这些实现就是具体的业务逻辑。

以下是一个简单的例子:

package biz

type UserUseCase interface {
	CreateUser(ctx context.Context, name string) (User, error)
	GetUser(ctx context.Context, id int) (User, error)
}

type User struct {
	ID   int
	Name string
}

在这个例子中,我们定义了一个 UserUseCase,它有两个方法:CreateUserGetUser。这就代表了两个业务用例:创建用户和获取用户

通过这种方式,我们可以将业务逻辑从底层的数据存储和网络传输中解耦出来,这使得业务逻辑更容易编写和测试,并且也更加灵活。比如,我们可以根据需要为同一个 Use Case 提供多个实现,或者将一个 Use Case 的实现替换为另一个,而不需要修改其他的代码。


在 Kratos 架构中,biz 层定义的 Use Case 接口应当由 biz 层的具体实现类来实现,而不是在 data 层或 service 层。data 层和 service 层会与 biz 层进行交互,但它们本身不直接包含业务逻辑。

biz 层,会实现 Use Case 接口,这些实现类会依赖于底层的 data 层提供的数据存储和检索服务。实际的业务逻辑会在 biz 层实现类中完成。

以前面的例子为例,可以在 biz 层实现 UserUseCase 接口:


package biz

import "context"

type UserRepository interface {
	Create(ctx context.Context, user User) error
	Get(ctx context.Context, id int) (User, error)
}

type UserUseCaseImpl struct {
	repo UserRepository
}

func NewUserUseCase(repo UserRepository) UserUseCase {
	return &UserUseCaseImpl{repo: repo}
}

func (u *UserUseCaseImpl) CreateUser(ctx context.Context, name string) (User, error) {
	user := User{Name: name}
	err := u.repo.Create(ctx, user)
	return user, err
}

func (u *UserUseCaseImpl) GetUser(ctx context.Context, id int) (User, error) {
	return u.repo.Get(ctx, id)
}

在这个例子中,我们定义了一个 UserRepository 接口,它表示了数据存储和检索的抽象。然后,我们实现了 UserUseCaseImpl,它实现了 UserUseCase 接口,并依赖于 UserRepository 接口来完成数据存储和检索的操作。这样,业务逻辑就完全封装在 biz 层中了。

data 层会实现 UserRepository 接口,而 service 层会调用 biz 层的 UserUseCase 接口来执行业务逻辑。这样,我们就实现了业务逻辑与底层数据存储和网络传输的解耦。


service是否可以直接调data?

在Kratos架构中,推荐的方式是让service层通过biz层来访问data层。这样做的目的是为了保持架构的清晰和解耦,每一层都有明确的职责:

  • service层:负责处理网络请求,转发请求到biz层,并将biz层返回的结果格式化为网络响应。
  • biz层:包含核心的业务逻辑,负责处理业务请求。它会调用data层提供的服务来实现具体的数据操作。
  • data层:负责数据的存储和检索,为biz层提供数据服务。

service层直接访问data层可能会导致架构变得混乱,业务逻辑可能会分散在多个层次中,这使得代码更难理解和维护。此外,如果service层直接依赖于data层,那么当我们需要改变数据存储的方式时,可能需要修改service层的代码,这违反了单一职责原则和开闭原则。

当然,这只是一种推荐的方式,实际上在某些特定的情况下,service层直接访问data层可能是更合适的选择。比如,在某些简单的场景下,例如只是简单的数据获取并没有涉及复杂的业务逻辑,这时候service层直接访问data层可能会更加直接和高效。但是,需要清楚的知道这样做的潜在风险,这可能使得你的应用的业务逻辑分散在service层和biz层之间,这可能使得后续的维护变得更复杂。

另外,在一些需要性能优化的场景,例如批量数据操作,如果经过biz层会产生大量的不必要的数据转换,也可以考虑service层直接访问data层。

总的来说,这是一个权衡的问题,我们要在保持架构清晰和代码复杂性之间进行权衡。在大多数情况下,我们推荐保持架构的清晰,即让service层通过biz层来访问data层,这样可以更好的应对业务的变化和扩展。在有特殊需要的情况下,可以适当的让service层直接访问data层,但是需要清楚的知道这样做的潜在风险。


data变化对biz的影响

如果改变了data数据存储的方式,确实可能需要修改biz层的实现,但这正是biz层的职责之一:抽象和封装底层数据的操作,为service层提供一致的接口。这样,service层就可以避免直接依赖具体的数据存储方式,从而达到松耦合的效果。

当数据存储方式改变时,biz层可能需要修改,但service层并不需要知道这个变化,它依然调用biz层的接口就可以了。这就是所谓的开闭原则:对扩展开放,对修改封闭。在这种架构下,当我们需要添加新的数据存储方式时,我们只需要在biz层添加新的实现,而不需要修改service层的代码。

这种分层的设计可以帮助我们更好地管理代码的复杂性,每一层只需要关注自己的职责,不需要关心其他层的具体实现。这样,当系统需要扩展或者修改时,我们可以更容易地进行修改,而不会影响到整个系统。

在大多数应用程序架构中,包括 Kratos,service 层和 biz 层具有不同的职责:

  1. service 层:这一层主要处理网络协议和格式,比如 HTTP、gRPC、Thrift 等。它的职责是解析请求,调用适当的业务逻辑(在 biz 层),然后将结果格式化为适当的响应。service 层并不应该包含任何业务逻辑,它只是一个“传递者”或“翻译者”。
  2. biz 层:这是应用程序的核心,包含了所有的业务逻辑。biz 层负责实现用例,并调用 data 层以持久化数据。biz 层应该尽可能地独立于网络和数据存储,以便它们可以根据需要更改,而不会影响到业务逻辑。

如果你将数据访问逻辑直接放在 service 层,那么你就将业务逻辑(在 biz 层)和数据访问逻辑混合在了一起。这将使得你的代码更难维护和测试。此外,如果你将来需要更改数据存储方式,你可能需要更改 service 层的代码,这将导致更多的工作,并可能引入错误。

相反,如果你将数据访问逻辑放在 biz 层,那么 service 层就只需要关注网络协议和数据格式化,而不需要关心如何获取或存储数据。这使得你的代码更易于理解和维护。如果你需要更改数据存储方式,你只需要更改 biz 层的代码,而不需要更改 service 层的代码。

总的来说,将数据访问逻辑放在 biz 层而不是 service 层,可以使你的代码更易于维护,更具有灵活性,更易于测试,而且更符合关注点分离的原则。

开闭原则:对扩展开放,对修改封闭

"对扩展开放,对修改封闭" 是面向对象设计原则中的开闭原则(Open-Closed Principle, OCP)。它的核心理念是,软件实体(类、模块、函数等等)应该对扩展开放,对修改封闭。这意味着已有的代码是封闭的(不应该修改),新的功能是通过扩展来实现的(添加新的代码)。

以 Kratos 的 biz 层和 service 层为例。假设我们现在需要添加一个新的数据存储方式。如果我们在 service 层直接调用了 data 层,那么我们可能需要修改 service 层的代码来适配新的数据存储方式。这就违反了开闭原则,因为我们需要修改已有的 service 层代码。

然而,如果我们在 biz 层封装了数据操作的接口,那么 service 层就只需要调用这个接口,而不需要关心具体的数据存储方式。这样,当我们添加新的数据存储方式时,我们只需要在 biz 层添加新的接口实现,而不需要修改 service 层的代码。这就符合了开闭原则,因为我们通过扩展(添加新的 biz 层代码)来实现新的功能,而不是通过修改已有的代码。

总的来说,开闭原则鼓励我们使用接口和抽象类,使得我们的代码更具有灵活性和可维护性,更易于扩展和修改。

代码示例

简洁示例1

假设我们有一个用于绘制各种形状的系统,其中有一个 Draw 函数用于绘制形状:

type Shape struct {
	Type string
}

func Draw(s Shape) {
	if s.Type == "circle" {
		fmt.Println("Draw a circle")
	} else if s.Type == "rectangle" {
		fmt.Println("Draw a rectangle")
	}
}

现在,如果我们需要添加一个新的形状,比如三角形,那么我们需要修改 Draw 函数,这就违反了开闭原则。

我们可以通过引入接口来改进这个设计,使其符合开闭原则:

type Shape interface {
	Draw()
}

type Circle struct{}

func (c Circle) Draw() {
	fmt.Println("Draw a circle")
}

type Rectangle struct{}

func (r Rectangle) Draw() {
	fmt.Println("Draw a rectangle")
}

func Draw(s Shape) {
	s.Draw()
}

现在,如果我们需要添加一个新的形状,我们只需要创建一个实现了 Shape 接口的新类型,而不需要修改 Draw 函数:

type Triangle struct{}

func (t Triangle) Draw() {
	fmt.Println("Draw a triangle")
}

这就是开闭原则的一个简单例子:通过扩展(添加新的类型)来增加新的功能,而不是通过修改已有的代码。这样的设计使得代码更具有灵活性和可维护性。

新增data层的数据存储方式示例

如何在 Kratos 的架构中应用开闭原则。假设项目已经有了对 MySQL 的支持,现在需要添加对 Postgres 的支持。

首先,在 data 层,我们定义一个 DataRepo 接口,这个接口定义了所有的数据操作:

package data

type DataRepo interface {
	// 定义了所有需要的数据操作
	// ...
}

data 层,我们分别为 MySQL 和 Postgres 实现这个接口:

type MySQLRepo struct {
	// MySQL 的具体实现
	// ...
}

func (r *MySQLRepo) ... {
	// MySQL 的具体操作
	// ...
}

type PostgresRepo struct {
	// Postgres 的具体实现
	// ...
}

func (r *PostgresRepo) ... {
	// Postgres 的具体操作
	// ...
}

然后,在 biz 层,我们依赖 DataRepo 接口,而不是具体的实现:

package biz

type Biz struct {
	repo data.DataRepo
	// ...
}

func NewBiz(repo data.DataRepo) *Biz {
	return &Biz{repo: repo}
}

这样,当需要添加对新的数据存储方式的支持时,只需要在 data 层添加新的 DataRepo 接口的实现,而不需要修改 biz 层的代码。这就符合了开闭原则:对扩展开放,对修改封闭。

在应用启动时,根据配置或者其他逻辑,可以选择初始化哪种 DataRepo 的实现,并将其注入到 biz 层,这样 biz 层就可以透明地切换不同的数据存储方式。


var repo data.DataRepo
if useMySQL {
	repo = &data.MySQLRepo{...}
} else if usePostgres {
	repo = &data.PostgresRepo{...}
}
biz := biz.NewBiz(repo)

这样,就通过接口和依赖注入,实现了对开闭原则的遵守,保证了代码的可扩展性和可维护性。

文章来自个人专栏
文章 | 订阅
0条评论
作者已关闭评论
作者已关闭评论
0
0