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:
- 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.
- 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).)
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 variableTx
of typegorm.DB
. -
Tx
is initialized throughConnInterceptor.ConnectionHandler()
as the result of callingdao.Db.Begin()
. - Package
dao
usesConnInterceptor.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.)
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).
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
.
Hello Mr.Christophberger, Thanks for the reply. I’m following your way now.
This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.