Как корректно использовать пакет fx от Uber?

Я новичок в Go и стараюсь разобраться с интерфейсами и пакетом fx от Uber. 

Я провел несколько тестов:

https://go.dev/play/p/NPpwDXcsepi - здесь все работает отлично; мы предоставляем структуру, которую затем хотим использовать.

https://go.dev/play/p/ZPJjsb6Xytl - здесь возникают проблемы, но аннотации работают корректно.

https://go.dev/play/p/jL6VNnmdw2p - здесь возникают проблемы; в функции `do` я ожидаю интерфейс, которому структура A полностью соответствует. 

https://go.dev/play/p/gBR5pnaQTMu - здесь все в порядке, но в методе `makeA` я поменял возвращаемое значение на `IA` вместо `*A`.

Может быть, для использования этой библиотеки не получится объявлять интерфейсы там, где они используются, чтобы избежать циклического импорта? Разве не нужно отдельно объявлять интерфейсы и в методе `New` возвращать интерфейс, а не его реализацию?
  • 3 декабря 2023 г. 10:19
Ответы на вопрос 1
Во многих языках программирования применяется идеология "Разрабатывайте интерфейсы, а не реализации". В Go же предпочтительнее другой подход: интерфейсы чаще всего определяются THAT, которые используют структуры, а не те, которые их создают. 

Это позволяет уменьшить размер интерфейсов и переложить ответственность за ограничения поведения на вызывающую сторону, а не на вызываемую.

В контексте DI и fx это означает, что вы должны определить ваши интерфейсы и используйте их в сигнатурах функций, которые fx будет вызывать. Затем вы предоставите конкретные реализации этих интерфейсов fx-ом. 

Ваши функции-поставщики (provider functions) должны возвращать конкретные типы (объекты структур, которые реализуют ваши интерфейсы). У вас не должно возникнуть проблем с циклическим импортом, если вы структурируете код должным образом.

Пакет fx достаточно гибкий, и вы можете использовать его по-разному в зависимости от требований вашего проекта. Главное, что нужно помнить, заключается в том, что функции-поставщики (provider functions) должны возвращать конкретные типы (например, указатели на структуры), а функции-потребители (consumer functions или invokers) должны принимать интерфейсы. 

Пример:

```go
type Foo interface {}

type Bar struct {}

func NewBar() *Bar {
    return &Bar{}
}

func doSomethingWithFoo(f Foo) {
    // Реализация функции
}

func main() {
    fx.New(
         fx.Provide(NewBar),
         fx.Invoke(doSomethingWithFoo)
    ).Run()
}
```

В этом примере `NewBar()` возвращает конкретный тип `*Bar`, который реализует интерфейс `Foo`. Функция `doSomethingWithFoo()` принимает интерфейс `Foo`, и fx способен сопоставить `*Bar` с `Foo`. 

Если ваши интерфейсы и реализации разбросаны по различным пакетам, вы можете заинжектить их как зависимости, используя метод provide. При этом важно помнить, что сами реализации должны быть локальной областью видимости пакета (private), чтобы избежать доступа к ним напрямую из другого пакета.
Похожие вопросы