Fixing import cycle in Go - go

So I have this import cycle which I'm trying to solve. I have this following pattern:
view/
- view.go
action/
- action.go
- register.go
And the general idea is that actions are performed on a view, and are executed by the view:
// view.go
type View struct {
Name string
}
// action.go
func ChangeName(v *view.View) {
v.Name = "new name"
}
// register.go
const Register = map[string]func(v *view.View) {
"ChangeName": ChangeName,
}
And then in view.go we invoke this:
func (v *View) doThings() {
if action, exists := action.Register["ChangeName"]; exists {
action(v)
}
}
But this causes a cycle because View depends on the Action package, and vice versa. How can I solve
this cycle? Is there a different way to approach this?

An import cycle indicates a fundamentally faulty design. Broadly speaking, you're looking at one of the following:
You're mixing concerns. Perhaps view shouldn't be accessing action.Register at all, or perhaps action shouldn't be responsible for changing the names of views (or both). This seems the most likely.
You're relying on a concretion where you should be relying on an interface and injecting a concretion. For example, rather than the view accessing action.Register directly, it could call a method on an interface type defined within view, and injected into the View object when it is constructed.
You need one or more additional, separate packages to hold the logic used by both the view and action packages, but which calls out to neither.
Generally speaking, you want to architect an application so that you have three basic types of packages:
Wholly self-contained packages, which reference no other first-party packages (they can of course reference the standard library or other third-party packages).
Logic packages whose only internal dependencies are of type 1 above, i.e., wholly self-contained packages. These packages should not rely on each other or on those of type 3 below.
"Wiring" packages, which mostly interact with the logic packages, and handle instantiation, initialization, configuration, and injection of dependencies. These can depend on any other package except for other type 3 packages. You should need very, very few packages of this type - often just one, main, but occasionally two or three for more complex applications.

Basically, you are able to break dependencies by introducing interface and injecting the interface instead of a struct.
With your example it would look like:
// view.go
package view
import "import_cycles/action"
type View struct {
Name string
}
func (v *View) ModifyName(name string) {
v.Name = name
}
func (v *View) DoThings() {
if action, exists := action.Register["ChangeName"]; exists {
action(v)
}
}
// action.go
package action
func ChangeName(v NameChanger) {
v.ModifyName("new name")
}
// register.go
package action
type NameChanger interface {
ModifyName(name string)
}
var Register = map[string]func(v NameChanger){
"ChangeName": ChangeName,
}
Please note that NameChanger interface is introduced. Here following things to be point:
this interface injected in function ChangeName instead of passing struct
struct View is implementing this interface
As a result package "action" no more need to import package "view" since the interface is placed in the same package "action"
In main.go we can test the result:
v := &view.View{
Name: "some name",
}
v.DoThings()
fmt.Println(v)
// &{new name}

In my own case, I created a simple import cycle in my unit tests. My normal app was fine.
To answer your question on fixing, first I isolated the function that caused the import cycle. In my case, the import cycle only happened when running tests.
Then I checked what type of import cycle. This helped to visualize the error. I found that Package B tests depended on Package A.
I moved the tests into Package A and no more import cycle [ and cleaner tests ].

Import cycles are the result of a design error. Structs which depend on each other in both directions must be in the same package, or else an import cycle will occur. By the way, Go is not the only programming language with this restriction. It also exist in C++ and Python, for example.

Related

In GoLang, how do you isolate packages that return more than a primitive value?

If I have a requests package that defines an interface TextExtractor with a GetText method that returns a Text type, the implementations must fulfill the TextExtractor contract exactly, and they are forced to import the Text type.
I have two possible implementations of TextExtractor - one that uses AWS Comprehend and one that uses AWS Textract.
aws_comprehend.go
package aws_comprehend
type AwsComprehend struct{}
func (a *AwsComprehend) GetText() *Text {
// do some stuff with aws comprehend...
return &Text{
Text: "text",
Language: "language",
}
}
type Text struct {
Text string
Language string
}
request.go
package requests
import "fmt"
type TextExtractor interface {
GetText() *Text
}
type Text struct {
Text string
Language string
}
func HandleRequest(textExtractor TextExtractor) {
text := textExtractor.GetText()
fmt.Println(text)
}
main.go
package main
import (
"aws_comprehend"
"requests"
)
func main() {
textExtractor := new(aws_comprehend.AwsComprehend)
requests.HandleRequest(textExtractor)
// this does not work:
// cannot use textExtractor (variable of type *aws_comprehend.AwsComprehend) as
// requests.TextExtractor value in argument to requests.HandleRequest:
// *aws_comprehend.AwsComprehend does not implement requests.TextExtractor
// (wrong type for method GetText)
// have GetText() *aws_comprehend.Text
// want GetText() *requests.Text
}
I understand why this doesn't work. It's because GoLang doesn't support Covariant Result Types. But my question is, what is the standard way to code this situation? The fact that GoLang provides implicit interfaces means that isolating packages is very easy: the calling package defines the interfaces that it uses, and it is passed implementations that fulfill those interfaces. This means that packages don't have to reference each other at all. But if a package defines an interface that returns anything more than a primitive value, then you have to deliberately share those value types. The code above would be fine if GetText returned a string. But the fact that it returns a struct or another interface, means the code can't be written this way.
I want the requests package not to know anything about the aws_comprehend package. This is because I have two implementations of the TextExtractor interface: One that uses AWS Comprehend, and one that uses AWS Textract. I also would prefer not to include a "intermediate" package that has interfaces that both the requests package and the aws_comprehend package inherit from. If both packages have to inherit the same interface, then it seems like it's just indirect coupling to me and it ruins the idea of a implicit interfaces.
I understand that GoLang is very opinionated - So what is the standard way to solve this problem?
first, your file layout is not valid. you cant have two files in the same
folder, with different packages. so below is a corrected layout. also I removed
all pointers, because they aren't needed for this example and are just distracting.
Finally, I updated the method to return the correct type, so that the code
actually compiles:
aws_comprehend/aws_comprehend.go:
package aws_comprehend
import "hello/requests"
type AwsComprehend struct{}
func (AwsComprehend) GetText() requests.Text {
return requests.Text{}
}
requests/request.go:
package requests
import "fmt"
type TextExtractor interface {
GetText() Text
}
type Text struct {
Language string
Text string
}
func HandleRequest(textExtractor TextExtractor) {
text := textExtractor.GetText()
fmt.Println(text)
}
main.go:
package main
import (
"hello/aws_comprehend"
"hello/requests"
)
func main() {
var textExtractor aws_comprehend.AwsComprehend
requests.HandleRequest(textExtractor)
}

Unable to work with interface imported from another pkg, says method is missing but its there

I'm unable to work with an interface imported from another package and not sure what I should do. Example code:
var inner types.TxData // <-- interface imported from types package
inner = &InternalTx{
ChainId: (*big.Int)(dec.ChainID),
Data: *dec.Data,
}
Out of all the methods listed in the interface, 1 method is not accepted:
func (tx *InternalTx) accessList() types.AccessList { return nil }
Go complains that InternalTx does not implement accessList() to satisfy types.TxData interface, but if i capitalise accessList() to Accesslist() then I get another complaint stating that :
have AccessList() types.AccessList
want accessList() types.AccessList
So i'm very confused what I need to do here?
Edit:
I've implemented also the following based on recent suggestion:
type TxData struct {
types.TxData
}
var inner TxData
internalTx := &InternalTx{
ChainId: (*big.Int)(dec.ChainID),
Data: *dec.Data,
}
inner = TxData{TxData: internalTx}
Issue still persists.
If an interface is declared with an unexported method (such as accessList), then you cannot implement that interface in another package, because that name is not exported. This is a common way to force you to use implementations in the original package.
One way to deal with this is to embed that interface, and then delegate functionality to an implementation of it:
type TxData struct {
types.TxData
}
someImplementation:=// Get an implementation of types.TxData
inner:=TxData{TxData: someImplementation}
This will use the accessList of someImplementation.

Export functions from internal package in Go

I'm testing the idea of putting most of my code in an internal package and then picking exactly which of the methods / types from there I'm exposing to the outside world. Code would look like:
/mypackage/internal/catapult
package catapult
func Load(boulder Boulder) {
// ...
}
func Trigger() *Launch {
// ...
}
// ...
Maybe Load is being called by other internal packages (/mypackage/internal/randomevents/boredsoldier and /mypackage/internal/actualattackstrategy) but shouldn't be allowed by users outside of internal. All those are allowed to do is Trigger the catapult once it's loaded.
So now I'd like to have a package above internal (/mypackage/general) where Trigger is exposed but not Load. I was hoping to do something like:
package general
const TriggerCatapult = catapult.Trigger
// ^ does not work because a function cannot be a const
var TriggerCatapult = catapult.Trigger
// ^ technically works but now the value of TriggerCatapult can be overwritten by any package user
func TriggerCatapult() *catapult.Launch {
return catapult.Trigger()
}
// ^ works. It's just "painful" to have to reproduce the entire function's signature every time
Is there a better way to do this?
No, there is no better way to do this that the way you provide:
func TriggerCatapult() *catapult.Launch {
return catapult.Trigger()
}
You shouldn't return unexported types though, and most linters would catch this for you.
If a user is going to interact directly with things in catapult, then that package should not be internal.

How can an external package implement an interface implicitly?

I'm writing a piece of code that relies on some implementation.
I want to decouple the implementation from my code, and make the implementation as independent as possible.
I thought of achieving this approach by using interfaces instead of concrete types, like so:
package mypackage
type MyType interface {
Title() string
Price() int
}
type TypeGetter interface {
GetType() MyType
}
func MyHandler(tg TypeGetter) {
t := tg.GetType()
fmt.Printf("Title: %s, Price: %d", t.Title(), t.Price())
}
And an implementation might be something like this:
package external
// CustomType implicitly implements the MyType interface
type CustomType struct {
title string
price int
}
func (t CustomType) Title() string { return t.title }
func (t CustomType) Price() int { return t.price }
// CustomTypeGetter implicitly implements the TypeGetter interface. Or is it???
type CustomTypeGetter struct {
}
func (g CustomTypeGetter) GetType() CustomType {
return CustomType{"Hello", 42}
}
Then, the code would do something like this:
package main
import "mypackage"
import "external"
func main() {
tg := external.CustomTypeGetter{}
mypackage.MyHandler(tg) // <--- the compiler does not like this
}
I hope the example speaks for itself: I have no coupling between "mypackage" and the "external" package, which may be replaced, substituted my mocks for testing, etc.
The problem: the compiler complains that the call to MyHandler has an object that implements:
func GetType() CustomType, instead of:
func GetType() MyType
The only solution I found is to move the interface declarations (MyType and TypeGetter) to a third package, and then both "mypackage" and "external" packages can use it.
But I want to avoid that.
Isn't Go's concept of implicit implementation of interfaces contradict the idea of a third common package?
Is there a way to implement such thing, without binding the two packages together?
Isn't Go's concept of implicit implementation of interfaces contradict the idea of a third common package?
I think it does. Go authors introduced an implicit interface implementation to eliminate unnecessary dependencies between packages. That works well for simple interfaces like io.Reader, but you cannot apply it everywhere.
One of the language creators, Rob Pike, says that the non-declarative satisfaction of interfaces is not the essential part of the idea behind interfaces in Go. It's a nice feature, but not all elements of the language are practical or possible to use every time.
For complex interfaces, you need to import a package where the interface is defined. For example, if you want to implement an SQL driver that works with the sql package from the standard library, you must import the sql/driver package.
I would recommend not introducing interfaces at the beginning of your project. Usually, it leads to situations where you need to solve artificial problems like rewriting the interface each time you updates your understanding of the domain model. It is hard to come up with a good abstraction from the first attempt, and, in many cases, it is unnecessary, in my opinion.
I need to query external source for products. I don't care how the external sources store the data (db, file, network). I just need a "product" type. So it's either I define a Product type, forcing the external implementations to import and use it, or the Go way - define a Product interface and let the implementations implicitly implement this interface. Which apparently doesn't work
I see two loosely related goals here:
Define an interface to swap implementations of the product source.
A package that implements the product source should not import the package that defines the interface.
From my experience, I would recommend doing point 1 only when you have at least one working implementation of the product source service.
Point 2 is not always possible to achieve, and it is fine; please see the example from the standard Go library above.
P.S.
Please, consider not creating Product interface. While it does makes sense to come up with the PorductSource interface eventually, Product is most probably just a set of data; struct is a perfect way to represent such information. Please, see this very relevant code smaple and this article for inspiration.
The problem with your approach is that you want someone to implement an interface that refers to your type (MyType). This obviously cannot be done without the implementation referring to your type. This is the only thing that prevents the above code from working.
If you get rid of the MyType:
type TypeGetter interface {
GetType() interface {
Title() string
Price() int
}
}
And the implementation:
func (g CustomTypeGetter) GetType() interface {
Title() string
Price() int
} {
return CustomType{"Hello", 42}
}
Then this code will work:
func main() {
tg := external.CustomTypeGetter{}
mypackage.MyHandler(tg)
}
Yes, this requires repetition, but only because you don't want an unknown / future implementation to refer to your type (to not depend on it).
In this case you may change MyHandler() to take a value of type MyType (get rid of the "factory"):
func MyHandler(t MyType) {
fmt.Printf("Title: %s, Price: %d", t.Title(), t.Price())
}
And any value that implements MyType may be passed. Add a "factory" to the external package:
func NewCustomType(title string, price int) CustomType {
return CustomType{
title: title,
price: price,
}
}
And use it like this:
func main() {
t := external.NewCustomType("title", 1)
mypackage.MyHandler(t)
}
If you truly require the factory pattern, then yes, creating a 3rd package that will hold MyType is the way to go. Then both your app and the implementations may refer to this 3rd package.

Implement interface using custom type without importing package

Suppose there's a 3rd party package that makes an interface available:
package giantpackage
type UselessInfo struct {
wontUse string
alsoWontUse string
}
type CoolInterface interface {
DoSomethingAwesome(ui UselessInfo) (string, error)
}
It seems easy to implement as such:
package main
import giantpackage
type Jedi struct {
name string
age int
}
func (j Jedi) DoSomethingAwesome(ui giantpackage.UselessInfo) (string, error)
return "Hello world.", nil
}
Assuming:
1) I don't actually use the UselessInfo struct in my DoSomethingAwesome function.
2) The package that I have to import is HUGE.
3) The package that I have to import is no longer maintained, and cannot be modified.
My question:
Is there any way to implement CoolInterface without importing giantpackage?
There is not.
To implement the giantpackage.CoolInterface, your type must have a method:
DoSomethingAwesome(giantpackage.UselessInfo) (string, error)
And to have a method that matches this signature, you have to import giantpackage, else using any other type for the parameter, it won't match the required method.
In your comments you indicated you plan to create a library, and providing an implementation of this interface will be a "nice to have" feature to some of the users of your library.
Then the recommended way is to create a "core" package of your library which does not contain this interface implementation and thus does not depend on giantpackage. Users of your library that do not need this will only import your "core" package and so they will also not depend on giantpackage.
Create another "extension" package of your library which will contain the implementation of this interface (and which may also use your "core" package if needed). Users that do need this can import this package as well.

Resources