Scala Коллаж и зависимость от торта

Я пытаюсь внедрить инъекцию зависимостей в Scala с шаблоном Cake, но я столкнулся с конфликтами зависимостей. Поскольку я не смог найти подробный пример с такими зависимостями, вот моя проблема:

Предположим, что мы имеем следующую характеристику (с двумя реализациями):

trait HttpClient {
  def get(url: String)
}

class DefaultHttpClient1 extends HttpClient {
  def get(url: String) = ???
}

class DefaultHttpClient2 extends HttpClient {
  def get(url: String) = ???
}

И следующие два шаблонных модуля шаблонов (которые в этом примере являются API-интерфейсами, которые зависят от нашего HttpClient для их функциональности):

trait FooApiModule {
  def httpClient: HttpClient        // dependency
  lazy val fooApi = new FooApi()    // providing the module service

  class FooApi {
    def foo(url: String): String = {
      val res = httpClient.get(url)
      // ... something foo specific
      ???
    }
  }
}

и

trait BarApiModule {
  def httpClient: HttpClient        // dependency
  lazy val barApi = new BarApi()    // providing the module service

  class BarApi {
    def bar(url: String): String = {
      val res = httpClient.get(url)
      // ... something bar specific
      ???
    }
  }
}

Теперь, когда вы создаете окончательное приложение, использующее оба модуля, нам нужно предоставить зависимость HttpClient для обоих модулей. Но что, если мы хотим обеспечить его реализацию для каждого из модулей? Или просто предоставить разные экземпляры зависимостей, настроенных по-другому (например, с помощью другого ExecutionContext)?

object MyApp extends FooApiModule with BarApiModule {
  // the same dependency supplied to both modules
  val httpClient = new DefaultHttpClient1()

  def run() = {
    val r1 = fooApi.foo("http://...")
    val r2 = barApi.bar("http://...")
    // ...
  }
}

Мы могли бы назвать зависимости по-разному в каждом модуле, префикс их с именем модуля, но это было бы громоздким и неэлегантным, а также не будет работать, если мы не будем полностью контролировать сами модули.

Любые идеи? Я неверно истолковал шаблон пирога?

Ответ 1

Вы правильно получаете шаблон, и вы только что обнаружили его важное ограничение. Если два модуля зависят от какого-либо объекта (скажем, HttpClient) и, случается, объявляют его под тем же именем (например, httpClient), игра окончена - вы не будете настраивать их отдельно внутри одного Cake. Либо есть две Торты, как Даниэль советует или меняет источники источников, если вы можете (как намекает Томер Габель).

Каждое из этих решений имеет свои проблемы.

Наличие двух тортов (рекомендации Дэниела) выглядит так долго, что им не нужны некоторые общие зависимости.

Переименование некоторых зависимостей (при условии, что это возможно) заставляет вас настроить весь код, который их использует.

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

Ответ 2

"Вы делаете это неправильно" (TM). У вас будет такая же проблема с Spring, Guice или любым контейнером IoC: вы обрабатываете типы как имена (или символы); вы говорите "Дайте мне HTTP-клиент" вместо "Дайте мне HTTP-клиент, подходящий для общения с fooApi".

Другими словами, у вас есть несколько HTTP-клиентов, все с именем httpClient, что не позволяет делать различие между разными экземплярами. Это похоже на использование @Autowired HttpClient без какого-либо способа квалифицировать ссылку (в случае Spring, обычно с помощью идентификатора bean с внешней проводкой).

В шаблоне пирога один из способов разрешить это состоит в том, чтобы квалифицировать это различие с другим именем: FooApiModule требует, например, a def http10HttpClient: HttpClient и BarApiModule требует def connectionPooledHttpClient: HttpClient. Когда "заполнение" разных модулей, разные имена ссылаются на два разных экземпляра, но также указывают на ограничения, которые два модуля размещают на своих зависимостях.

Альтернативой (работоспособной, хотя и не такой чистой, на мой взгляд) является просто требование зависящей от модуля именованной зависимости, т.е. def fooHttpClient: HttpClient, которая просто вынуждает явное внешнее подключение к тому, кто микширует ваш модуль.

Ответ 3

Вместо расширения FooApiModule и BarApiModule в одном месте - что будет означать, что они разделяют зависимости - сделайте их как отдельными объектами, каждый с их зависимостями будет разрешен соответствующим образом.

Ответ 4

Кажется, это известная проблема "ног робота". Вам нужно построить две ноги робота, однако вам нужно снабжать их двумя различными ногами.

Как использовать шаблон торта, чтобы иметь как общие зависимости, так и отдельные?

Пусть имеет L1 <- A, B1; L2 <- A, B2. И вы хотите иметь Main <- L1, L2, A.

Чтобы иметь отдельные зависимости, нам нужны два экземпляра меньших тортов, параметризованных с помощью общих зависимостей.

trait LegCommon { def a:A}
trait Bdep { def b:B }
class L(val common:LegCommon) extends Bdep { 
  import common._
  // declarations of Leg. Have both A and B.
}
trait B1module extends Bdep {
  val b = new B1
}
trait B2module extends Bdep {
  def b = new B2
}

В Main у нас будет общая часть в торте и двух ногах:

trait Main extends LegCommon {
  val l1 = new L(this) with B1module
  val l2 = new L(this) with B2module
  val a = new A
}

Ответ 5

Ваше последнее приложение должно выглядеть так:

object MyApp {
  val fooApi = new FooApiModule {
    val httpClient = new DefaultHttpClient1()
  }.fooApi
  val barApi = new BarApiModule {
     val httpClient = new DefaultHttpClient2()
  }.barApi
  ...

 def run() = {
  val r1 = fooApi.foo("http://...")
  val r2 = barApi.bar("http://...")
  // ...
 }
}

Это должно сработать. (Адаптировано из этого сообщения в блоге: http://www.cakesolutions.net/teamblogs/2011/12/19/cake-pattern-in-depth/)