Zero-cost, compile-time, type-safe dependency injection library providing macros for automatic instance creation in Scala applications
—
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.
The wireWith function family provides overloads for factory functions with 0 to 22 parameters:
def wireWith[RES](factory: () => RES): RES
def wireWith[A, RES](factory: (A) => RES): RES
def wireWith[A, B, RES](factory: (A, B) => RES): RES
def wireWith[A, B, C, RES](factory: (A, B, C) => RES): RES
def wireWith[A, B, C, D, RES](factory: (A, B, C, D) => RES): RES
def wireWith[A, B, C, D, E, RES](factory: (A, B, C, D, E) => RES): RES
// ... continues up to 22 parameters
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](
factory: (A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V) => RES
): RESParameters:
factory - Function that creates the desired instance, with parameters that will be wired automaticallyReturns: Result of calling the factory function with wired dependencies
Behavior:
wire)import com.softwaremill.macwire._
class DatabaseAccess()
class SecurityFilter()
class UserFinder(databaseAccess: DatabaseAccess, securityFilter: SecurityFilter)
trait UserModule {
lazy val databaseAccess = new DatabaseAccess()
lazy val securityFilter = new SecurityFilter()
// Factory function for custom initialization
def createUserFinder(db: DatabaseAccess, security: SecurityFilter): UserFinder = {
println("Creating UserFinder with custom logic")
new UserFinder(db, security)
}
// wireWith automatically provides dependencies to the factory
lazy val userFinder = wireWith(createUserFinder _)
}case class DatabaseConfig(url: String, maxConnections: Int)
class DatabaseAccess(config: DatabaseConfig)
trait ConfigModule {
lazy val dbConfig = DatabaseConfig("jdbc:postgresql://localhost/mydb", 20)
// Factory with configuration logic
def createDatabase(config: DatabaseConfig): DatabaseAccess = {
println(s"Connecting to ${config.url}")
new DatabaseAccess(config)
}
lazy val databaseAccess = wireWith(createDatabase _)
}class EmailService(smtpHost: String, port: Int, username: String, password: String)
class NotificationService(emailService: EmailService, templateEngine: TemplateEngine)
trait NotificationModule {
lazy val smtpHost = "smtp.example.com"
lazy val smtpPort = 587
lazy val smtpUsername = "user@example.com"
lazy val smtpPassword = "password"
lazy val templateEngine = new TemplateEngine()
// Factory for complex email service setup
def createEmailService(host: String, port: Int, user: String, pass: String): EmailService = {
val service = new EmailService(host, port, user, pass)
service.connect() // Custom initialization
service
}
// Factory for notification service with logging
def createNotificationService(email: EmailService, template: TemplateEngine): NotificationService = {
println("Setting up notification service")
new NotificationService(email, template)
}
lazy val emailService = wireWith(createEmailService _)
lazy val notificationService = wireWith(createNotificationService _)
}class CacheService(maxSize: Int, ttlSeconds: Int)
trait CacheModule {
lazy val maxCacheSize = 1000
lazy val cacheTtl = 3600
// Inline factory function
lazy val cacheService = wireWith { (maxSize: Int, ttl: Int) =>
println(s"Creating cache with size $maxSize and TTL $ttl")
new CacheService(maxSize, ttl)
}
}class MetricsCollector()
class MonitoredService[T](wrapped: T, metrics: MetricsCollector)
def wrapWithMonitoring[T](service: T, metrics: MetricsCollector): MonitoredService[T] =
new MonitoredService(service, metrics)
trait MonitoringModule {
lazy val metricsCollector = new MetricsCollector()
lazy val userService = new UserService()
// Factory that wraps existing service with monitoring
lazy val monitoredUserService = wireWith((service: UserService, metrics: MetricsCollector) =>
wrapWithMonitoring(service, metrics))
}trait Logger
class ConsoleLogger extends Logger
class FileLogger(filename: String) extends Logger
trait LoggingModule {
def isDevelopment: Boolean = sys.props.get("env").contains("dev")
lazy val logFilename = "app.log"
def createLogger(filename: String): Logger = {
if (isDevelopment) {
new ConsoleLogger()
} else {
new FileLogger(filename)
}
}
lazy val logger = wireWith(createLogger _)
}class DatabasePool(url: String, maxConnections: Int)
class DatabaseAccess(pool: DatabasePool)
class UserRepository(db: DatabaseAccess)
trait DatabaseModule {
lazy val dbUrl = "jdbc:postgresql://localhost/mydb"
lazy val maxConnections = 10
// Nested factory functions
def createPool(url: String, max: Int): DatabasePool = {
val pool = new DatabasePool(url, max)
pool.initialize()
pool
}
def createDatabaseAccess(pool: DatabasePool): DatabaseAccess = {
new DatabaseAccess(pool)
}
def createUserRepository(db: DatabaseAccess): UserRepository = {
new UserRepository(db)
}
lazy val databasePool = wireWith(createPool _)
lazy val databaseAccess = wireWith(createDatabaseAccess _)
lazy val userRepository = wireWith(createUserRepository _)
}def createRepository[T](dao: DAO[T]): Repository[T] = new Repository(dao)
trait RepositoryModule {
lazy val userDao = new DAO[User]()
// Generic factory preserves type parameters
lazy val userRepository = wireWith((dao: DAO[User]) => createRepository(dao))
}class ServiceWithImplicits(config: Config)(implicit ec: ExecutionContext)
trait ServiceModule {
lazy val config = loadConfig()
implicit lazy val executionContext: ExecutionContext = ExecutionContext.global
// Factory handles implicit parameters automatically
def createService(conf: Config)(implicit ec: ExecutionContext): ServiceWithImplicits =
new ServiceWithImplicits(conf)
lazy val service = wireWith(createService _)
}class DatabaseConnection(url: String) {
if (url.isEmpty) throw new IllegalArgumentException("URL cannot be empty")
}
trait DatabaseModule {
lazy val dbUrl = "jdbc:postgresql://localhost/mydb"
def createConnection(url: String): DatabaseConnection = {
try {
new DatabaseConnection(url)
} catch {
case e: IllegalArgumentException =>
println(s"Invalid database URL: $url")
throw e
}
}
lazy val connection = wireWith(createConnection _)
}wiredef methodName)val f = (x: Int) => ...)(x: Int) => ...)methodName _)wire - compile-time error if factory parameters cannot be resolvedInstall with Tessl CLI
npx tessl i tessl/maven-com-softwaremill-macwire--macros