James
James

Reputation: 1548

Golang "import cycle not allowed" after splitting up my program into subpackages

I have a large Go program that is spread across 50+ miscellaneous Go files in the root of my package folder. I know that this is considered terrible, so I've decided to embark upon splitting up the program into some subpackages for better organization.

Unfortunately, after splitting off the logical parts of my programs into subpackages, I'm running into the dreaded "import cycle not allowed" error. This is because the Go compiler refuses to compile anything with circular imports. But the different logical parts of my program need to communicate with each other...

I've done some research online and found some excellent resources, like this excellent StackOverflow question that attempts to explain what to think about to solve this problem at a high level.

My apologies, but this post is way over my head, and I was wondering if someone could spell out an exact solution for my specific code situation, and hopefully in simpler language aimed at a complete beginner to Go.

A brief description of how my code is organized and what it does:

Upvotes: 2

Views: 4118

Answers (4)

WaltPurvis
WaltPurvis

Reputation: 1530

It sounds like the server/protocol packages are useful on their own, and the requirement to send a message from one kind of a server to another kind is a feature of your specific application. In other words, the server/protocol packages don't need to send messages to each other, your application needs to.

I usually put application-specific functionality into an app package. Package app can import all your protocol packages.

You can also do this in package main, but I've found an app package to be a more useful instrument. (My main package is usually just the single main.go file.)

Upvotes: 1

Adrian
Adrian

Reputation: 46452

In addition to the channel-based approaches proposed by TechSphinX and Oleg, you can use an interface-based approach and simple dependency injection.

You can use a setup function, probably in or called from main(), that creates instances of each service client. These should each implement Send() and have fields for the other clients they need to use. Create a Sender interface in its own package, and put your message struct in there as well.

After creating the instances, you can then set the other clients on each instance. This way they can send to whatever they need to send to, without circular dependencies. You can even put all the clients into a struct to make the injection easier.

For example:

// pkg sender
type Sender interface {
    Send(m Message) error // or whatever it needs to be
}

type Message struct {
    // Whatever goes in a message
}

type Dispatcher struct {
    TwitchClient Sender
    DiscordClient Sender
    WebClient Sender
}

// pkg main
func setup() {
    d := sender.Dispatcher{
        TwitchClient: twitch.New(),
        DiscordClient: discord.New(),
        WebClient: web.New(),
    }
    d.TwitchClient.Dispatcher = d
    d.DiscordClient.Dispatcher = d
    d.WebClient.Dispatcher = d
}

// pkg twitch
type TwitchClient struct {
    Dispatcher sender.Dispatcher
    // other fields ...
}

func New() *TwitchClient {
    return new(TwitchClient) // or whatever
}

func (t *TwitchClient) Send(m sender.Message) error {
    // send twitch message...

    // Need to send a Discord message?
    t.Dispatcher.DiscordClient.Send(m)
}

Upvotes: 2

TehSphinX
TehSphinX

Reputation: 7440

Tailored to your particular case:

From what you describe the only reason for the packages to import each other is that they need to call each others Send() functions.

Channels to communicate

Create channel(s) in main and give it to both packages on init. Then they can communicate with each other without knowing of each others existence.

Upvotes: 1

Oleg
Oleg

Reputation: 205

It looks like you want to keep your protocol specific code in separate packages. If you don't want much refactor, I'd suggest you to create a package with dispatcher. Each server imports dispatcher package and register a handler for specific protocol. When it needs to call another server, just send a message via dispatcher to specified handler.

Upvotes: 2

Related Questions