Прошу простить длину этого вопроса.
Мне часто нужно создать некоторую контекстуальную информацию на одном уровне моего кода и использовать эту информацию в другом месте. Обычно я использую неявные параметры:
def foo(params)(implicit cx: MyContextType) = ...
implicit val context = makeContext()
foo(params)
Это работает, но требует, чтобы неявный параметр передавался по большому счету, загрязняя методы подписи слоя после компоновки промежуточных функций, даже если они не заботятся об этом сами.
def foo(params)(implicit cx: MyContextType) = ... bar() ...
def bar(params)(implicit cx: MyContextType) = ... qux() ...
def qux(params)(implicit cx: MyContextType) = ... ged() ...
def ged(params)(implicit cx: MyContextType) = ... mog() ...
def mog(params)(implicit cx: MyContextType) = cx.doStuff(params)
implicit val context = makeContext()
foo(params)
Я нахожу этот подход уродливым, но у него есть одно преимущество: оно безопасно. Я с уверенностью знаю, что mog
получит объект контекста нужного типа или не будет компилироваться.
Это облегчит беспорядок, если я смогу использовать некоторую форму "инъекции зависимостей", чтобы найти соответствующий контекст. Кавычки указывают на то, что это отличается от обычных шаблонов инъекций зависимостей, найденных в Scala.
Начальная точка foo
и конечная точка mog
могут существовать на самых разных уровнях системы. Например, foo
может быть контроллером входа пользователя, а mog
может выполнять SQL-доступ. Могут быть одновременно зарегистрированы многие пользователи, но есть только один экземпляр уровня SQL. Каждый раз, когда mog
вызывается другим пользователем, необходим другой контекст. Таким образом, контекст не может быть запечен в принимающем объекте, и вы не хотите объединить два слоя каким-либо образом (например, шаблон Cake). Я также предпочел бы не полагаться на библиотеку DI/IoC, такую как Guice или Spring. Я нашел их очень тяжелыми и не очень хорошо подходит для Scala.
Мне кажется, что мне нужно что-то, что позволяет mog
извлекать правильный контекстный объект для него во время выполнения, немного как ThreadLocal
со стеком в нем:
def foo(params) = ...bar()...
def bar(params) = ...qux()...
def qux(params) = ...ged()...
def ged(params) = ...mog()...
def mog(params) = { val cx = retrieveContext(); cx.doStuff(params) }
val context = makeContext()
usingContext(context) { foo(params) }
Но это упадет, как только асинхронный актер будет задействован в любой точке цепи. Неважно, какую актерскую библиотеку вы используете, если код работает в другом потоке, тогда он теряет ThreadLocal
.
Итак... есть ли трюк, который мне не хватает? Способ передачи информации контекстно в Scala, который не загрязняет сигнатуры промежуточного метода, не испекает контекст в приемнике статически и по-прежнему безопасен по типу?