1

In many Go programming books, the author usually put data access logic within the same function that handles business logic. While I understand that this may be merely for teaching purposes, but I wonder if people actually separate BLL from DAL in real world development.

I have tried to apply layered design to my Go project but have not felt any benefits from it. For instance, my DAL functions are typically like this (in appdal package):

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

And my typical BLL functions would be something like this:

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

Often I found that my BLL is essentially coupled with the database schema since scanning requires that I know which column that my query has read, so I found it's not a bad idea to merge some DAL function into BLL (such as merge the query to BLL instead). In addition, having a DAL also increases the amount of code that I have to maintain as well.

However, many software architects also encourages layered designs, and it makes sense to have BLL and DAL and assign clear responsibilities on each layer.

While I also understand design and patterns are not necessarily dependent on programming languages, but I often found little benefit from having both BLL and DAL in my project. Am I missing something important in design or Go? Thanks!

fallaway
  • 423
  • 2
  • 10
  • See if this helps: http://stackoverflow.com/q/42791536/5779732, http://stackoverflow.com/a/42500771/5779732, http://stackoverflow.com/a/41824700/5779732 – Amit Joshi May 19 '17 at 06:04

2 Answers2

5

As noted by you, this question is not Go-specific and can apply to any language.

Here are some points I think you should consider regarding this:

  • As with other design matters, there is no right way of doing this, but the general practice is to actually separate Business Logic from Data Access.

  • Business Logic should not be tied to the actual Data Access implementation so that if you then decide to move away from SQL and save objects in plain files, or in a No-SQL storage you don't necessarily need to change the Business Logic layer.

  • In your case, the GetCustomerAccountInfo returns sql.Rows. This actually couples your Business Logic to that particular implementation. The usual practice is to return actual model objects (a CustomerAccount for example).

  • Also note that your example is quite simple so it's true that even by separating it you might not see a lot of benefit. But sometimes things are not that simple.

    • Data Access logic might involve more complex queries joining tables, or even making separate queries inside a database transaction. By separating this you don't pollute Business Logic with these low level details. Also, you can change the underlying table structure by only making changes on the Data Access layer, without altering business logic layer.

    • Business Logic might also in term consist on more complex calculations such as merging different objects, applying defaults and perform domain validations. Separating this logic (which is independent of the storage being used) allows you to change Business Logic without having to necessarily change Data Access logic.

Basically, by separating this, you can develop (and also importantly: test) each of the Business and Data Access logic separately and have a more modular design.

I hope that helps.

eugenioy
  • 11,825
  • 28
  • 35
  • Much appreciated. However, according to layered design, DAL is not supposed to be dependent on BLL's model. Having DAL functions returning BLL models can also introduce dependency cycle issue (BLL calls' DAL function that returns a BLL object). Would this be a problem? Or should DAO be implemented in this case? – fallaway May 19 '17 at 03:18
  • You can have the DAL return a DAO which is separate from the BLL object. I would only do that if BLL objects are complex enough that building them from the DAL would tie DAL layer to BLL logic. Otherwise if BLL objects are simple, the mapping from DAO to BLL ends up being a trivial but unnecessary step, and in that case returning BLL objects from the DAL should not be an issue. This depends a lot on your actual use case. – eugenioy May 19 '17 at 12:08
2

If you are looking for practical answer here is one of my thought.

Lets say that you wanted to get the Customer Account and then modify it accordingly to the input from the API.

So writing the data layer or query would look something like this :

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
}

Save the code above naming customer_account.go.

Now lets say that you want to decouple the database query from your business logic or in this case your DAL with BLL. you can use interface for that. Creating an interface type that match witch your model query method above like this :

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

save it as customer_account_interface.go.

And now we would like to write a business logic which will be responsible for the modifying the data and we will call the CusomerAccountInterface to the business Logic. Since we're creating an API so wen we used handler for this :

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)

  })

}

From the above approach we have decoupled the handler which is the business logic with the data access or query database so that we can create a unit test for the business Logic in the handler something like this :

Creating CustomerAccountMock for to mock the result query from data access :

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
}

Now we can write out test something like this :

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.

  }

}

Above just for getting the idea how to I approach on separate the data layer and business logic layer. you can refer to my complete source code here. The code refers to another test case like updating driver data but it is the same approach.

But there are some cons on this approach though, for me it is like writing a thousands article when it comes to testing, you have to be patience!.

So coming to your question

Is it necessary to have DAL and BLL in Go Web App?

Yes, it does. Separating the data access with the business logic layer it's important so that we can unit test it.

In the above example the logic is pretty simple, but imagine if you have a complex logic to manipulate the data and you are not separate the DAL and BLL. It will hurt you in the future and other developer when it comes to changes the logic or query.

Feeling afraid to change and frustrated when something gone wrong is definitely you want to avoid to happen in your professional life.

Gujarat Santana
  • 9,854
  • 17
  • 53
  • 75