Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use dependency injection such as dig for commands #1041

Closed
YuriyChmil opened this issue Feb 22, 2020 · 2 comments
Closed

Use dependency injection such as dig for commands #1041

YuriyChmil opened this issue Feb 22, 2020 · 2 comments
Labels
kind/support Questions, supporting users, etc.

Comments

@YuriyChmil
Copy link

YuriyChmil commented Feb 22, 2020

Hello! I am using the dig package as a dependency injection tool for my project.
I have one command with has some dependency:

package cmd

import (
	"github.com/test/event-analytic/db"
	"github.com/test/event-analytic/db/commands"
	"github.com/test/event-analytic/di"
	"github.com/spf13/cobra"
)
var serveCmd = &cobra.Command{
	Use:   "migrate",
	Short: "Run migrations for clickhouse database",
	Run: func(cmd *cobra.Command, args []string) {
		container := di.BuildContainer()
		container.Invoke(func(client *db.ClickHouseClient) {
			commands.Migrate(client)
		})
	},
}

func init() {
	rootCmd.AddCommand(serveCmd)
}

All works fine, but in case if I need create more commands I need to repeat this stuff:

container := di.BuildContainer()
		container.Invoke(func(client *db.ClickHouseClient) {
			newCommand
		})

every time.
Are there any possibility to pass container once in main file, something like this:

package main

import (
	"github.com/joho/godotenv"
	"log"
	"os"
)

func main() {
	err := godotenv.Load(os.Getenv("ENV_FILE"))
	if err != nil {
		log.Fatal("Error loading .env file")
	}
        container := di.BuildContainer()
	cmd.Execute(container)
}

And then in custom command use something like this:

var serveCmd = &cobra.Command{
	Use:   "migrate",
	Short: "Run migrations for clickhouse database",
	Run: func(cmd *cobra.Command, args []string, container ) {
		container.Invoke(func(client *db.ClickHouseClient) {
			commands.Migrate(client)
		})
	},
}

@jharshman jharshman added the kind/support Questions, supporting users, etc. label Feb 26, 2020
@github-actions
Copy link

This issue is being marked as stale due to a long period of inactivity

@johnSchnake
Copy link
Collaborator

I think the pattern you're looking for is something like below. You don't define the commands directly but create functions that will create closures around that variable:

package main

import (
	"github.com/joho/godotenv"
	"log"
	"os"
)

func main() {
	err := godotenv.Load(os.Getenv("ENV_FILE"))
	if err != nil {
		log.Fatal("Error loading .env file")
	}
        container := di.BuildContainer()
        ... 
        rootCmd.AddCommand(newServeCmd(container))
        ...
}

func newServeCmd(c Container) *cobra.Command{
  return &cobra.Command{
	Use:   "migrate",
	Short: "Run migrations for clickhouse database",
	Run: func(cmd *cobra.Command, args []string, container ) {
		c.Invoke(func(client *db.ClickHouseClient) {
			commands.Migrate(client)
		})
	},
}

Closing as part of #1600 but feel free to reopen or discuss if you still need any assistance.

mbland added a commit to mbland/elistman that referenced this issue May 21, 2023
Enabling dependency injection using a cobra.Command factory
-----------------------------------------------------------
While searching for ideas on how to make Cobra commands testable, I
stumbled upon:

- spf13/cobra#1041 (comment)

Basically, the idea is to create a factory that can close values around
the cobra.Command structure and its functions. init() can call it with
sensible defaults, and tests can inject what they want.

Injecting factories into the factory
------------------------------------
The problem remained of how to instantiate an AWS config once, on
demand, and then use it to create other AWS clients as needed.
Eventually I realized that we can inject other factory functions, hence
AwsConfigFactoryFunc and DynamoDbFactoryFunc.

Though I haven't written any tests, in this way, I can inject factories
that return a null AWS config and a DynamoDb with a TestDynamoDbClient.
(After I move that lattermost class to testutils, of course.)

AWS related interfaces and helpers in cmd/aws.go
------------------------------------------------
I also realized that every command that relies on AWS will have to load
the config and check its err value. Hence the NewAwsCommandFunc for
wrapping the run function, which takes an additional aws.Config
argument.

Adding these AWS related interfaces and helpers to cmd/aws.go seemed to
sense, as opposed to creating another package. But that's always an
option if I change my mind later.

Disabling usage on error via Command.SilenceUsage
-------------------------------------------------

Finally, it was annoying that whenever the run function returned an
error, Cobra would also print the usage message. A quick search turned
up the recommendation to set cmd.SilenceUsage = true in the run
function, which did the trick:

- spf13/cobra#340 (comment)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind/support Questions, supporting users, etc.
Projects
None yet
Development

No branches or pull requests

3 participants