Skip to content

Commit 51fb223

Browse files
committed
Test case: Dependency injection via Providers
1 parent 65a3d70 commit 51fb223

File tree

2 files changed

+191
-0
lines changed

2 files changed

+191
-0
lines changed

tests/run/Providers.check

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
11
2+
hi
3+
List(1, 2, 3)
4+
hi
5+
6+
Direct:
7+
You've just been subscribed to RockTheJVM. Welcome, Daniel
8+
Acquired connection
9+
Executing query: insert into subscribers(name, email) values Daniel [email protected]
10+
You've just been subscribed to RockTheJVM. Welcome, Martin
11+
Acquired connection
12+
Executing query: insert into subscribers(name, email) values Martin [email protected]
13+
14+
Injected
15+
You've just been subscribed to RockTheJVM. Welcome, Daniel
16+
Acquired connection
17+
Executing query: insert into subscribers(name, email) values Daniel [email protected]
18+
You've just been subscribed to RockTheJVM. Welcome, Martin
19+
Acquired connection
20+
Executing query: insert into subscribers(name, email) values Martin [email protected]

tests/run/Providers.scala

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
import language.experimental.modularity
2+
import compiletime.constValue
3+
import compiletime.ops.int.S
4+
5+
// Featherweight dependency injection library, inspired by the use case
6+
// laid out in the ZIO course of RockTheJVM.
7+
8+
/** Some things that are not part of Tuple yet, but that would be nice to have. */
9+
object TupleUtils:
10+
11+
/** The index of the first element type of the tuple `Xs` that is a subtype of `X` */
12+
type IndexOf[Xs <: Tuple, X] <: Int = Xs match
13+
case X *: _ => 0
14+
case _ *: ys => S[IndexOf[ys, X]]
15+
16+
/** A trait describing a selection from a tuple `Xs` returning an element of type `X` */
17+
trait Select[Xs <: Tuple, X]:
18+
def apply(xs: Xs): X
19+
20+
/** A given implementing `Select` to return the first element of tuple `Xs`
21+
* that has a static type matching `X`.
22+
*/
23+
given [Xs <: NonEmptyTuple, X] => (idx: ValueOf[IndexOf[Xs, X]]) => Select[Xs, X]:
24+
def apply(xs: Xs) = xs.apply(idx.value).asInstanceOf[X]
25+
26+
/** A featherweight library for dependency injection */
27+
object Providers:
28+
import TupleUtils.*
29+
30+
/** A provider is a zero-cost wrapper around a type that is intended
31+
* to be passed implicitly
32+
*/
33+
opaque type Provider[T] = T
34+
35+
def provide[X](x: X): Provider[X] = x
36+
37+
def provided[X](using p: Provider[X]): X = p
38+
39+
/** Project a provider to one of its element types */
40+
given [Xs <: Tuple, X] => (ps: Provider[Xs], select: Select[Xs, X]) => Provider[X] =
41+
select(ps)
42+
43+
/** Form a compound provider wrapping a tuple */
44+
given [X, Xs <: Tuple] => (p: Provider[X], ps: Provider[Xs]) => Provider[X *: Xs] =
45+
p *: ps
46+
47+
given Provider[EmptyTuple] = EmptyTuple
48+
49+
end Providers
50+
51+
@main def Test =
52+
import TupleUtils.*
53+
54+
type P = (Int, String, List[Int])
55+
val x: P = (11, "hi", List(1, 2, 3))
56+
val selectInt = summon[Select[P, Int]]
57+
println(selectInt(x))
58+
val selectString = summon[Select[P, String]]
59+
println(selectString(x))
60+
val selectList = summon[Select[P, List[Int]]]
61+
println(selectList(x))
62+
val selectObject = summon[Select[P, Object]]
63+
println(selectObject(x)) // prints "hi"
64+
println(s"\nDirect:")
65+
Explicit()
66+
println(s"\nInjected")
67+
Injected()
68+
69+
/** Demonstrator for explicit dependency construction */
70+
class Explicit:
71+
72+
case class User(name: String, email: String)
73+
74+
class UserSubscription(emailService: EmailService, db: UserDatabase):
75+
def subscribe(user: User) =
76+
emailService.email(user)
77+
db.insert(user)
78+
79+
class EmailService:
80+
def email(user: User) =
81+
println(s"You've just been subscribed to RockTheJVM. Welcome, ${user.name}")
82+
83+
class UserDatabase(pool: ConnectionPool):
84+
def insert(user: User) =
85+
val conn = pool.get()
86+
conn.runQuery(s"insert into subscribers(name, email) values ${user.name} ${user.email}")
87+
88+
class ConnectionPool(n: Int):
89+
def get(): Connection =
90+
println(s"Acquired connection")
91+
Connection()
92+
93+
class Connection():
94+
def runQuery(query: String): Unit =
95+
println(s"Executing query: $query")
96+
97+
val subscriptionService =
98+
UserSubscription(
99+
EmailService(),
100+
UserDatabase(
101+
ConnectionPool(10)
102+
)
103+
)
104+
105+
def subscribe(user: User) =
106+
val sub = subscriptionService
107+
sub.subscribe(user)
108+
109+
subscribe(User("Daniel", "[email protected]"))
110+
subscribe(User("Martin", "[email protected]"))
111+
112+
/** The same application as `Explicit` but using dependency injection */
113+
class Injected:
114+
import Providers.*
115+
116+
case class User(name: String, email: String)
117+
118+
class UserSubscription(using Provider[(EmailService, UserDatabase)]):
119+
def subscribe(user: User) =
120+
provided[EmailService].email(user)
121+
provided[UserDatabase].insert(user)
122+
123+
class EmailService:
124+
def email(user: User) =
125+
println(s"You've just been subscribed to RockTheJVM. Welcome, ${user.name}")
126+
127+
class UserDatabase(using Provider[ConnectionPool]):
128+
def insert(user: User) =
129+
val conn = provided[ConnectionPool].get()
130+
conn.runQuery(s"insert into subscribers(name, email) values ${user.name} ${user.email}")
131+
132+
class ConnectionPool(n: Int):
133+
def get(): Connection =
134+
println(s"Acquired connection")
135+
Connection()
136+
137+
class Connection():
138+
def runQuery(query: String): Unit =
139+
println(s"Executing query: $query")
140+
141+
given Provider[EmailService] = provide(EmailService())
142+
given Provider[ConnectionPool] = provide(ConnectionPool(10))
143+
given Provider[UserDatabase] = provide(UserDatabase())
144+
given Provider[UserSubscription] = provide(UserSubscription())
145+
146+
def subscribe(user: User)(using Provider[UserSubscription]) =
147+
val sub = provided[UserSubscription]
148+
sub.subscribe(user)
149+
150+
subscribe(User("Daniel", "[email protected]"))
151+
subscribe(User("Martin", "[email protected]"))
152+
153+
// explicit version, not used here
154+
object explicit:
155+
val subscriptionService =
156+
UserSubscription(
157+
using provide(
158+
EmailService(),
159+
UserDatabase(
160+
using provide(
161+
ConnectionPool(10)
162+
)
163+
)
164+
)
165+
)
166+
167+
given Provider[UserSubscription] = provide(subscriptionService)
168+
end explicit
169+
170+
171+

0 commit comments

Comments
 (0)