Go Web App 中是不是需要有 DAL 和 BLL?

Posted

技术标签:

【中文标题】Go Web App 中是不是需要有 DAL 和 BLL?【英文标题】:Is it necessary to have DAL and BLL in Go Web App?Go Web App 中是否需要有 DAL 和 BLL? 【发布时间】:2017-10-19 00:05:40 【问题描述】:

在许多 Go 编程书籍中,作者通常将数据访问逻辑放在处理业务逻辑的同一个函数中。虽然我知道这可能只是出于教学目的,但我想知道人们是否真的在现实世界的开发中将 BLL 与 DAL 分开。

我曾尝试将分层设计应用到我的 Go 项目中,但并没有从中受益。例如,我的 DAL 函数通常是这样的(在 appdal 包中):

func GetCustomerAccountInfo (accountID int) (*sql.Rows, error) 
    sql := `SELECT * FROM CUSTOMER_ACCOUNT WHERE ID = $1`
    return GLOBAL_PSQL.Query(sql, accountID)

我的典型 BLL 函数是这样的:

func NewCustomerAccountBLL (accountID int) (* CustomerAccountBLL) 
    rows, err := appdal.GetCustomerAccountInfo(accountID)
    // create an instance of CustomerAccountBLL (bll) and scan rows....
    return &bll

我经常发现我的 BLL 本质上是与数据库模式耦合的,因为扫描需要我知道我的查询读取了哪一列,所以我发现将一些 DAL 函数合并到 BLL 中并不是一个坏主意(例如合并改为查询 BLL)。此外,拥有 DAL 也会增加我必须维护的代码量。

但是,许多软件架构师也鼓励分层设计,有 BLL 和 DAL 并在每一层上分配明确的职责是有意义的。

虽然我也了解设计和模式不一定依赖于编程语言,但我经常发现在我的项目中同时使用 BLL 和 DAL 并没有什么好处。我是否错过了设计或 Go 中的重要内容?谢谢!

【问题讨论】:

看看这是否有帮助:***.com/q/42791536/5779732,***.com/a/42500771/5779732,***.com/a/41824700/5779732 【参考方案1】:

正如您所指出的,这个问题不是 Go 特定的,可以适用于任何语言。

我认为您应该考虑以下几点:

与其他设计事项一样,没有正确的方法这样做,但一般做法是将业务逻辑与数据访问分开。

业务逻辑不应与实际的数据访问实现绑定,因此,如果您决定放弃 SQL 并将对象保存在普通文件或非 SQL 存储中,则不一定需要更改业务逻辑层。

在您的情况下,GetCustomerAccountInfo 返回sql.Rows。这实际上将您的业务逻辑与该特定实现相结合。通常的做法是返回实际的模型对象(例如 CustomerAccount)。

还请注意,您的示例非常简单,因此即使将其分开,您也可能看不到很多好处。但有时事情并没有那么简单。

数据访问逻辑可能涉及连接表的更复杂的查询,甚至在数据库事务中进行单独的查询。通过将其分开,您不会用这些低级细节污染业务逻辑。此外,您可以通过仅在数据访问层进行更改而不更改业务逻辑层来更改底层表结构。

业务逻辑也可能包含更复杂的计算,例如合并不同的对象、应用默认值和执行域验证。分离此逻辑(独立于所使用的存储)允许您更改业务逻辑,而不必更改数据访问逻辑。

基本上,通过将其分开,您可以分别开发(并且重要的是:测试)每个业务和数据访问逻辑,并拥有更加模块化的设计。

希望对你有帮助。

【讨论】:

非常感谢。但是,根据分层设计,DAL 不应该依赖于 BLL 的模型。让 DAL 函数返回 BLL 模型也会引入依赖循环问题(BLL 调用返回 BLL 对象的 DAL 函数)。这会是个问题吗?还是应该在这种情况下实现DAO? 您可以让 DAL 返回一个与 BLL 对象分离的 DAO。如果 BLL 对象足够复杂以至于从 DAL 构建它们会将 DAL 层与 BLL 逻辑联系起来,我只会这样做。否则,如果 BLL 对象很简单,那么从 DAO 到 BLL 的映射最终会成为一个微不足道但不必要的步骤,在这种情况下,从 DAL 返回 BLL 对象应该不是问题。这在很大程度上取决于您的实际用例。【参考方案2】:

如果您正在寻找实用的答案,这是我的想法之一。

假设您想要获取客户帐户,然后根据 API 的输入相应地对其进行修改。

所以编写数据层或查询看起来像这样:

type CustomerAccount struct
   id string // this data type will differ depends on your database.
   Name string
   Address string
   Age int
   // and any other attribute. this is just for example.



func (ca *CustomerAccount)GetCustomerAccount (id int) (CustomerAccount,error) 
  var ca CostumerAccount
  // write your query using any databases.
  // return an error if error happens when you do query to the database.

  return ca,nil
 

func (ca *CustomerAccount)SaveCustomerAccount(ca CustomerAccount) error 
  // find and update the data from given CustomerAccount
  return nil

保存上面命名customer_account.go的代码。

现在假设您想将数据库查询与您的业务逻辑或在本例中为您的 DAL 与 BLL 分离。您可以为此使用界面。创建一个与您的模型查询方法匹配的接口类型,如下所示:

type CustomerAccountInterface interface 
   GetCustomerAccount (id int) (CustomerAccount,error)
   SaveCustomerAccount(ca CustomerAccount) error

另存为customer_account_interface.go

现在我们想编写一个负责修改数据的业务逻辑,我们将调用CusomerAccountInterface 给业务逻辑。由于我们正在创建一个 API,所以我们为此使用了处理程序:

func EditCustomerAccount(ca CustomerAccountInterface) http.Handler 

  return http.HandleFunc(func(w http.ResponseWritter, r *http.Request)
     // get all the input from user using *http.Request like id and other input.

     // get our CustomerAccount Data to modify it
     customerAccount,err := ca.GetAccountCustomer(id)

     // modify customerAccount Accordingly from the input data, for example
      customerAccount.Name = inputName // you can change what ever you want with the data here. In this case we change the name only for example purpose.

     // save your customerAccount to your database
     err := ca.SaveCustomerAccount(customerAccount) 

     // send the response 200 ok resonse if no error happens
     w.WriteHeader(http.StatusOk) 
     resp := response // you can create your response struct in other places.
     resp.Message = "success update data"
     json.NewEncoder(w).Encode(resp)

  )


从上述方法中,我们将作为业务逻辑的处理程序与数据访问或查询数据库解耦,以便我们可以在处理程序中为业务逻辑创建一个单元测试,如下所示:

创建CustomerAccountMock 用于模拟来自数据访问的结果查询:

type CustomerAccountMock struct 
   err error
   Data CutstomerAccount


func (ca *CustomerAccountMock)GetCustomerAccount (id int) (CustomerAccount,error) 
  return ca.Data,nil
 

func (ca *CustomerAccountMock)SaveCustomerAccount(ca CustomerAccount) error 
  return ca.err

现在我们可以写出类似这样的测试:

func TestEditCustomerAccount(t *testing.T)
  testObjects := []struct
    CMock CutomerAccountMock
  
    
      CMock : CustomerAccountMock
         err : errors.New("Test error")
         Data : CustomerAccount // return an empty data 
      ,
    , 
  

  for _, testObject := range testObjects 
     actualResponse := createRequestToHandler(testObject.CMock)
     // here you can check your response from calling your request testing to your handler.

  


以上只是为了了解如何分离数据层和业务逻辑层。你可以参考我完整的source code here。该代码引用了另一个测试用例,例如更新驱动程序数据,但方法相同。

但是这种方法也有一些缺点,对我来说,就测试而言,这就像写数千篇文章一样,您必须耐心等待!。

所以来回答你的问题

Go Web App 中必须要有 DAL 和 BLL 吗?

是的,确实如此。将数据访问与业务逻辑层分开很重要,这样我们就可以对其进行单元测试。

在上面的示例中,逻辑非常简单,但是想象一下,如果您有一个复杂的逻辑来操作数据,并且您没有将 DAL 和 BLL 分开。当涉及到更改逻辑或查询时,它将在未来伤害您和其他开发人员。

在出现问题时感到害怕改变和沮丧绝对是你想要避免在你的职业生涯中发生的事情。

【讨论】:

以上是关于Go Web App 中是不是需要有 DAL 和 BLL?的主要内容,如果未能解决你的问题,请参考以下文章

静态方法是不是适用于 Linq To SQL DAL?

通用 DAL / BLL 类

为Go Web App 创建一个主页面

在 Web 应用程序中分离 BLL、PL 和 DAL

手动编写业务对象还是使用 DAL 对象?

Go Web 编程之 数据库