Três anos de Scala no NewsMonitor -...

Post on 09-Nov-2018

224 views 0 download

Transcript of Três anos de Scala no NewsMonitor -...

Três anos de Scala no NewsMonitor

Felipe Hummel

• 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

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

Elastic SearchElastic Search

Crawlers

Arquitetura Geral

Stark Indexer

MySQLMySQL Redis

Search API

Elastic Search

autocomplete

Arquitetura Coleta de Notícias

SocialCrawler

Feed Crawler

Article Crawler

Site Crawler

Fila de ColetaArticle

Crawler

Article Crawler

…Article

Crawler

SeedCrawlerSeedCrawlerSeedCrawler

Scala

Scala é muito boa como dizem

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

Scala, por quê?

val idsDeAlunos = List(1423, 23245, 5343) val cursosPorNome = Map("Computação" -> 1234, "Matemática" -> 423, "Física" -> 5322, "Biologia" -> 1312)

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

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

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

Scala, por quê?

val idsDeAlunos = List(1423, 23245, 5343) val cursosPorNome = Map("Computação" -> 1234, "Matemática" -> 423, "Física" -> 5322, "Biologia" -> 1312)

Scala, por quê?

case class Aluno(id: Int, curso: Curso, idade: Int) val alunosPorIdade = idsDeAlunos.map( id => carregaAluno(id) )

Scala, por quê?

case class Aluno(id: Int, curso: Curso, idade: Int) val alunosPorIdade = idsDeAlunos.map( id => carregaAluno(id) ) .filter(_.curso.nome == "Computação")

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)

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)

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)

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

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)

Scala é isso

80% de Scala é isso

Scala pode ser complicada como dizem

__ _ _ _ Placeholder __ __ _ _

val lista = List(“maria”, “José”, “joão”, “Carlos") lista.map( n => n.toUpperCase ) // List(“MARIA”, …) lista.map( _.toUpperCase ) // List(“MARIA”, …)

__ _ _ _ Placeholder __ __ _ _

val lista = List(“maria”, “José”, “joão”, “Carlos") lista.map( n => n.toUpperCase ) // List(“MARIA”, …) lista.map( _.toUpperCase ) // List(“MARIA”, …) lista.map( _.map(_.toUpper)) // List(“MARIA”, …)

__ _ _ _ Placeholder __ __ _ _

val lista = List(“maria”, “José”, “joão”, “Carlos") lista.map( n => n.toUpperCase ) // List(“MARIA”, …) lista.map( _.toUpperCase ) // List(“MARIA”, …) lista.map( _.map(_.toUpper)) // List(“MARIA”, …) lista.map { str => str.map( c => c.toUpper) } // List(“MARIA”, …)

Implicits conversions

10.seconds // Int não tem o método seconds ^~~~~~ erro de compilação

Implicits conversions

import scala.concurrent.duration.DurationInt 10.seconds // Int agora “tem" o método seconds

Implicits conversions import scala.concurrent.duration.DurationInt val time: Duration = 10.seconds

import org.scalatest.Matchers._

val resultado: String = … resultado should equal ("42")

Implicits conversionsimport wildcard

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

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)

Scala é complicada como dizem

Scala é complicada como dizem

em poucas coisas

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

Estilos de Scala• Muitas formas de escrever o mesmo código

• Um mais cool, outro mais funcional, outro mais imperativo

• Qual usar?

Estilos de Scala

val lista = List(0, 1, 2, 3, 4) lista.map( n => n + 1 )

Estilos de Scala

val lista = List(0, 1, 2, 3, 4) lista.map { n => n + 1 }

Estilos de Scala

val lista = List(0, 1, 2, 3, 4) lista map { n => n + 1 }

Estilos de Scala

val lista = List(0, 1, 2, 3, 4) lista.map { _ + 1 }

Estilos de Scala

val lista = List(0, 1, 2, 3, 4) lista.map ( _ + 1 )

Estilos de Scala

val lista = List(0, 1, 2, 3, 4) def add1(n: Int) = n + 1 lista.map (add1)

Estilos de Scala

val lista = List(0, 1, 2, 3, 4) def add1(n: Int) = n + 1 lista map add1

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

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

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

Scala mudou a forma

como programamos

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)

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)

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

!!!!!!!!!

Null Pointer Exc… NÃO

• Uso de Option[MeuTipo] quando necessário

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

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

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

case class Person(firstName: String, lastName: String)

imutável é a palavra

Scala é a melhor coisa do mundo?

Scala é a melhor coisa do mundo?

$escapedName = mysql_escape_string($u) $escapedCurso = mysql_real_escape_string($c) $sql = “SELECT * FROM usuarios WHERE name LIKE '%”.$escapedName."%’ AND curso = ‘“.$escapedCurso;

A minha linguagem/framework/biblioteca

não é a melhor possível

Mais conciso e expressivo que Scala é impossível

Mais conciso e expressivo que Haskell é impossível

Programação é sempre um comando seguido do outro

Programação pode ser só (funções (chamando (funções ‘!’)))

em Clojure

Programar interfaces é ter que lidar com estado mutável o tempo todo

Programar interfaces é isolar o estado mutável com ReactJS

Ou damos free() na mão ou usamos Garbage Collector

Ou damos free() na mão ou usamos Garbage Collector

ou usamos Rust e o seu sistema de ownership

Sempre dá pra melhorar

Obrigado! Dúvidas?

@felipehummel felipe.hummel@newsmonitor.com.br

Akka

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

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…