Nesta palestra, veremos como o NewsMonitor, uma ferramenta de monitoração de notícias e clipping digital, começou a usar Scala em 2012. Desde então, temos usado a linguagem como diferencial técnico. A linguagem permitiu que uma pequena equipe criasse uma grande base de código em pouco tempo, mantendo o custo de infraestrutura baixo. Foram desenvolvidos desde sistemas de recomendação até um avançado coletor de notícias em tempo real.
Serão discutidos os pontos positivos da escolha da linguagem Scala, especialmente as funcionalidades que fizeram diferença no desenvolvimento: case classes, imutabilidade, implícitos e biblioteca de coleções. Além disso, serão mostrados os desafios enfrentados pelo grupo: falta de padrões de estilo, complexidade de código, "fanatismo funcional" e perda de performance.
Por fim, veremos a arquitetura que foi implementada no back-end do NewsMonitor, em especial como é organizado o coletor distribuído de notícias.
2. • Site profissional para monitoramento de notícias em
tempo real
• 170M de notícias
• 5M/mês
• 2 Devs Backend + 3 Devs PHP/Frontend
• ~30K linhas de código Scala
3. Como escolhemos Scala
• busk.com
• Fechado e pivotado
para o NewsMonitor
• Decisão de reaproveitar
código legado em Ruby
ou criar novo
• Aplicação ficou em
PHP
• Backend em Scala
9. Scala, por quê?
• Concisão de código sem perca de legibilidade
• Inferência de tipos
• Sintaxe extremamente sucinta para funções anônimas
• Coleções: List, Array, Map, Seq, Set, String
• map, filter, groupBy, sortBy, distinct
• Quase tudo faz parte da biblioteca e não é sintaxe
especial da linguagem
10. Scala, por quê?
val idsDeAlunos = List(1423, 23245, 5343)
val cursosPorNome =
Map("Computação" -> 1234,
"Matemática" -> 423,
"Física" -> 5322,
"Biologia" -> 1312)
11. Scala, por quê?
val idsDeAlunos = List(1423, 23245, 5343)
val cursosPorNome =
Map("Computação" -> 1234,
"Matemática" -> 423,
"Física" -> 5322,
"Biologia" -> 1312)
Inferência de tipos
12. Scala, por quê?
val idsDeAlunos = List(1423, 23245, 5343)
val cursosPorNome =
Map("Computação" -> 1234,
"Matemática" -> 423,
"Física" -> 5322,
"Biologia" -> 1312)
Inferência de tipos
13. Scala, por quê?
val idsDeAlunos = List(1423, 23245, 5343)
val cursosPorNome =
Map("Computação" -> 1234,
"Matemática" -> 423,
"Física" -> 5322,
"Biologia" -> 1312)
Não é sintaxe
especial
14. Scala, por quê?
val idsDeAlunos = List(1423, 23245, 5343)
val cursosPorNome =
Map("Computação" -> 1234,
"Matemática" -> 423,
"Física" -> 5322,
"Biologia" -> 1312)
15. Scala, por quê?
case class Aluno(id: Int, curso: Curso, idade: Int)
val alunosPorIdade =
idsDeAlunos.map( id => carregaAluno(id) )
16. Scala, por quê?
case class Aluno(id: Int, curso: Curso, idade: Int)
val alunosPorIdade =
idsDeAlunos.map( id => carregaAluno(id) )
.filter(_.curso.nome == "Computação")
17. Scala, por quê?
case class Aluno(id: Int, curso: Curso, idade: Int)
val alunosPorIdade =
idsDeAlunos.map( id => carregaAluno(id) )
.filter(_.curso.nome == "Computação")
.filter(_.idade > 18)
18. Scala, por quê?
case class Aluno(id: Int, curso: Curso, idade: Int)
val alunosPorIdade =
idsDeAlunos.map( id => carregaAluno(id) )
.filter(_.curso.nome == "Computação")
.filter(_.idade > 18)
.groupBy(_.idade)
19. Scala, por quê?
case class Aluno(id: Int, curso: Curso, idade: Int)
val alunosPorIdade: Map[Int, List[Aluno]] =
idsDeAlunos.map( id => carregaAluno(id) )
.filter(_.curso.nome == "Computação")
.filter(_.idade > 18)
.groupBy(_.idade)
20. Scala, por quê?
case class Aluno(id: Int, curso: Curso, idade: Int)
val alunosPorIdade: Map[Int, List[Aluno]] =
idsDeAlunos.map( id => carregaAluno(id) )
.filter(_.curso.nome == "Computação")
.filter(_.idade > 18)
.groupBy(_.idade)
funções anônimas
sucintas
21. Scala, por quê?
• Várias pequenas funcionalidades que ajudam muito no
dia a dia
• Default e named parameters
• case classes: syntactic sugar que vale a pena
• Pattern Matching: switch case+++
• Preferência por imutabilidade mas sempre dando
opção por mutabilidade (quando necessária)
32. implicit conversions
• Conceito similar ao que dá pra fazer em Ruby ou
Javascript, estendendo classes mas com tipagem
estática!
• Você precisa importar explicitamente a conversão
• Cuidado para não abusar
• Melhor usado em DSLs
33. Interoperabilidade com Java
• Em geral funciona bem
• Mas alguns conceitos de Java e Scala são
irreconciliáveis
• Na prática as 3 maiores dores são:
• Código Java que acha legal retornar null
• Código Scala que usa coleções Java e vice-versa
• Tipos primitivos: Integer e int (Java) <-> Int (Scala)
36. Compilador
• Poderoso mas “lento”
• Pra quem vem de Java, Go e linguagens
dinâmicas
• Rápido pra quem vem de C++
• Na prática: sbt ~compile ou sbt ~test
37. Estilos de Scala
• Muitas formas de escrever o mesmo código
• Um mais cool, outro mais funcional, outro mais
imperativo
• Qual usar?
43. Estilos de Scala
val lista = List(0, 1, 2, 3, 4)
def add1(n: Int) = n + 1
lista.map (add1)
44. Estilos de Scala
val lista = List(0, 1, 2, 3, 4)
def add1(n: Int) = n + 1
lista map add1
45. Estilos de Scala
lista.map ( n => n + 1 ) <—- Usamos
lista.map { n => n + 1 } <—- Usamos
lista map { n => n + 1 }
lista.map { _ + 1 }
lista.map ( _ + 1 ) <—- Usamos
lista.map (add1) <—- Usamos
lista map add1
46. Uso de memória e
tempo de startup
• Estar na JVM tem vantagens e desvantagens
• Uso de memória
• Scala piora um pouquinho a situação
• Queremos structs! Value Types (Java 9?)
• Go e Rust podem ser alternativas em casos que isso
é crítico
47. Scala Avançado e
Curva de Aprendizado
• Algumas partes da comunidade enveredam demais pelo caminho funcional
• Você não é obrigado a usar. Mas a divisão da comunidade não é legal
• Com grandes poderes vem grandes responsabilidades
• Abuso de features (overloading de operadores)
• <o> <!> <<! ># >! >:> >|> (NÃAO, só DSLs onde operador já faz sentido)
• Aprendizado
• Muito fácil fazer Scala “javado” (mas é um início fácil)
• Traz conceitos novos e não familiares para quem vem da linhagem de C:
implicits, pattern matching, typeclasses
49. Parar de acessar
o que não está lá
• NullPointerException (Java)
• undefined is not a function (Javascript)
• AttributeError: 'NoneType' object has no
attribute (Python)
• Call to a member function on a non-object
(PHP)
50. Parar de acessar
o que não está lá
• NullPointerException (Java)
• undefined is not a function (Javascript)
• AttributeError: 'NoneType' object has no
attribute (Python)
• Call to a member function on a non-object
(PHP)
51. Null Pointer Exc… NÃO
• NullPointerException (Java)
• undefined is not a function (Javascript)
• AttributeError: 'NoneType' object has no
attribute (Python)
• Call to a member function on a non-object
(PHP)
SCALA
NÃO TEM
!!!!!!!!!
53. Tipagem Estática!
• Linguagens com tipagem estáticas pegaram fama de serem
verbosas
• Não necessariamente
• Scala consegue ser tão concisa quanto as linguagens dinâmicas
• Tendência de adicionar tipos opcionais nas linguagens
dinâmicas
• Javascript, PHP, Python
• Difícil viver sem um compilador me ajudando
54. Imutabilidade
• Coisas a menos pra guardar na sua cabeça
• Não me preocupo se alguém pode ou vai mudar meu objeto
• Você pode passar objetos imutáveis pra lá e pra cá de boa
• Thread-safe por padrão
• Meio que obrigatório para chaves de Map, valores dentro de
Set ou Caches em geral
• Se você observa um valor numa variável, você sabe que ela foi
sempre aquilo
55. public class Person {
private final String firstName;
private final String lastName;
String getFirstName() { return firstName; }
String getLastName() { return lastName; }
public Person(String first, String last) {
this.firstName = first;
this.lastName = last;
}
public int hashCode() {
return goodHashCode(firstName, lastName);
}
public boolean equals(Object o) {
if ( this == aThat ) return true;
if ( !(aThat instanceof Person) ) return false;
Person that = (Person)aThat;
return EqualsUtil.areEqual(this.firstName, that.firstName) &
EqualsUtil.areEqual(this.lastName, that.lastName);
}
}
77. Akka :)
• Simplifica muito o modelo mental para trabalhar com concorrência
• Cria "bolhas" onde você pode ser mutável à vontade (com moderação)
• Muito bom quando você tem Actors de vida longa e com estado
mutável
• Não precisa lidar com criação/manutenção de filas (na maior parte do
tempo)
• Tunar threadpools (dispatchers) não é necessário no início e é bem fácil
(via config)
• O código do Actor não contém (quase) nada de lógica lidando com
concorrência, threads ou threadpool
78. Akka :(
• Toma conta do código (framework)
• A relação entre actors não é tão óbvia no código quanto a relação entre objetos
num código tradicional (perca de tipagem)
• Você pode acabar tunando ExecutionContexts e ThreadPool de qualquer forma.
E não é trivial (independente do Akka).
• Mais "difícil" de testar
• Dá pra isolar algumas coisas e criar determinismo
• No final das contas tudo é concorrente
• Debugar é bem diferente
• Rastrear de onde uma coisa veio, pra onde vai…