Статьи

DI в Scala: плюсы и минусы торта

Я искал альтернативы для контейнеров DI и DI в стиле Java, которые использовали бы чистый Scala; многообещающим кандидатом является Cake Pattern (см. мой предыдущий пост в блоге для получения информации о том, как работает Cake Pattern). Энтузиасты FP также утверждают, что им не нужны никакие DI-структуры, так как функций более высокого порядка достаточно.

Недавно Debasish Ghosh также написал в блоге на подобную тему . Я думаю, что его статья — очень хорошее введение в предмет.

Ниже приведены некоторые проблемы, с которыми я столкнулся с Cake Pattern. (Функции высшего порядка будут рассмотрены в следующем посте.) Если у вас есть решения по любому из них, дайте мне знать!

Параметризация системы с помощью компонента реализации

Прежде всего, невозможно параметризовать систему с помощью компонентной реализации. Предположим, у меня есть три компонента: DatabaseComponent, UserRepositoryComponent, UserAuthenticatorComponent с реализациями, среда / точка входа верхнего уровня системы будет создана следующим образом:

1
2
3
val env = new MysqlDatabaseComponentImpl
   with UserRepositoryComponent
   with UserAuthenticatorComponent

Теперь, чтобы создать среду тестирования с фиктивной базой данных, мне нужно сделать:

1
2
3
val env = new MockDatabaseComponentImpl
   with UserRepositoryComponent
   with UserAuthenticatorComponent

Обратите внимание, сколько кода совпадает. Это не проблема с 3 компонентами, но если есть 20? Все они, кроме одного, нужно повторить, чтобы изменить реализацию одного компонента. Это явно приводит к значительному дублированию кода.

Конфигурация компонента

Довольно часто компонент должен быть настроен. Допустим, у меня есть UserAuthenticatorComponent, который зависит от UserRepositoryComponent. Однако компонент аутентификатора имеет абстрактный val encryptionMethod, используемый для настройки алгоритма шифрования. Как я могу настроить компонент? Есть два способа. Абстрактный val может быть конкретизирован при определении env, например:

1
2
3
4
5
val env = new MysqlDatabaseComponentImpl
   with UserRepositoryComponent
   with UserAuthenticatorComponent {
   val encryptionMethod = EncryptionMethods.MD5
}

Но что, если я хочу повторно использовать настроенный компонент? Очевидный ответ — расширить черту UserAuthenticatorComponent. Однако, если у этого компонента есть какие-либо зависимости (которые в Cake Pattern выражаются с использованием self-типов), их необходимо повторить, так как self-типы не наследуются. Таким образом, повторно используемый сконфигурированный компонент может выглядеть так:

1
2
3
4
5
6
trait UserAuthenticatorComponentWithMD5
         extends UserAuthenticatorComponent  {
   // dependency specification duplication!
   this: UserRepositoryComponent =>
   val encryptionMethod = EncryptionMethods.MD5
}

Если мы не будем повторять эти типы, компилятор будет жаловаться на неправильное использование UserAuthenticatorComponent.

Нет контроля над порядком инициализации

Проблема, также связанная с конфигурацией, заключается в том, что не существует безопасного типа для гарантии того, что компоненты инициализируются в правильном порядке. Предположим, как указано выше, что UserAuthenticatorComponent имеет абстрактный метод encryptionMethod, который должен быть указан при создании компонента. Если у нас есть другой компонент, который зависит от UserAuthenticatorComponent:

1
2
3
4
5
trait PasswordEncoderComponent {
   this: UserAuthenticatorComponent =>
   // encryptionMethod comes from UserAuthenticatorComponent
   val encryptionAlgorithm = Encryption.getAlgorithm(encryptionMethod)
}

и инициализировать нашу систему следующим образом:

1
2
3
4
5
6
val env = new MysqlDatabaseComponentImpl
   with UserRepositoryComponent
   with UserAuthenticatorComponent
   with PasswordEncoderComponent {
   val encryptionMethod = EncryptionMethods.MD5
}

тогда в момент инициализации алгоритма шифрования метод шифрования будет нулевым! Единственный способ предотвратить это — смешать UserAuthenticatorComponentWithMD5 перед PasswordEncoderComponent. Но проверка типов не скажет нам этого.

Pros

Не поймите меня неправильно, что мне не нравится Cake Pattern — я думаю, он предлагает очень хороший способ структурировать ваши программы. Например, это устраняет необходимость в фабриках ( которые я не очень большой поклонник ) или приятно разделяет зависимости от компонентов и зависимости от данных (*). Но все же, это могло бы быть лучше;).

(*) Здесь каждый фрагмент кода имеет фактически два типа аргументов: обычные аргументы метода, которые могут использоваться для передачи данных, и аргументы компонента, выраженные как собственный тип содержащего компонента. Должны ли эти два типа аргументов рассматриваться по-разному — хороший вопрос :).

Какой у вас опыт работы с DI в Scala? Используете ли вы инфраструктуру Java DI, один из подходов, использованных выше, или каким-либо другим способом?

Ссылка: DI в Scala: плюсы и минусы Cake Pattern от нашего партнера JCG Адама Варски в блоге Адама Варски .

Статьи по Теме :