CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-com-softwaremill-macwire--macros

Zero-cost, compile-time, type-safe dependency injection library providing macros for automatic instance creation in Scala applications

Pending
Overview
Eval results
Files

factory-based-wiring.mddocs/

Factory-Based Wiring

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.

Core Functions

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
): RES

Parameters:

  • factory - Function that creates the desired instance, with parameters that will be wired automatically

Returns: Result of calling the factory function with wired dependencies

Behavior:

  • Resolves each factory parameter using context-dependent wiring (same as wire)
  • Calls the factory function with all resolved dependencies
  • Returns the result of the factory function call

Usage Examples

Basic Factory Wiring

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 _)
}

Configuration-Based Factories

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 _)
}

Multi-Parameter Factories

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 _)
}

Anonymous Function Factories

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)
  }
}

Higher-Order Factory Functions

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))
}

Conditional Factory Logic

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 _)
}

Factory Composition

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 _)
}

Advanced Patterns

Generic Factory Functions

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))
}

Implicit Parameter Handling

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 _)
}

Error Handling in Factories

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 _)
}

Behavior and Limitations

Dependency Resolution

  • Uses the same context-dependent resolution as wire
  • Parameters are resolved from the surrounding scope
  • Exact type matching required for all parameters

Function Types Supported

  • Named functions (def methodName)
  • Function values (val f = (x: Int) => ...)
  • Anonymous functions ((x: Int) => ...)
  • Eta-expanded methods (methodName _)

Compilation Requirements

  • Available in both Scala 2 and Scala 3
  • All dependency resolution happens at compile time
  • Factory functions must have statically analyzable parameter types

Error Conditions

  • Missing Dependencies: Same as wire - compile-time error if factory parameters cannot be resolved
  • Unsupported Factory Types: Complex function types may not be supported
  • Parameter Limit: Maximum 22 parameters due to Scala tuple limitations

Install with Tessl CLI

npx tessl i tessl/maven-com-softwaremill-macwire--macros

docs

context-dependent-wiring.md

context-free-wiring.md

dynamic-instance-access.md

factory-based-wiring.md

index.md

tile.json