Skip to content

πŸš€ Blazingly fast getting rid of uber-dig in your code

License

Notifications You must be signed in to change notification settings

3timeslazy/anti-dig

Folders and files

NameName
Last commit message
Last commit date

Latest commit

b6f66cb Β· Aug 6, 2024

History

44 Commits
Jun 26, 2023
Oct 15, 2023
Oct 15, 2023
Jun 14, 2023
Apr 2, 2023
Apr 2, 2023
Apr 2, 2023
Aug 6, 2024
Apr 2, 2023
Apr 2, 2023
Aug 6, 2024
May 20, 2023
May 20, 2023
Apr 2, 2023
Apr 10, 2023
Apr 2, 2023
Apr 2, 2023
Apr 2, 2023
Apr 2, 2023
Apr 2, 2023
Apr 2, 2023
Apr 2, 2023
Apr 2, 2023
Apr 2, 2023
Apr 2, 2023
Jun 18, 2023
Jun 18, 2023
Apr 2, 2023
Apr 2, 2023
Apr 2, 2023
Apr 2, 2023
Apr 12, 2023
May 16, 2023
Apr 2, 2023
Apr 2, 2023
Apr 2, 2023
Jul 2, 2023
Apr 2, 2023
Apr 2, 2023
Apr 2, 2023
Apr 2, 2023
Apr 2, 2023
Apr 2, 2023
Apr 2, 2023
Apr 2, 2023
Apr 2, 2023
Apr 2, 2023

Repository files navigation

πŸͺš anti-dig

An anti-dependency-injection drop-in replacement toolkit for go.uber.org/dig.

Why?

I have worked in many companies. In every one of them, I've seen someone using go.uber.org/dig. And in each case, after a while the team wanted to get rid of it, but it didn't always work because it always took a lot of time and effort. Faced with this problem again, I decided to write a tool to help others get rid of the library.

Here are the slides from the original Berlin Go Meetup, where anti-dig was initially presented. In the case Google killed the Slides, there is a pdf file in the repo.

Table of Contents

Guide

To use anti-dig, follow the steps:

  1. Replace go.uber.org/dig with github.com/3timeslazy/anti-dig everywhere in your code
import (
    dig "github.com/3timeslazy/anti-dig" // instead of "go.uber.org/dig"
)
  1. Download the package by running go mod tidy

  2. Run your main() function. It will generate a new file with explicit initialization of your dependencies

Example

Suppose we have a main() function with several providers passed to dig

package main

import (
	"github.com/3timeslazy/anti-dig/example/config"
	"github.com/3timeslazy/anti-dig/example/consumer"
	"github.com/3timeslazy/anti-dig/example/consumer/queue"
	"github.com/3timeslazy/anti-dig/example/cron"
	"github.com/3timeslazy/anti-dig/example/db"
	grpcserv "github.com/3timeslazy/anti-dig/example/grpc/server"
	"github.com/3timeslazy/anti-dig/example/handlers/flatten"
	"github.com/3timeslazy/anti-dig/example/handlers/handlerv0"
	"github.com/3timeslazy/anti-dig/example/handlers/handlerv1"
	httpserv "github.com/3timeslazy/anti-dig/example/http/server"
	"github.com/3timeslazy/anti-dig/example/observability"

 	dig "github.com/3timeslazy/anti-dig" // "go.uber.org/dig"
)

func main() {
	container := dig.New()

	err := container.Provide(observability.NewObservability)
	if err != nil {
		panic(err)
	}
	err = container.Provide(flatten.NewListOfHandlers)
	if err != nil {
		panic(err)
	}
	err = container.Provide(handlerv0.NewHandlerV0, dig.Group("http_handlers"))
	if err != nil {
		panic(err)
	}
	err = container.Provide(handlerv1.NewHandlerV1)
	if err != nil {
		panic(err)
	}
	err = container.Provide(cron.NewCron)
	if err != nil {
		panic(err)
	}
	err = container.Provide(db.NewDB)
	if err != nil {
		panic(err)
	}
	err = container.Provide(config.NewConfig)
	if err != nil {
		panic(err)
	}
	err = container.Provide(httpserv.NewServer)
	if err != nil {
		panic(err)
	}
	err = container.Provide(grpcserv.NewServer)
	if err != nil {
		panic(err)
	}
	err = container.Provide(consumer.New)
	if err != nil {
		panic(err)
	}
	err = container.Provide(queue.New1, dig.Name("queue_1"))
	if err != nil {
		panic(err)
	}
	err = container.Provide(queue.New2, dig.Name("queue_2"))
	if err != nil {
		panic(err)
	}
	err = container.Invoke(Run)
	if err != nil {
		panic(err)
	}
}

The code above will generate the following:

package main

import (
	"github.com/3timeslazy/anti-dig/example/config"
	"github.com/3timeslazy/anti-dig/example/consumer"
	"github.com/3timeslazy/anti-dig/example/consumer/queue"
	"github.com/3timeslazy/anti-dig/example/cron"
	"github.com/3timeslazy/anti-dig/example/db"
	grpcserver "github.com/3timeslazy/anti-dig/example/grpc/server"
	"github.com/3timeslazy/anti-dig/example/handlers"
	"github.com/3timeslazy/anti-dig/example/handlers/flatten"
	"github.com/3timeslazy/anti-dig/example/handlers/handlerv0"
	"github.com/3timeslazy/anti-dig/example/handlers/handlerv1"
	"github.com/3timeslazy/anti-dig/example/http/server"
	"github.com/3timeslazy/anti-dig/example/observability"
)

func Provide() (cron.Cron, *server.Server, *grpcserver.Server) {
	config := config.NewConfig()
	db, err := db.NewDB(config)
	if err != nil {
		return nil, nil, nil
	}
	queue1 := queue.New1()
	queue := queue.New2()
	consumerParams := consumer.ConsumerParams{
		Queue1: queue1,
		Queue2: queue,
	}
	consumer := consumer.New(consumerParams)
	cron := cron.NewCron(db, consumer, config)
	observability := observability.NewObservability(config)

	listOfHandlers := flatten.NewListOfHandlers(observability.Metrics)

	handler, err := handlerv0.NewHandlerV0(db)
	if err != nil {
		return nil, nil, nil
	}
	httpHandlers := []handlers.Handler{
		handler,
	}
	httpHandlers = append(httpHandlers, listOfHandlers.Handlers...)
	serverParams := server.ServerParams{
		Config:   config,
		Handlers: httpHandlers,
	}
	server := server.NewServer(serverParams)
	handlerV1 := handlerv1.NewHandlerV1()

	grpcHandlers := []handlers.Handler{
		handlerV1.Handler,
	}
	grpcserverServerParams := grpcserver.ServerParams{
		Handlers: grpcHandlers,
	}
	grpcserverServer := grpcserver.NewServer(grpcserverServerParams)
	return cron, server, grpcserverServer
}

Before running the generated code, make sure to perform the following steps:

  1. Fix any error handling in the generated file, if required
  2. Replace the Invoke(Run) statement in the original file with Run(Provide())
  3. Remove go.uber.org/dig from your project πŸ₯³πŸ₯³πŸ₯³

Following these steps will allow you to execute the generated code, providing a clear view of your dependencies

Configuration

anti-dig has two configuration options.

Optimise

This option is enabled by default.

If true, then anti-dig will generate code with some 'optimisations' such as human-readable variables, deleted unused variables, etc.

package main

import dig "github.com/3timeslazy/anti-dig"

func main() {
	dig.Anti.Optimise(true)
}

Rename

This option is disabled by default.

It is often the case that functions passed to dig as a provider are private functions not visible in the main package. In this case, you can enable this parameter. Then anti-dig will not only generate code but also change the names of used functions, making them public and available in the main

package main

import dig "github.com/3timeslazy/anti-dig"

func main() {
	dig.Anti.Rename(true)
}

Limitations

When creating this library, I focused on the go.uber.org/dig features used in my company. Therefore, at the moment, not all features are supported, such as:

  • dig.As
  • Decorators
  • Callbacks
  • Perhaps, something else

Also, not everything can be easily and correctly generated. For example, anonymous function calls

So if you are using a feature not supported by anti-dig, feel free to contribute by raising PRs and issues!