Cyclic Dependency

How to solve the cyclic package dependency problem in golang

Can you provide a sample of this problem? What are you trying to do?

As you do it everywhere… reorganize your code, so that you do not have cyclic dependencies…

Two possible options:

  1. Put tightly coupled code into the same package. (Thus removing one or more package dependencies.) And maybe you need to completely rethink your package structure in case your packages represent layers rather than responsibilities, based on the comments here and here.
  2. Use Interfaces to decouple code. (I wrote an article about this a while ago, and this thread on Reddit is also quite helpful (especially the comment from /u/JHunz).)
3 Likes

Actually the scenario is like , I want to use middleware that will trap every request and always create new transaction. It is like I’m taking the transaction object from DAO layer where I’ve written code of opening connection to database (because we can’t start the transaction before having connection) in below manner.
Here I’m giving you a sample code

                 package main
                 import "dao"
                 func main() {
                         dao.DBConfig()
                  }

           //Here I've not posted Controller and Service Code 

                 package dao
                 import 
                 (
                  _ "github.com/go-sql-driver/mysql"
                   "github.com/jinzhu/gorm"                     //ORM Library
                        "ConnInterceptor"                      //this is my middleware package created
                       _ "database/sql"
                  )

                  var Db *gorm.DB
                  func DBConfig() {
                  var err error
                  Db, err = gorm.Open("mysql", "root:root@tcp(127.0.0.1:3306)/employeedb?charset=utf8&parseTime=True")
                 if err != nil {
                            //Some error handling code
                  }
 	             Db.AutoMigrate(&dto.UserDTO{})
                    }

Now my ConnInterceptor package is need to work as a middleware that will trap every request and create new transaction for every request.
So for this I need to access that “Db” instance variable here from dao package so for this I’m accessing it.

                     package  ConnInterceptor              
                      import (
                                 "dao"
                                 "github.com/jinzhu/gorm"
                        )
                      var Tx *gorm.Db
                       func ConnectionHandler(next http.Handler) http.Handler {
                                return http.HandlerFunc(func(writer http.ResponseWriter, req *http.Request) {
                                          tx := dao.Db.Begin()
		                     Tx = tx
		                     next.ServeHTTP(writer, req)
		                     Tx.Commit()
		                      defer func() {
			               Tx.Rollback()
			               Tx.Close()
		                      }()
                          }

So ,when main ( ) starts It will create and open connection to database by calling dao.DBConfig( ) and after that when I’ll give request then it will call middleware ConnInterceptor first where I need to get “Db” object from dao package.So for this I’ve imported dao package and in dao also I’ve imported ConnInterceptor to get Tx object.

So when I’m executing (Above is just a sample code) it is giving me Import Package Cycle not allowed error.

I don’t see the ConnInterceptor package being used in the first code snippet. Where are you using it? Which functions or objects of ConnInterceptor are you accessing from within dao?

Yes you are rightt. I forgot to post that function. Here is my SaveUser( ) which is using that Tx object

        func (UDao *UserDAOImpl) SaveUser(userDto dto.UserDTO) (dto.UserDTO, error) {
	    var err error
        tx := ConnInterceptor.Tx           //Here I'm trying to access this Tx object
        err := tx.Save(&userDto).Error
        tx.Find(&userDto)
        return userDto, err
       }

Thanks. So to summarize what I can see:

  • Package ConnInterceptor provides a global variable Tx of type gorm.DB.
  • Tx is initialized through ConnInterceptor.ConnectionHandler() as the result of calling dao.Db.Begin().
  • Package dao uses ConnInterceptor.Tx when saving a user.

Question: Does Tx really belong to ConnInterceptor, or could you move it to dao as a global variable similar to dao.Db?

In this case, you only need to change ConnectionHandler to

func ConnectionHandler(next http.Handler) http.Handler {
    return http.HandlerFunc(func(writer http.ResponseWriter, req *http.Request) {
        dao.Tx := dao.Db.Begin()
        next.ServeHTTP(writer, req)
        dao.Tx.Commit()
        defer func() {
            dao.Tx.Rollback()
            dao.Tx.Close()
    }()
}

and dao then can access Tx directly and would not need to refer to ConnInterceptor anymore.

(As an aside, I recommend getting into the habit of formatting your Go code with go fmt, for better readability for you and others. Many programmer editors even have Go plugins that automatically format Go code when saving, so you do not have to do this manually.)

1 Like

Hello Mr.Christophberger, actually this Tx object I need to use in the ConnInterceptor only. And sorry to say about formatting as I am using gofmt but while posting I forgot to format it properly. Even If I create the Tx object in dao and inported here, this Tx object again I need to use in dao after this dao.Tx:=dao.Db.Begin( ).

actually this Tx object I need to use in the ConnInterceptor only.

But you do use it in dao, too (in the SaveUser() method); otherwise, you would not have to import ConnInspector into dao, right?

If you mainly use Tx in ConnInterceptor, then turn my previous question around and ask yourself, does SaveUser() need to use the Tx object, and if so, would SaveUser() better fit into the ConnInterceptor package?

Even If I create the Tx object in dao and inported here, this Tx object again I need to use in dao after this dao.Tx:=dao.Db.Begin( ).

Not sure if I can follow you here. When you move Tx to package dao, then you can access it from within dao as “Tx”, and from within ConnInspector as “dao.Tx”.

From an architectural point of view, if the variable Db (a database) belongs to package dao, then so would Tx (a database transaction).

1 Like

Hello Mr. Chrisrophberger, Thanks for the instant reply. If I do the way you are saying like taking Tx object in dao and access it in ConnInterceptor, yes this one is fine but when I’m using this Tx object for my SaveUser( ) then I need this Tx object in SaveUser( ) from ConnInterceptor only. Because I have to start transaction in ConneInterceptor like dao.Db.Begin( ) so that time value of Tx object will change as it will start the transaction. If I use the Tx object value from dao package that one will different. Is this right ??

I am not sure if I can follow you. SaveUser() is in the dao package, right? So if Tx is in dao, then SaveUser() can access Tx directly.

Because I have to start transaction in ConneInterceptor like dao.Db.Begin( ) so that time value of Tx object will change as it will start the transaction. If I use the Tx object value from dao package that one will different.

Again, not sure if I can follow you. What will be different with regard to the value of the Tx object when you move Tx into the dao package? Basically, you only change the access path to Tx.

1 Like

Hello Mr.Christophberger, Thanks for the reply. I’m following your way now.

1 Like

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.