如何根据Clean架构在Golang实现presenter?
正确的软件架构是创建可维护项目的关键。正确的方法是100%主观的, 但最近我喜欢并尝试追随罗伯特·C·马丁(又名鲍勃叔叔) 虽然我很喜欢这个理论,但它缺乏一些实用的实现指南,无法解决开发人员可能面临的常见技术挑战。 例如,我一直在努力解决的问题之一就是正确实现presenter层 演示者负责接受来自我的用例的“响应”,并以某种方式格式化它 它可以“呈现”到我的输出设备(无论它是web还是CLI应用程序) 解决此问题有多种方法,但它们通常属于以下类别之一:如何根据Clean架构在Golang实现presenter?,go,architecture,clean-architecture,Go,Architecture,Clean Architecture,正确的软件架构是创建可维护项目的关键。正确的方法是100%主观的, 但最近我喜欢并尝试追随罗伯特·C·马丁(又名鲍勃叔叔) 虽然我很喜欢这个理论,但它缺乏一些实用的实现指南,无法解决开发人员可能面临的常见技术挑战。 例如,我一直在努力解决的问题之一就是正确实现presenter层 演示者负责接受来自我的用例的“响应”,并以某种方式格式化它 它可以“呈现”到我的输出设备(无论它是web还是CLI应用程序) 解决此问题有多种方法,但它们通常属于以下类别之一: 演示者由用例本身通过某种输出接口调用 用
package my_domain
import "http"
type useCase struct {
presenter presenter
}
func (uc *useCase) doSomething(arg string) {
uc.presenter("success")
}
type presenter interface {
present(respone interface{})
}
type controller struct {
useCase useCase
}
func (c *controller) Action(rw http.ResponseWriter, req *http.Request) {
c.useCase("argument")
}
基本上,它完全按照上面所描述的和干净体系结构中的方式进行:有一个控制器调用用例(通过一个边界,这里不存在)。用例会做一些事情并调用演示者(虽然没有实现,但这正是问题所在)
我们的下一步可能是实现presenter…但是考虑到Go HTTP处理程序中的输出是如何工作的,有一个很好的问题需要解决。即:请求范围
每个请求都有自己的响应编写器(传递给http处理程序),在那里应该编写响应。演示者无法访问全局请求范围,它需要响应编写器。因此,如果我想遵循选项1(用例调用呈现者),我必须以某种方式将其传递给呈现者,呈现者以这种方式成为请求范围,而应用程序的其余部分是完全无状态的,没有请求范围,它们只实例化一次
这也意味着我要么将响应编写器本身传递给用例和演示者(我不希望这样做),要么为每个请求创建一个新的演示者
我在哪里可以这样做:
根据我所读到的主题:其他人也没有。我可以告诉你我在Clean Architecture方面的经验。我花时间在这个问题上,阅读文章和测试代码。因此,我想向您推荐以下帖子和附带的源代码,这对我帮助很大:
希望这也能对您有所帮助。我认为您可以将
rw http.ResponseWriter
传递给演示者,以便编写实际的响应。它看起来是这样的:
type presenter interface {
present(rw http.ResponseWriter, response interface{})
}
type myPresenter struct {}
func (p *myPresenter) present(rw http.ResponseWriter, response interface{}) {
fmt.Fprintf(rw, "Hello, %+v", response)
}
或者另一种方法是在闭包中传递请求编写器。这是金戈尼克的密码
HTTP处理程序:
// presenter.go
type successWriter func(result interface{})
type errorWriter func(err error)
type Presenter interface {
OnSuccess(dw successWriter, result interface{})
OnError(ew errorWriter, err error, meta map[string]string) // we pass error and some extras
}
type resourcePresenter struct{}
func (p *resourcePresenter) OnSuccess(dw successWriter, data interface{}) {
preaparedResult = ... // prepare response data with data
dw(preaparedResult)
}
func (p *resourcePresenter) OnError(ew errorWriter, err error, meta map[string]string)
{
// here we can wrap error to add extra information
enrichedError := &MyError{ err: err, meta: meta }
ew(enrichedError)
}
以及呼叫控制器中的演示者:
// controller.go
// RegisterController attaches component to route.
func RegisterController(rg *gin.RouterGroup, uc MyUsecase) {
ctl := &Controller{
usecase: uc,
presenter: &resourcePresenter{},
}
rg.GET("", ctl.list())
}
func (ctl *aController) list() gin.HandlerFunc {
return func(c *gin.Context) {
usecaseResp, err := ctl.usecase.List()
if err != nil {
ctl.presenter.OnError(
func(err error) { // closure to pass the HTTP context (ResposeWriter)
c.Error(err) // add error to gin.Errors, middleware will handle it
},
err,
map[string]string{
"client": c.ClientIP(),
}
)
}
return
}
ctl.presenter.OnSuccess(func(data interface{}) {
fmt.Println(`ctl.presenter.OnSuccess `)
c.JSON(http.StatusOK, data)
}, usecaseResp)
}
我已经读过这篇文章,我认为这很好,但它也通过简单地返回响应而不从用例中调用演示者来解决问题。我宁愿看到解决办法。不过还是要谢谢你!我不认为这是鲍勃叔叔的意思,如果你把网络从控制器或演示器中拿出来怎么办?演示者应该被注入控制器,控制器应该被注入网络,网络需要处理响应、请求、cookie、标题等。这样,如果你有一个控制台应用程序,控制器和演示者的功能保持不变。但这是不可能的,因为go http处理程序的工作方式是一个闭包。@BoazHoch Controller它只是域用例和基础结构之间的粘合剂-它不可能是通用的。当一个人将任何新的基础设施连接到应用程序时,他必须创建与之相关的新控制器。例如,您需要附加CLI接口,因此可以通过“CLI控制器”将其粘附到域逻辑。可能是controller
这个词有误导性,我们考虑了不同的概念。我不明白为什么需要为每个基础设施创建一个新的控制器。如果控制器需要@input或实现某种定义良好的接口,那么所有其他基础设施都需要服从它。我认为应该提供一个新的适配器,负责转换来自新基础结构的传入数据