You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: 2024_12_09_dependency-injection.md
+12-11Lines changed: 12 additions & 11 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -22,19 +22,19 @@ Find time to modernize dependency injection in some services. The previous versi
22
22
However, for relatively small applications, it is possible to live with.
23
23
24
24
25
-
If we switch to type-driven context resolving (i.e., use AppContext[Service1] instead of appContext.service1 ), we will solve the modularization problem.
25
+
If we switch to type-driven context resolving (i.e., use `AppContext[Service1]` instead of `appContext.service1` ), we will solve the modularization problem.
26
26
27
27
The first look was at the approach described by @odersky in https://old.reddit.com/r/scala/comments/1eksdo2/automatic_dependency_injection_in_pure_scala/.
Unfortunately, the proposed technique is not directly applicable to our cases. The main reason is that machinery with math types works only when all types in the tuple are distinct. Therefore, this means all components participating in dependency injection should not be traits. (because if we have two traits (A, B), we can't prove that A and B in general are distinct; therefore, Index[(A, B)] will not resolved. The limitation not to use traits as components is too strict for practical usage. Two other problems (absence of chaining of providers and absence of caching) are fixable and demonstrate a usual gap between 'proof of concept’ and real stuff.
30
+
Unfortunately, the proposed technique is not directly applicable to our cases. The main reason is that machinery with matсh types works only when all types in the tuple are distinct. Therefore, this means all components participating in dependency injection should not be traits. (because if we have two traits (A, B), we can't prove that A and B in general are distinct; therefore, Index[(A, B)] will not resolved. The limitation not to use traits as components is too strict for practical usage. Two other problems (absence of chaining of providers and absence of caching) are fixable and demonstrate a usual gap between 'proof of concept’ and real stuff.
31
31
32
32
We will use approaches very close to what sideEffffECt describe in https://www.reddit.com/r/scala/comments/1eqqng2/the_simplest_dependency_injection_pure_scala_no/ with few additional steps.
33
33
34
34
35
35
# Basic
36
36
37
-
We will think that component `C` is provided if we can find AppProvicer[C]]
37
+
We will think that component `C` is provided if we can find AppProvider[C]]
38
38
39
39
```Scala
40
40
traitAppContextProvider[T] {
@@ -58,8 +58,9 @@ If we have an implicit instance of the object, we think it's provided:
givenofGivem[T](usingT):AppContextProvider[T] with {
65
66
defget:T= summon[T]
@@ -178,9 +179,9 @@ object AppContextProviders {
178
179
```
179
180
180
181
181
-
(complete code is available in the repository: https://github.com/rssh/scala-appcontext )
182
+
(complete code is available in the repository: https://github.com/rssh/scala-appcontext; permalink to generateImpl: https://github.com/rssh/scala-appcontext/blob/666a02e788aa57922104569541511a16431690fb/shared/src/main/scala/com/github/rssh/appcontext/AppContextProviders.scala#L52)
182
183
183
-
We separate `AppContextProvidersSearch` and `AppContextProviders` because we don't want to trigger AppContextProviders' implicit generation during implicit search outside of service instance generation.
184
+
We separate `AppContextProvidersSearch` and `AppContextProviders` because we don't want to trigger `AppContextProviders` implicit generation during implicit search outside of service instance generation.
184
185
Note that Scala currently has no way to make a macro that generates a given instance to fail an implicit search silently. We can only make errors during the search, which will abandon the whole compilation.
185
186
186
187
Can we also remove the boilerplate when defining the implicit AppContext provider?
@@ -208,7 +209,7 @@ object UserSubscription {
208
209
209
210
But this will still be boilerplate: We must enumerate dependencies twice and write trivial instance creation. On the other hand, this instance creation is not entirely meaningless: we can imagine the situation when it's not automatic.
210
211
211
-
To minimize this kind of boilerplate, we can introduce a convention for AppContextProviderModule, which defines its dependencies in type and automatic generation of instance providers:
212
+
To minimize this kind of boilerplate, we can introduce a convention for `AppContextProviderModule`, which defines its dependencies in type and automatic generation of instance providers:
212
213
213
214
```Scala
214
215
traitAppContextProviderModule[T] {
@@ -285,7 +286,7 @@ object AppContext {
285
286
```
286
287
287
288
288
-
And let's deploy a simple convention: if the service requires `AppContext.Cache` as a dependency, then we consider this service cached. I.e., with manual setup of AppContextProvider this should look like this:
289
+
And let's deploy a simple convention: if the service requires `AppContext.Cache` as a dependency, then we consider this service cached. I.e., with manual setup of `AppContextProvider` this should look like this:
289
290
290
291
```Scala
291
292
objectFuelUsage {
@@ -316,7 +317,7 @@ class TestUserSubscription(using TestUserSubscription.DependenciesProviders)
316
317
...
317
318
```
318
319
319
-
#Preventing pitfalls
320
+
#Preventing pitfalls
320
321
321
322
Can this be considered a complete mini-framework? Still waiting.
322
323
Let’s look at the following code:
@@ -346,9 +347,9 @@ println(c3.doSomething())
346
347
347
348
What will be printed?
348
349
349
-
The correct answer is “dep1:module:dep2:local”, because resolving of Dependency1 from the companion object of Dependency1 will be preferred over resolving from the AppContextProvider companion object. Unfortunately, I don’t know how to change this.
350
+
The correct answer is `“dep1:module:dep2:local”`, because resolving of `Dependency1` from the companion object will be preferred over resolving from the `AppContextProvider` companion object. Unfortunately, I don’t know how to change this.
350
351
351
-
We can add a check to determine whether supplied providers are needed. Again, unfortunately, we can’t add it ‘behind the scenes' by modifying the generator of AppContextProvider because the generator is inlined in the caller context for the component instance, where all dependencies should be resolved.
352
+
We can add a check to determine whether supplied providers are needed. Again, unfortunately, we can’t add it ‘behind the scenes' by modifying the generator of `AppContextProvider` because the generator is inlined in the caller context for the component instance, where all dependencies should be resolved.
352
353
We can write a macro that should be called from the context inside a component definition. This will require the developer to call it explicitly.
353
354
354
355
I.e., a typical component definition will look like this:
0 commit comments