0
# Factory-Based Wiring
1
2
Factory-based wiring uses the `wireWith` family of functions to automatically wire dependencies for factory functions. This approach is ideal when you need custom initialization logic or when working with functions that create instances.
3
4
## Core Functions
5
6
The `wireWith` function family provides overloads for factory functions with 0 to 22 parameters:
7
8
```scala { .api }
9
def wireWith[RES](factory: () => RES): RES
10
def wireWith[A, RES](factory: (A) => RES): RES
11
def wireWith[A, B, RES](factory: (A, B) => RES): RES
12
def wireWith[A, B, C, RES](factory: (A, B, C) => RES): RES
13
def wireWith[A, B, C, D, RES](factory: (A, B, C, D) => RES): RES
14
def wireWith[A, B, C, D, E, RES](factory: (A, B, C, D, E) => RES): RES
15
// ... continues up to 22 parameters
16
def wireWith[A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, RES](
17
factory: (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V) => RES
18
): RES
19
```
20
21
**Parameters:**
22
- `factory` - Function that creates the desired instance, with parameters that will be wired automatically
23
24
**Returns:** Result of calling the factory function with wired dependencies
25
26
**Behavior:**
27
- Resolves each factory parameter using context-dependent wiring (same as `wire`)
28
- Calls the factory function with all resolved dependencies
29
- Returns the result of the factory function call
30
31
## Usage Examples
32
33
### Basic Factory Wiring
34
35
```scala
36
import com.softwaremill.macwire._
37
38
class DatabaseAccess()
39
class SecurityFilter()
40
class UserFinder(databaseAccess: DatabaseAccess, securityFilter: SecurityFilter)
41
42
trait UserModule {
43
lazy val databaseAccess = new DatabaseAccess()
44
lazy val securityFilter = new SecurityFilter()
45
46
// Factory function for custom initialization
47
def createUserFinder(db: DatabaseAccess, security: SecurityFilter): UserFinder = {
48
println("Creating UserFinder with custom logic")
49
new UserFinder(db, security)
50
}
51
52
// wireWith automatically provides dependencies to the factory
53
lazy val userFinder = wireWith(createUserFinder _)
54
}
55
```
56
57
### Configuration-Based Factories
58
59
```scala
60
case class DatabaseConfig(url: String, maxConnections: Int)
61
class DatabaseAccess(config: DatabaseConfig)
62
63
trait ConfigModule {
64
lazy val dbConfig = DatabaseConfig("jdbc:postgresql://localhost/mydb", 20)
65
66
// Factory with configuration logic
67
def createDatabase(config: DatabaseConfig): DatabaseAccess = {
68
println(s"Connecting to ${config.url}")
69
new DatabaseAccess(config)
70
}
71
72
lazy val databaseAccess = wireWith(createDatabase _)
73
}
74
```
75
76
### Multi-Parameter Factories
77
78
```scala
79
class EmailService(smtpHost: String, port: Int, username: String, password: String)
80
class NotificationService(emailService: EmailService, templateEngine: TemplateEngine)
81
82
trait NotificationModule {
83
lazy val smtpHost = "smtp.example.com"
84
lazy val smtpPort = 587
85
lazy val smtpUsername = "user@example.com"
86
lazy val smtpPassword = "password"
87
lazy val templateEngine = new TemplateEngine()
88
89
// Factory for complex email service setup
90
def createEmailService(host: String, port: Int, user: String, pass: String): EmailService = {
91
val service = new EmailService(host, port, user, pass)
92
service.connect() // Custom initialization
93
service
94
}
95
96
// Factory for notification service with logging
97
def createNotificationService(email: EmailService, template: TemplateEngine): NotificationService = {
98
println("Setting up notification service")
99
new NotificationService(email, template)
100
}
101
102
lazy val emailService = wireWith(createEmailService _)
103
lazy val notificationService = wireWith(createNotificationService _)
104
}
105
```
106
107
### Anonymous Function Factories
108
109
```scala
110
class CacheService(maxSize: Int, ttlSeconds: Int)
111
112
trait CacheModule {
113
lazy val maxCacheSize = 1000
114
lazy val cacheTtl = 3600
115
116
// Inline factory function
117
lazy val cacheService = wireWith { (maxSize: Int, ttl: Int) =>
118
println(s"Creating cache with size $maxSize and TTL $ttl")
119
new CacheService(maxSize, ttl)
120
}
121
}
122
```
123
124
### Higher-Order Factory Functions
125
126
```scala
127
class MetricsCollector()
128
class MonitoredService[T](wrapped: T, metrics: MetricsCollector)
129
130
def wrapWithMonitoring[T](service: T, metrics: MetricsCollector): MonitoredService[T] =
131
new MonitoredService(service, metrics)
132
133
trait MonitoringModule {
134
lazy val metricsCollector = new MetricsCollector()
135
lazy val userService = new UserService()
136
137
// Factory that wraps existing service with monitoring
138
lazy val monitoredUserService = wireWith((service: UserService, metrics: MetricsCollector) =>
139
wrapWithMonitoring(service, metrics))
140
}
141
```
142
143
### Conditional Factory Logic
144
145
```scala
146
trait Logger
147
class ConsoleLogger extends Logger
148
class FileLogger(filename: String) extends Logger
149
150
trait LoggingModule {
151
def isDevelopment: Boolean = sys.props.get("env").contains("dev")
152
lazy val logFilename = "app.log"
153
154
def createLogger(filename: String): Logger = {
155
if (isDevelopment) {
156
new ConsoleLogger()
157
} else {
158
new FileLogger(filename)
159
}
160
}
161
162
lazy val logger = wireWith(createLogger _)
163
}
164
```
165
166
### Factory Composition
167
168
```scala
169
class DatabasePool(url: String, maxConnections: Int)
170
class DatabaseAccess(pool: DatabasePool)
171
class UserRepository(db: DatabaseAccess)
172
173
trait DatabaseModule {
174
lazy val dbUrl = "jdbc:postgresql://localhost/mydb"
175
lazy val maxConnections = 10
176
177
// Nested factory functions
178
def createPool(url: String, max: Int): DatabasePool = {
179
val pool = new DatabasePool(url, max)
180
pool.initialize()
181
pool
182
}
183
184
def createDatabaseAccess(pool: DatabasePool): DatabaseAccess = {
185
new DatabaseAccess(pool)
186
}
187
188
def createUserRepository(db: DatabaseAccess): UserRepository = {
189
new UserRepository(db)
190
}
191
192
lazy val databasePool = wireWith(createPool _)
193
lazy val databaseAccess = wireWith(createDatabaseAccess _)
194
lazy val userRepository = wireWith(createUserRepository _)
195
}
196
```
197
198
## Advanced Patterns
199
200
### Generic Factory Functions
201
202
```scala
203
def createRepository[T](dao: DAO[T]): Repository[T] = new Repository(dao)
204
205
trait RepositoryModule {
206
lazy val userDao = new DAO[User]()
207
208
// Generic factory preserves type parameters
209
lazy val userRepository = wireWith((dao: DAO[User]) => createRepository(dao))
210
}
211
```
212
213
### Implicit Parameter Handling
214
215
```scala
216
class ServiceWithImplicits(config: Config)(implicit ec: ExecutionContext)
217
218
trait ServiceModule {
219
lazy val config = loadConfig()
220
implicit lazy val executionContext: ExecutionContext = ExecutionContext.global
221
222
// Factory handles implicit parameters automatically
223
def createService(conf: Config)(implicit ec: ExecutionContext): ServiceWithImplicits =
224
new ServiceWithImplicits(conf)
225
226
lazy val service = wireWith(createService _)
227
}
228
```
229
230
### Error Handling in Factories
231
232
```scala
233
class DatabaseConnection(url: String) {
234
if (url.isEmpty) throw new IllegalArgumentException("URL cannot be empty")
235
}
236
237
trait DatabaseModule {
238
lazy val dbUrl = "jdbc:postgresql://localhost/mydb"
239
240
def createConnection(url: String): DatabaseConnection = {
241
try {
242
new DatabaseConnection(url)
243
} catch {
244
case e: IllegalArgumentException =>
245
println(s"Invalid database URL: $url")
246
throw e
247
}
248
}
249
250
lazy val connection = wireWith(createConnection _)
251
}
252
```
253
254
## Behavior and Limitations
255
256
### Dependency Resolution
257
258
- Uses the same context-dependent resolution as `wire`
259
- Parameters are resolved from the surrounding scope
260
- Exact type matching required for all parameters
261
262
### Function Types Supported
263
264
- Named functions (`def methodName`)
265
- Function values (`val f = (x: Int) => ...`)
266
- Anonymous functions (`(x: Int) => ...`)
267
- Eta-expanded methods (`methodName _`)
268
269
### Compilation Requirements
270
271
- Available in both Scala 2 and Scala 3
272
- All dependency resolution happens at compile time
273
- Factory functions must have statically analyzable parameter types
274
275
### Error Conditions
276
277
- **Missing Dependencies**: Same as `wire` - compile-time error if factory parameters cannot be resolved
278
- **Unsupported Factory Types**: Complex function types may not be supported
279
- **Parameter Limit**: Maximum 22 parameters due to Scala tuple limitations