Play Framework is a popular web application framework for building scalable and maintainable web applications using the Scala programming language. One of the essential aspects of building a Play application is configuring it to work as expected. Configuration in Play is typically done using a file called application.conf
, where you can specify various settings and parameters for your application.
In this article, we’ll delve into the details of accessing and managing Play configuration in Scala, covering topics such as reading configuration values, handling different data types, and dealing with configuration defaults.
Setting up the Configuration File
Before we start accessing Play configuration, let’s ensure that you have a properly configured application.conf
file in your Play project. This file is usually located in the conf
directory of your project.
Here’s a simple example of an application.conf
file:
myapp {
database {
url = "jdbc:mysql://localhost/mydb"
username = "username"
password = "password"
}
maxConnections = 10
secretKey = "mysecret"
}
Accessing Configuration Values
Play provides a convenient way to access configuration values using the Configuration
object. You can inject this object into your controllers or services to retrieve configuration values.
Injecting Configuration
To inject the Configuration
object into your controller or service, you can use Play’s built-in dependency injection. First, make sure your class is annotated with @Inject
to enable dependency injection. Then, inject the Configuration
object like this:
import play.api.Configuration
import javax.inject.Inject
class MyController @Inject()(config: Configuration) {
// Your controller code here
}
Retrieving Configuration Values
Once you have injected the Configuration
object, you can use it to retrieve configuration values. Here’s how you can access the values from the example application.conf
file:
import play.api.Configuration
import javax.inject.Inject
class MyController @Inject()(config: Configuration) {
val databaseUrl: String = config.get[String]("myapp.database.url")
val databaseUsername: String = config.get[String]("myapp.database.username")
val databasePassword: String = config.get[String]("myapp.database.password")
val maxConnections: Int = config.get[Int]("myapp.maxConnections")
val secretKey: String = config.get[String]("myapp.secretKey")
// Your controller code here
}
In the code above, we use the get[T]
method of the Configuration
object to retrieve configuration values of different types (String and Int in this case). The argument to get[T]
is the configuration path, which is a hierarchical dot-separated path to the value you want to retrieve.
Handling Default Values
It’s a good practice to provide default values for configuration settings in case they are missing from the application.conf
file. You can specify default values using the getOrElse
method.
val databaseUrl: String = config.get[String]("myapp.database.url")
.getOrElse("jdbc:mysql://localhost/defaultdb")
val maxConnections: Int = config.get[Int]("myapp.maxConnections")
.getOrElse(10)
The getOrElse
method tries to retrieve the configuration value, and if it’s not found, it falls back to the provided default value.
Working with Complex Types
In addition to simple data types, you can also work with complex types like lists and objects in your configuration.
Lists
To read a list of values from your configuration, you can use the get[List[T]]
method:
val myList: List[String] = config.get[List[String]]("myapp.myList")
Assuming your application.conf
contains:
myapp {
myList = ["item1", "item2", "item3"]
}
Objects
You can read complex objects by using a case class and the get[CaseClass]
method:
case class DatabaseConfig(url: String, username: String, password: String)
val dbConfig: DatabaseConfig = config.get[DatabaseConfig]("myapp.database")
Given the application.conf
snippet from earlier, this code will successfully create a DatabaseConfig
object.
Advanced Configuration Techniques
In addition to the basics of accessing and managing configuration values, Play Framework offers several advanced techniques for working with configuration that can be particularly useful in real-world applications.
Configuration Subtrees
Play allows you to organize your configuration into sub-configurations, making it easier to manage and access related settings. This can be especially handy for large and complex applications. Here’s an example of defining and accessing a configuration subtree:
myapp {
database {
url = "jdbc:mysql://localhost/mydb"
username = "username"
password = "password"
}
}
In your Scala code, you can access the database
subtree like this:
val databaseConfig: Configuration = config.get[Configuration]("myapp.database")
val dbUrl: String = databaseConfig.get[String]("url")
val dbUsername: String = databaseConfig.get[String]("username")
val dbPassword: String = databaseConfig.get[String]("password")
Environment-Specific Configuration
Sometimes, you may need different configurations for different environments (e.g., development, production, testing). Play allows you to define environment-specific configurations in separate files. By default, Play loads application.conf
, but you can specify a different configuration file based on your environment.
For example, you can create a prod.conf
file for production and a dev.conf
file for development, each containing environment-specific settings. Play will automatically pick up the appropriate configuration based on the application’s environment.
To specify the configuration file to use, set the config.resource
system property when starting your Play application. For example:
sbt -Dconfig.resource=prod.conf run
Type Safe Configuration with PureConfig
While Play’s built-in configuration support is powerful, you can achieve type-safe configuration using external libraries like PureConfig. PureConfig allows you to map your configuration directly to case classes, providing compile-time checks and type safety.
Here’s a brief example of using PureConfig to read configuration into a case class:
- Add PureConfig to your dependencies in
build.sbt
:
libraryDependencies += "com.github.pureconfig" %% "pureconfig" % "0.16.0"
- Define a case class to represent your configuration:
import pureconfig.ConfigSource
import pureconfig.generic.auto._
case class MyAppConfig(database: DatabaseConfig, maxConnections: Int, secretKey: String)
case class DatabaseConfig(url: String, username: String, password: String)
- Load your configuration using PureConfig:
import pureconfig._
val myAppConfig: Either[ConfigReaderFailures, MyAppConfig] = ConfigSource.default.load[MyAppConfig]
PureConfig will automatically map the configuration values to your case classes. If there are any mismatches or missing values, it will provide descriptive error messages during compile-time.
Conclusion
In this article, we’ve explored advanced techniques for managing and accessing configuration in Play Framework applications using Scala. We discussed the organization of configuration into sub-configurations, environment-specific configuration, and type-safe configuration using external libraries like PureConfig. These techniques can help you create more maintainable and robust applications by managing configuration effectively.