kafka {
url = "0.0.0.0:9092"
url = ${?KAFKA_URL}
}
other-section {
year = 1222
year = ${?THIS_IS_ENV_FOR_YEAR}
a = ${ENV_B}
b = "some string"
}
other-section.b = "another string"
val config = ConfigFactory.load()
val kafkaUrl = Try(config.getString("kafka.url"))
.getOrElse("0.0.0.0:9988")
val year = Try(config.getInt("other-section.year"))
.getOrElse("2019")
can cause errors difficult to find
version: '3.3'
services:
some-app:
image: asdasdasd/app:1.2.3
restart: always
ports:
- "9000:8080"
environment:
ABC: "845"
KAFKA_URI: "0.0.0.0:9092"
QUITE_LONG_ENV_NAME: 1222
THIS_IS_ENV_OR_YEAR: 2016
THIS_ONE_IS_EVEN_LONGER_ENV_NAME: 123456
Did you notice missplellings in the env names?
val config = ConfigFactory.load()
lazy val kafkaUrl = config.getString("kafka.url")
lazy val year = config.getInt("other-section.year")
lazy val isFoo = config.getBoolean("foo.enabled")
lazy val x = ...
when using it in performance critical places
object Settings {
val config = ConfigFactory.load()
val kafkaUrl = config.getString("kafka.url")
val year = config.getInt("other-section.year")
val isFoo = config.getBoolean("other-section.foo.enabled")
}
all values will be evaluated if any will be accessed
Settings.kafkaUrl // or just Settings
class Settings(config: Config) {
object kafka {
val url = config.getString("kafka.url")
}
object otherSection {
val year = config.getInt("other-section.year")
val isFoo = config.getBoolean("other-section.foo.enabled")
}
}
inner objects behaves exactly as lazy vals
settings.kafka.url
will not instantiate otherSection object
sealed trait Command
case class CreateUser(id: Long,
name: String,
tags: Set[String]) extends Command
case class UpdateUser(id: Long,
name: String,
tags: Set[String]) extends Command
case class RemoveUser(id: Long) extends Command
val userCreatedMapping: PartialFunction[AnyRef, AnyRef] = {
case CreateUser(id, name, tags) =>
generated.CreateUser.newBuilder()
.setUserId(id)
.setName(name)
.setTags(tags.asJava)
.build()
case cmd: generated.CreateUser =>
CreateUser(cmd.id, cmd.name, cmd.tags.asScala)
}
val userCreatedMapping: PartialFunction[AnyRef, AnyRef] = {
case s: String => "???"
case CreateUser(id, name, tags) =>
generated.CreateUser.newBuilder()
.setUserId(id)
.setName(name)
.setTags(tags.asJava)
.build()
case cmd: generated.CreateUser =>
CreateUser(cmd.id, cmd.name, cmd.tags.asScala)
}
val userCreatedMapping: PartialFunction[AnyRef, AnyRef] = {
case CreateUser(id, name, tags) =>
generated.CreateUser.newBuilder()
.setUserId(id)
.setName(name)
.setTags(tags.asJava)
case cmd: generated.CreateUser =>
UpdateUser(cmd.id, cmd.name, cmd.tags.asScala)
}
Did you notice what is wrong?
abstract class AbstractSerializer[ScalaType <: AnyRef,
ProtosType <: AnyRef] {
def convertFromScala(scalaType: ScalaType): ProtosType
def convertFromProtos(protosType: ProtosType): ScalaType
val mapping: PartialFunction[AnyRef, AnyRef] = {
case scalaType: ScalaType =>
convertFromScala(scalaType)
case protosType: ProtosType =>
convertFromProtos(protosType)
}
}
object CreateUserSerializer extends
AbstractSerializer[CreateUser, generated.CreateUser] {
override def fromScala(cmd: CreateUser): generated.CreateUser =
generated.CreateUser.newBuilder()
.setUserId(cmd.id)
.setName(cmd.id.name)
.setTags(cmd.id.tags.asJava)
// .build() // will be caught by compiler
override def fromProtos(cmd: generated.CreateUser): CreateUser = {
// incorrect type will be caught by compiler
UpdateUser(cmd.id, cmd.name, cmd.tags.asScala)
}
}
type Receive = PartialFunction[Any, Unit]
class UserActor extends Actor {
override def receive: Receive = {
case Create(...) => ...
// ...
// suppose a lot cases here
case AddGroup(tag) => // do stuff
case RemoveGroup(id) => // do stuff
}
}
Is it safe to remove last 2 cases?
def handle: PartialFunction[Command, List[Event]] = {
case AddTag(tag) =>
// ...
List(...)
case RemoveTag(tag) =>
// ...
List(...)
}
override def receive: Receive = {
case cmd: Command =>
val events = handle(cmd)
// ...
}
Part of code extracted to increase type safety
implicit val formats: Formats = DefaultFormats
// ...
onComplete(futureResponse) {
case Success(response) =>
complete(OK -> response)
case Failure(throwable) =>
complete(InternalServerError -> throwable)
}
response and status are implicitly serialized to json
java.lang.StackOverflowError: null
at scala.collection.generic.Growable$class.loop$1(Growable.scala:52)
at scala.collection.generic.Growable$class.$plus$plus$eq(Growable.scala:57)
at scala.collection.mutable.MapBuilder.$plus$plus$eq(MapBuilder.scala:25)
at scala.collection.generic.GenMapFactory.apply(GenMapFactory.scala:48)
at org.json4s.TypeHints$class.serialize(Formats.scala:237)
at org.json4s.NoTypeHints$.serialize(Formats.scala:297)
at org.json4s.Extraction$.internalDecomposeWithBuilder(Extraction.scala:113)
at org.json4s.Extraction$.addField$1(Extraction.scala:110)
at org.json4s.Extraction$.decomposeObject$1(Extraction.scala:140)
at org.json4s.Extraction$.internalDecomposeWithBuilder(Extraction.scala:228)
at org.json4s.Extraction$.addField$1(Extraction.scala:110)
at org.json4s.Extraction$.decomposeObject$1(Extraction.scala:140)
at org.json4s.Extraction$.internalDecomposeWithBuilder(Extraction.scala:228)
implicit val formats: Formats = DefaultFormats
// ...
onComplete(futureResponse) {
case Success(response) =>
complete(OK -> response)
case Failure(throwable) =>
complete(InternalServerError -> throwable.getMessage)
}
maybe newer versions fixed this
List(1, 2).toString // List(1, 2, 3)
Array(1, 2).toString // [I@34ce8af7
(new {}).toString // $$anon$1@b684286
case class User(id: Long, name: String)
case class DoNotShowMe(name: String)
implicit val userShow: Show[User] =
Show.show("User: " + _.name)
User(7, "Michał").show // "User: Michał"
DoNotShowMe("private").show // doesn't compile as expected
// implicit class
val timeout: FiniteDuration =
config.getDuration("timeout").asFiniteDuration
// implicit def
val timeout: FiniteDuration =
config.getDuration("timeout")
def find(limit: Int, offset: Int): Task[E] = {
...
prepare(limit, offset, ...)
}
def find(limit: Int, offset: Int, ...): Task[E] = {
...
prepare(limit, offset, ...)
}
...
val limit = 5
val offset = 100
find(limit, offset, ...)
find(offset, limit, ...)
find(17, 8, ...)
type Limit = Int
type Offset = Int
def find(limit: Limit, offset: Offset): Task[E] = ...
val limit: Limit = 5
val offset: Offset = 100
type Age = Int
val age: Age = 35
find(limit, offset, ...) // still compiles
find(offset, limit, ...) // still compiles
find(17, age, ...) // still compiles
Type aliases
Value classes
case class Limit(value: Int) extends AnyVal
case class Offset(value: Int) extends AnyVal
def find(limit: Limit, offset: Offset): Task[E] = ...
val limit: Limit = Limit(5)
val offset: Offset = Limit(100)
find(limit, offset, ...) // ok
find(offset, limit, ...) // doesn't compile
find(17, 12, ...) // doesn't compile
Value classes
case class UserId(id: Long) extends AnyVal
Array[UserId] // elements of array will be fully allocated
Tagged types
type @@[+T, +U] = T with Tag[U]
Tagged types
trait OffsetTag
type Offset = Int @@ OffsetTag
@inline def Offset(i: Int): Offset = i.taggedWith[OffsetTag]
trait LimitTag
type Limit = Int @@ LimitTag
@inline def Limit(i: Int): Limit = i.taggedWith[LimitTag]
val limit: Limit = Limit(5)
val offset: Offset = Limit(100)
find(limit, offset, ...) // ok
find(offset, limit, ...) // doesn't compile
find(17, 12, ...) // doesn't compile
softwaremill/scala-common example
Tagged types
trait UserIdTag
type Offset = Int @@ UserIdTag
@inline def UserId(i: Int): UserId = i.taggedWith[UserIdTag]
val id = UserId(123)
Array(id)
can be used similar to value classes
Option(nullable).get