Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Combined Populate and Provide APIsΒ #725

@tyler-at-fast

Description

@tyler-at-fast

Hello fx-ers, I'm curious if an idea like this has been discussed yet:

Common DI Patterns

Today, I imagine the most common way to construct a concrete type and then depend on the interface is as follows (https://play.golang.org/p/riYmEo2wtsL):

  1. Interface Quacker with method Quack()
  2. Concrete type Duck (with private fields) with constructor NewDuck that returns a Quacker
  3. An fx.Provide provides NewDuck
  4. A function depends on the Quacker and invokes Quack()

Problem: this ends up with some boilerplate still. If you want to add a new field to your Duck, you need to copy it in three places: (1) the struct definition, (2) the constructor function signature, and (3) when constructing the concrete type in the return statement.

Alternative Available Today

As an alternative, you could make the fields of your concrete type public, and let DI do some of the heavy lifting for you (https://play.golang.org/p/wAewK_sbXK-):

  1. Interface Quacker with method Quack()
  2. Concrete type Duck that embeds fx.In and has all public fields.
  3. An fx.Provide binds the concrete type to the interface func(d Duck) Quacker { return d }
  4. A function depends on Quacker and invokes Quack()

This works, but only because it abuses the fx.In statement, by turning the concrete type into a parameter struct. It forces the implementation to be aware of fx, which is not ideal. For example, your concrete type can't have a pointer receiver, making many usages impossible.

Alternatives of the Future?

What if we could Populate() the concrete Duck and then Provide() it as a dependency that implements the Quacker interface? That would be pretty magical:

  1. Interface Quacker with method Quack()
  2. Concrete type Duck with all public fields.
  3. We fx.PopulateAndProvide(&Duck{}, func(d *Duck) Quacker { return d }) (...or something, maybe Supply is better)
  4. A function depends on the Quacker and invokes Quack()

This would effectively populate the Duck with all the properties it needs, then provide it as a Quacker to the fx graph.

Is this even idiomatic Go?

Maybe, maybe not. On the one hand, the Duck now has these public fields like Name, which shouldn't be available to the rest of the application. On the other hand, since it's bound to the Quacker interface in the fx graph, those public fields are not directly accessible without type asserting to the concrete type, at which point you're breaking encapsulation anyway so everything's out the window.

But why really?

Recently I migrated a codebase to fx, and idioms can vary based on experience/maturity of the application. If a codebase makes significant use of public structs/fields that implement interfaces, then more code must be written to take advantage of fx. And at the end of the day, fx's goal should be to reduce boilerplate code, not require more of it.

πŸ¦„ πŸ¦„ πŸ¦„
tyler

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions