Skip to content

BindToProvider: Remember values requested from multiple providers #500

@abhinav

Description

@abhinav

I'm creating this issue to discuss a change before I try make the PR.

Context

BindToProvider takes a function in the form func(A, B, C, ...) (X, error) (or without the error return), where the arguments of the functions are dependencies of that provider, and the return value is the result.

For every dependency in the provider, if it's been bound with kong.Bind or kong.BindTo, the value is used as-is.
For values that come from other providers, those providers are invoked recursively, and the resulting value is used.

Problem

Based on API, I would expect that provided values are singletons. That is, if the value produced by a provider is requested from two other providers, the original provider is only invoked once.

In terms of code:

graph LR
  NewA["func NewA(C) A"]
  NewB["func NewB(C) B"]
  NewA & NewB -->|call| NewC["func NewC() C"]
Loading

Reproduction:

package main

import (
	"fmt"

	"github.com/alecthomas/kong"
)

type (
	Connection struct{}
	ClientA    struct{ conn *Connection }
	ClientB    struct{ conn *Connection }
)

func main() {
	var cmd mainCmd
	kctx := kong.Parse(&cmd,
		kong.BindToProvider(func() *Connection {
			fmt.Println("opening connection")
			return &Connection{}
		}),
		kong.BindToProvider(func(conn *Connection) *ClientA { return &ClientA{conn: conn} }),
		kong.BindToProvider(func(conn *Connection) *ClientB { return &ClientB{conn: conn} }),
	)
	kctx.FatalIfErrorf(kctx.Run())
}

type mainCmd struct{}

func (*mainCmd) Run(clientA *ClientA, clientB *ClientB) error {
	return nil
}

The following output is expected:

❯ go run .
opening connection

But we get:

❯ go run .
opening connection
opening connection

There is no de-duplication.
The provider for Connection is requested by two providers, and is invoked twice.

Solution

I believe that this was just an oversight, and that these providers were not intended to be re-invoked like this.
In that case, this is just a bug and it should be straightforward to fix: I am happy to provide a fix.

However, if this was intended behavior, I'd like to propose an opt-in means of having these values treated as singletons. This could be a kong.BindOnce() option.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions