Best practice for for resolving a circular dependency in Go
Asked Answered
L

1

6

I have a Go project that contains both of these packages:

  • logging
    Imports config, because I want to send the current configuration to my error reporting system
  • config
    Imports logging, because I want to log if the program is unable to open the config files

What is the best practice for resolving this dependency error?

Llewellyn answered 3/3, 2019 at 5:53 Comment(1)
Possible duplicate of re-design circular dependency flawLucknow
S
9

In what format are you sending the configuration to the error reporting system? Maybe pass that to logging? Say, if it's json, then marshal the config before giving it to logging and then give logging the []byte result only.

But in general, if two packages depend on each other directly what you can do is have one of them declare an interface that matches the other one's behaviour and then have a separate package pass the concrete instance as the interface.

For example:

/myapp/config

import "myapp/logging"

type Config struct {
     // ...
}

func NewConfig() *Config {
    // ...
    if err != nil {
        logging.LogError(err)
    }
}

/myapp/logging

import "myapp/config"

func LogConfig(c *config.Config) {
    // ...
}

func LogError(err error) {
    // ...
}

So in this case you could declare an interface in the config package that matches the behaviour it needs from logging.

/myapp/config

type Logging interface {
    LogError(error)
}

var logging Logging

func SetLogging(l Logging) {
    logging = l
}

type Config struct {
     // ...
}

func NewConfig() *Config {
    // ...
    if err != nil {
        logging.LogError(err)
    }
}

And then have a type in the logging package implement that interface by simply delegating to the original functions.

/myapp/logging

import "myapp/config"

func LogConfig(c *config.Config) {
    // ...
}

func LogError(err error) {
    // ...
}

type Logging struct{}

func (Logging) LogError(err error) {
    LogError(err)
}

And lastly have a third package make it all work together.

/myapp

import "myapp/config"
import "myapp/logging"

func init() {
    config.SetLogging(logging.Logging{})
}
Sheldon answered 3/3, 2019 at 6:24 Comment(2)
Thank you very much for your answer! You helped me a lot.Llewellyn
This is a nice way to solve circular dependencies though for the specific config related circular dependencies see https://mcmap.net/q/1773626/-re-design-circular-dependency-flaw/5788720Lucknow

© 2022 - 2024 — McMap. All rights reserved.