Skip to content

Latest commit

 

History

History
101 lines (78 loc) · 2.38 KB

README.md

File metadata and controls

101 lines (78 loc) · 2.38 KB

indi: simple initialization optimizer

Parallelize initialization of your dependencies with ease.

The problem

Imagine you have a big application that access same stateful resources in different places. You write something like this (go style!):

func main() {
    a := a.New()
    b := b.New(a)
    c := c.New(a, b)
}

Every step requires some time. You want to spend as little time as possible.

Now, it's not hard to see that the way initialization process goes is kind of the best already. You can't start with initializing c because it depends on a and b. And b depends on a. And you can't parallelize anything.

But as your code base grows, you have more and more such dependencies. And it becomes harder to keep track of them and initialize them the optimal way. You try to initialize several things in parallel.

func main() {
	var (
        a *A
        b *B
        ...
        z *Z
    )

    aReady := make(chan struct{})
    var wg sync.WaitGroup
    wg.Add(3)
	
    go func() {
        defer wg.Done()
        a = a.New()
        close(aReady)
        b = b.New(a)
        c = c.New(a, b)
    }()

    go func() {
        defer wg.Done()
        <-aReady
        d = d.New(a)
        e = e.New(d)
    }()
    
    go func() {
        ...
    }
}

You're not sure anymore that you are doing it the optimal way.

So you need a sort of dependency graph...

Here comes the indi

// Dependency tree:
// A -> B -> C
// A -> D

func main() {
    var (
        a A
        b B
        c C
        d D
    )

    indi.Declare(&a, func () (*A, error) { return NewA(&b, &d) }, &b, &d)
    indi.Declare(&b, func () (*B, error) { return NewB(&c) }, &c)
    indi.Declare(&c, NewC)
    indi.Declare(&d, NewD)

    err := indi.Init()
    ...
}

See example/main.go to get the idea.

You casually declare all the stuff regardless of the order, and indi takes care of it.

P.S. Generics are used instead of interface{} on purpose:

  1. To avoid type assertions (well, there are still some under the hood).
  2. To force you to place all the dirty initialization with actual types in main package, or init package, or wherever but far from the actual code that uses those dependencies. main is usually the place where you pass implementations as interface functions parameters.
  3. To support the principle "accept interfaces, return actual types".
  4. Just for fun!

Enjoy!