在许多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吗?谢谢!
答案 0 :(得分:4)
正如您所指出的,这个问题不是Go特定的,可以适用于任何语言。
以下是我认为你应该考虑的一些观点:
与其他设计事项一样,没有正确的方式这样做,但通常的做法是将业务逻辑与数据访问分开。
业务逻辑不应该与实际的数据访问实现相关联,这样如果您决定放弃SQL并将对象保存在普通文件中,或者保存在No-SQL存储中,则不必需要更改业务逻辑层。
在您的情况下,GetCustomerAccountInfo
会返回sql.Rows
。这实际上将您的业务逻辑与特定实现相结合。通常的做法是返回实际的模型对象(例如CustomerAccount
)。
另请注意,您的示例非常简单,所以即使将其分开,您也可能看不到很多好处。但有时情况并非如此简单。
数据访问逻辑可能涉及更复杂的查询连接表,甚至在数据库事务中进行单独的查询。通过分离,您不会使用这些低级细节污染业务逻辑。此外,您可以通过仅对数据访问层进行更改来更改基础表结构,而无需更改业务逻辑层。
业务逻辑也可能包含更复杂的计算,例如合并不同的对象,应用默认值以及执行域验证。分离此逻辑(与所使用的存储无关)允许您更改业务逻辑,而无需更改数据访问逻辑。
基本上,通过分离它,您可以单独开发(并且同样重要的是:测试)每个业务和数据访问逻辑,并具有更模块化的设计。
我希望有所帮助。
答案 1 :(得分:1)
如果你正在寻找实际的答案,这是我的一个想法。
假设您想要获取客户帐户,然后根据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分开。在更改逻辑或查询时,它将在未来和其他开发人员中受到伤害。
当出现问题时,感到害怕改变和沮丧绝对是你想避免在职业生涯中发生的事情。