Reputation: 1548
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:
main.go
file.Upvotes: 2
Views: 4118
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
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
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.
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
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