# 单体或微服务或两者兼有:ServiceWeaver 简介
如果我们能利用一种编程模型或运行时,将代码的逻辑边界与部署的物理边界解耦,这样在开始开发时就不需要将应用程序紧耦合为单体或微服务来部署,那将会非常棒。谷歌的新开源项目 Service Weaver 提供了将代码与代码部署方式解耦的想法。Service Weaver 是一个编程框架,用于用 Go 编程语言编写和部署云应用程序,其中的部署决策可以委托给自动运行时。Service Weaver 可以将应用程序部署为单体和微服务。因此,它是单体和微服务的完美结合。
使用 Service Weaver,您可以将应用程序编写成模块化单体,使用组件将应用程序模块化。Service Weaver 中的组件以 Go 接口为模型,您可以为业务逻辑提供具体实现,而无需与网络或序列化代码耦合。组件是一种代表计算实体的 Actor。这些围绕核心业务逻辑构建的模块化组件可以像本地方法调用一样调用其他组件的方法,无论这些组件是以模块化二进制形式运行还是以微服务形式运行,都无需使用 HTTP 或 RPC。当这些组件作为微服务或在不同机器上运行时,Service Weaver 会使用 RPC 机制进行方法调用。否则,将是本地方法调用。您不必担心您的组件如何调用其他组件的方法。您可以在本地测试 Service Weaver 应用程序,然后将其部署到云中。Service Weaver 可让您专注于代码的工作,而不必担心代码在哪里运行。您可以在同一进程中以单体二进制形式运行组件,也可以在不同机器上以微服务形式运行组件,还可以轻松地扩大或缩小规模,以应对可扩展性挑战。在构建现代应用程序时,添加可观察性组件非常重要。Service Weaver 集成了可观察性,并拥有日志、度量和跟踪库。
# 例子
让我们编写一个简单的演示,了解如何使用 Service Weaver 编写应用程序。这个示例演示通过去除实现过程中的所有复杂因素,只关注 Service Weaver 的基本原理。
## 安装 ServiceWeaver
以下命令会将 weaver 安装到 $GOBIN:
```
go install github.com/ServiceWeaver/weaver/cmd/weaver@latest
```
## ****Service Weaver Components****
使用 Service Weaver,应用程序由一组组件组成。其中包括实现 `weaver.Main` 接口的组件,以及 `weaver.Main` 构成系统所需的其他组件。模块化二进制文件将由实现 `weaver.Main` 接口的组件创建。
演示应用程序由以下组件组成:
- studyService: 执行 weaver.Main 接口。该组件用于运行学习的 HTTP 服务器。
- interviewService: 该组件为每次学习的面试。
- workingService:该组件为面试通过后进入工作。
下面的代码块为 `interviewService` 提供了组件接口及其相应的实现:
1. 组件接口以及它的实现
```
type Service interface {
MakeInterview(ctx context.Context, golang model.Golang) error
}
type implementation struct {
weaver.Implements[Service]
}
func (s *implementation) MakeInterview(ctx context.Context, golang model.Golang) error {
defer s.Logger(ctx).Info(
"talk about golang",
"channel: ", golang.Channel,
"goroutine: ", golang.Goroutine,
)
return nil
}
```
在前面的代码块中,我们定义了一个 `Service` 接口,将其作为 interview 组件的模型,并通过嵌入通用类型 `weaver.Implements[T]`,在 `implementation` 结构中提供了具体的实现。
与 interview 服务类似,我们实现一个 working 的服务。
1. working 服务的实现
```
type Service interface {
Working(ctx context.Context, golang model.Golang) error
}
type implementation struct {
weaver.Implements[Service]
}
func (s *implementation) Working(ctx context.Context, golang model.Golang) error {
defer s.Logger(ctx).Info(
"working, fixing golang bug",
"channel bug: ", golang.Channel,
"goroutines bug: ", golang.Goroutine,
)
return nil
}
```
通过嵌入 `weaver.Implements[T]`,我们可以在 Service Weaver 中创建组件。模块化二进制组件将由实现 `weaver.Implements[weaver.Main]` 类型的主组件创建。让我们编写一个组件,在 `studyService` 组件中嵌入 `weaver.Implements[weaver.Main]` 类型,该组件用于运行 HTTP 服务器进行学习。
1. Server 结构体将 `weaver.Implements[weaver.Main]` 嵌入其中
```
type Server struct {
weaver.Implements[weaver.Main]
handler http.Handler
interviewService weaver.Ref[interview.Service]
working weaver.Ref[working.Service]
studyapi weaver.Listener `weaver:"studyapi"`
}
```
## 与其他组件比较
除 `weaver.Main` 组件外,`weaver.Main` 临时需要的其他组件。无论是否是 `weaver.Main` 组件,任何组件都可以引用其他组件。在列表 3 中,表示 `study` 组件的结构体 `Server` 需要 `interview` 组件来进行面试,还需要 `working` 组件来工作。一个 Service Weaver 组件可以通过引用 `weaver.Ref[T]` 调用其他组件的方法,其中 T 是要引用的组件的接口。在这里,我们要对 `interview` 和 `working` 进行引用,以便在结构服务器中创建属性。
```
interviewService weaver.Ref[interview.Service]
working weaver.Ref[working.Service]
```
`study` 组件通过公开 HTTP 服务器来允许网络流量,因此我们保留了 `http.Handler` 类型的属性处理程序来创建 HTTP 服务器。
```
handler http.Handler
```
## 网络监听
Service Weaver 组件的实现可能会使用网络监听器。例如,study 组件需要为 HTTP 网络流量提供服务。为此,必须为实现结构提供一个类型为 `weaver.Listener` 的属性。
```
studyapi weaver.Listener `weaver:"studyapi"`
```
`weaver.Listener` 字段可以从 `.toml` 配置文件中读取监听器地址。结构标记 `weaver: "studyapi"` 从名为 `weaver.toml` 的配置文件中读取监听器地址。Weaver Services 使用 .toml 文件进行配置。
1. weaver.toml 配置文件
```
[serviceweaver]
binary = "./studyapp"
[single]
listeners.studyapi = {address = "localhost:13000"}
[multi]
listeners.studyapi = {address = "localhost:3000"}
```
## 初始化组件
Service Weaver 组件可选择在组件实现的结构中提供一个 `Init` 方法。如果有 `Init` 方法,Service Weaver 自动创建组件实例时就会调用该方法。
1. 初始化 Server
```
func (s *Server) Init(ctx context.Context) error {
s.Logger(ctx).Info("Init")
r := chi.NewRouter()
r.Route("/api/study", func(r chi.Router) {
r.Post("/", s.Study)
r.Get("/{type}", s.GetKnowledges)
})
s.handler = r
return nil
}
```
## 为 Service Weaver 应用程序提供服务
Service Weaver 应用程序的运行需要调用函数 `weaver.Run`,该函数需要一个函数作为参数。
让我们编写一个具有适当签名的函数,用于 `weaver.Run` 函数,以运行 Service Weaver 应用程序。
1. 服务启动方法
```
func Serve(ctx context.Context, s *Server) error {
s.Logger(ctx).Info("StudyApi listener available.", "addr:", s.studyapi)
httpServer := &http.Server{
Handler: s.handler,
}
httpServer.Serve(s.studyapi)
return nil
}
```
在 `Serve` 函数中,我们创建了一个 HTTP 服务器,并使用 `weaver.Listener` 属性为其提供服务。
下面是服务器结构(主组件)的完整实现:
```
type Server struct {
weaver.Implements[weaver.Main]
handler http.Handler
interviewService weaver.Ref[interview.Service]
working weaver.Ref[working.Service]
studyapi weaver.Listener `weaver:"studyapi"`
}
func (s *Server) Init(ctx context.Context) error {
s.Logger(ctx).Info("Init")
r := chi.NewRouter()
r.Route("/api/study", func(r chi.Router) {
r.Post("/", s.Study)
r.Get("/{type}", s.GetKnowledges)
})
s.handler = r
return nil
}
func Serve(ctx context.Context, s *Server) error {
s.Logger(ctx).Info("StudyApi listener available.", "addr:", s.studyapi)
httpServer := &http.Server{
Handler: s.handler,
}
httpServer.Serve(s.studyapi)
return nil
}
```
## 调用引用组件的方法
在 Server 结构体中,组件 `interview` 和 `working` 是通过 `weaver.Ref[T]` 引用的,其中 T 是组件的接口。
```
interviewService weaver.Ref[interview.Service]
working weaver.Ref[working.Service]
```
当 Service Weaver 创建组件实例时,它也会自动创建引用的组件(`weaver.Ref[T]` 字段)。为了从 `weaver.Ref[T]` 字段中获取组件实例,调用组件的方法,只需调用 `weaver.Ref[T]` 字段中的 `Get` 方法即可。例如,下面的代码块获取了 interview 组件:
1. 获取 interview 组件
```
interview := s.interviewService.Get()
```
在本示例演示中,学习时,interview 组件用于面试,working 用于工作。这是在 HTTP 处理程序代码中实现的,该代码用于处理 study 的 HTTP Post 请求。
1. 调用引用组件方法的 HTTP 处理程序代码
```
var ctx = context.Background()
func (s *Server) Study(writer http.ResponseWriter, request *http.Request) {
var golang model.Golang
err := json.NewDecoder(request.Body).Decode(&golang)
if err != nil {
http.Error(writer, "Invalid Course Data", 500)
return
}
golang.Goroutine = "GMP"
golang.Channel = "blocking"
if err := s.interviewService.Get().MakeInterview(ctx, golang); err != nil {
s.Logger(ctx).Error(
"interview failed",
"error:", err,
)
http.Error(writer, "interview failed", http.StatusInternalServerError)
return
}
golang.Goroutine = "fixing goroutine bug"
golang.Channel = "non-blocking"
// send notification using notificationService component
if err := s.working.Get().Working(ctx, golang); err != nil {
s.Logger(ctx).Error(
"fixing bug failed",
"error:", err,
)
}
s.Logger(ctx).Info("those bugs has been fixed")
writer.Header().Set("Content-Type", "application/json")
writer.WriteHeader(http.StatusOK)
}
```
在前面的代码块中,通过调用 `weaver.Ref[T]` 字段的 `Get` 方法和调用组件的方法,创建了引用组件。
```
if err := s.interviewService.Get().MakeInterview(ctx, golang); err != nil {}
if err := s.working.Get().Working(ctx, golang); err != nil {}
```
## 使用 weaver generate 命令生成代码
无论什么时候,当你使用 Service Weaver 实现某些功能时,都应该在构建(`go build`)或使用 `go run` 运行应用程序之前运行 weaver 生成命令。这会生成一些代码,文件名为 `weaver_gen.go`。
下面的命令会生成当前目录下的代码
`weaver generate .`
下面的命令为当前目录及其所有子目录生成代码:
`weaver generate ./...`
基于上面的代码,当我们执行上面语句的时候,会提示我们没有序列化:
```
interview\interview.go:10:2: Method `MakeInterview(ctx context.Context, golang model.Golang) (error)` of Service Weaver component "Service" has incorrect argument types. Argument 1 has type model.Golang, which is not serializable. All arguments, besides the initial context.Context, must be serializable.
model.Golang: named structs are not serializable by default. Consider using weaver.AutoMarshal.
working\working.go:10:2: Method `Working(ctx context.Context, golang model.Golang) (error)` of Service Weaver component "Service" has incorrect argument types. Argument 1 has type model.Golang, which is not serializable. All arguments, besides the initial context.Context, must be serializable.
model.Golang: named structs are not serializable by default. Consider using weaver.AutoMarshal.
-: # github.com/timliudream/go-test/serviceWeaverDemo/study
study\study.go:30:22: s.GetKnowledges undefined (type *Server has no field or method GetKnowledges)
D:\gopath\src\github.com\timliudream\go-test\serviceWeaverDemo\study\study.go:30:22: s.GetKnowledges undefined (type *Server has no field or method GetKnowledges)
```
下面我们来讲讲序列化的事情。
## 用于调用组件方法的可序列化类型
在调用组件的方法时,必须对方法的参数和返回类型进行序列化,以便通过网络发送。通过嵌入 `weaver.AutoMarshal` 类型,可以将结构类型序列化。
1. 结构类型使其可序列化
```
type Golang struct {
weaver.AutoMarshal
Channel string
Goroutine string
}
```
## 运行 Service Weaver 应用程序
weaver 运行函数将 `weaver.Main` 组件作为 `Service Weaver` 应用程序运行。
下面的代码块将运行 `Service Weaver` 应用程序:
```
func main() {
if err := weaver.Run(context.Background(), study.Serve); err != nil {
log.Fatal(err)
}
}
```
在前面的代码块中,`study.Serve` 是一个函数,其签名为 `func(context.Context, *T) error`,其中 T 是主组件的结构实现。函数 `weaver.Run` 会自动创建一个实现 `weaver.Main` 的主组件实例。如果组件实现中提供了任何 `Init` 方法,则将用于创建实例。
## 在单进程中运行应用程序
在编译代码或使用 go run 命令运行应用程序之前,请确保执行 `weaver generate` 命令来生成代码,这对 Service Weaver 应用程序至关重要。
下面的命令运行应用程序:
```
go run .
```
服务运行在“127.0.0.1:50076”,输出如下:
```
╭───────────────────────────────────────────────────╮
│ app : serviceWeaverDemo.exe │
│ deployment : 615d74c6-0e33-4f18-ae0c-f380b49c8e91 │
╰───────────────────────────────────────────────────╯
I0914 14:01:50.363086 weaver.Main 85b8796b study.go:26 │ Init
I0914 14:01:50.363086 weaver.Main 85b8796b study.go:76 │ StudyApi listener available. addr:="[::]:50076"
```
下面的命令显示 Service Weaver 应用程序的状态:
```
weaver single status
```
如下图所示,状态显示了每个部署、组件和监听器:
```
╭──────────────────────────────────────────────────────────────────────╮
│ DEPLOYMENTS │
├───────────────────────┬──────────────────────────────────────┬───────┤
│ APP │ DEPLOYMENT │ AGE │
├───────────────────────┼──────────────────────────────────────┼───────┤
│ serviceWeaverDemo.exe │ 615d74c6-0e33-4f18-ae0c-f380b49c8e91 │ 2m33s │
╰───────────────────────┴──────────────────────────────────────┴───────╯
╭───────────────────────────────────────────────────────────────────────╮
│ COMPONENTS │
├───────────────────────┬────────────┬───────────────────┬──────────────┤
│ APP │ DEPLOYMENT │ COMPONENT │ REPLICA PIDS │
├───────────────────────┼────────────┼───────────────────┼──────────────┤
│ serviceWeaverDemo.exe │ 615d74c6 │ weaver.Main │ 4356 │
│ serviceWeaverDemo.exe │ 615d74c6 │ interview.Service │ 4356 │
│ serviceWeaverDemo.exe │ 615d74c6 │ working.Service │ 4356 │
╰───────────────────────┴────────────┴───────────────────┴──────────────╯
╭────────────────────────────────────────────────────────────╮
│ LISTENERS │
├───────────────────────┬────────────┬──────────┬────────────┤
│ APP │ DEPLOYMENT │ LISTENER │ ADDRESS │
├───────────────────────┼────────────┼──────────┼────────────┤
│ serviceWeaverDemo.exe │ 615d74c6 │ studyapi │ [::]:50076 │
╰───────────────────────┴────────────┴──────────┴────────────╯
```
你也可以运行 `weaver single dashboard` 来打开仪表盘,在浏览器中打开仪表盘。
![Snipaste_2023-09-14_14-06-47.png](https://static.golangjob.cn/230914/9d2eddb0314debd0a040505fa8c8834f.png)
可以看到非常详细的信息,以及监控、trace 等等。
## 以多进程运行程序
Service Weaver 应用程序可在单进程和多进程中运行。要在多进程中运行,请使用 `go build` 编译代码。然后,使用下面的命令和配置文件 weaver.toml 在多进程中运行应用程序:
```
weaver multi deploy weaver.toml
```
![Snipaste_2023-09-14_14-14-18.png](https://static.golangjob.cn/230914/22a58d94603d444ccce92562ce0ef4f9.png)
下面的命令显示了 Service Weaver 应用程序在多个进程中的状态:
```
weaver multi status
```
```
╭──────────────────────────────────────────────────────────────────────╮
│ DEPLOYMENTS │
├───────────────────────┬──────────────────────────────────────┬───────┤
│ APP │ DEPLOYMENT │ AGE │
├───────────────────────┼──────────────────────────────────────┼───────┤
│ serviceWeaverDemo.exe │ 615d74c6-0e33-4f18-ae0c-f380b49c8e91 │ 2m33s │
╰───────────────────────┴──────────────────────────────────────┴───────╯
╭───────────────────────────────────────────────────────────────────────╮
│ COMPONENTS │
├───────────────────────┬────────────┬───────────────────┬──────────────┤
│ APP │ DEPLOYMENT │ COMPONENT │ REPLICA PIDS │
├───────────────────────┼────────────┼───────────────────┼──────────────┤
│ serviceWeaverDemo.exe │ 615d74c6 │ weaver.Main │ 4356 │
│ serviceWeaverDemo.exe │ 615d74c6 │ interview.Service │ 4356 │
│ serviceWeaverDemo.exe │ 615d74c6 │ working.Service │ 4356 │
╰───────────────────────┴────────────┴───────────────────┴──────────────╯
╭────────────────────────────────────────────────────────────╮
│ LISTENERS │
├──────────────┼──────────────────────────────────────┼──────┤
│ studyapp.exe │ 0f15e18b-a336-44b6-ac92-b7c9436ad8ce │ 1m4s │
╰──────────────┴──────────────────────────────────────┴──────╯
╭──────────────────────────────────────────────────────────────╮
│ COMPONENTS │
├──────────────┬────────────┬───────────────────┬──────────────┤
│ APP │ DEPLOYMENT │ COMPONENT │ REPLICA PIDS │
├──────────────┼────────────┼───────────────────┼──────────────┤
│ studyapp.exe │ 0f15e18b │ weaver.Main │ 21500, 23724 │
│ studyapp.exe │ 0f15e18b │ interview.Service │ 23908, 24372 │
│ studyapp.exe │ 0f15e18b │ working.Service │ 22936, 26616 │
╰──────────────┴────────────┴───────────────────┴──────────────╯
╭───────────────────────────────────────────────────────╮
│ LISTENERS │
├──────────────┬────────────┬──────────┬────────────────┤
│ APP │ DEPLOYMENT │ LISTENER │ ADDRESS │
├──────────────┼────────────┼──────────┼────────────────┤
│ studyapp.exe │ 0f15e18b │ studyapi │ 127.0.0.1:3000 │
╰──────────────┴────────────┴──────────┴────────────────╯
```
# 代码
此文章的代码已放到 github 地址上:
https://github.com/TimLiuDream/go-test/tree/master/serviceWeaverDemo
有疑问加站长微信联系(非本文作者)