Quantcast
Channel: Programação – Blog da Caelum: desenvolvimento, web, mobile, UX e Scrum
Viewing all 24 articles
Browse latest View live

Desvendando o editor Vim

$
0
0

Vim é um editor de texto poderoso e altamente configurável. Contudo, ele possui uma curva de aprendizado estranha – às vezes parece mais uma escada de degraus desiguais do que uma curva. O primeiro contato com ele é quase sempre de estranhamento.

Tudo isso é motivo suficiente para afastar os menos pacientes ou curiosos, mas não desista sem ao menos tentar junto a esse guia!

Há várias razões para o vim ter se tornado um editor famoso: Além de possuir uma comunidade forte – e cheia de truques nas mangas – há uma diferença notável na produtividade ao editar arquivos, principalmente para programadores. E não se engane, este não é só um editor com “alguns truques a mais”.

Este tutorial vem com um arquivo junto. Caso queira acompanhar e testar as dicas enquanto aprende, recomendo que faça o download do arquivo.

Atalhos e comandos

Vamos começar abrindo o editor:

Primeiro abra o terminal (OSX/Linux) ou o prompt(Windows). O simples comando vim abre o editor vazio (será necessário especificar onde salvar posteriormente). Alternativamente, vim vimtutorial.txt abre o arquivo de nome “vimtutorial.txt”.

É bem provável que a primeira impressão seja: “não consigo digitar!”. Bem, o vim possui alguns modos, e o que você pode fazer muda em cada um deles. Não se preocupe, logo você entenderá como eles funcionam.

Antes disso, vamos aprender como funcionam atalhos no vim. Uma das sacadas dos criadores foi tentar reduzir ao máximo o uso de modificadores como CTRL ou SHIFT (que de fato causam uma baita tendinite). Eles conseguiram isso com a distinção de modos, fazendo com que a maioria das teclas sejam um atalho.

De agora em diante, passe a testar os atalhos conforme forem apresentados, isso lhe dará familiaridade e conforto com o editor.

Antes de começar, tenha uma coisa em mente: muitos comandos do vim podem ser repetidos múltiplas vezes se digitarmos um número antes dele. Por exemplo: digite “10dd” e seu editor apagará as próximas dez linhas. Use isso a seu favor!

Para os que estão seguindo o arquivo-tutorial, tem um exemplo de uso desse comando no arquivo.

CUIDADO – há distinção entre letras maiúsculas e minúsculas no vim. Isto é, o comando r pode ser diferente do R. A tecla CAPSLOCK inverte o comportamento.

Preparado para começar com o vim? Então bora conhecer os seus modos.

Modo ‘normal’

Este é o modo no qual o vim inicia. Você identifica-o quando não há nenhuma informação no canto inferior esquerdo da tela:

Essa é a tela inicial quando não especificamos nenhum arquivo ao abrir o editor. Note que ele inicia no normal mode.

Ele serve para navegação e manipulação de texto. Nele você pode utilizar as setas, home e end para se mover. Segue o exemplo de alguns comandos muito utilizados:

  • dd: remove a linha atual (delete)
  • dt: remove tudo da linha que está entre seu cursor e a primeira ocorrência do caractere digitado logo em seguida. (delete ‘till)
  • x: recorta o caractere atual para colar depois (cross)
  • yy: copia a linha atual (yank)
  • p: cola o que está colado, o clássico paste
  • w: andar o cursor uma palavra (word)
  • b: volta o cursor uma palavra (back)
  • u: equivalente ao CTRL+z, ou seja, undo
  • CTRL+R: é o redo, ou seja, desfaz o undo

Note que, no geral, os comandos vem do nome das ações, o que ajuda muito a memorizar tudo.

Outra sacada dos criadores foi dar um jeito de manter a mão do programador sempre pelas letras. Assim, h, j, k e l podem ser usados como setas neste modo (esquerda, baixo, cima, direita).

Nesse modo você também pode digitar / (forward-slash) e digitar uma palavra ou até uma expressão regular. Essa é a busca.

Ao apertar enter, você poderá navegar pelos resultados com SHIFT+n e n (resultado anterior/próximo resultado). Use ESC para sair dela.

Caso esteja acompanhando com o arquivo-tutorial, experimente digitar “/palavras” e ver a busca em ação.

Note que a busca, por padrão, não destaca todas as palavras, mas apenas move o cursor. É claro que podemos mudar isso – um exemplo do quão configurável o vim é.

Modo ‘insert’

Esse modo é o mais procurado por iniciantes – o modo de escrita! Para acioná-lo basta apenas mover o cursor para o local desejado e então digitar a tecla i. Agora já podemos escrever! Veja:

Note que no canto inferior esquerdo aparece o INSERT, portanto, com este visual, pode editar seu arquivo à vontade.

Além do i, há outras alternativas de entrar neste modo. A diferença é o que acontece com o cursor um momento antes de você poder digitar:

  • A: move o cursor para o final da linha
  • I: move o cursor para o começo da linha
  • o: abre uma linha vazia abaixo da atual e move o cursor para ela
  • O: abre uma linha vazia acima da atual e move o cursor para ela

Legal, estamos digitando, mas como podemos, por exemplo, sair desse modo e voltar para o normal? Simples, basta apenas usar a tecla ESC! E é basicamente isso. Aproveite o momento para fazer o exercício descrito no arquivo.

Modo ‘visual’

Esse modo serve para selecionar e manipular texto. Nele podemos usar quase todos os comandos do modo normal, mas eles serão aplicados em toda a seleção.

Esse é o visual mode. Nós temos uma indicação no canto esquerdo inferior e o texto selecionado é destacado.

Um aviso: é necessário passar pelo modo normal sempre que você for trocar de modos. Por exemplo, se você está em insert e gostaria de ir para visual, é necessário usar ESC primeiro e partir daí. Isso vale para qualquer troca entre modos que não envolva o normal.

Inclusive, é considerado boa prática (e também mais conveniente) manter-se no modo normal se não estiver fazendo nada (idle).

Dito isso, veja os comandos para entrar no modo visual, e o tipo de seleção que eles proporcionam:

  • v: seleção caractere a caractere
  • V: seleção de linhas
  • CTRL+V: seleção de bloco

Você pode, por exemplo, selecionar várias linhas, copiar com y e colar em outro lugar com p.

Uma outra utilização do modo visual é o dobrar (folding) várias linhas em uma só. Por exemplo, para esconder as linhas de uma função que você não vai modificar no futuro próximo.

Para fazer isso selecione as linhas que você quer juntar temporariamente (é apenas um efeito visual) com V e digite zf. Depois, para desdobrar as linhas comprimidas, navegue o cursor até ela e pressione zo. Para treinar, siga o tutorial no arquivo.

Veja como o vim indica que ele comprimiu visualmente 12 linhas para uma só:

Modo ‘command-line’

Até agora conseguimos editar, navegar e selecionar texto no nosso arquivo. Mas não temos nenhuma pista de como salvar o arquivo ou fechar nosso editor.

Claro, podemos fechar o terminal, mas isso – além de inconveniente – não resolve o problema de salvar. Mas então, qual o atalho para cada uma dessas funções? Mais do que um atalho, certas ações precisam usar comandos. O modo que permite isso é o command-line:

Ao pressionar ‘:’ seu cursor existirá apenas no canto inferior esquerdo, onde você pode digitar e executar comandos com ENTER. O histórico pode ser navegado com as setas para cima e para baixo. Segue alguns comandos importantes:

  • w: salva o arquivo, pode opcionalmente colocar um nome em seguida para salvar no arquivo com este nome
  • q: sai do vim e volta para o terminal. Você pode usar q! para sair sem salvar
  • %s/padrão/substituto/opções: substituição
  • vs: abre na mesma tela o arquivo especificado, dividindo o espaço verticalmente
  • split: abre na mesma tela o arquivo especificado, dividindo o espaço horizontalmente

O comando de substituição merece mais atenção. Vamos começar com um exemplo: %s/antigaVariavel/novaVariavel/g. Ao executar esse comando, toda ocorrência de antigaVariavel será trocada por novaVariavel. Veja antes:

e depois:

O g no final indica global, ou seja, no arquivo todo. Você pode adcionar c (/gc) para que o editor pergunte, a cada ocorrência, se você deseja fazer a substituição:

Como na busca, tanto padrão quanto substituto podem ser expressões regulares, o que dá muito poder para o editor! Para treinar, existe um exercício no arquivo disponibilizado.

Uma funcionalidade interessante é que os splits podem ser feitos recursivamente, ou seja, o espaço da tela designado para um espaço pode ser dividido com outro arquivo. Vamos ver um exemplo (extremo) dessa característica:

Veja como os splits funcionam! Fica claro porque eu não recomendo que passe de 4 telas. Mas mesmo assim ainda não sabemos como lidar com tantas telas. Alguns atalhos nos ajudam com isso:

Para navegar entre cada tela (como um CTRL+TAB no browser), CTRL+w pode ser usado ao ser pressionado duas vezes seguidas. Alternativamente, você pode ir para uma direção com CTRL+w+h/j/k/l (esquerda, baixo, cima, direita). Por fim, para fechar um split, basta fechar o arquivo com :q.

Conclusão

Embora você já consiga abrir o seu (possívelmente) novo editor preferido e usá-lo, ainda existem muitos truques e configurações que dão mais poder ao usuário.

Do jeito que está ele é feio, não tem informações importantes à vista e ainda apresenta comportamentos estranhos. Ainda bem que, como você já deve ter adivinhado, tudo isso pode ser customizado de modo a atender suas necessidades e gostos.

O próximo passo é aprender a preencher o seu arquivo de configurações, o .vimrc. Há muitas configurações possíveis, e apenas com elas podemos tirar o máximo de todas features que você acabou de aprender. O ideal é pegar um .vimrc base e ir adicionando o necessário.

Por sua sorte, a comunidade do vim é bem forte e leal – e material para aprender é o que não falta. Além disso, aproveite e deixe o seu comentário sobre o que achou do vim.

Aprender uma ferramenta e suas features é sempre bacana, pois o nosso trabalho se torna cada vez mais produtivo, certo? Então que tal aprender alguns truques com o Eclipse IDE também?

Na Alura, temos o curso online de produtividade com o Eclipse IDE no qual aborda muitas das técnicas e dicas visando a produtividade com uma das ferramentas mais famosas entre os desenvolvedores Java. Na Caelum ele também é usado durante os cursos, assim como o Android Studio no curso de Android!

The post Desvendando o editor Vim appeared first on Blog da Caelum: desenvolvimento, web, mobile, UX e Scrum.


Como criar um servidor HTTP com HapiJS

$
0
0

Esse é o terceiro post que estou fazendo sobre JavaScript utilizando o NodeJS como plataforma. No primeiro aprendemos a criar um servidor HTTP sem qualquer framework e no segundo vimos como criar o servidor HTTP com ExpressJS. Agora veremos como criar o servidor com o HapiJS, um concorrente do ExpressJS, que gostei de usar, acho que vou adicionar um apêndice com coisas do HapiJS no curso da Caelum de NodeJS. 🙂

Rotas que criaremos com HapiJS

Nesse post nos vamos criar um pequeno projeto com 3 URLs:

  1. Nossa Home que terá apenas um texto dizendo Hello! — http://localhost:3000/ (GET)
  2. Um formulário de contato com dois campos — http://localhost:3000/contato (GET)
  3. Por último uma rota com o mesmo path da anterior mas que responda ao método POST — http://localhost:3000/contato (POST)

Criando um servidor web com HapiJS

Para conseguirmos responder às 3 rotas que vimos acima, primeiro precisamos preparar o campo criando um servidor web. Para isso, criaremos um arquivo server.js que ficará dentro de uma pasta chamada app. Por favor, crie a pasta app no Desktop (Área de trabalho).

Dentro do nosso arquivo server.js faremos o código responsável por criar o nosso servidor HTTP com HapiJS:

const Hapi = require('hapi')
const server = new Hapi.Server()
const config = {
  port : 3000,
  host: 'localhost'
}

server.connection(config)

server.start((err) => {
  if (err) {
    throw err
  }

  console.log(`Server rodando em: ${server.info.uri}`);
  console.log('Para derrubar o servidor: ctrl + c');
})

Com o código finalizado e salvo, podemos pedir para o NodeJS executá-lo. Abra o terminal e execute as seguintes linhas:

~$ cd ~/Desktop/app
~/Desktop/app$ node server.js

Infelizmente após rodarmos o segundo comando receberemos uma mensagem de erro 🙁

module.js:471
    throw err;
    ^

Error: Cannot find module 'hapi'
    at Function.Module._resolveFilename (module.js:469:15)
    at Function.Module._load (module.js:417:25)
    at Module.require (module.js:497:17)
    at require (internal/module.js:20:19)
    at Object.<anonymous> (/Users/marcobrunobr/gh/posts/teste-hapijs/server.js:1:76)
    at Module._compile (module.js:570:32)
    at Object.Module._extensions..js (module.js:579:10)
    at Module.load (module.js:487:32)
    at tryModuleLoad (module.js:446:12)
    at Function.Module._load (module.js:438:3)

Obs: Talvez seja necessário você utilizar o scroll horizontal para ver toda a mensagem de erro.

Mesmo as mensagens de erro de JavaScript executados do lado do servidor não são amigáveis. Felizmente desta vez temos uma boa pista do que fizemos de errado:

Error: Cannot find module 'hapi

Definindo o HapiJS como dependência do projeto

É isso aí, nós não instalamos e definimos o hapi como dependência do nosso projeto. Bora resolver esse problema.
Primeiro precisamos criar um arquivo chamado package.json que tem a reponsabilidade de cuidar das informações do nosso projeto e suas dependências. Para criar o nosso package.json vamos rodar o comando abaixo dentro do diretório do projeto no terminal:

~/Desktop/app$ npm init

Após rodar esse comando teremos que responder à algumas perguntas como (name, version, description, entry point, test command, git repository, keywords, author and license) referente ao projeto. Após respondermos estas perguntas, teremos o package.json dentro da pasta do nosso projeto. O conteúdo do package.json deverá ser algo parecido com isso:

{
  "name": "app",
  "version": "1.0.0",
  "description": "",
  "main": "server.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node server.js"
  },
  "author": "Marco Bruno",
  "license": "ISC"
}

Pronto! Com o package.json criado podemos instalar o HapiJS e defini-lo como dependência do nosso projeto. Para fazemos isso, é bem simples, basta executarmos a seguinte linha no terminal:

~/Desktop/app$ npm install hapi --save

Após a instalação ter sido finalizada, teremos 3 linhas a mais no nosso package.json:

  "dependencies": {
    "hapi": "^16.1.1"
  }

Será que agora conseguimos subir o nosso servidor de boas. Volter ao terminal e execute o seguinte comando para o NodeJS executar o arquivo server.js:

~/Desktop/app$ node server.js

Se tudo der certo você terá a seguinte saída no terminal:

Server rodando em: http://localhost:3000
Para derrubar o servidor: ctrl + c

Se você abrir o browser (navegador) e tentar utilizar o servidor que acabamos de criar pela URL http://localhost:3000/, receberá como resposta um json elegante:

{"statusCode":404,"error":"Not Found"}

Este json aparece pois ainda não temos nenhuma rota que responda a esta URL.

Como criar rotas no HapiJS

Com o nosso servidor criado podemos construir as 3 rotas do nosso pequeno projeto. Vamos começar criando a rota para a Home. Abra o arquivo server.js e adicione as 7 linhas abaixo da linha que passamos as configurações do nosso servidor:

const Hapi = require('hapi')
const server = new Hapi.Server()
const config = {
  port : 3000,
  host: 'localhost'
}

server.connection(config)

// Aqui criamos a rota
server.route({
  method : 'GET',
  path : '/',
  handler : (req, res) => {
    res('Hello!')
  }
})

server.start((err) => {
  if (err) {
    throw err
  }

  console.log(`Server rodando em: ${server.info.uri}`);
  console.log('Para derrubar o servidor: ctrl + c');
})

Antes de ir ao browser, precisamos derrubar o servidor apertando as teclas CTRL e c juntas, e em seguida rodar o comando abaixo para subir o servidor novamente:

~/Desktop/app$ node server.js

Agora sim, vá até o browser e acesse http://localhost:3000/, você deverá ter como resposta um texto com o conteúdo Hello!.

Show! Agora podemos criar a nossa segunda rota que irá responder para o path /contato. Precisamos novamente abrir o arquivo server.js e adicionar algumas linhas após o código responsável pela rota da home:

const Hapi = require('hapi')
const server = new Hapi.Server()
const config = {
  port : 3000,
  host: 'localhost'
}

server.connection(config)

server.route({
  method : 'GET',
  path : '/',
  handler : (req, res) => {
    res('Hello!')
  }
})

// Criamos aqui a segunda rota
server.route({
  method : 'GET',
  path : '/contato',
  handler : (req, res) => {
    res(`
      <h1>Contato</h1>

      <form action="/contato" method="POST">
        <label for="email">Email:</label>
        <input type="email" name="email" id="email">

        <label for="mensagem">Mensagem:</label>
        <textarea name="mensagem" id="mensagem"></textarea>

        <input type="submit" value="Enviar">
      </form>
    `)
  }
})

server.start((err) => {
  if (err) {
    throw err
  }

  console.log(`Server rodando em: ${server.info.uri}`);
  console.log('Para derrubar o servidor: ctrl + c');
})

Todas vez que nós mudarmos o arquivo server.js precisamos derrubar o servidor pressionando as teclas CTRL e c juntas, em seguida executar o comando reponsável por subir o servidor com o código atualizado:

~/Desktop/app$ node server.js

Indo até o browser e acessando a URL http://localhost:3000/contato você terá como resposta um form bem simples e com um layout feito como este:

Clicando no botão enviar do formulário seremos enviados para a mesma rota que nós estamos mas com o método POST. Como não criamos um código para responder a essa rota, veremos como resposta o elegante JSON de 404 no nosso browser: {“statusCode”:404,”error”:”Not Found”}

Para resolvermos esse problema precisamos criar a nossa rota dentro do arquivo server.js. Nesse arquivo vamos adicionar outras 7 linhas após o código responsável pela rota do formulário:

const Hapi = require('hapi')
const server = new Hapi.Server()
const config = {
  port : 3000,
  host: 'localhost'
}

server.connection(config)

server.route({
  method : 'GET',
  path : '/',
  handler : (req, res) => {
    res('Hello!')
  }
})

server.route({
  method : 'GET',
  path : '/contato',
  handler : (req, res) => {
    res(`
      <h1>Contato</h1>

      <form action="/contato" method="POST">
        <label for="email">Email:</label>
        <input type="email" name="email" id="email">

        <label for="mensagem">Mensagem:</label>
        <textarea name="mensagem" id="mensagem"></textarea>

        <input type="submit" value="Enviar">
      </form>
    `)
  }
})

// Criando a última rota
server.route({
  method : 'POST',
  path : '/contato',
  handler : (req, res) => {
    res('<h1>Precisamos aprender a pegar os valores que o usuário digitou!</h1>')
  }
})

server.start((err) => {
  if (err) {
    throw err
  }

  console.log(`Server rodando em: ${server.info.uri}`);
  console.log('Para derrubar o servidor: ctrl + c');
})

Se você for até o terminal e reiniciar o servidor teremos a nossa rota com o método POST criada e pronta para ser utilizada. Volte ao browser, acesse o formulário e logo em seguida clique no botão enviar. Desta vez não teremos mais o erro, nossa resposta será a seguinte mensagem:

Precisamos aprender a pegar os valores que o usuário digitou!

Ser tiver qualquer dúvida ou o seu código do post não funcionou, por favor não deixe de perguntar pelo comentário ou se preferir pode me adicionar nas redes socias da vida, você vai conseguir me achar por MarcoBrunoBR. Até o próximo post sobre KoaJS.

The post Como criar um servidor HTTP com HapiJS appeared first on Blog da Caelum: desenvolvimento, web, mobile, UX e Scrum.

Como criar um servidor HTTP com KoaJS

$
0
0

Esse é o quarto e último post de uma série de posts que estou escrevendo. No primeiro post nós aprendemos a criar um servidor web com a API HTTP do NodeJS, no segundo post vimos como criar o servidor web com o ExpressJS e no terceiro como fazemos com o HapiJS. Agora é a vez de implementarmos um servidor web com o KoaJS.
Vamos rever o projeto que fizemos nos posts anteriores, pois faremos o mesmo agora.

As 3 rotas com KoaJS

Vamos implementar 3 rotas com o KoaJS, duas delas responderão ao verbo GET e a restante responderá ao POST.

  1. Nossa Home que terá apenas um texto dizendo Olás — http://localhost:3000/ (GET)
  2. Um formulário de contato com dois campos — http://localhost:3000/contato (GET)
  3. Por último uma rota com o mesmo path da anterior mas que responda ao método POST — http://localhost:3000/contato (POST)

Criar um servidor web com o KoaJS

Primeiro precisamos criar um servidor web para depois conseguirmos criar uma resposta para as três rotas.
Por favor, abra o terminal e execute o comando abaixo que é responsável por entrar no seu Desktop (área de trabalho), criar uma pasta app e em seguida entrar na pasta:

~$ cd ~/Desktop && mkdir app && cd app 

Dentro da pasta app, crie um arquivo com o nome de server.js, dentro desse arquivo adicionaremos o código responsável por criar o nosso servidor http com o KoaJS:

const Koa = require('koa')
const app = new Koa()
const port = 3000

app.use(ctx => ctx.body = 'Olás')

app.listen(port, () => {
  console.log(`Servidor criado em: http://localhost:${port}`)
  console.log('Para derrubar o servidor: ctrl + c')
})

O código é bem pequeno e direto. Se você está vendo pela primeira vez a const e a () => {}, recomendo você dar uma olhada no curso avançado de JavaScript na Alura do Flávio Almeida.

Agora só precisamos rodar o comando abaixo para colocar o servidor que acabamos de criar com o KoaJS:

~/Desktop/app$ node server.js 

Infelizmente, acabamos recebendo uma mensagem de erro. Algo como isso:

module.js:472
    throw err;
    ^

Error: Cannot find module 'koa'
    at Function.Module._resolveFilename (module.js:470:15)
    at Function.Module._load (module.js:418:25)
    at Module.require (module.js:498:17)
    at require (internal/module.js:20:19)
    at Object.<anonymous> (/Users/marcobruno/github/server-http-with-koajs/app/server.js:1:75)
    at Module._compile (module.js:571:32)
    at Object.Module._extensions..js (module.js:580:10)
    at Module.load (module.js:488:32)
    at tryModuleLoad (module.js:447:12)
    at Function.Module._load (module.js:439:3)

Recebemos esse erro porque não definimos o KoaJS como dependência do nosso projeto. Precisamos voltar no terminal e rodar o comando abaixo para resolver esse problema:

~/Desktop/app$ npm install --save koa

Nesse ponto vai levar alguns segundos, dependendo da sua internet pode levar alguns minutos 🙂
Agora que o KoaJS está instalado e definido como dependência do nosso projeto, podemos rodar o comando para subir o nosso servidor web em cima da plataforma NodeJS:

~/Desktop/app$ node server.js

Se tudo deu certo você receberá a seguinte saída no terminal:

Servidor criado em: http://localhost:3000
Para derrubar o servidor: ctrl + c

Agora vá até seu browser (navegador) favorito que com certeza é o Firefox 🙂 e acesse a URL http://localhost:3000. Se tudo estiver OK você terá o texto Olás como resposta no browser. Acesse outro path, por exemplo http://localhost:3000/contato. Infelizmente teremos sempre a mesma resposta. Chegou a hora de criarmos respostas diferentes para as 3 rotas que comentamos no começo do post.

Como criar rotas no KoaJS?

Dentro do parâmetro ctx que recebemos no callback do app.use tem um json com o nome de request, é dele que vamos tirar a informação para saber qual foi o path da request.

Se quisermos responder um formulário de contato quando o usuário fizer uma requisição para o path /contato. Vamos adicionar um if dentro do callback que o app.use tem, este if validará se o path da request é /contato, caso seja, responderemos no body da contexto o formulário de contato com um título. Abra o arquivo server.js e altere o código:

const Koa = require('koa')
const app = new Koa()
const port = 3000

app.use(ctx => {
  if(ctx.request.url == '/contato') {
      ctx.body = (`
        <h1>Contato</h1>

        <form action="/contato" method="POST">
          <label for="email">Email:</label>
          <input type="email" name="email" id="email">

          <label for="mensagem">Mensagem:</label>
          <textarea name="mensagem" id="mensagem"></textarea>

          <input type="submit" value="Enviar">
        </form>
      `)
  }
})

app.listen(port, () => {
  console.log(`Servidor criado em: http://localhost:${port}`)
  console.log('Para derrubar o servidor: ctrl + c')
})

Show! Já conseguimos implementar uma rota, só estão faltando duas. Vamos preparar nosso código para responder ao mesmo path (/contato) só que agora com o método POST. Para responder a métodos diferente mas com o mesmo path será necessário adicionar mais uma condição ao if que acabamos de adicionar, além de criar um segundo if para a requisição feita por POST para o mesmo path (/contato):

const Koa = require('koa')
const app = new Koa()
const port = 3000

app.use(ctx => {
  if(ctx.request.url == '/contato' && ctx.request.method == 'GET') {
      ctx.body = (`
        <h1>Contato</h1>

        <form action="/contato" method="POST">
          <label for="email">Email:</label>
          <input type="email" name="email" id="email">

          <label for="mensagem">Mensagem:</label>
          <textarea name="mensagem" id="mensagem"></textarea>

          <input type="submit" value="Enviar">
        </form>
      `)
  }
  
  if(ctx.request.url == '/contato' && ctx.request.method == 'POST') {
    ctx.body = '<h1>Precisamos aprender a pegar os valores que o usuário digitou!</h1>'
  }
})

app.listen(port, () => {
  console.log(`Servidor criado em: http://localhost:${port}`)
  console.log('Para derrubar o servidor: ctrl + c')
})

Dando uma olhada no código, ele não está muito elegante e bem complicado para ler, na verdade ele está bem parecido com o código que fizemos no post que utilizamos a API HTTP do NodeJS. Para não ficarmos com um código tão complicado para manter podemos instalar o module koa-router para cuidar das rotas para gente, dessa forma podemos deixar de utilizar os ifs da vida.

Por favor, abra o terminal e instale o koa-router e deixe ele como dependência do projeto:

~/Desktop/app$ npm install --save koa-router

Obs. Cuidado para não instalar o module errado chamado koa-route.

Agora que temos o module instalado precisamos mudar o código e tirar os ifs chatos. Abra o server.js e deixe o código dele assim… Ah! Já que vamos mudar o código para deixar ele mais feliz, bora aproveitar esse momento para adicionar também a rota da home:

const Koa = require('koa')
const Router = require('koa-router')

const app = new Koa()
const router = new Router()
const port = 3000

router
  .get('/', (ctx, next) => {
    ctx.body = `<h1>Você está na Home!</h1>`
  })
  .get('/contato', (ctx, next) => {
    ctx.body = (`
      <h1>Contato</h1>

      <form action="/contato" method="POST">
        <label for="email">Email:</label>
        <input type="email" name="email" id="email">

        <label for="mensagem">Mensagem:</label>
        <textarea name="mensagem" id="mensagem"></textarea>

        <input type="submit" value="Enviar">
      </form>
    `)
  })
  .post('/contato', (ctx, next) => {
    ctx.body = '<h1>Precisamos aprender a pegar os valores que o usuário digitou!</h1>'
  })

app.use(router.routes())

app.listen(port, () => {
  console.log(`Servidor criado em: http://localhost:${port}`)
  console.log('Para derrubar o servidor: ctrl + c')
})

Agora é só derrubar o servidor e subir ele novamente para ver se tudo está funcionando de boas e feliz:

~/Desktop/app$ node server.js

Vá até o browser pela última vez e verifique se as três rotas estão funcionando normalmente 🙂
Esse foi o meu último post dessa série, logo mais eu volto com mais post sobre NodeJS e os famosos frameworks escritos em JavaScript.
Se tiver qualquer dúvida, sugestões ou observações, por favor não deixe de falar, seja pelo comentário no post ou me mandando mensagem pelas redes sociais, você vai conseguir me achar em qualquer lugar por @MarcoBrunoBR.

The post Como criar um servidor HTTP com KoaJS appeared first on Blog da Caelum: desenvolvimento, web, mobile, UX e Scrum.

WebStorage: persistência no front-end

$
0
0

O mundo front-end está recheado de novas tecnologias para resolver os nossos problemas do dia a dia de forma mais elegante. Falamos bastante aqui sobre a ideia de aplicativos stateless e tokens, e programar com essa abordagem significa deixar a camada cliente mais rica e dinâmica, com possibilidade de armazenar temporariamente alguns informações importantes. Essas informações podem ser desde usuário e token logados até informações de cadastros para aplicativos web offline. Mas, onde e como iremos guardar esses dados?

Existem implementações de APIs que ajudam nessa solução, como visto neste ótimo artigo. Essas libs estão prontas para uso e funcionam até bem. No entanto, há a solução nativa implementada pelos navegadores mais atuais (e mobiles) que possibilita persistir dados no próprio navegador: o WebStorage. Com isso, não precisaríamos adicionar nenhuma lib ao nosso projeto para usufruir dessa opção de uso. A ideia aqui neste post é mostrar o uso nativo do WebStorage com exemplo de código para leitura.

Contudo, usar o WebStorage possui algumas limitações:

  • Armazenamento somente de Strings (incluindo Strings vazias).
  • Certa lentidão pela necessidade de parsear objetos JSON para Strings (caso necessário).
  • Navegadores sem suporte a HTML5 não funcionam.
  • Limite de armazenamento, depende do navegador. A média é de 2.5mb a 5 mb.

Dentro do WebStorage existem duas opções de implementação: o localstorage e o sessionstorage. A diferença entre eles é que no SessionStorage os dados são perdidos ao fechar o navegador, e já no LocalStorage eles permanecem mesmo reabrindo o browser.

Cadê o código?

Pelo JavaScript, há disponível o objeto window.localStorage para guardarmos qualquer dado que precisamos. Ele é baseado na ideia de chave-valor, então:

var objetoQualquer = {nome:'bla', documento:'123'};
window.localStorage['chavePersistida'] = JSON.stringify(objetoQualquer);

E para retornar:

var valorString = window.localStorage['chavePersistida'];
var objetoQualquer = JSON.parse(valorString);
console.log( 'Nome: '+objetoQualquer.nome );
console.log( 'Documento: '+objetoQualquer.documento );

Abra o console do seu navegador e tente executar os comandos acima. Veja que como ele guarda somente textos, usamos o JSON para transformação entre objetos e Strings. No entanto, você pode parsear um array de objetos mais complexos, e assim, simular uma base de dados na camada front-end.

Eu queria simular dados mais complexos

Na tentativa de simular um banco de dados de objetos no JavaScript, criei uma generalização do WebStorage disponível neste GitHub. Leia a implementação, ela está um tanto quanto simples, mas funcional para este propósito. Note que o objetivo de armazenar dados no client-side é dar suporte a funcionalidades ricas no cliente, mas nunca espelhar regras de negócio do seu cliente. O back-end sempre será a melhor solução em termos de segurança e independência quanto a validação de dados e regras de negócios. Guardar dados sensíveis no cliente pode ser um problema para a sua aplicação, não faça isso!

Foi criado e também disponível no GitHub um Service AngularJS para facilitar o uso do WebStorage. Assim, os exemplos abaixo usarão esta tecnologia.

Imagine um CRUD de cliente com os campos “nome” e “documento”. Em AngularJS, existirá um controller para ela. Para efeito do exemplo, irei juntar no mesmo controller tanto as funcionalidades de cadastro e de listagem. Segue os trechos importantes:

app.controller("ClienteController", function ($window, Cliente) {
    var ctrl = this;

    //...
    ctrl.remover = function(id) {
        Cliente.remover(id).then(function (result) {
            addMensagemRetorno(ctrl, result);
            ctrl.lista = result.lista;

        }, function (result) {
            /
        });
    };

    ctrl.gravar = function() {
        Cliente.gravar(ctrl.form).then(function (result) {
            addMensagemRetorno(ctrl, result);
            ctrl.irParaListagem();
 
        }, function (result) {
            addMensagemValidacao(ctrl, result);
        });
    };
    //...
    ctrl.init = function () {
        Cliente.listarTodos().then(function (result) {
            ctrl.lista = result.lista;
 
        }, function (result) {
            addMensagemValidacao(ctrl, result);
        });
    }; 
    ctrl.init(); 
}); 

Veja que este controller acessa um Service chamado Cliente. Esse Service é que encapsula o acesso aos dados do Cliente. Em um sistema normal, ele iria ao servidor enviar e buscar JSONs. Mas aqui, iremos simular este acesso ao backend com o uso do WebStorage.

app.service("Cliente", ["JPStorage", function (JPStorage) {
    this.nome = "Cliente"; //identificacao obrigatoria
    
    var dadosIniciaisCliente = [
    {
        id: 1,
        nome: 'Nome do Cliente Exemplo 01',
        documento: '12345678900'
    },
    {
        id: 2,
        nome: 'Nome do Cliente Exemplo 02',
        documento: '98765432100',
    }
    ];
    JPStorage.prepararBaseStorage(this, dadosIniciaisCliente);

    this.gravar = function (form) {
        return JPStorage.atualizarObjetoStorage(this, form);
    }

    this.remover = function (id) {
        return JPStorage.removerObjetoStorage(this, id);
    }

    this.buscarPorId = function (id) {
        return JPStorage.buscarObjetoPorIdStorage(this, id);
    }

    this.listarTodos = function () {
        return JPStorage.buscarTodosStorage(this);
    }

    this.filtrarPorNomeOuDocumento = function(form){
        return JPStorage.listarPorFiltrosStorage(this, {'nome':[form.pesquisa,'LIKE'], 'documento':[form.pesquisa,'EQUALS']});
    }
 }
]);

O objeto JPStorage possui funções básicas de CRUD para guardar, remover, retornar e filtrar objetos, encapsulando internamente o parse de objeto para texto, o acesso ao LocalStorage e simulando a estrutura de retorno de um “promise”. Assim, o seu uso se parece muito com acesso real ao servidor como $http ou Restangular. Por fim, para usar esse exemplo, deve-se registrar o modulo JPStorageModule na raiz do seu app.js.

Note que inicialmente criamos alguns dados de exemplo para popular o “banco” de clientes no storage. Como o Service do AngularJS é um singleton, ele somente irá criar esses dados no primeiro acesso ao Service. Por fim, veja que temos a possibilidade de filtrar dados simulando um “where” de consultas simples.

Mas qual o uso disso?

Bom, um dos usos para WebStorage é não quebrar seu aplicativo web caso a conexão do usuário caia, fazendo-o parecer um aplicativo offline (standalone). Isso funciona bem, mas é necessário pensar em estratégias de sincronia dos dados do servidor com o que está no LocalStorage. Outro uso que pode ser comum é o cache temporario de algum dado importante para melhoria de performance. Imagina um relatório pesado ou um dashboard com gráficos administrativos que não necessitam de atualização em tempo real.

Outra possibilidade de uso pode ser em relação a criação de MVP de um produto. Imagine que você precisa provar uma ideia de produto em um curto espaço de tempo, sendo que depois da avaliação e feedback, este aplicativo irá morrer. Hoje em dia, criar um back-end pode até ser simples, mas organizar banco de dados, acesso REST, configurações, etc, pode demorar um tempo (que talvez você não tenha). Com o uso dessa abordagem, pode-se criar um MVP simples persistindo os dados locais no WebStorage.

Uma outra opção seria a criação de mockups de tela para validação simples ao cliente, tudo meio funcional e com tecnologias Front-end.

E os Cookies?

Você pode estar se perguntando porque não usamos o bom e velho Cookie para guardar os dados. Pode sim ser usado, mas são abordagens distintas e resolvem problemas distintos. Por exemplo, caso você queira que os dados não se misturem entre abas do próprio navegador, não funcionará com Cookie, já que ele é compartilhado, diferente da implementação SessionStorage. Já em comparação com o LocalStorage (que também é compartilhado), o Cookie sempre troca dados entre o cliente e servidor a toda Request, enquanto que os dados no LocalStorage permanecerão somente no navegador (esse dado nunca irá ao servidor).

Então, qual deve-se usar, depende do que você quer implementar e da solução a ser resolvida. Mas diz aí, você costuma usar WebStorage? Para qual tipo de abordagem?

E não deixe de conferir nossas Trilhas de Front-end no Alura e dos Cursos Presencias na Caelum.

The post WebStorage: persistência no front-end appeared first on Blog da Caelum: desenvolvimento, web, mobile, UX e Scrum.

Novo curso voltado para o mercado de trabalho

$
0
0

Em nossa constante política de manter nossas apostilas atualizadas com práticas aplicadas em mercado, fizemos uma grande reestruturação do FJ-22, pensando no aluno que quer ingressar no mercado de trabalho através da Formação Java.

 

Uma das principais motivações que tivemos para fazer essa migração foi a troca do projeto do curso, que até então era sobre a Bolsa de Valores. Para deixar o projeto um pouco mais ajustável com possíveis cenários que podem ser encontrados no mundo real, algo próximo a um e-commerce: um Sistema de Venda de Ingresso para Cinema.

 

 

O que mudou

No momento que discutimos essa reestruturação, a primeira coisa que discutimos foi sobre a arquitetura que usávamos, onde tínhamos o MVC component based, atráves do uso de JSF com Primefaces, depois de analisarmos as vagas de emprego abertas para desenvolvedores Java, identificamos um constante pedido das empresas em solicitar Spring MVC, dado a isso mudamos nosso curso para Spring MVC, utilizando o MVC action based.

 

 

Além disso, esse curso vai proporcionar ao aluno a experiência e o cotidiano de um programador que acabou de entrar numa empresa, onde já há um projeto em andamento. Nesse ponto o curso mostrará git – versionador de código – e também maven – gerenciador de dependências.

 

Mantivemos do curso antigo alguns conceitos que serão explicados durante cada nova funcionalidade que iremos implementar como realizar webservices, aplicar design patterns e fazer testes com o JUnit.

 

Dá uma olhadinha na nova ementa aqui!

Esperamos comentários e feedbacks!

The post Novo curso voltado para o mercado de trabalho appeared first on Blog da Caelum: desenvolvimento, web, mobile, UX e Scrum.

Buscas Eficientes com Elasticsearch

$
0
0

Com o aumento do trafego e armazenamento de dados nas empresas as soluções voltadas para BigData são os atrativos do momento. Falando sobre NoSQL, existem várias formas e tecnologias para implementá-las, e aqui, vamos dar uma olhada no tão comentado Elasticsearch.

A ideia do Elasticsearch é que alem de armazenar os dados de forma não relacional, ele prove uma infra interna muito boa para retornar buscas muito pesadas. Por ser um motor de pesquisa textual altamente escalável, permite armazenar e analisar grandes volumes de informações praticamente em tempo real (eles mesmos usam o termo near real time).

Para instalá-lo, é muito simples, basta seguir o tutorial do próprio site deles. Na verdade, os tutoriais e API (em inglês) são muito simples e fácil de ler, tirando quase todas as dúvidas sobre a tecnologia. No entanto, vamos comentar aqui alguns passos e detalhá-los.

Em ambiente Unix, vamos baixá-lo e já estamos pronto para uso.

curl -L -O https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-5.4.1.tar.gz
tar -zxvf elasticsearch-5.4.1.tar.gz

E para executá-lo:

 
cd elasticsearch-5.4.1/bin
./elasticsearch -Ecluster.name=search-cluster-exemplo -Enode.name=search-node-exemplo

Isso irá subir o servidor Elasticsearch, deixar pronto para acesso na porta 9200 e apto a receber consultas via Rest. Ao bater no navegador a url raiz http://127.0.0.1:9200, temos o resultado abaixo, indicando que esta no ar e com detalhes sobre a versão.

{
  "name" : "search-node-exemplo",
  "cluster_name" : "search-cluster-exemplo",
  "cluster_uuid" : "oXT9j3sSTiy-fc8mKT-bBg",
  "version" : {
    "number" : "5.4.1",
    "build_hash" : "2cfe0df",
    "build_date" : "2017-05-29T16:05:51.443Z",
    "build_snapshot" : false,
    "lucene_version" : "6.5.1"
  },
  "tagline" : "You Know, for Search"
}

Cluster, Node e Index

No primeiro comando que executamos, você deve ter notado que passamos os parâmetros cluster.name e node.name. Eles não são parâmetros obrigatórios, mas é importante criar os nomes de cada um deles para sabermos o que estamos criando. Vamos então definir rapidamente os conceitos internos do Elasticsearch. Irei aqui subdividi-los em dois tipo.

Configuração:

  1. Clusters: a estrutura do topo, serve para agrupamento dos nós.
  2. Nodes: entidade servidora. Agrupa todos os índices de dados.

Dados:

  1. Index: categoriza os dados da aplicação e agrupa os tipos.
  2. Types: subcategoria dos dados, organiza e agrupa os documentos.
  3. Documents: JSON com os dados crus gravados. Um documento esta fisicamente dentro de um Type.

Imagine a arquitetura de árvore de dados, onde “o topo” são os Cluster e “as folhas” são os Documents. Veja que falamos no plural, porque o Elasticsearch tem o conceito clusterização vertical. Ele foi justamente feito para ser bem simples de gerenciar esses clusters e nós, de forma que a qualquer momento pode-se aumentar ou diminuir eles, sem nenhuma perda de dados. Pela url http://127.0.0.1:9200/_cat/health?v, podemos verificar o estado no nosso cluster, com contagem de nós e indices.

Inicialmente não temos nenhum Index criado por padrão, tanto que caso tentamos listar os indices pela url http://127.0.0.1:9200/_cat/indices?v, não temos nenhum resultado gerado. Mas, criar um indice é muito simples, basta criar um PUT para o seu node que ele irá criá-lo sem dificuldades. Em nossos exemplos, irei utilizar o comando cURL do linux. Com o Elasticsearch de pé, faremos no terminal o seguinte comando:

 
curl -X PUT http://127.0.0.1:9200/produtos

Isso vai criar um Index chamado produtos. Dentro dele, podemos adicionar os Types e os Documents, e esses dois serão adicionados ao mesmo tempo por um segundo comando PUT. Para fins de visualização, vou quebrar o conteúdo JSON em linhas, mas caso for executar, deixe sem quebras.

curl -X PUT http://127.0.0.1:9200/produtos/eletronicos/1 -d
'{
"descricao": "Liquidificador Potente",
"marca": "Chinesa",
"preco": "230.0"
}'

Isso vai criar o Type eletronicos dentro do Index produtos, com o id 1 e os dados indicados pelo JSON. Esse comando pode ser usado tanto para inserir quanto para atualizar um dado no Elasticsearch. Caso queira deletar esse dado, basta mudar o verbo HTTP para delete, sem passar nenhum dado interno.

curl -X DELETE http://127.0.0.1:9200/produtos/eletronicos/1

Consultas

As consultas são o grande trunfo do Elasticsearch. Podemos fazer vários tipos de agrupamentos e organizações com retorno muito rápido. Internamente, ele mantém os dados em cache para deixar o resultado ainda mais performático. As consultas são feitos por verbo HTTP GET passando os parâmetros desejados. Pegando o exemplo de dado criado anteriormente, caso queira consultar pelo nome do produto, podemos fazer de duas formas: direto como parâmetro GET da URL ou criando um corpo JSON interno. A segunda opção garante melhor visualização e muito mais opções de uso.

Pela URL GET:

curl http://127.0.0.1:9200/produtos/_search?pretty&q=descricao:liquid

Pelo JSON interno:

curl http://localhost:9200/produtos/_search?pretty -d
'{
  "query": {
    "match": {
      "descricao": "liquid"
    }
  }
}'

Note que usamos um parâmetro chamado pretty. Ele serve somente para melhorar o retorno dos dados de forma tabulada. Existem alguns tipos diferentes de seletores match, além de agrupadores, ordenadores, paginação e outros formatos de consultas. Ele faz tudo o que uma base relacional e um pouco mais além com suas opções de reduções.

E na realidade?

Vamos tentar analisar um exemplo mais real. Vamos simular um banco de dados relacional PostgreSQL e, como a mesma quantidade de dados, analisar o Elasticsearch. Vamos pegar como base de uma tabela de Pessoa, contendo:

  • ID.
  • Data de criação.
  • Nome.
  • Tipo de pessoa.

Inserimos nos dois bancos 1.466.485 registros aleatórios de pessoas e vamos analisar os resultados de consultas simples.

PostgresSQL

select * from pessoa where nome like '%a%' order by nome, tipopessoa;
-- Quantidade de resultados: 602.187
-- Tempo: 1m 6s, independente de quantidade de vezes consultadas.

select * from pessoa order by nome, tipopessoa;
-- Quantidade de resultados: 1.466.485
-- Tempo: 2m 37s, independente de quantidade de vezes consultadas.

Elasticsearch

curl http://localhost:9200/pessoas_idx/_search?pretty -d '{"query": { "match": { "nome": "a" }}, "sort": [{ "nome": "asc", "tipopessoa": "asc" }] }'
-- Quantidade de resultados: 602.187
-- Tempo: 1.2s no primeiro acesso, depois media de 10 milesegundos

curl http://localhost:9200/pessoas_idx/_search?pretty -d '{"query": { "match_all": {} }, "sort": [{ "nome": "asc", "tipopessoa": "asc" }] }'
-- Quantidade de resultados: 1.466.485
-- Tempo: 1.9s no primeiro acesso, depois media de 12 milesegundos

Como esperado, claro, o Elasticsearch foi muito mais rápido. As estruturas internas muito diferentes e o cacheamento automático do Elastic fazem as consultas ficarem absurdamente mais rápidas a partir do segundo acesso.

Um detalhe bem importante. Fizemos no Elasticsearch uma consulta com ordenador por um atributo textual. Por padrão, ordenadores e agregadores neste tipo de campo não são possíveis por questões de performance. Assim, temos que habilitar os campos textuais a ser um “fielddata”. Para o nosso exemplo, seria o comando abaixo, antes de executar as consultas.

curl -X PUT http://localhost:9200/pessoas_idx/_mapping/pessoas_type?pretty -d '{"properties": {"nome": {"type": "text","fielddata": true}, "tipopessoa": {"type": "text","fielddata": true}}}'

A comparativa entre os bancos não nos diz que relacional ou o PostgreSQL não deva ser usado. Pelo contrário! Bancos NoSQL possuem seus defeitos, como não garantir integridade relacional entre os dados. Assim, relacional e NoSQL devem ser usados ao seu propósito. Vamos a um por exemplo:

  • Em uma aplicação onde os dados devem ter integridades relacionais, possuem um banco de dados padrão como PostgreSQL.
  • Mas, há uma funcionalidade que precisa de feedback muito rápido na Home do sistema, como uma daquelas caixas de texto com autocomplete automático sugerido: o usuário ao digitar o nome dos produtos, o sistema vai filtrando e exibindo o resultado para pesquisa.

Fazer isso em um banco relacional ou ficará lento, ou necessitará de ajustes finos, com configurações especificas no SQL ou mesmo um cache interno na própria aplicação. O que poderia ser melhor arquitetado é atrelar somente essa funcionalidade ao Elasticsearch, e o restante da aplicação, continuar a ser banco relacional PostgreSQL.

Mas, você pode estar se perguntando como os dados dos produtos estarão no Elasticsearch, já que a base principal é um relacional? Existem configurações que automatizam o Elasticsearch a sincronizar os dados com outras bases, relacionais ou não. Para isso há a ferramenta chamada LogStash, que pode ser configurado a suportar sincronização automática com o PostgreSQL. Dessa forma, o programador se preocupa em gravar os produtos no relacional, e deixa a cargo do LogStash em puxar os dados para o Elastic. Além do mais, essa configuração é muito simples, basicamente editar um arquivo de configuração contendo os dados de acesso a base relacional e para qual Node ele sincronizará os dados.

Outra vantagem do Elastisearch é a ferramenta visual chamada Kibana. Ele mostra uma análise de dados com gráficos automáticos. Inclusive pode-se usar o Kibana para executar os scripts de query do Elastisearch sem precisar ir ao terminal Linux.

Caso queiram tentar simular as consultas deste post, os scripts estão neste GitHub.

O que tem mais?

Você notou que ao subir o Elasticsearch ele disponibiliza várias URL Rest para gerenciamento dos dados. No entanto, você pose usá-lo em sua linguagem de programação preferida por meio de suas APIs, como por exemplo, a Java API. Elas dispõem exatamente as mesmas features das URL Rest, mas você programa como uma DSL.

Outro ponto interessante são os servidores Cloud já configurados e prontos para uso. A AmazonAWS, o Google Platform e o próprio Elastic Cloud possuem ótimas ferramentas Cloud, mas pagas. Se você pode investir nessa infra, vale muito a pena.

Sobre as consultas, o Elasticsearch tem conceitos de paralelismo e consultas múltiplas. Caso queira, dê uma olhada na documentação sobre isso.

Por fim, passamos aqui bem por cima nos conceitos do Elasticseach, mas existem muita coisa boa para minerar dados, até mesmo usando Machine Learning.

E você, esta utilizando alguma forma de busca não relacional? Quais ferramentas BigData estão usando? Não deixe de conferir nossa Trilha de Infraestrutura no Alura.

 

 

 

 

The post Buscas Eficientes com Elasticsearch appeared first on Blog da Caelum: desenvolvimento, web, mobile, UX e Scrum.

Invocando métodos assíncronos com Spring

$
0
0

O ecossistema Spring é muito usado por várias aplicações Java mundo afora e uma das features interessantes desse framework é a possibilidade de criarmos chamadas assíncronas entre nossos métodos. O tutorial do site do Spring é bem explicativo, mas vamos aqui dar um exemplo mais real que passei em um dos projetos em que trabalho.

Por mais que um sistema tende a ser automatizado existe a necessidade de importar ou exportar arquivos para integrações, e algumas vezes, estes arquivos são muito grandes e pesados, levando minutos para processamento. Ao criar uma tela de upload, o usuário teria que ficar esperando esses minutos sem sair da tela para poder ver o resultado da importação. Com as chamadas assíncronas podemos processá-los em paralelo, exibindo uma mensagem independente de onde ele esteja no sistema.

Outro ponto de atenção é que se você configurou no seu sistema o OpenEntityManagerInViewFilter e acessar uma funcionalidade mais demorada, independente do que seja, pode ocasionar timeout de transação. Criar essa chamada de forma assíncrona resolveria esse tipo de problema.

Configurações

A configuração para usar métodos assíncronos depende de um TaskExecutor. Esse executor é um cron Quartz interno do Spring e pode ser configurado pelo SpringBoot ou por xml. Por padrão, o Spring cria uma instancia da classe SimpleAsyncTaskExecutor, que é um executor que não cria um pool de thread, e assim, cada ação assíncrona cria uma nova thread no seu sistema. Mas, podemos configurar para usar o ThreadPoolTaskExecutor, que como o nome diz, cria o pool.

A diferenciação qual dessas duas classes usar depende de um único parâmetro. Configurando assim:

<task:executor id="executorSimples"/>
<task:annotation-driven executor="executorSimples"/>

Criarmos o TaskExecutor implementado pela classe SimpleAsyncTaskExecutor. Agora, se usarmos assim:

<task:executor id="executorComPool" <strong>pool-size="5" queue-capacity="500"</strong>/>
<task:annotation-driven executor="executorComPool"/>

Configuramos implementado pelo ThreadPoolTaskExecutor. De prontidão, reaproveitar threads com pool seria a melhor opção. Caso esteja usando SpringBoot, a configuração ficaria dessa forma:

@SpringBootApplication
@EnableAsync
//...
public class Application {

  //...

  @Bean
  public Executor asyncExecutor() {
    ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
    executor.setCorePoolSize(2);
    executor.setMaxPoolSize(5);
    executor.setQueueCapacity(500);
    executor.initialize();
    return executor;
  }
}

Uma última configuração necessária é a anotação @Async no método do seu @Service que você queira que seja invocado assincronamente. Esse método pode ter qualquer tipo de parâmetro, mas seu retorno deverá ser uma implementação da interface Future, mais precisamente, a classe CompletableFuture. Essa classe é utilizada para criar funcionalidades reativas em java, onde a execução assíncrona das regras é feita por uma Thread paralela, não bloqueando a execução principal. O método principal dessa classe é o “get()”, que aguarda até as regras serem executadas para poder retornar o resultado.

Veremos como montar um método para ser chamado assincronamente.

@Async
public CompletableFuture<String> metodoAssincrono(String parametro1, Integer parametro2) { 
   //...

   return CompletableFuture.completedFuture("Bla");
}

Ao invocar esse método você já irá receber a resposta instantaneamente, pois sua execução foi iniciada por uma outra Thread. O retorno CompletableFuture é um promisse, uma promessa de retorno, e você pode verificar por ele se a execução já foi concluída ou se ainda esta em andamento. Caso já tenha sido concluída, é possível pegar o conteúdo configurado como retorno, no meu caso, o texto “Bla”. Caso ainda esteja em andamento, pode-se aguardar a sua conclusão.

Um último ponto é que esta configuração contem dentro do jar core do Spring. Então, basta a configuração de jars padrão no seu pom.xml que já provê essa possibilidade.

Carregando um arquivo grande

Com a possibilidade de execução assíncrona, criei um exemplo de importação de um arquivo contendo milhões de dados. Nesse exemplo teremos a opção de execução sincronizada, como qualquer ação normal, e outra para execução assíncrona, tratado neste post.

Pensando em um sistema web, quando criamos chamadas assincronas, a verificação do estado da execução, ou seja, se está em andamento ou já concluída, deve ser feito periodicamente. Podemos criar uma conexão Websocket para enviar o aviso de conclusão ao usuário da aplicação, ou de forma bem simples, mas não tão performática, criar um pooling javascript, onde o navegador fica “perguntando” ao servidor se já está concluído. Para fins deste exemplo, usarei o pooling, mas em uma aplicação real, considere o Websocket.

No controlador, teremos os seguintes métodos:

@Controller
@RequestMapping(value = "/pessoa/exec")
public class PessoaController {
   
   @Autowired
   private ImportacaoService importacaoService;
   //...
   
   @RequestMapping(value = "/assinc", method = RequestMethod.GET)
   public String requisicaoAsync(Model m, HttpSession session){

      CompletableFuture<AsyncForm> promisse = importacaoService.importarAsync();
      this.addSessaoPromisse(session, promisse);
      return "pagina-assinc";
   }

   @ResponseBody
   @RequestMapping(value = "/assinc/verificar", method = RequestMethod.GET)
   public AsyncForm verificarAsync(HttpSession session) throws InterruptedException, ExecutionException {

      CompletableFuture<AsyncForm> promisse = this.getSessaoPromisse(session);
      if (promisse == null) { 
         return null; 
      } else { 
         return promisse.getNow(new AsyncForm(this.getSessaoLog(session))); 
      }
   }

   private void addSessaoPromisse(HttpSession session, CompletableFuture<AsyncForm> promisse) { 

      session.setAttribute("FUTURE_IMPORTACAO", promisse); 
   }

   private CompletableFuture<AsyncForm> getSessaoPromisse(HttpSession session) {

      return (CompletableFuture<AsyncForm>) session.getAttribute("FUTURE_IMPORTACAO");
   }

   //... 
}

A classe ImportacaoService é onde temos o nosso método assincrono, chamado pelo requisicaoAsync do controlador. Essa execução para o controller é instantânea: ele não aguarda a invocação ser finalizada e já faz o response da requisição. No nosso exemplo, além da execução assincrona, eu quero poder avaliar a conclusão com o pooling javascript. No entanto, como uma request não conhece a anterior, a requisição do pooling não teria acesso ao CompletableFuture para validar o resultado. Dessa forma, o que faço aqui é guardar o promisse na sessão do meu usuário corrente, para futura verificação da conclusão, que é feita pelo método verificarAsync. Este faz um retorno JSON para a chamada Ajax do meu html.

O método getNow do Future tem a caracteristica de retornar o conteúdo da ação, caso ela tenha sido concluída, mas caso não, ele retorna um padrão. Esse padrão foi criado no parâmetro do método getNow. Existe ainda um outro método chamado somente get, que trava e espera até que a conclusão aconteça, e um outro isDone para verificar se a execução já foi concluída ou não.

Agora veja um trecho do Service.

@Async
public CompletableFuture<AsyncForm> importarAsync() {
 
   List<Pessoa> pessoas = this.importarArquivoCSVGrande();
 
   for(Pessoa p : pessoas){
      this.pessoaServico.gravar(p);
   }
 
   AsyncForm form = new AsyncForm();
   form.addLog("Importado "+pessoas.size()+" itens.");
   form.setPessoas(pessoas);

   return CompletableFuture.completedFuture(form);
}

O método importarAsync será executado por uma Thread separada da execução principal, onde o promisse CompletableFuture terá o resultado após a conclusão.  Com isso, o tempo de importação e gravação, que pode demorar minutos, não interrompe o usuário de navegar pelo sistema, e ele será devidamente avisado da conclusão. Para ver o código mais detalhado, acesse este GitHub.

Mas diz aí, já precisou de alguma chamada assincrona no servidor? Não deixe de conferir nossas turmas presenciais de Java e Web.

The post Invocando métodos assíncronos com Spring appeared first on Blog da Caelum: desenvolvimento, web, mobile, UX e Scrum.

Como preparar o ambiente e escrever seu primeiro código com Kotlin

$
0
0

Recentemente tivemos diversas novidades em um dos maiores eventos de tecnologia do mundo, o Google I/O 2017.

Dentre as diversas novidades, uma das que muitos programadores ficaram atentos foi justamente no anúncio da Google informando que agora o Kotlin terá suporte total no Android Studio a partir da versão 3.0.

Tal informação gerou um grande impacto na comunidade que começou a dar bastante atenção na linguagem e conhecer os diferenciais que a linguagem tem em relação ao famoso Java.

Por mais que eu goste da linguagem Kotlin, não significa que ela é melhor ou pior que a linguagem Java ou outras linguagens existentes. Portanto, fique à vontade em avaliar e decidir o que faz mais sentido para o seu dia a dia.

Afinal, o que é Kotlin?

Para quem nunca ouviu falar, Kotlin é uma linguagem de programação desenvolvida pela JetBrains com as seguintes características:

  • Multiparadigma: além de suportar a orientação a objetos, existem features comuns do paradigma funcional.
  • Multiplataforma: mesmo rodando em seu modo nativo ela também pode ser compilada e executada em um ambiente Java, isto é, na própria JVM. Também, é possível escrever em Kotlin e gerar código JavaScript.
  • Consistente: permite criar estruturas mais enxutas, se comparado com a quantidade de código que outras linguagens apresentam, como é o caso de um POJO no Java.

Existem muitas outras características motivadoras para considerarmos a utilização da linguagem, entretanto, o foco deste post será apresentar um primeiro contato com a linguagem para desenvolvedores Java.

Em outras palavras, não vou entrar em muitos detalhes sobre os motivos pelos quais você deve considerar o Kotlin, mas não se preocupe, deixo aqui a referência da excelente documentação que especifica com muitos detalhes tudo que veremos aqui. Bora começar?

Preparando o ambiente

Como exemplo para o post, farei uso de um projeto para um sistema bancário da empresa Bytebank. Algo similar ao que vemos na apostila de Java Orientação a Objetos.

Caso queira testar o Kotlin, recomendo fortemente o uso do IntelliJ IDEA que é a IDE oficial da JetBrains para desenvolvimento Java, como também, para desenvolver em Kotlin. Claro, você pode estar pensando:

“Puts, eu vou ter que pagar o IntelliJ pra testar o Kotlin?”

Não necessariamente, pois a própria versão Community já permite criar um projeto Kotlin sem nenhuma restrição, ou seja, tanto projeto Kotlin “puro” ou com uma Build Tool como é o caso do Maven ou Gradle.

Para os amantes do Eclipse, não fiquem chateados a JetBrains também dá suporte para ele.

Dica: Caso seja o seu primeiro contato com o IntelliJ IDEA, aproveite para dar uma olhada em algumas das dicas para quem está começando com a ferramenta 🙂

Criando o primeiro código no Kotlin

Após realizar o setup do projeto, percebemos que temos praticamente o mesmo resultado quando criamos um projeto Java, isto é, temos o nosso famoso src para escrevermos o nosso código fonte.

Sendo assim, vamos criar o arquivo Principal.kt, em seguida, adicionaremos o método main que é responsável em executar a nossa aplicação, neste momento você deve estar pensando:

“Bom, então bora para o public static void main…

Pois é, a princípio pensamos isso mesmo, entretanto, como eu havia mencionado, o Kotlin tende ser mais consistente durante a escrita de código e por isso temos o seguinte resultado para o nosso famoso main:


fun main (args: Array){
    println("Bem vindo ao Bytebank")
}

Com o main implementado, precisamos apenas executar para verificar o que acontece! Nesse instante, provavelmente, você deve estar pensando:

“Calma aí! E como fica a estrutura da classe para deixar esse código do main?”

Um dos grandes detalhes do Kotlin é que não precisamos de uma estrutura de classe para criar uma função, nem para executar o main! Portanto, ao executar o código do jeito que tá, temos o seguinte resultado:


Bem vindo ao Bytebank

Algo já esperado né? Mas, além de não precisar de uma classe, note que temos algumas mudanças na assinatura do nosso main:

  • A primeira delas é justamente a keyword fun, isso significa que no Kotlin, ao invés de declarar métodos, declaramos funções. Na prática acabam sendo a mesma coisa, a diferença é que identificamos as funções por meio de uma palavra reservada da linguagem.
  • Note que agora não precisamos mais indicar o modificador de acesso public, ou seja, por padrão o Kotlin já considera que todos os membros sejam públicos, isto é, todas as variáveis, funções ou classes serão marcadas como public caso não tenha indicado um modificador de acesso em específico.
  • Também em nenhum momento falamos qual é o retorno da função, isso significa que por padrão, quando não indicamos o retorno, o Kotlin já devolve uma classe chamada Unit, essa classe representa o nosso famoso void.

Claro, observe que o nosso famoso sysout (System.out.Println()) ficou bastante resumido também! Apenas chamando o println(), como também, não está com os famosos ponto e vírgula (;)…

Observações: Por mais que não tenha o ” ; ” para indicar o final da instrução de uma linha de código, não significa que teremos sérios problemas, por exemplo, ao tentar fazer o seguinte código: println("Bem vindo ao Bytebank") println(“novo print”).

O Kotlin tenta interpretar o segundo println() e, por isso, apresenta um erro.

Em outras palavras, o ” ; “ é opcional, portanto, para fazer com que o código de exemplo funcionasse, bastaria deixar da seguinte maneira: println("Bem vindo ao Bytebank"); println(“novo print").

O mesmo código em Java, teria o seguinte aspecto:


public class Principal {
    public static void main(String[] args) {
        System.out.println("Bem vindo ao Bytebank");
    }
}

Veja que nesse primeiro passo o Kotlin foge bastante dos famosos boilerplates que vemos no Java.

Utilizando as primeiras variáveis

Bacana, fizemos o nosso primeiro contato, portanto vamos avançar. O nosso próximo passo é representar uma conta por meio dos seguintes valores:

  • Número da conta
  • Titular
  • Saldo

No Java teríamos o seguinte código:


public class Principal {
    public static void main(String[] args) {
        System.out.println("Bem vindo ao Bytebank");
        int numeroDaConta = 1;
        String titular = "Alex";
        double saldo = 100.0;
    }
}

E no Kotlin? Vejamos:


fun main(args: Array) {
    println("Bem vindo ao Bytebank")
    val numeroDaConta = 1
    val titular = "Alex"
    val saldo = 100.0
}

Novamente, temos algumas diferenças um tanto quanto notáveis:

  • As variáveis não apresentam seus tipos;
  • Para declarar variáveis estamos usando a keyword val.

Nesse momento você pode estar pensando:

“O que está acontecendo no código? As variáveis não possuem tipos?”

A princípio pode parecer que as variáveis não possuem tipos, mas se tentarmos, por exemplo, verificar qual é a classe do Java que é utilizada em runtime:


println(numeroDaConta.javaClass)
println(titular.javaClass)
println(saldo.javaClass)

Temos o seguinte resultado:


int
class java.lang.String
double

Veja que o Kotlin já consegue assinar pra gente o tipo da variável em tempo de compilação! Mas agora vem aquela questão bem comum:

“Mas se no caso, eu quiser restringir o tipo da variável?”

Lidando com a tipagem das variáveis

Pensando justamente nesse detalhe, temos a capacidade de atribuir o tipo que esperamos para a variável usando o seguinte padrão: nomeDaVariavel: Tipo. Considerando o nosso exemplo inicial, poderíamos escrever o nosso código da seguinte maneira:


val numeroDaConta: Int = 1
val titular: String = "Alex"
val saldo: Double = 100.0

Repare que essa abordagem deixa o código mais flexível, isto é, uma vez que é bem claro qual o tipo da variável, podemos deixar o tipo implícito para evitar um código ambíguo durante a leitura do mesmo.

Entretanto, nos casos que assinamos uma variável e não é tão claro o tipo que ela está recebendo, podemos deixar o tipo de forma explícita para facilitar a leitura e compreensão.

Um dos comportamentos comuns quando usamos variáveis em qualquer linguagem de programação, é justamente o fato de alterarmos os seus valores, por exemplo, ao invés de deixar o titular com apenas o primeiro nome, é muito comum colocarmos o nome completo, no meu caso Alex Felipe, portanto, vamos atribuir novamente o valor da variável titular:


val numeroDaConta: Int = 1
val titular: String = "Alex"
val saldo: Double = 100.0
titular = "Alex Felipe"

Nesse instante o Kotlin apresenta um erro: Val cannot be reassigned. Em outras palavras, ele está nos dizendo que não podemos reatribuir um valor para a variável titular. E agora?

Maneiras de declarar variáveis

Como vimos até agora, tivemos que utilizar a keyword val para declarar uma variável no Kotlin, mas o que isso realmente significa?

Para a galera de Java, a única preocupação durante a criação da variável é justamente em indicar o seu tipo junto do seu nome sucessivamente, certo?

Mas no Kotlin o conceito é um pouco diferente, quando usamos o val estamos indicando que queremos uma variável read-only, ou seja, apenas para leitura, ou melhor, uma constante!

É justamente por este motivo que o Kotlin apresentou aquele erro… Com toda certeza você está pensando:

“E como eu declaro, de fato, uma variável que permite alterar o seu valor”

Para isso o Kotlin reserva a keyword var. Continuando com o nosso código anterior, poderíamos alterar o nome do titular com o seguinte ajuste:


val numeroDaConta: Int = 1
var titular: String = "Alex"
val saldo: Double = 100.0
titular = "Alex Felipe"

De forma resumida, caso suas variáveis precisarem mudar de valor, utilize o var, caso contrário, utilize o val.

É válido mencionar que, se sua variável não foi criada para ser modificada no código, sempre considere o uso do val para garantir a integridade do seu código 🙂

Criando classes

Veja que já conseguimos representar uma conta do banco por meio das variáveis que criamos, mas sabemos que, ao invés de usarmos variáveis soltas, utilizamos classes para representar uma entidade do mundo real, como é o caso da nossa conta!

Sendo assim, vamos criar a classe que conterá as variáveis que acabamos de criar. No Java temos a seguinte estrutura:


public class Conta {

    private int numeroDaConta;
    private String titular;
    private double saldo;

    public int getNumeroDaConta() {
        return numeroDaConta;
    }

    public void setNumeroDaConta(int numeroDaConta) {
        this.numeroDaConta = numeroDaConta;
    }

    public String getTitular() {
        return titular;
    }

    public void setTitular(String titular) {
        this.titular = titular;
    }

    public double getSaldo() {
        return saldo;
    }

    public void setSaldo(double saldo) {
        this.saldo = saldo;
    }

}

Como podemos ver, temos atributos privados junto dos seus métodos de acesso (getters e setters). E como fica no Kotlin?


class Conta {
    var numeroDaConta: Int = 0
    var titular: String = ""
    var saldo: Double = 0.0
}

Bem menor, concorda? Mas perceba que temos um detalhe bem peculiar:

“Por que tivemos que inicializar os membros da classe?”

Seguindo as boas práticas de programação

Um dos detalhes bem bacana dentro do Kotlin, é justamente a ideia de seguir diversas das boas práticas de programação, ou seja, a linguagem, por padrão, não permite que uma variável não seja inicializada e, como podemos ver, isso conta para membros de classes também.

Portanto, se não inicializamos via construtor, por exemplo, precisamos apresentar um valor para a variável por padrão.

Claro, existem casos que inicializar uma variável não é um comportamento esperado, como é o caso do conceito de injeção de dependência. No Kotlin, temos algumas alternativas para lidar com esse tipo de situação, no caso da injeção de dependência, poderíamos usar o recurso lateinit.

Muito legal, mas… Como faço pra instanciar uma classe e atribuir os valores em seus atributos no Kotlin?

Criando objetos e atribuindo valores aos seus membros

Sendo mais prático, faríamos o seguinte código para criar uma conta no Java:


Conta conta = new Conta();
conta.setNumeroDaConta(1);
conta.setTitular("Alex Felipe");
conta.setSaldo(100.0);

No Kotlin ficaria da seguinte maneira:


val conta = Conta()
conta.numeroDaConta = 1
conta.titular = "Alex Felipe"
conta.saldo = 100.0

Note que não é necessário usar a keyword new para criar uma instância, também, perceba que usamos o operador “=” para atribuir um valor para cada membro da classe Conta. E tenho quase certeza que você está se perguntando:

“Considerando as boas práticas de OO, acessar um atributo diretamente é perigoso, justamente por permitir que qualquer um manipule o valor como bem entender… Então por que deixamos assim no Kotlin?”

Utilização de properties

A princípio, parece que estamos acessando diretamente os atributos da classe olhando a estrutura do código escrito em Kotlin, mas, na verdade, estamos acessando uma property, ou seja, ao invés de acessar o campo em si, acessamos de fato os seus “getters” e “setters”.

“Mas Alex, como você me garante que é uma property mesmo? Pois no meu ponto de vista parece um acesso direto…”

Essa é uma pergunta recorrente e é fácil testarmos o código para garantir se realmente estamos lidando com uma property ou um campo de fato. Para isso, podemos implementar um setter qualquer de uma property, por exemplo, vamos alterar o setter da property numeroDaConta:


class Conta {
    var numeroDaConta: Int = 0
    set(value) {
        println("Recebendo valor na property numeroDaConta")
        numeroDaConta = value
    }
    var titular: String = ""
    var saldo: Double = 0.0
}

Veja que foi adicionada a keyword set logo abaixo da property numeroDaConta. Dentro do set estamos fazendo um print indicando recebemos um valor na property. Vamos testar?

Isso mesmo, tomamos um StackOverFlowError por realizar uma operação sem fim! Em outras palavras, fizemos um setter que fica chamando ele mesmo, ou melhor, um setter recursivo!

Mudando o valor de uma property diretamente

Para evitar esse tipo de situação, isto é, caso seja necessário modificar o próprio campo de uma property dentro do seu setter, o Kotlin oferece um recurso conhecido como Backing Fields. Basicamente, usamos a keyword field e temos acesso direto ao atributo:


class Conta {
    var numeroDaConta: Int = 0
    set(value) {
        println("Recebendo valor na property numeroDaConta")
        field = value
    }
    var titular: String = ""
    var saldo: Double = 0.0
}

Testando novamente percebemos que a mensagem “Recebendo valor na property numeroDaConta” é apresentada apenas uma única vez, e se imprimirmos a property:


println(conta.numeroDaConta)

Apenas o valor dela é apresentado!

Para saber mais

Por mais que tenhamos visto bastante das peculiaridades do Kotlin, ainda é só o começo, isto é, existem diversas outras features interessantes da linguagem para colocarmos na balança se é algo que vale a pena investir o tempo de estudo ou não, veja alguma delas:

Claro, além delas também existe o caso da interoperabilidade que existe entre o código Kotlin e Java que de fato é um dos grandes destaques de usar o Kotlin no ambiente Java, ou seja, chamar classes do Java dentro do código Kotlin como chamar classes do Kotlin no Java.

Mais conteúdos de Kotlin

Agora que você já teve o primeiro contato com Kotlin, vou aproveitar e deixar um agregador de conteúdo onde listo todos os conteúdos de Kotlin que escrevi e que ainda vou escrever. Espero o seu feedback lá também 😉

View story at Medium.com

Conclusão

Como vimos, o Kotlin vem com a proposta de tentar simplificar o máximo possível o nosso código, isto é, evitando os famosos boilerplates no código. Também, a ideia da linguagem é tentar aplicar muitas das boas práticas de programação existentes em outras linguagens, como é caso do Swift, C# entre outras. De modo geral aprendemos os seguintes tópicos:

  • Criação da função main
  • Declaração de variáveis e seus tipos
  • Criação de classes e objetos
  • Utilização e acesso direto de properties

Agora que teve esse primeiro contato com o Kotlin, aproveite e deixe o seu comentário sobre o que achou da linguagem 🙂

The post Como preparar o ambiente e escrever seu primeiro código com Kotlin appeared first on Blog da Caelum: desenvolvimento, web, mobile, UX e Scrum.


O mínimo que você deve saber de Java 9

$
0
0

O título não é uma coincidência, esse post foi criado com o mesmo intuito daquele que eu escrevi em 2014 junto com o Paulo Silveira, anunciando as principais alterações da nova versão da linguagem Java. Com pequenas e enormes mudanças, o JDK 9 já está disponível para download.

São muitas as novidades e eu vou compartilhar aqui um resumo das principais delas, além de passar vários links e recursos que você pode utilizar para se atualizar.

Um java interativo

Ao baixar e configurar o Java 9 em seu ambiente você já consegue utilizar uma das grandes introduções da versão, que é a nova ferramenta REPL (Read-Eval-Print Loop).

Ela traz uma forma muito mais interessante de experimentar código, sem toda burocracia e verbosidade das classes com método main. Basta abrir o console, digitar jshell — que é o nome da ferramenta — e sair escrevendo código Java!

turini ~ $ jshell
| Welcome to JShell — Version 9
| For an introduction type: /help intro

Eu li a documentação completa da nova especificação da linguagem com o JShell aberto, experimentando cada uma das mudanças e com zero preocupações de setup. Foi uma experiência incrível.

Nesse outro post eu mostro vários exemplos com os principais detalhes e possibilidades.

E é muito legal também saber que você pode escrever algum snippet de código nele e depois compartilhar, pra que outras pessoas possam facilmente testar sua implementação — ou mesmo modificá-la e te enviar de volta.

Quer um exemplo? Podemos criar um método com o mesmo código usado no post de Java 8, para retornar a média de tamanho de uma lista de palavras.

Vou abrir o JShell e escrever o código em um método chamado averageLenght:

jshell> public double averageLenght(List<String> words) {
…> return words.stream()
…> .mapToInt(String::length).average()
…> .orElse(0);
…> }
	
| created method averageLenght(List<String>)

Agora criar uma lista de palavras:

jshell> List<String> words = List.of(“podemos”, “criar”, “listas”, “assim”);

E experimentar minha implementação:

jshell> averageLenght(words)
	
==> 5.2

Funciona!

Para salvar esse código e compartilhar, vou usar o comando /save.

jshell> /save average-lenght

O arquivo average-lenght foi criado. Experimente agora baixar esse arquivo e depois abrir o seu jshell com ele como a seguir:

turini ~ $ jshell average-lenght

A lista de palavras e o método averageLenght já vão estar aí, disponíveis para você testar.

Imagina que legal no fórum da Alura ou GUJ, por exemplo, você conseguir enviar o pedacinho de código problemático para que o pessoal abra e te ajude encontrar o problema? Você consegue criar classes, métodos e inclusive usar dependências externas — JARs de bibliotecas etc — nesse ambiente interativo, e tudo que a pessoa precisa é ter o Java 9 instalado.

Um Java reativo

O JDK 9 também evoluiu a arquitetura do pacote java.util.concurrent, com a introdução da Flow API que implementa a especificação de Reactive Streams. A API é composta pela classe abstrata Flow e suas interfaces internas, que seguem o padrão de publicador e assinante — publisher/subscriber pattern.

java.util.concurrent.Flow
java.util.concurrent.Flow.Publisher
java.util.concurrent.Flow.Subscriber
java.util.concurrent.Flow.Processor

Apesar do nome extremamente familiar, Reactive Streams não tem relação direta com a API de Streams do Java 8. Essa é uma confusão completamente comum, mas nesse caso o termo Streams remete a fluxo, fluxos reativos. Um passo criado a partir do caminho definido pelo famoso manifesto reativo.

Ele vem pra resolver o famoso problema de processamentos assíncronos em que um componente de origem envia dados sem saber ao certo se isso está em uma quantidade maior do que aquela com a qual o consumidor pode lidar.

Perceba que na imagem o componente de origem está recebendo e processando várias informações, mas muitas delas estão bloqueadas enquanto as outras estão sendo processadas. Os dados chegam em uma velocidade muito maior do que podem ser atendidos. Com a Flow API você consegue resolver esse e outros problemas avançados em execuções assíncronas com back pressure.

O fluxo seria assim:

No lugar de a origem empurrar uma quantidade arbitrária de dados para o destino, é o destino que puxa apenas a quantidade de dados que ele certamente poderá atender. Só então, os dados são enviados e na quantidade certa.

No exemplo a seguir estamos criando um publicador e registrando implicitamente um consumidor que apenas imprime as Strings conforme elas são enviadas:

jshell> SubmissionPublisher<String> publisher = new SubmissionPublisher<>();
	
jshell> publisher.consume(System.out::println);
	
jshell> List.of("esse", "código", "é", "assíncrono").forEach(publisher::submit);
	
esse
código
é
assíncrono

Um Java modular

Depois de mais de 20 anos muitos dos problemas da antiga estrutura monolítica da plataforma Java foram resolvidos com a introdução de um novo sistema de módulos e a modularização do próprio JDK. Neste outro post eu mostro o Jigsaw e o que muda na estrutura de seus projetos, além de mencionar brevemente como ficou a divisão de suas principais APIs.

No Java 9 tudo é modular, mas você não precisa sair migrando os seus projetos para usar essa nova versão. Especialmente por motivos de compatibilidade, projetos que não definem explicitamente o uso do sistema de módulos vão funcionar normalmente — internamente é declarado um unamed module, com acesso a todos os módulos da plataforma de forma parecida ao que acontece nas versões anteriores.

Apesar disso, depois de conhecer um pouco mais sobre os benefícios dessa migração eu arriscaria dizer que você vai querer migrar. Uma das grandes vantagens dessa nova estrutura é que, diferente da abordagem atual do classpath, as informações do módulo precisam ficar disponíveis da mesma forma durante as fases de compilação e execução. Isso garante uma integridade muito maior nos projetos, evitando problemas da abordagem atual do legado classpath — como o famoso JAR hell —  ou, pelo menos, reportando-os muito antes, em tempo de compilação.

Outro ganho enorme é no encapsulamento dos projetos, já que agora ser público não significa mais ser acessível. Em um sistema modular você precisa definir explicitamente o que pode ou não ser acessado por fora do módulo, e isso guia a construção de APIs com designs mais lógicos.

No exemplo a seguir o arquivo module-info.java, responsável pela definição de um módulo, exemplifica um caso em que o módulo br.com.caelum.desktop precisa do módulo base do Java FX para funcionar, e deixar acessível apenas seu pacote de componentes:

module br.com.caelum.desktop {
	requires javafx.base;
	exports br.com.caelum.desktop.components;
}	

Ah, e em projetos modulares você consegue criar imagens de execução customizadas com apenas o pedacinho da JRE que você está usando! Podemos falar que essa é uma extensão aos compact profiles do JDK 8, mas que funcionam de forma muito mais interessante já que você literalmente só carrega o que precisa. Muitos projetos pequenos podem ter só o java.base, no lugar de todos os 94 módulos e suas milhares de classes. Isso tem uma série de vantagens de performance e consumo de memória, já que a aplicação fica com um footprint inicial muito menor.

Novas APIs

A versão também recebeu diversas novas APIs. Um destaque especial vai para a de HTTP/2 Client, que possui uma interface publica completamente repaginada ante ao legado HttpURLConnection API e com suporte para requisições HTTP/2 e WebSockets.

Um exemplo de código para retornar o conteúdo do corpo de um request seria:

String contentBody = newHttpClient().send(
 newBuilder()
 .uri(new URI(“https://turini.github.io/livro-java-9/"))
 .GET()
 .build(), asString())
 .body();

O mais interessante dessa API é que ela é o piloto de um novo conceito da linguagem, que são os módulos em incubação. A ideia é permitir que APIs passem por um período de testes antes de entrar definitivamente para a linguagem, com objetivo de reduzir a possibilidade de introdução de erros na plataforma, que tem o forte peso da retrocompatibilidade.

Módulos em incubação ainda não fazem parte do Java SE. Eles ficam em um pacote jdk.incubator e não são resolvidos por padrão na compilação ou execução de suas aplicações. Para testar esse meu exemplo no jshell, por exemplo, você precisa iniciar a ferramenta com essa opção explicitamente:

turini ~ $ jshell --add-modules jdk.incubator.httpclient
| Welcome to JShell — Version 9
| For an introduction type: /help intro

O JDK 9 também traz uma nova API de logging, que te possibilita criar um provedor padrão de mensagens que poderá ser usado tanto em seu código como no do próprio JDK. E uma API de Stack-Walking, que tira proveito dos poderosos recursos da API de Streams para que você consiga passear pela Stack de sua aplicação de forma extremamente eficiente. Com ela podemos facilmente coletar em uma lista todas as classes de um pacote específico, ou alguma outra condição especial de filtro:

StackWalker.getInstance().walk(stream -> 
  stream.filter(frame -> frame.getClassName().contains("br.com.caelum")
))
.collect(toList());

Eu consigo ver um futuro próximo em que as IDEs e ferramentas de profilling tiram bastante proveito dessa API para fornecer alternativas performáticas e poderosas para analise e investigação de problemas de runtime.

Diversas mudanças nas APIs

Eu mostrei nesse post que as Collections receberam diversos factory methods trazendo uma forma mais próxima aos collection literals para criação de listas, sets e mapas.

Map.of(1,"Turini", 2,"Paulo", 3,"Guilherme");
	
List.of(1, 2, 3);
	
Set.of("SP", "BSB", "RJ");

O Stream também recebeu novos métodos como dropWhile, takeWhile, ofNullable e uma sobrecarga do iterate que permite definir uma condição para que a iteração termine. Repare a semelhança com o clássico for com index no exemplo em que imprime números de 1 a 10:

Stream
	.iterate(1, n -> n<=10, n -> n+1)
	.forEach(System.out::println);

Além das mudanças no Stream em si, diversas outras APIs receberam métodos que tiram proveito de seus recursos. Um exemplo seria o Optional, que agora consegue projetar seus valores diretamente para o Stream:

Stream<Integer> stream = Optional.of(ids).stream();

Essa transformação é extremamente útil quando você precisa aplicar diversas operações nos valores e tirar proveito da característica lazy da API.

O java.time também recebeu uma forma bastante interessante de retornar streams com intervalo de datas:

Stream<LocalDate> dates = 
	LocalDate.datesUntil(jdk10Release);

E o Scanner agora possui métodos como o findAll onde, dada uma expressão regular, retorna um stream de possíveis resultados.

String input = "esperei 3 anos pelo lançamento do java 9";

List<String> matches = new Scanner(input)
    .findAll("\\d+")
    .map(MatchResult::group)
    .collect(toList());

Extensão aos recursos da linguagem

Além de novos métodos e APIs, a linguagem recebeu um suporte maior na inferência de tipos e nos recursos existentes. O try-with-resources agora pode usar variáveis efetivamente finais, sem precisar re-instanciar como em sua versão agora antiga.

O código que era assim:

public void read(BufferedReader reader) {

  try (BufferedReader reader = reader) {
    //...
  } 
}

Agora pode ser escrito sem a necessidade de criar um novo tipo e da re-atribuição:

public void read(BufferedReader reader) {

  try (reader) {
    //...
  } 
}

O uso do operador diamante em classes anônimas e suporte aos métodos privados em interfaces — para reutilizar código dos default methods sem quebrar encapsulamento — são algumas das várias outras possibilidades. Muitas delas estão definidas na proposta Milling Project Coin, que recebeu esse nome por ser um aperfeiçoamento dos recursos introduzidos no Project Coin do JDK 7 — como o próprio operador diamante.

Performance

As melhorias relacionadas a performance também são incríveis. Um destaque especial foi a adoção do G1 como Garbage Collector padrão, como já era esperado. Ele é um algoritmo bem mais previsível, que executa seu trabalho em diferentes threads e em uma frequência muito maior, compactando o heap de memória durante a execução. O G1 também faz o reaproveitamento de Strings e tem diversos outros benefícios interessantes.

Por falar em Strings, elas agora possuem seu valor representado de uma forma muito mais eficiente em memória. Se você conferir na implementação das versões anteriores da linguagem, perceberá que uma String guarda seu valor interno em um array de caracteres, que mantém dois bytes para cada um deles.

private final char value[];

O problema da abordagem atual é que, na maior parte dos casos, as Strings usam valores em formato ISO-8859–1/Latin-1 que precisam de apenas um byte por caractere. Em outras palavras, poderíamos usar metade do espaço!

Bem, é o que estamos fazendo agora com Java 9! Experimente abrir o JShell e ver como esse campo de valor é declarado hoje:

jshell> String.class.getDeclaredField("value")
	
private final byte[] value;

Um array de bytes!

Com isso usamos apenas um byte, que será o suficiente na esmagadora maioria dos casos. Para os demais, foi adicionado um campo adicional de 1 byte para indicar qual o encoding que está sendo usado. Isso será feito automaticamente, baseado no conteúdo da String.

Essa proposta é conhecida como Compact Strings.

Ufa! Esse é resumo mínimo do que entrou na nova versão, mas acredite quando eu digo que tem muito mais além do que foi aqui mencionado. A própria Oracle amadureceu bastante suas documentações e trabalho na comunidade, com vários artigos e tutoriais bem interessantes. Vou deixar alguns links aqui pra quem quiser se aprofundar mais.

Onde estudar mais?

Entre os documentos oficiais dos arquitetos da plataforma, você certamente poderá se interessar em dar uma olhada nos release notes, com links inclusive para alguns tutoriais de migração, na lista com todas as especificações que entraram, além de alguns videos de experts introduzindo as mudanças.

No livro Java 9: Interativo, reativo e modularizado eu entro a fundo nas principais mudanças colocando tudo em um projeto prático, que no final é modularizado e você sente com as mãos na massa como é o processo de migração e os grandes benefícios da nova arquitetura modular da plataforma.

Também falei um pouco sobre as mudanças em um episódio da Alura Live, junto com o Gabriel — nosso host — e o Phil, que trabalha conosco no time de desenvolvimento da Alura.

Logo você também deve ver mais um monte de conteúdo aberto nosso sobre o assunto, entrando a fundo em detalhes específicos das muitas novidades da versão.

E ai, o que achou das mudanças?

The post O mínimo que você deve saber de Java 9 appeared first on Blog da Caelum: desenvolvimento, web, mobile, UX e Scrum.

Você não é pago para programar!

$
0
0

Você, que desenvolve software e programa todos os dias em que vai ao trabalho, recebe o salário no final do mês por quê? Bem, o título estraga a surpresa, mas é isso mesmo. Você não é pago para programar.

O código fonte que você gera todos os dias é, na verdade, um subproduto do que é realmente o seu trabalho. Mas por que então você foi contratado? Generalizando:

O seu trabalho é resolver problemas de negócio da sua empresa.

Entregar resultados, através de tecnologia. Isso não significa necessariamente escrever linhas de código o tempo todo, nem exclui tal tarefa. O código é uma das formas de resolver os problemas da sua empresa, do seu usuário. Mas há outras formas que não envolvem commits, não envolvem um framework novo, nem uma linguagem de programação pouco conhecida.

Colocar um novo site no ar com wordpress, usar um SAAS que resolva 90% do seu problema, contratar um sistema de helpdesk em vez de escrever seu crm e até mesmo usar o bom e velho excel/spreadsheets são muitas vezes ideias mais rápidas, baratas e que vão criar menos código legado e manutenção. O seu MVP pode envolver bem menos programação do que você imagina.

Repare que isso já está no nosso inconsciente: detestamos quando nos medem a produtividade através de métricas como linhas de código e número de commits.

Linhas de código são um compromisso a longo prazo. Você vai ficar com elas no seu repositório por muito, muito tempo, e precisa garantir que elas funcionem enquanto estão em produção. Isso não quer dizer que você não precisa ter um conhecimento profundo de programação. Pelo contrário. Para poder julgar melhor quando usar e quando não usar determinada ferramenta e linguagem, apenas uma grande experiência vai poder te ajudar a decidir e a encontrar alternativas.

Certamente não sou o primeiro a fazer essas observações. Krzysztof Zabłocki diz em um artigo incrível sobre soft skills:

As pessoas te contratam porque elas precisam resolver problemas específicos do business delas, seja numa app ou outra coisa, não para escrever código somente pelo código. Entregar resultados importa.

E também há o artigo de quem eu copiei o título descaradamente:

Nós não somos pagos para escrever código, nós somos pagos para adicionar valor (ou reduzir custos) do negócio.

A sua tarefa de hoje então é essa: você é pago para fazer o quê? Qual é o valor que você pode entregar para a sua empresa, para a sua comunidade open source, para suas amigas e amigos programadores? Sim, pode ser que haja bastante código envolvido, mas também pode existir uma maneira mais fácil e simples de chegar a esses mesmos resultados.

Programe para resolver problemas e entregar resultados, não apenas por programar. Novamente: isso não impede de você almejar ser um exímio programador, continua necessário.

The post Você não é pago para programar! appeared first on Blog da Caelum: desenvolvimento, web, mobile, UX e Scrum.

Bean Validation no Kotlin

$
0
0

Em uma API que estávamos construindo para captar algumas avaliações de cursos, optamos por utilizar o Spring como framework juntamente com o Kotlin como linguagem de programação.

Em um dos pontos deste sistema precisávamos receber um JSON que continha algumas informações referentes a uma avaliação feita por um aluno, para depois preparar as informações que queríamos persistir.

No dia-a-dia de um desenvolvedor, é muito comum lidar com validações, com o principal objetivo de manter a consistência dos dados de uma aplicação.

Nesta aplicação não foi diferente, precisamos de algumas validações básicas para garantir consistência. Em um dos casos tínhamos um modelo que representava uma resposta:


data class Resposta(

        var observacao: String = "",

        var respostas: SortedSet<String> = sortedSetOf(),

        var idPergunta: Long = 0,

        var hashTurmaId: String = ""

)

Como algumas informações precisam ser validadas, afinal não queríamos dados inconsistentes circulando na nossa aplicação, utilizar o Bean Validation seria suficiente. Logo:


data class Resposta(

        var observacao: String = "",

        @Size(min = 1) var respostas: SortedSet<String> = sortedSetOf(),

        @NotNull var idPergunta: Long = 0,

        @NotBlank var hashTurmaId: String = ""

)

Então com a classe feita ficou faltando nosso Controller para receber a requisição. A princípio, simplesmente queremos validar se existem erros e, se existir, ele devolve um HTTP Status Code 400, senão ele devolve um Status 202:


@RequestMapping("resposta")
@RestController
class RespostaController {

    @Autowired
    private lateinit var respostaAlunoService: RespostaAlunoService

    @PostMapping(consumes = arrayOf(MediaType.APPLICATION_JSON_VALUE))
    fun salva(@Valid @RequestBody resposta: Resposta, bindingResult: BindingResult): ResponseEntity<Any> {

        if (bindingResult.hasErrors()) {

            return ResponseEntity.badRequest().build()

        }

        respostaAlunoService.save(resposta)

        return  ResponseEntity.accepted().build()

    }

}

Validando os dados

Agora que temos um mínimo necessário, chegou o momento de verificar se nossa validação usando Bean Validation trará os resultados esperados. Fazemos então uma requisição com dados consistentes:


{

    "observacao":"uma observação",

    "respostas":["uma resposta", "outra resposta"],

    "idPergunta":777,

    "hashTurmaId":"202cb962ac59075b964b07152d234b70"

}

Como esperado deu tudo certo, primeiro passo concluído.

Agora tentamos fazer uma requisição com dados inválidos, para indicar que tenho problemas de validação:


{

    "observacao":"uma observação",

    "respostas":[],

    "hashTurmaId":""

}

No final das contas ele passa de boa e não identifica que existe erros! Onde será que erramos?

Decompile para Java

Afinal, se fizermos um comparativo de como ficaria isso em Java, seria algo como:


public class Resposta {

    private String observacao;

    @Size(min = 1)
    private  SortedSet<String> respostas = new TreeSet();

    @NotNull
    private Long idPergunta;

    @NotBlank
    private String hashTurmaId;

}

No Kotlin, o bytecode gerado na compilação pode ser compatível desde Java 6 ao Java 8 , sendo assim, se pudéssemos ver como fica nosso código feito em Kotlin para Java poderíamos ver o que está acontecendo por baixo dos panos.

Na IDE IntelliJ, pertencente a empresa que desenvolveu o Kotlin, existe uma forma de decompilar o bytecode gerado a partir do Kotlin para Java. Dentro da IDE seguindo o menu Tools > Kotlin > Show Kotlin Bytecode podemos ver o bytecode gerado:

Kotlin Bytecode

No topo no lado esquerdo temos o botão Decompile e podemos assim decompilar para Java e visualizar o código equivalente:


public final class Resposta {
   @NotNull
   private String observacao;
   @NotNull
   private SortedSet<String> respostas;
   private long idPergunta;
   @NotNull
   private String hashTurmaId;

   @NotNull
   public final String getObservacao() {
      return this.observacao;
   }

   public final void setObservacao(@NotNull String var1) {
      Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
      this.observacao = var1;
   }

   @NotNull
   public final SortedSet getRespostas() {
      return this.respostas;
   }

   public final void setRespostas(@NotNull SortedSet var1) {
      Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
      this.respostas = var1;
   }

   public final long getIdPergunta() {
      return this.idPergunta;
   }

   public final void setIdPergunta(long var1) {
      this.idPergunta = var1;
   }

   @NotNull
   public final String getHashTurmaId() {
      return this.hashTurmaId;
   }

   public final void setHashTurmaId(@NotNull String var1) {
      Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
      this.hashTurmaId = var1;
   }

   public Resposta(@NotNull String observacao, @Size(min = 1) @NotNull SortedSet respostas, @javax.validation.constraints.NotNull long idPergunta, @NotBlank @NotNull String hashTurmaId) {
      Intrinsics.checkParameterIsNotNull(observacao, "observacao");
      Intrinsics.checkParameterIsNotNull(respostas, "respostas");
      Intrinsics.checkParameterIsNotNull(hashTurmaId, "hashTurmaId");
      super();
      this.observacao = observacao;
      this.respostas = respostas;
      this.idPergunta = idPergunta;
      this.hashTurmaId = hashTurmaId;
   }
 }

Quando observamos as annotations que nos interessam, referente as validações, notamos que elas foram não para os atributos mas sim para o construtor. Mas, afinal, porque este comportamento?

Para responder essa pergunta temos que ver onde podemos aplicar as annotations utilizadas na validação. Veja, por exemplo, o @NotNull:


@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(
    validatedBy = {}
)
public @interface NotNull {

    //trecho omitido

}

Note que no @Target fica definido a aplicabilidade da annotation, informando os locais possíveis: METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR e PARAMETER

Como fazemos em Kotlin

O que vale lembrar é que no Kotlin conseguimos acessar uma property, e não diretamente os atributos da classe então embora o Target da annotaion suporte a utilização em um field, precisamos indicar para o Kotlin onde queremos utilizar:


data class Resposta(

        var observacao: String = "",

        @field:Size(min = 1) var respostas: SortedSet<String> = sortedSetOf(),

        @field:NotNull var idPergunta: Long = 0,

        @field:NotBlank var hashTurmaId: String = ""

)

Note agora como fica o equivalente em Java após decompilar:


public final class Resposta {
   @NotNull
   private String observacao;
   @Size(
      min = 1
   )
   @NotNull
   private SortedSet<String> respostas;
   @javax.validation.constraints.NotNull
   private long idPergunta;
   @NotBlank
   @NotNull
   private String hashTurmaId;

   @NotNull
   public final String getObservacao() {
      return this.observacao;
   }

   public final void setObservacao(@NotNull String var1) {
      Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
      this.observacao = var1;
   }

   @NotNull
   public final SortedSet getRespostas() {
      return this.respostas;
   }

   public final void setRespostas(@NotNull SortedSet var1) {
      Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
      this.respostas = var1;
   }

   public final long getIdPergunta() {
      return this.idPergunta;
   }

   public final void setIdPergunta(long var1) {
      this.idPergunta = var1;
   }

   @NotNull
   public final String getHashTurmaId() {
      return this.hashTurmaId;
   }

   public final void setHashTurmaId(@NotNull String var1) {
      Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
      this.hashTurmaId = var1;
   }

   public Resposta(@NotNull String observacao, @NotNull SortedSet respostas, long idPergunta, @NotNull String hashTurmaId) {
      Intrinsics.checkParameterIsNotNull(observacao, "observacao");
      Intrinsics.checkParameterIsNotNull(respostas, "respostas");
      Intrinsics.checkParameterIsNotNull(hashTurmaId, "hashTurmaId");
      super();
      this.observacao = observacao;
      this.respostas = respostas;
      this.idPergunta = idPergunta;
      this.hashTurmaId = hashTurmaId;
   }

}

Nesse caso indicamos onde exatamente queremos utilizar a annotation, e fazendo com que agora nossa validação funcione!

E você, já teve que lidar com essa situação? Usou outra forma para resolver? Comente com a gente! 😉

The post Bean Validation no Kotlin appeared first on Blog da Caelum: desenvolvimento, web, mobile, UX e Scrum.

Erros com JAXB no Java 9+

$
0
0

Vamos dizer que temos a seguinte classe, que lê o arquivo livro.xml transformando-o num objeto da classe Livro:


import javax.xml.bind.JAXBException;
//outros imports...
public class TesteUnmarshal {
  public static void main(String[] args) throws JAXBException {
    JAXBContext context = JAXBContext.newInstance(Livro.class);
    Unmarshaller unmarshaller = context.createUnmarshaller();
    Livro livro = (Livro) unmarshaller.unmarshal(new File("livro.xml"));
    System.out.println(livro.getTitulo());
  }
}

O código anterior usa a API JAXB, responsável por transformar objetos Java em XML e vice-versa. Foi lançada em 2006, como parte da especificação Java EE 5. A partir do Java SE 6, de 2009, já vem embutida na JRE.

Com o Java 8, sem nenhum JAR adicional, a classe seria compilada com sucesso. Ao executá-la com o Java 8, o nome do livro seria exibido na saída padrão.

Tudo funciona perfeitamente.

JAXB e erros com Java 9+

Porém, se tentássemos rodar a classe compilada anteriormente com o Java 9, teríamos uma exceção:


Error: Unable to initialize main class TesteUnmarshal
Caused by: java.lang.NoClassDefFoundError: javax/xml/bind/JAXBException

Se compilássemos com o Java 9, teríamos o seguinte erro de compilação:


TesteUnmarshal.java:1: error: package javax.xml.bind is not visible
import javax.xml.bind.JAXBContext;
                ^
  (package javax.xml.bind is declared in module java.xml.bind,
   which is not in the module graph)

Os mesmos erros acontecem com o Java 10 e continuarão acontecendo em versões posteriores.

Exceções semelhantes

Basta executar uma aplicação razoavelmente simples após atualizar para o Java 9 (ou 10) que é bem possível que obtenhamos algumas dessas exceções:

  • Ao usar o Hibernate e/ou Hibernate Validator, a própria java.lang.ClassNotFoundException: javax.xml.bind.JAXBException
  • Ao usar o Jersey, a java.lang.ClassNotFoundException: javax.xml.bind.Unmarshaller
  • Em uma aplicação Spring Boot, a java.lang.ClassNotFoundException: javax.xml.bind.ValidationException

No fórum da Alura, por exemplo, são erros relativamente comuns.

Parece, então, que o JAXB foi removido a partir do Java 9. Mas não é bem assim…

Java 9+ e o Class Path

Uma das principais novidades do Java 9 foi o Java Platform Module System (JPMS), um sistema de módulos que resolve vários problemas do Classpath e permite um nível de encapsulamento acima dos pacotes.

A própria JDK foi dividida em módulos. O módulo java.base contém os pacotes mais básicos como o java.lang, java.util, java.io e java.time. Há ainda módulos para as diversas APIs do Java: JDBC (java.sql), JNDI (java.naming), Swing (java.desktop), JMX (java.management), entre outros.

Toda aplicação modularizada só tem disponíveis, por padrão, as classes do módulo java.base. Se for necessário usar o JDBC ou Swing, por exemplo, os módulos correspondentes devem ser declarados explicitamente como dependências no arquivo module-info.java. Os JARs de bibliotecas devem colocados no Module Path.

Para permitir uma migração mais suave, ainda é possível usar o velho Class Path com o JPMS. Dessa maneira, é possível executar classes compiladas com o Java 8 (ou anterior) e desenvolver uma aplicação não modularizada.

O conteúdo do Class Path no JPMS também é chamado de unnamed module, o “módulo sem nome”.

Quando usamos o Class Path com a JDK modularizada do Java 9 em diante, quais classes ficam disponíveis? Se fossem só as do módulo java.base, muitas aplicações não seriam executadas corretamente.

Por isso, foi criado o módulo agregador java.se que deixa disponível no Class Path o conteúdo dos módulos java.base, java.sql, java.desktop, java.management e uma série de outros.

A ideia é que o java.se contenha a maioria das bibliotecas disponíveis em versões anteriores do Java. Mas não o JAXB…

JAXB deprecated no Java 9

A JEP 320 pretende remover da JRE APIs como JAXB, JAX-WS, JTA, JAF, CORBA e algumas outras.

Segundo a proposta, são APIs do Java EE que possuem implementações de terceiros e podem ser usadas como bibliotecas. Por isso, não precisariam estar na JRE.

A remoção deve ser efetivada no Java 11, a ser lançado em setembro de 2018.

Enquanto essa remoção não acontece, módulos para o JAXB (java.xml.bind), JAX-WS (java.xml.ws), JTA (java.transaction), JAF (java.activation), CORBA (java.corba) e alguns outros foram incorporados ao Java 9. Todos esses módulos foram anotados com @Deprecated.

Os módulos do Java EE não fazem parte do módulo agregador java.se e, por isso, não ficam disponíveis no Class Path do Java 9, nem ficarão em versões posteriores do Java. Foi criado um outro módulo agregador, o java.se.ee, que junta o java.se a esses módulos do Java EE, mas que não é disponibilizado por padrão.

Como usar esses módulos do Java EE para que a classe TesteUnmarshal seja compilada e executada com sucesso?

Uma solução temporária

É possível usar a opção --add-modules para adicionarmos módulos ao Class Path.

Com Java

Poderíamos adicionar ao Class Path o módulo java.xml.bind, do JAXB, durante a compilação da seguinte maneira:


javac --add-modules java.xml.bind TesteUnmarshal.java

Executaríamos de maneira parecida:


java --add-modules java.xml.bind TesteUnmarshal

Se quisermos disponibilizar todas as APIs do Java EE que estão deprecated, basta adicionar o módulo java.se.ee.

Com Maven

Se estivermos usando o Maven no build de nosso projeto, é necessário configurarmos um argumento do compilador:


<plugin>
  <groupid>org.apache.maven.plugins</groupid>
  <artifactid>maven-compiler-plugin</artifactid>
  <version>3.7.0</version>
  <configuration>
    <compilerargs>
      <arg>--add-modules</arg>
      <arg>java.xml.bind</arg>
    </compilerargs>
  </configuration>
</plugin>

Com Tomcat e Jetty

Se tivermos uma aplicação rodando no Tomcat e estivermos obtendo alguns dos erros mencionados acima com o Java 9, podemos adicionar módulos sem mudarmos nem a aplicação nem configurações do próprio Tomcat: é só definirmos a variável de ambiente JAVA_OPTS. No Linux, seria algo como:


export JAVA_OPTS="--add-modules java.xml.bind"

O Jetty tem uma variável de ambiente semelhante, chamada JAVA_OPTIONS:


export JAVA_OPTIONS="--add-modules java.xml.bind"

A solução definitiva

A JEP 320, que propõe a remoção do JAXB, JAX-WS, JTA e outros da JRE, recomenda que as aplicações incluam no Class Path os JARs dessas APIs e alguma implementação.

Se estivermos usando o Maven, basta adicionarmos as seguintes dependências:


<dependency>
  <groupid>javax.xml.bind</groupid>
  <artifactid>jaxb-api</artifactid>
  <version>2.2.11</version>
</dependency>
<dependency>
  <groupid>com.sun.xml.bind</groupid>
  <artifactid>jaxb-core</artifactid>
  <version>2.2.11</version>
</dependency>
<dependency>
  <groupid>com.sun.xml.bind</groupid>
  <artifactid>jaxb-impl</artifactid>
  <version>2.2.11</version>
</dependency>

Com o JAR incorporado às dependências do nosso projeto, teremos uma implementação do JAXB no Class Path. Dessa maneira, nosso código estaria preparado para a remoção da API no Java 11!

foto por Paul Wilkinson

The post Erros com JAXB no Java 9+ appeared first on Blog da Caelum: desenvolvimento, web, mobile, UX e Scrum.

O mínimo que você deve saber de Java 10

$
0
0

Sim, você leu certo: apenas 6 meses depois do lançamento do Java 9, uma nova versão da linguagem já está disponível para download. Com um movimento que ficou conhecido como Java Release Train, a Oracle está introduzindo o novo sistema de versionamento da linguagem e uma cultura de releases muito mais frequentes na plataforma.

Na prática, o que isso significa?

Em resumo, que você não precisa esperar 3 ou mais anos para encontrar alguma inovação na linguagem! Atualizações menores não precisam mais ficar em espera até que as grandes features como lambda ou jigsaw fiquem prontas. Isso tem uma serie de pontos positivos, mas gera muitas dúvidas e desconforto com relação ao futuro.

O objetivo desse post é explicar o motivo desse grande passo, discutir possíveis problemas e preocupações, e sumarizar tudo o que você deve saber sobre o Java 10.

Entendendo as motivações do Java Release Train

Em 2013, enquanto escrevia o livro Java 8 Prático com o Paulo Silveira, recebemos a notícia de que novamente o lançamento da nova versão da linguagem seria prorrogado por alguns meses, por falhas de segurança e detalhes em aberto do projeto lambda. O mesmo aconteceu com o Jigsaw do Java 9, que atrasou aproximadamente um ano seu release final.

Esse é um problema que rodeou o Java por anos: enquanto as grandes features não estavam prontas, pequenas novidades, diversas melhorias em APIs, na JVM e outros recursos ficavam em espera. Depois de 3 ou 4 anos uma tonelada de alterações entravam de uma só vez.

O Mark Reinhold, arquiteto chefe da Oracle, comenta em um de seus posts que para aliviar essa dor chegaram a considerar um formato de lançamentos de novas versões a cada 2 anos, ao estilo train-mode. Em poucas palavras, se a feature não estava pronta até lá, perdia o trem e entrava no próximo.

O grande problema dessa abordagem foi que 2 anos pareceram tempo demais! Foi preferível atrasar 8 meses e lançar o Java 8 com Lambdas, Streams e outros recursos, do que esperar mais 2 anos por isso. Foi então que chegaram nesse novo formato de lançamentos a cada seis meses. Ele é rápido o bastante para minimizar a dor da espera do próximo trem, e ainda assim, lento o bastante para que cada um deles seja entregue com o mesmo padrão de testes e qualidade de hoje.

A ideia é que existam update-releases a cada trimestre, feature-releases a cada semestre e long-term support releases a cada 3 anos. O primeiro deles é extrito a correções de bugs e detalhes de segurança. O segundo, a cada 6 meses, envolve lançamentos de novas funcionalidades, atualizações em APIs, melhorias na JVM entre outros. O último, e mais longo, é equivalente aos 3 anos que esperamos hoje para o lançamento de uma nova versão. Empresas com perfil mais conservador e que preferem maior estabilidade, podem se planejar para migrar de versão do Java a cada 3 anos. Aventureiros, como eu, já estão rodando o Java 10 em produção desde hoje.

O grande objetivo é que existam atualizações mais frequentes e mais oportunidades de inovar e melhorar a linguagem, como já tem acontecido, mas em um formato extremamente mais rápido.

Novo versionamento baseado em tempo

Após instalar o Java 10, experimente executar o comando –version. Diferente de todas as outras vezes, a partir de agora ao lado da versão você encontrará uma data:

java 10 2018-03-20
Java(TM) SE Runtime Environment 18.3 (build 10+43)
Java HotSpot(TM) 64-Bit Server VM 18.3 (build 10+43, mixed mode)

Com isso você consegue identificar qual a versão específica do Java 10 que está sendo utilizada, conforme os novos builds forem saindo. Se quiser, você encontra mais informações sobre o novo modelo de versionamento aqui em sua JEP.

Features e principais novidades do Java 10

Mas afinal, o que mudou?

Dois anos atrás escrevi um post no blog da Alura sobre uma das mais polêmicas propostas do JDK 9, da inferência de tipos em variáveis locais. Apesar de não ter sido adotada na época, a proposta que despertou o entusiasmo de boa parte da comunidade está agora inaugurando o novo formato de releases da plataforma.

Seu grande ganho está na legibilidade.

O livro clean code bate bastante na tecla de que qualquer desenvolvedor consegue escrever código que uma máquina consegue ler, mas poucos fazem da forma em que humanos também consigam. O problema é que o uso de nomes super descritivos muitas vezes vai ao encontro da legibilidade e acaba gerando uma verbosidade excessiva, como no exemplo:

ArrayIndexOutOfBoundsException exception
	= new ArrayIndexOutOfBoundsException();

Quantas vezes você já viu um código como este?

Claro, usar um tipo abstrato do lado esquerdo já ajudaria um pouco, mas não existia uma forma simples de fugir dessa formalidade de declará-lo. Com o novo sistema de inferência de variáveis locais o mesmo código poderá ser escrito assim:

var exception
	= new ArrayIndexOutOfBoundsException();

Legal, não é? E mesmo em exemplos simples, de atibuições diretas ou dos tipos mais comuns da linguagem, o ganho de legibilidade pode ser discretamente notado:

// String name = "Rodrigo Turini";
var name = "Rodrigo Turini";

// User user = users.findBy(name);
var user = users.findBy(name);

// List<String> names = new ArrayList<>();
var names = new ArrayList<String>();

// Stream<String> stream = names.stream();
var stream = names.stream();

Vale lembrar que uma quantidade menor de linhas de código nem sempre resulta em qualidade melhor. O recurso só ajuda a não repetir palavras o tempo todo, e evitar as redundancias frequentes entre as fases de declaração de tipo e instanciação do valor (Map = new Map, User = new User e assim por diante). Foi um passo a mais na jornada do project coin, do JDK 7, que trouxe o operador diamante e outras possíbilidades como o try-with-resources que ajudaram, aos poucos, a diminuir a verbosidade e melhorar legibilidade do código Java.

Limitações do recurso

Talvez sua reação natural seja se preocupar com situações em que o var, em conjunto com um nomes de variáveis ruins, possa diminuir sua capacidade de identificar tipos durante a leitura do código. Um exemplo seria usá-lo em parâmetros de um método:

public void hadle(var x, var y) {
    // ...
}

O compilador teria que inferir um Object nesse caso, já que não existe uma definição do tipo que pode ser usado. No lugar disso, o código não compila. Você não pode usar o var em parâmetros de método dessa forma, assim como qualquer outra situação em que a definição do tipo não esteja explicita do lado direito de sua declaração, como no exemplo:

var delta; //oq é isso? um numero? um aviao? não... um erro de compilação!

Além dessa, que foi a principal novidade, o garbage collector G1 recebeu melhorias de performance em sua execução de full-gc paralelo, e uma nova interface favorece o isolamento de seu código fonte abrindo caminho para novas implementações como o zgc com maior facilidade. Compartilhamento de classes entre aplicações e a remoção do javah foram outras das pequenas mudanças que entraram.

Você encontra a lista com todas as novidades aqui.

O custo da atualização e preocupações futuras

O Stephen Colebourne, criador do Joda e spec lead do java.time, compartilhou em um de seus posts várias preocupações sobre o Java Release Train. O principal fator para ele é que com lançamentos muito frequentes as versões anteriores ficariam obsoletas — sem receber atualizações da Oracle.

E essa preocupação faz sentido!

Mas não é nada inesperado, nem mesmo assustador. Justamente para isso existem as versões LTS (long-term-support). Quem hoje está usando o Java 8, que é o LTS, não precisa se preocupar em atualizar pelos próximos 3 anos, quando sairá o JDK 11 e tomará seu lugar como LTS. O próprio Mark menciona em seu post que podem continuar acontecendo atualizações de segurança depois dos 3 anos, de acordo com a necessidade.

Outras preocupações são com relação as IDEs e bibliotecas da linguagem, que nem sempre vão estar atualizadas e preparadas para as novidades tão frequentes.

É um risco e um custo para você que, assim como eu, gosta de sempre usar os novos recursos e se manter atualizado! No time da Alura fazemos isso não só com Java, mas também muitas de suas bibliotecas. Usamos a última versão do Hibernate, CDI (weld), entre outros.

Quer se aprofundar mais no assunto?

Eu sempre gosto de indicar a página oficial da nova versão, que contém a lista das específicações que entraram com suas motivações e alguns exemplos:

http://openjdk.java.net/projects/jdk/10/

Se você ainda não se atualizou com as versões anteriores, talvez queira dar uma olhada em meu livro Java 9: Interativo, reativo e modularizado e nos diversos posts sobre o assunto aqui do blog.

E você, qual versão tem usado em seus projetos Java?

The post O mínimo que você deve saber de Java 10 appeared first on Blog da Caelum: desenvolvimento, web, mobile, UX e Scrum.

Programação funcional no Python

$
0
0

Criei um sistema para controlar meus gastos mensais, fazendo com que todos os gastos do meu cartão fiquem salvos em um arquivo no meu computador, no caso gastos.txt, dessa forma:


18.90
5.50
19.99
25.00
52.15
200.00
...

Depois de um tempo, meu arquivo começou a encher muito e ficar confuso. Percebi que ficaria mais claro se os gastos estivessem formatados de acordo com as normas da moeda brasileira, no padrão R$ 10,00.

Comecei a alterar valor por valor, manualmente, mas já não aguentava mais. O problema é que quando eu resolvi mudar, meu arquivo já estava muito cheio!

Imagine fazer todo esse processo manualmente para uma lista de mais mil registros. Será que não conseguimos automatizar tudo isso?

Já sabemos o que fazer para formatar uma moeda de acordo com o padrão de algum país, mas nesse caso temos um arquivo cheio de valores, não apenas um. O que podemos fazer?

Uma alternativa seria usar nossa conhecida técnica da compreensão de lista. Vamos tentar:


## código omitido

gastos = open(‘gastos.txt’, ‘r’)
gastos_formatados = [locale.currency(gasto) for gasto in gastos]

E o retorno:


Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
MemoryError

Recebemos uma exceção de tipo MemoryError! Como já sabemos, isso ocorre porque a compreensão de lista tenta armazenar todos os valores na lista ao mesmo tempo, sobrecarregando a memória RAM do nosso computador.

Vimos que esse problema de memória pode ser resolvido com iteradores. Mas a construção de uma classe iteradora é bastante complexa, será que vamos precisar de todo esse trabalho novamente? Não há uma maneira mais sucinta de se conseguir o que queremos?

Considerando nosso problema atual, o ideal seria se conseguíssemos aplicar a função currency() para cada linha do arquivo, mas sem sobrecarregar nossa memória. Como podemos fazer isso?

Utilizando a função map()

Os exemplos que serão apresentados a seguir exigem o uso do Python 3. Caso esteja curioso para entender o motivo, confira a seção Para saber mais (depois de ler o post inteiro, para não tomar spoiler!).

O Python, se aproximando um pouco mais do paradigma de programação funcional, nos provê algumas funções que nos auxiliam na manipulação de iteráveis, baseando-se no uso de iteradores.

A função built-in map() consegue aplicar uma mesma função para todos os elementos de um iterável. Ela vai percorrendo os elementos desse iterável e executando a função para cada um deles.

Queremos que a função currency() seja executada para cada linha de gastos. Vamos tentar usar o map(), que vai receber como parâmetros a função e a leitura do arquivo. Podemos usá-la dessa forma:


## código omitido

gastos = open(‘gastos.txt’, ‘r’)
gastos_formatados = map(locale.currency, gastos)

for gasto in gastos_formatados:
    print(gasto)

E o resultado:


Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib64/python3.6/locale.py", line 265, in currency
    s = format('%%.%if' % digits, abs(val), grouping, monetary=True)
TypeError: bad operand type for abs(): 'str'

Ué, uma exceção! Mas por quê? O que acontece é que o Python, quando lê arquivos, trata automaticamente o conteúdo como string, enquanto a função currency() só aceita tipos numéricos como parâmetro de valor.

Temos que, antes de usar a função currency(), converter todos os valores para float:


## código omitido

gastos = open(‘gastos.txt’, ‘r’)
gastos_float = map(float, gastos)
gastos_formatados = map(locale.currency, gastos_float)

for gasto in gastos_formatados:
    print(gasto)

Vamos ver o resultado dessa vez:


R$ 18,90
R$ 5,50
R$ 19,99
R$ 25,00
R$ 52,15
R$ 200,00
...

Repare que o funcionamento do map() depende da função passada para ele. Funções como a map(), que tomam outras funções como parâmetro, são chamadas de funções de alta ordem, ou, tecnicamente, high-order functions.

Deu certo!

Cuidados com funções de alta ordem

Note que tivemos que usar a função map() duas vezes apenas para isso, o que é um pouco repetitivo. A gente pode tentar juntar tudo em um só map():


gastos_formatados = map(locale.currency(float()), gastos)
for gasto in gastos_formatados:
    print(gasto)

Vamos ver se dá certo:


Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'str' object is not callable

Ué, recebemos uma exceção indicando que objetos de tipo string não podem ser chamados como funções. Mas por quê?

O que acontece é que o primeiro parâmetro do map() deve ser a referência de uma função, não a chamada dela. Se chamamos ela no parâmetro, o retorno dela que será enviado para o map(). O que podemos fazer para executar as duas instruções de uma vez para cada elemento??

Passando nossa própria função para o map()

Sabemos que podemos passar qualquer função para o map(), então uma alternativa seria nós mesmos criarmos nossa própria função que formata a string para float e depois para a moeda brasileira:


## código omitido

def formata_moeda(valor):
    return locale.currency(float(valor))

gastos = open(‘gastos.txt’, ‘r’)
gastos_formatados = map(formata_moeda, gastos)

E também funciona perfeitamente! Mas não parece um pouco estranho termos que definir uma função tão simples só para isso? Funções tem algumas finalidades claras, como encapsular e reaproveitar código.

Nesse caso, não só não há muita necessidade de encapsular uma só linha, como não vamos usar o mesmo código em nenhum outro lugar! Será que vamos, mesmo assim, precisar criar uma função para cada pequeno comportamento que quisermos usar com o map()?

Funções anônimas com lambda

Temos uma alternativa para esses casos, em que declaramos as funções diretamente no parâmetro que as espera. Essas funções são conhecidas como funções anônimas, porque não possuem um nome e não são reutilizáveis – são acessíveis apenas como parâmetro da função que a recebeu.

No Python, podemos declarar funções anônimas com expressões lambda.

Expressões lambda reduzem a declaração de uma função para uma linha, e podem ser usadas como parâmetro de outras funções, o que é ótimo no nosso caso, com o map. Vamos aplicar o lambda ao nosso código:


## código omitido

gastos = open(‘gastos.txt’, ‘r’)
gastos_formatados = map(lambda valor: locale.currency(float(valor)), gastos)

for gasto in gastos_formatados:
    print(gasto)

Vamos ver se deu certo:


R$ 18,90
R$ 5,50
R$ 19,99
R$ 25,00
R$ 52,15
R$ 200,00
...

Legal, o resultado foi certo como o anterior e ainda conseguimos escrever menos código! Isso foi bom porque não precisamos utilizar esse código em nenhum outro momento no programa, mas é importante notar que o uso de funções anônimas pode ser ruim na parte de reaproveitamento de código.

Mas como funciona essa expressão lambda? Vamos tomar como exemplo uma expressão lambda de uma função que soma dois valores passados como parâmetros, para entendermos melhor a sintaxe:


lambda a, b: a + b

A sintaxe de uma expressão lambda é a palavra chave lambda seguida pelos nomes dos parâmetros separados por vírgula (a, b) e dois pontos (:) dividindo os parâmetros da expressão de retorno (a + b).

Funções lambda, diferente de funções normais declaradas com def, são restritas a expressões de uma única linha e omitem a palavra chave return, retornando automaticamente o que é computado na linha.

Como diz a própria documentação do Python, funções lambda, semanticamente, tem apenas a finalidade de simplificar um pouco nosso código. Na computação, temos um termo para esse tipo de coisa – açúcar sintático, ou syntax sugar.

Entretanto, há muitas críticas quanto ao lambda e sua legibilidade, inclusive feitas pelo próprio criador do Python, como veremos no final do texto. Sendo assim, por um lado elas podem diminuir e simplificar nosso código, mas por outro podem acabar, às vezes, se tornando uma complicação no programa.

Além de formatar os valores para moeda brasileira, para maior organização de todos esses dados, decidi dividir melhor os gastos. Quero separar os valores mais baixos, ou seja, menores ou iguais a 20.00. Como podemos fazer isso?

Já vimos que é possível usar a compreensão de lista como forma de filtro, mas isso nos traria de volta o problema da memória. Precisamos, de alguma forma, realizar essa filtragem mantendo um iterador.

Filtrando valores de um iterável com a função filter()

O Python nos disponibiliza outra função útil para nosso caso – a filter(), que retorna um iterador com os elementos que foram filtrados por meio do critério escolhido.

A função filter(), assim como a map() recebe dois parâmetros – a função que dará base ao filtro e o iterável que deverá ser filtrado. Entretanto, diferentemente do map(), a função passada no primeiro parâmetro deverá retornar um boolean, ou seja, True para um valor que deve ser mantido e False para um valor que deve ser descartado.

Como o filter() recebe uma função que retorna um boolean, vamos criá-la então:


def eh_menor_ou_igual_a_vinte(valor):
    return float(valor) <= 20.00

Certo! Mas espera… uma função só com uma linha para uma operação tão comum em programação básica? Podemos usar o lambda, agora que já conhecemos ele:


gastos = open(‘gastos.txt’, ‘r’)
gastos_baixos = filter(lambda valor: float(valor) <= 20.00, gastos)

for gasto in gastos_baixos:
    print(gasto)

E o resultado:


18.90
5.50
19.99
...

Certo! Para imprimir no formato brasileiro, podemos usar o retorno do filter(), que é um iterador (portanto, um iterável) como parâmetro do map(), que aceita qualquer iterável, dessa forma:


gastos_baixos = filter(lambda valor: float(valor) <= 20.00, gastos)
gastos_baixos_formatados = map(lambda valor: locale.currency(float(valor)), gastos_baixos)

for gasto in gastos_baixos:
    print(gasto)

E então:


R$ 18,90
R$ 5,50
R$ 19,99
...

Conseguimos filtrar os gastos como queríamos!

Para finalizar, só me resta uma coisa que eu gostaria de saber – o meu gasto total, ou seja, a soma de todos os meus gastos. Já conhecemos a função sum(), que pode tomar como parâmetro qualquer iterável, então uma alternativa possível seria fazer dessa forma:


gastos = open(‘gastos.txt’, ‘r’)
gastos_float = map(float, gastos)
soma_gastos = sum(gastos_float)

E já teríamos um resultado correto. Mas, depois de ver tanta coisa simplificada, fica a dúvida – será que não há uma espécie de syntax sugar para isso, também?

Utilizando a função reduce() para reduzir um iterável a um só valor

No Python, há uma função com abordagem similar às outras duas que vimos, mas que consegue combinar todos os valores de um iterável em um só, de acordo com uma outra função que especificarmos.

Essa função chama-se reduce(), e também recebe como parâmetros uma função e um iterável. Podemos usá-la dessa forma:


from functools import reduce

gastos = open(‘gastos.txt’, ‘r’)
total_gastos = reduce(lambda a, b: float(a) + float(b), gastos)

No Python 2, reduce() é uma função built-in, mas no Python 3 fica no módulo functools, necessitando um import para seu uso.

E teremos um resultado correto da soma de todos os valores!

Repare que a função passada como argumento da reduce() deve receber dois parâmetros, em vez de um (como era na map() e filter()). Mas por quê? Afinal, como essa função funciona?

A função reduce() vai executando a função passada usando os valores do iterador de dois em dois. Usando como exemplo a lista de valores que tínhamos desde o início (18.90, 5.50, 19.99, 25.00, …), a seguinte imagem demonstra mais claramente esse funcionamento:

Funcionamento do reduce()

Assim ficamos com um só resultado no final!

Para saber mais

Por que focamos tanto no Python 3 nesse post? Acontece que, no Python 2, as funções que aprendemos nesse post se comportavam de uma forma um pouco diferente. Tanto a map(), quanto a filter() retornavam uma lista, não um iterador, o que manteria nosso problema de memória do começo.

Por conta disso, o criador do Python, Guido van Rossum, havia decidido remover as funções map(), filter(), reduce() e até o lambda na mudança para o Python 3. Quanto ao filter() e ao map(), para ele era muito simples – compreensões de lista eram quase sempre mais claras, além de mais rápidas comparadas a quando essas funções usavam um lambda.

Quanto ao lambda, ele argumentava que havia muita confusão sobre, tendo até uma certa concepção errada na comunidade de que lambda podia fazer coisas que def não podia – o que não é verdade! Além do mais, sem as funções map(), filter() e reduce() não haveria mais muita necessidade de funções anônimas de uma só expressão.

Enfim, sobre a função reduce(), Guido van Rossum explica que seu uso estava ficando complicado demais, sendo, assim, anti-pythônico e de difícil entendimento. Ele ainda cita um dos usos mais comuns dessa função, que é multiplicar todos os números de um iterável, dessa forma, por exemplo:


numeros = [1, 2, 3, 4, 5]
produto = reduce(lambda a, b: a * b, numeros)
print(produto)

Para então termos o resultado:


120

E, quando fala sobre esse uso, complementa dizendo que preferiria até criar uma função nativa product(), similar à função sum().

Entretanto, depois de muita pressão por certa parte da comunidade, o criador do Python decidiu não remover essas funcionalidades no Python 3. Em vez disso, ele simplesmente fez algumas mudanças, movendo reduce() da biblioteca padrão da linguagem para o módulo functools e fazendo com que map() e filter() retornem um iterador.

Ainda assim, é importante entender que, tanto no Python 2, como no 3, compreensões de lista são geralmente consideradas mais pythônicas que essas outras funções, principalmente por questões de legibilidade. Frente a um problema de memória, ainda podemos optar por funções ou expressões geradoras, também consideradas boas opções.

Conclusão

Nesse post, exploramos mais um pouco do paradigma de programação funcional em Python, com funções que recebem outras funções como argumentos. No caso, aprendemos a utilizar as funções map(), filter() e reduce().

Também aprendemos a criar pequenas funções anônimas com expressões lambda, simplificando, de certa forma, nosso código.

Além disso, discutimos um pouco o significado dessas técnicas no Python, entendendo o porquê da existência de alternativas consideradas melhores que elas.

Gostou de conhecer essa parte do Python? Coloque nos comentários o que você já conhecia e utiliza

The post Programação funcional no Python appeared first on Blog da Caelum: desenvolvimento, web, mobile, UX e Scrum.

Conhecendo os geradores no Python

$
0
0

Onde eu trabalho, temos um programa em cada computador da empresa que registra em um arquivo as datas e os horários em que o computador foi ligado. O arquivo, de nome registros.txt, é organizado dessa forma:


07/04/2018 11:00
07/04/2018 17:05
08/04/2018 13:23
09/04/2018 12:30
...

Com o tempo, percebemos que seria mais vantajoso para a empresa, que tem sede em outros países, se mantivéssemos o padrão internacional ANSI na formatação das datas. Decidimos, assim, fazer essa mudança.

Resolvi usar o Python para essa mudança de formatação. Assim, precisamos converter cada entrada do registro de string para o tipo datetime.

Já sabemos como fazer isso para uma string única, utilizando o método strptime() do datetime:


from datetime import datetime

data_hora_em_texto = ‘07/04/2018 11:00’
data_hora = datetime.strptime(data_hora_em_texto, ‘%d/%m/%Y %H:%M’)
print(data_hora)

Assim temos o resultado:


2018-04-07 11:00:00

Certo, mas e agora que temos linhas e linhas de strings como essa? Conhecemos as compreensões de lista, que poderiam resolver isso facilmente, mas também sabemos que elas não podem evitar os problemas de memória que podem surgir ao se ler um arquivo muito grande de uma vez.

Assim, uma outra ideia poderia ser usar a função map(), outra conhecida nossa. Usando essa função com o lambda, poderíamos fazer algo como o seguinte:


from datetime import datetime

registros = open(‘registros.txt’, ‘r’)
datas_e_horarios = map(lambda data_hora: datetime.strptime(data_hora,
                    ‘%d/%m/%Y %H:%M’), registros)
print(next(datas_e_horarios))

E o resultado:


Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib64/python3.6/_strptime.py", line 565, in _strptime_datetime
    tt, fraction = _strptime(data_string, format)
  File "/usr/lib64/python3.6/_strptime.py", line 365, in _strptime
    data_string[found.end():])
ValueError: unconverted data remains:

Opa, uma exceção! O ValueError indica que ainda há conteúdo na string da data que não é coberto pela formatação que passamos. Isso é porque cada linha de nosso arquivo termina com uma quebra de linha, ou seja, com um \n Vamos adicionar esse caractere na formatação:


datas_e_horarios = map(lambda data_hora: datetime.strptime(data_hora,
                    ‘%d/%m/%Y %H:%M\n’), registros)

for data_hora in datas_e_horarios:
    print(data_hora)

E dessa vez:


2018-04-07 11:00:00
2018-04-07 17:05:00
2018-04-08 13:23:00
2018-04-09 12:30:00
...

Agora sim, conseguimos! Mas repare a complexidade dessa única linha que escrevemos.

Já vimos que ambas, a função map() e a expressão lambda, já não são consideradas muito pythônicas. Dessa forma, com uma linha comprida e legibilidade complicada, temos um código que na certa pode ser melhorado. Mas como?

Conhecendo os geradores

Uma abordagem ideal seria juntar os benefícios de memória de um iterador com a praticidade que o Python nos disponibiliza com outras ferramentas.

No Python, temos uma feature especial que facilita (e muito!) a criação de objetos iteradores – os geradores.

Um gerador é, de fato, um iterador, mas com uma manipulação muito mais facilitada! Ele consegue fazer todo o trabalho que queremos que faça, mas sem precisar de todo o nosso esforço em construir uma classe que implemente __iter__() para ser iterável e enfim __next__() para ser iterador.

Esta imagem complementa a relação apresentada no post sobre iteradores:

Relações entre geradores, iteradores e iteráveis

Enfim, como criar um gerador?

Funções geradoras e o yield

A maneira tradicional de se criar geradores no Python é através de funções geradoras. Criar uma função geradora é tão simples quanto criar uma função normal:


def cria_gerador():
    numero_retorno = 1
    yield ‘Retorno número {}’.format(numero_retorno)
    numero_retorno += 1
    yield ‘Retorno número {}’.format(numero_retorno)
    numero_retorno += 1
    yield ‘Retorno número {}’.format(numero_retorno)

Funções geradoras são como funções comuns no Python, mas que, quando chamadas, retornam um objeto gerador. Em vez da palavra chave return, funções geradoras usam o yield, que indica o retorno do next() do gerador criado.

Assim, a palavra chave yield pode ser usada múltiplas vezes em uma função geradora, para cada vez que o next() for chamado:


gerador = cria_gerador()
print(next(gerador))
print(next(gerador))
print(next(gerador)) 

E o resultado:


Retorno número 1
Retorno número 2
Retorno número 3

Certo! O que acontece é que a função next() vai rodar todo o código da função geradora até encontrar um yield. Quando encontra um yield, ela retorna o valor passado para ele e pausa a execução da função.

Mas espera… como assim pausa a execução da função?!

Diferentemente das funções normais, as variáveis locais da função (no caso desse exemplo, a variável numero_retorno) não são apagadas, mas mantidas para a próxima chamada do next(), que continuará a rodar a função no mesmo lugar que tinha parado, para novamente pausar quando atingir o próximo yield.

Simulação de um gerador 1

Simulação de um gerador 2

Simulação de um gerador 3

Vimos que, com funções geradoras, temos um yield para cada retorno. Para nosso caso, precisamos de um retorno para cada data no arquivo, então será que vamos precisar escrever tantos yield assim?

Podemos tratar o retorno dentro de um loop que rodará sobre o arquivo, nos devolvendo a conversão de cada linha para datetime, uma por vez:


from datetime import datetime

def cria_gerador_datas(registros):
    for data_hora_texto in registros:
        data = datetime.strptime(data_hora_texto, ‘%d/%m/%Y %H:%M\n’)
        yield data

registros = open(‘registros.txt’, ‘r’)
gerador_datas = cria_gerador_datas(registros)
print(gerador_datas)

O que acontece se tentarmos imprimir esse gerador que criamos?


<generator object <genexpr> at 0x7f0b41a76b40>

Tudo bem, recebemos a identificação do gerador na memória. Isso é porque um gerador, por ser um iterador, não sabe quais elementos fazem parte dele, isto é, quais valores ele vai gerar, afinal ele computa apenas um valor de cada vez.

Podemos ir passando por cada elemento gerado, através da função next(), ou iterando sobre o gerador com um laço:


for data in gerador_datas:
    print(data)

Como resultado:


2018-04-07 11:00:00
2018-04-07 17:05:00
2018-04-08 13:23:00
2018-04-09 12:30:00
...

Legal! Conseguimos solucionar nosso problema de forma elegante usando geradores. Mas ainda não conseguimos atingir o nível de simplicidade, similar ao que vemos em compreensões de lista… Será que não tem jeito, mesmo?

Substituindo compreensões de lista por expressões geradoras

Uma outra maneira mais enxuta de se criar geradores é através de expressões geradoras. A sintaxe de uma expressão geradora é similar à de uma compreensão de lista. A única diferença fica no uso de parênteses no lugar de colchetes:


from datetime import datetime

registros = open(‘registros.txt’, ‘r’)
gerador_datas = (datetime.strptime(data, ‘%d/%m/%Y %H:%M\n') for data in registros)

for data in gerador_datas:
    print(data)

E o resultado:


2018-04-07 11:00:00
2018-04-07 17:05:00
2018-04-08 13:23:00
2018-04-09 12:30:00
...

Exatamente o mesmo que com a função geradora, mas com uma simplicidade ao nível das compreensões de lista! Um código de fato pythônico.

Expressões geradoras vs Compreensões de lista

Agora que conhecemos as expressões geradoras e vimos como elas são similares às compreensões de lista, como sabemos quando usar cada uma?

Ok, já vimos que se precisarmos poupar memória as expressões geradoras são preferíveis. Mas, ué, não é sempre bom otimizar nosso código? Então por que não usar elas sempre, em vez das compreensões de lista?

O uso das listas traz algumas vantagens necessárias em algumas ocasiões, como por exemplo caso precisemos iterar mais de uma vez sobre seus elementos.

Um gerador produz um valor e depois se desfaz dele, então se precisássemos dele de novo, precisaríamos computá-lo mais uma vez, praticamente tirando seu propósito de preservar memória.

Já uma lista, como guarda todos os elementos juntos de uma vez, não precisa avaliá-los de novo, eles continuarão dentro dela.

Às vezes as listas também são preferíveis por nos disponibilizarem pegar um elemento pelo índice, ou até mesmo sub-listas que cortam a lista principal, dentre outros métodos exclusivos de uma lista.

Um ponto a favor dos geradores muito interessante, entretanto, é a possibilidade de infinitude. Listas são sempre finitas – sempre, pois todos os seus valores são calculados de uma vez.

Se tentássemos criar uma lista infinita, teríamos, naturalmente, um problema de memória. Como o gerador produz um valor por vez, podemos ter um gerador com a capacidade de produzir para sempre.

Para uma simples iteração, não vai haver muita diferença entre um e outro, então escolha o que preferir e, caso encontre algum problema (seja de memória, seja de falta de métodos para solucionar algo), considere trocar para o outro!

Conclusão

Nesse post, aprendemos uma forma muito mais simples de criar nossos próprios iteradores, através de geradores.

Aprendemos, então, a criar nossos geradores tanto com funções geradoras, quanto com expressões geradoras, atingindo uma praticidade desejável!

Além do mais, vimos como os geradores podem ser ótimas alternativas para algumas práticas (como criar iteradores na mão, ou o uso do map()).

Gostou do conteúdo? Geradores são uma técnica para se levar para a vida de um programador Python! Me diga o que achou nos comentários!

The post Conhecendo os geradores no Python appeared first on Blog da Caelum: desenvolvimento, web, mobile, UX e Scrum.


Quais as diferenças entre Python 2 e Python 3?

$
0
0

Antes de se iniciar no Python, surge sempre a dúvida – qual versão da linguagem devo aprender? Talvez mais difícil que esta é a dúvida que vem de quem já programa em Python – devo fazer meus programas em qual versão do Python?

Tratando de algumas outras linguagens de programação, esta dúvida pode parecer menos usual, porque o que funciona em uma versão geralmente funciona também na próxima.

O ponto é que as duas maiores versões major do Python (2 e 3, as únicas em uso) têm diferenças cruciais. O Python 3 não é retrocompatível, e isso acaba trazendo algumas confusões e dúvidas para nós desenvolvedores.

Mas o que essa “retroincompatibilidade” significa? Basicamente, indica que código escrito em Python 2 pode não funcionar rodando no interpretador do Python 3.

O Python 3 foi lançado no final de 2008, pouco mais de 8 anos depois do Python 2, e veio com intenções claras – retificar as falhas de design que a linguagem trazia.

Mas e aí? Sabendo disso, o que a gente faz? Temos agora um dilema, o que aprender? E, ainda, o que usar? Nesse post vamos tentar debater isso, apresentando pontos explicativos das diferenças entre as versões e argumentando em prol de um código mais pythônico.

Principais diferenças entre Python 2 e Python 3

Antes de tudo, precisamos entender de fato o que muda de uma versão para outra. Assim, deixamos um índice separando cada tópico, para facilitar a leitura do post.

Mudanças sutis

As primeiras diferenças entre as duas versões em uso do Python são simples e, ao mesmo tempo, muito problemáticas.

O primeiro exemplo é o clássico da confusão já discutida por nós, a diferença das funções raw_input() e input(), que, para um desenvolvedor desatento com a mudança de versão, pode até causar um problema de segurança grave.

Além deste, temos os casos da função print() e do tipo file. No Python 2, print não é uma função, mas sim uma declaração, ou, tecnicamente, um statement. Assim, podíamos usá-la dessa forma, sem os parênteses:


print ‘Olá, mundo!’

E tudo funcionaria bem! No Python 3, entretanto, o print() vira de fato uma função, necessitando os parênteses para ser chamada. Vamos ver o que acontece quando tentamos usar o print como usávamos no Python 2:

Erro na chamada de print sem parênteses no Python 3

Uma exceção nos indicando a falta dos parênteses! Felizmente, essa questão é muito simples de ser resolvida, porque statements também funcionam com parênteses. Dessa forma, podemos fazer isso:


print(‘Olá, mundo!’)

Que é válido tanto no Python 2 quanto no Python 3.

Por último, mas não menos importante, temos o construtor file, conhecido no Python 2 com seu uso para abrir arquivos quaisquer. No Python 3, esse construtor foi simplesmente removido. A recomendação que fica é, portanto, usar a função open(), independente da versão do Python, justamente por questões de compatibilidade.

Programação preguiçosa

Já vimos o que são iteradores no Python e como eles podem ser vantajosos quando comparados a listas comuns. Assumindo isso, os desenvolvedores do Python e a própria comunidade começaram a dar maior suporte e preferência nativa aos iteradores do que às listas.

Apresentamos alguns exemplos disso quando falamos sobre programação funcional em Python, com a alteração das funções map() e filter() para retornarem iteradores em vez de listas. O que mais temos além disso?

Como outro exemplo interessante, que pode causar confusão para desenvolvedores acostumados com uma dessas duas versões do Python, temos a função range().

No Python 2, a função range() retorna uma lista representando uma progressão aritmética. Assim, ela era claramente limitada, por questões da memória do computador. Vamos testá-la:

Demonstração da função range() no Python 2

Além dela, tínhamos a classe xrange(), que fazia o mesmo, mas de maneira preguiçosa, isto é, um valor por vez.

No Python 3, o tipo xrange() foi renomeado para range() e o comportamento original da função range() perdeu sua função nativa própria (podendo ser facilmente simulado com list(range())):

Demonstração do range() no Python 3

Assim, utilizar métodos de lista com o range() ou assumir uma segurança na memória podem causar problemas na portabilidade do código para cada versão.

Comparação

A maneira clássica de comparação, que já estava sendo mudada no Python a partir da versão 2.1, com a chegada da comparação rica, tem seu estilo delimitado de vez no Python 3, com a remoção da função nativa cmp() e do método especial __cmp__().

Essas antigas funcionalidades eram baseadas no comportamento tradicional de comparação que vemos na maioria das linguagens de programação, como Java, em que se tem três tipos de retorno – -1, 0 e 1, para, respectivamente, um objeto menor, igual, ou maior que outro.

Quanto à comparação rica, ainda tivemos uma mudança importante no Python 3. A documentação dos métodos de comparação rica do Python 2 deixa claro que nenhum operador está ligado a outro. Assim, == não é naturalmente o oposto de !=:

No Python 2, comparações não tem conexão

Entretanto, no Python 3 isso é alterado para que o operador != resulte, nativamente, no oposto do operador ==. Dessa forma:

No Python 3, != é o oposto de ==

Em relação à comparação, também tivemos outra significativa mudança no Python 3, que acabou com um comportamento estranho que ainda era mantido no Python 2.

Na segunda versão, todos os objetos eram comparáveis ordinalmente entre si. Sim, todos. Isto significa que comparar se uma string é maior que um inteiro, ou que um float é menor que uma lista, tudo isso funcionaria, retornando True ou False dependendo do teste.

Claro, a lógica não era aleatória, o que não significa que era justificável, afinal dá margem para erros do desenvolvedor passarem despercebido.

A primeira regra desse tipo de comparação era que qualquer instância de uma classe definida pelo usuário era sempre menor que qualquer outro objeto nativo. Podemos facilmente testar isso no interpretador do Python:

No Python 2, instâncias de classes do usuário são consideradas menores que quaisquer outros objetos.

A segunda regra era que tipos númericos eram sempre menores que os outros objetos. Vamos comprovar com testes:

No Python 2, tipos numéricos são considerados menores que outros objetos

A última regra principal era que, com objetos de outros tipos (que não criados pelo usuário nem numéricos), a comparação de ordem era alfabética com base nos nomes dos tipos. Dessa forma:

No Python 2, comparações sem relação comparam as strings que representam os tipos dos objetos comparados

O resultado era True porque esse teste era equivalente a uma comparação de duas strings:

'str' > 'list'

Como isso acaba causando resultados inesperados no código, esse comportamento foi removido do Python 3 e, agora, uma tentativa de comparação ordinal entre dois objetos que não têm uma ordem natural causa uma exceção do tipo TypeError.

Números

Na mudança de versão, tivemos algumas mudanças estruturais em relação aos tipos numéricos. A primeira, em relação aos próprios tipos em si, é a remoção da classe long no Python 3.

No Python 2, o tipo int era limitado, ou seja, tinha um valor máximo que podia chegar. Além desse valor, o tipo já era outro – o long, representado com um L após os dígitos numéricos:

No Python 2, o tipo int era limitado. Acima desse limite, o número era considerado long

No Python 3 essa diferença, por fim, acaba, unificando os dois tipos em um só – o próprio int. Assim, o int perde seu limite e um número mais longo não é mais identificado com o sufixo L.

Divisão de números inteiros

Em relação aos números ainda temos uma mudança fundamental de ser entendida, pois pode acabar causando sérios problemas no funcionamento de um programa. Essa mudança se dá, mais especificamente, na divisão de números inteiros.

No Python 2, toda divisão envolvendo dois números inteiros resultava em um terceiro número inteiro:

Divisão de números inteiros no Python 2

No Python 3, porém, esse comportamento é bastante modificado – toda divisão envolvendo dois números inteiros agora resulta em um número float:

Divisão de números inteiros no Python 3

Nas duas versões do Python ainda temos um outro operador, //, que vai seguir o comportamento de divisão do Python 2:

Divisão de números inteiros arredondada

Dessa forma, é recomendado seguir um padrão que funcione da mesma forma nas duas versões do Python, seja com o uso do operador //, resultando em um int, seja com a conversão de um dos inteiros para float, resultando em um float:

Divisão de números de ponto flutuante

Tipos de texto

Uma das mudanças mais importantes na terceira grande versão do Python está nos tipos disponíveis para armazenar “texto”.

Mas por que texto entre aspas?”

Já vamos entender isso!

No Python 2, temos dois tipos principais para armazenamento de texto. O tipo padrão string (str) e o tipo unicode, identificado com o prefixo u antes das aspas:

Unicode no Python 2

Isso tudo indica duas coisas fundamentais: a primeira é que o tipo padrão string não suporta caracteres não-ASCII (unicode); a segunda, que não há um tipo próprio para armazenamento de bytes.

Por conta disso, costumava-se dizer, na comunidade Python, que o tipo string armazenava bytes, enquanto o tipo unicode era quem de fato armazenava texto.

Acreditar no potencial da string como texto de fato podia acabar causando problemas nos funcionamentos mais simples de uma aplicação, como é no caso de se imprimir um texto acentuado:


## Arquivo teste.py

print(‘Meu nome não é José’)

Ao rodarmos esse arquivo com o interpretador do Python 2, olha o que acontece:


  File "teste.py", line 1
SyntaxError: Non-ASCII character '\xc3' in file foo.py on line 1, but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details

Uma exceção indicando que nenhum encoding foi definido. Ou seja, precisamos definir um padrão para codificar as strings no programa que contêm caracteres não-ASCII.

No Python 3, as coisas mudam bastante. O tipo unicode se torna o tipo string (é renomeado para str) e um tipo nativo com o funcionamento da antiga string deixa de existir. Isso acaba permitindo algumas coisas muito interessantes no código, como o uso de caracteres unicode como nome de identificadores:

Suporte a nomes de identificadores em unicode no Python 3

Note que o uso de caracteres não-ASCII para nomes de identificadores, apesar de ser um comportamento interessante e divertido em alguns casos, não é recomendado se queremos compartilhar nosso programa, por conta do perigo de incompatibilidade com outros sistemas e ambientes.

Além disso, é acrescentado um outro tipo específico para lidar com bytes, a própria classe bytes, representado pelo prefixo b:

Tipo bytes no Python 3

Para um desenvolvedor que não conhece essa nova classe bytes, isso pode trazer alguns problemas, como por exemplo na identificação do tipo de um arquivo.

Tomando como exemplo arquivos GIF, que (quase) sempre começam com GIF89a, vamos ver como fica um código de identificação de GIFs que funciona no Python 2, em que abrimos o arquivo como leitura de bytes e checamos o começo:


arquivo = open(‘meu-gif.gif’, ‘rb’)

identificador = arquivo.read(6)
print(identificador == ‘GIF89a’)

Quando rodamos o código no interpretador do Python 2:


True

Agora olha o que acontece quando rodamos o mesmo código no Python 3:


False

Ué! Aí que fica clara a diferença de como o Python 3 lida com bytes, com a nova classe. O que acontece é que estamos comparando o identificador, que é do tipo bytes, por conta da leitura de dados, com uma string, o que vai retornar em False. Se tratarmos essa string como byte, olha como fica:


print(identificador == b’GIF89a’)

E agora:


True

Sintaxe

Além das mudanças na estrutura e lógica do código, tivemos algumas significativas mudanças na sintaxe do Python 2 para o Python 3. De algumas coisas adicionadas, outras removidas, vamos focar no que foi alterado.

Tratamento de exceções

Um pouco da sintaxe do tratamento de exceções, mais especificamente com raise e o except, foi alterada. No Python 2, para chamarmos uma exceção com uma determinada mensagem, podemos usar a seguinte sintaxe:

raise no Python 2

No Python 3, olha o que acontece se tentarmos usar a mesma sintaxe:

SyntaxError com raise no Python 3

Realmente recebemos uma exceção, mas nada a ver com o que tínhamos pedido! Foi um erro de sintaxe, porque essa maneira de usar o raise não é permitida no Python 3. Em vez disso, devemos tratar o tipo da exceção como uma função e passar a mensagem como parâmetro:

Maneira correta de usar o raise

E agora sim funciona. Felizmente, essa sintaxe funciona tanto no Python 3 quanto no Python 2, então é recomendável usá-la sempre, independente da versão que estamos usando enquanto programamos, para ampliar a compatibilidade de nosso programa.

Em relação ao except, a mudança também é bastante sutil. No Python 2, podíamos usar a seguinte sintaxe em um bloco try/except:


try:
    blablabla
except NameError, erro:
    print(erro)

Se rodarmos esse código no interpretador do Python 2:


name ‘blablabla’ is not defined

Certo! Mas se rodarmos esse código no Python 3, olha o que acontece:


  File "testeexcept.py", line 3
    except NameError, erro:
                    ^
SyntaxError: invalid syntax

Para o código funcionar no Python 3, devemos usar a palavra chave as:


try:
    blablabla
except NameError as erro:
    print(erro)

E agora:


name ‘blablabla’ is not defined

Certo! Esse código, assim como com o do raise, também funciona tanto no Python 3 quanto no Python 2, o que é ótimo para nós, desenvolvedores.

Indentação

O Python, por padrão, já é bastante rigoroso quanto à indentação de nosso código. Não usamos identificadores de começo e fim de blocos de código, como chaves ({}), mas os identificamos através da própria indentação.

Esse detalhe do Python acaba sendo muito vantajoso, por nos incentivar ao uso de boas práticas de design e indentação de código. Além dessa regra de sintaxe obrigatória, temos recomendações em relação a isso especificadas em nosso guia de estilo para código Python, o PEP-8.

O PEP-8 indica que 4 espaços são preferíveis a uma TAB para indentação. No final, é claro, fica a critério do desenvolvedor. Mas, acima de tudo, esse guia nos ensina que o melhor código é o código consistente. Isto é, é melhor só usar TABs para indentar, do que ficar mudando de TABs para espaços.

Quanto a isso, entretanto, não havia restrição alguma no Python 2. Em um mesmo bloco de código, podia-se alterar de TABs para espaços. No Python 3, isso é mudado, e uma inconsistência desse tipo resulta em uma exceção:


IndentationError: unindent does not match any outer indentation level

Essas são algumas das principais diferenças entre as duas versões que pode acabar atrapalhando mais um desenvolvedor que não as conhece.

Se quiser ver a lista oficial, o Python apresenta as novidades de cada versão em uma página na documentação. Mas e agora?

Qual versão aprender? (E, ainda, qual versão ensinar?)

Para alguém iniciando agora na linguagem, então, qual versão aprender? Qual versão focar?

É certo que a terceira versão do Python é um avanço da linguagem, tendo surgido para melhorar a versão anterior. Pode-se, é claro, argumentar a favor do Python 2, afinal algumas de suas características eram de fato melhores para certas necessidades. Mesmo assim, é difícil negar o aprimoramento que o Python 3 trouxe.

Além disso, ainda podemos ver o Python 3 como um conserto das falhas do Python 2. Assim, acaba sendo preferível para um iniciante que aprenda direto o Python 3, para não se atrapalhar com as falhas que a segunda versão tinha, evitando confusões de aprender algo já considerado errado e superado.

Apesar disso, é fato que código em Python 2 existe aos montes e continuará existindo por bastante tempo, então é importante que um desenvolvedor Python tenha clareza das principais diferenças entre as duas versões, para saber lidar com ambas à medida que aparecem em sua vida.

E agora, já tendo conhecimento em Python, qual versão eu devo escolher para desenvolver?

Para qual versão programar?

Pensando em qual versão programar, temos que ser realistas e considerar o mercado e o uso de Python que as empresas e os pontos de trabalho fazem atualmente. Uma pesquisa realizada em 2014 destinada para desenvolvedores Python pode nos ajudar a entender a visão que prevalece, ou ao menos prevalecia, há 4 anos.

O recomendado é que se priorize a última versão, quando possível. Sendo assim, se você está começando um projeto pessoal novo, prefira programar pensando no Python 3.

Começar um novo projeto com Python 3 pode evitar possíveis problemas que a versão anterior carrega e que já foram consertados e conseguir maior suporte para próximos avanços, afinal é dito que a partir de 2020 o Python 2 será descontinuado, perdendo grande parte de seu suporte.

Também é recomendado que código em Python 2 seja migrado para a terceira versão, e é o que grande parte dos importantes projetos em Python, inicialmente desenvolvidos na segunda versão, estão fazendo, como é o caso do Requests e BeautifulSoup.

Mesmo assim, sabemos que essa migração pode não ser tão simples, tanto no quesito técnico quanto no quesito burocrático – nem sempre uma empresa vai apoiar a migração, que pode trazer certos problemas no começo.

Dessa forma, a manutenção de código em Python 2 às vezes acaba sendo obrigatória e, além disso, pode ser vantajosa para os que já tem uma estabilidade garantida.

De qualquer maneira, geralmente queremos que nosso código seja compatível com o maior número de sistemas possíveis, para alcançar um maior público.

Assim, o ideal seria com que conseguíssemos fazer um código que funcione tanto para Python 2 quanto para Python 3, rodando com sucesso em qualquer um dos interpretadores. Será que conseguimos fazer isso?

Tornando seu programa compatível com as duas versões

Antes de aprendermos como podemos aumentar a disponibilidade de nossos programas, precisamos concluir para quem queremos fazer isso? Afinal esse processo torna-se muito complicado à medida que incluímos mais e mais detalhes, sistemas e versões.

Assim, a própria documentação do Python recomenda que não nos importemos, se possível, com o suporte para qualquer versão do Python abaixo da 2.7. Isso facilita muito a portabilidade de nosso código.

Se isso não for uma opção, temos alguns projetos na comunidade, como o six, que pode nos ajudar a manter um código compatível.

Com isso em mente, trabalharemos daqui pra frente considerando suporte para Python >= 2.7.

Algumas ferramentas, técnicas e práticas podem nos auxiliar bastante na portabilidade de nosso código. Vamos entender e testar algumas delas para podermos decidir quais são as melhores opções.

Escrevendo código compatível com as duas versões

Muitas vezes ainda é perfeitamente possível escrever código que funcione com as duas versões, afinal a mudança não é total.

Para isso, só temos que tomar cuidado com algumas práticas, lembrando sempre de usar, quando possível, uma funcionalidade ou sintaxe que funcione para Python 2 e 3. Um bom exemplo disso é com o print, que no Python 2 também pode ser usado com parênteses.

O problema é que em alguns casos temos problemas realmente incompatíveis, como com o input() e o raw_input(). Como podemos lidar com isso?

Uma abordagem seria checar qual versão do Python está rodando, e podemos fazer isso com o atributo version_info.major do módulo sys:


from sys import version_info

if version_info.major == 3:
    nome = input(‘Digite seu nome: ‘)
else:
    nome = raw_input(‘Digite seu nome: ‘)

Certo, esse código aparentemente funciona. Mas e se o Python 4 for lançado? A versão não será igual a 3 e o programa tentará usar a função raw_input(), que muito provavelmente nem ao menos existirá. É melhor, então, basear nossa checagem na versão 2 do Python:


if version_info.major > 2:
    nome = input(‘Digite seu nome: ‘)
else:
    nome = raw_input(‘Digite seu nome: ‘)

Assim já temos um melhor funcionamento. Mesmo assim, usar checagem de versão em vez de checagem de funcionalidade pode ser um problema, pois as coisas podem mudar e voltar a ser como eram antes de uma versão para outra. Confiar na detecção de versão pode atrapalhar a compatibilidade com mudanças futuras na linguagem.

Além do mais, é muito citada na comunidade Python uma frase de uma das mais importantes programadoras da história – Grace Hooper. A frase pode ser traduzida como “às vezes é mais fácil pedir desculpas do que pedir permissão“.

Com essa filosofia em mente, podemos considerar tratar a detecção de funcionalidade com tratamento de exceções no Python:


try:
    nome = raw_input(‘Digite seu nome: ‘)
except NameError:
    nome = input(‘Digite seu nome: ‘)

E agora temos o código ideal para esse tipo de situação!

Conclusão

Nesse post, pudemos ver algumas das principais diferenças entre as versões 2 e 3 do Python, entendendo o porquê dessas diferenças e o que elas afetam.

Além disso, discutimos qual versão devemos priorizar, concluindo que, no geral, Python 3 é preferível daqui pra frente.

Também pudemos trabalhar algumas técnicas para aprimorar a compatibilidade de nosso código, tornando-o usável com as duas versões.

E aí, gostou do conteúdo? Me diga aí qual sua opinião sobre isso e qual versão você costuma usar nos comentários!

The post Quais as diferenças entre Python 2 e Python 3? appeared first on Blog da Caelum: desenvolvimento, web, mobile, UX e Scrum.

Algumas dicas importantes sobre políticas de senha

$
0
0

Recentemente, realizei alguns testes de segurança em uma aplicação web que mantenho online.

Ao final, consegui acesso a uma grande parte das contas de diferentes usuários. Mesmo assim, os resultados do teste me mostraram que meu sistema, no geral, estava seguro. Não havia uma vulnerabilidade no meu banco de dados, nem uma falha própria do sistema. O problema, na verdade, estava nas senhas dos usuários!

O fato é que, por algum motivo, meus usuários estavam usando senhas fracas para suas próprias contas. E por que isso? Ademais, por que eu deveria me importar com isso? Afinal, aparentemente o problema de segurança não está do lado da aplicação, mas sim dos usuários… certo? Na verdade, não!

Desconsiderar esse problema (e é um grande problema!) pode se mostrar ser um grande erro. Em primeiro lugar, temos que levar em conta que o comprometimento das contas dos nossos usuários implica em um comprometimento da segurança da aplicação em si, o que é diretamente prejudicial para nós do lado do desenvolvimento.

Complementando isso, é tão ingênuo quanto malicioso acreditar que a responsabilidade de se ter senhas seguras está apenas nas mãos do usuário. É claro, não podemos (ou ainda, não devemos) garantir que a senha do usuário seja ideal e segura quanto desejamos. Mesmo assim, nossa participação nessa escolha de senha pode ser importantíssima.

Por conta disso, a maioria dos serviços online, hoje em dia, tem algumas políticas de senha que ditam regras sobre elas para o usuário. Dentre estas:

  • Sua senha deve conter pelo menos 8 caracteres“;
  • Sua senha deve conter um número, um símbolo, letras maiúsculas e minúsculas“;
  • Você deve alterar sua senha a cada 90 dias“;
  • Sua senha não pode conter caracteres repetidos

e mais um monte que encontramos por aí!

A questão é: quais dessas regras todas são realmente úteis e contribuem de fato para a segurança de nossa aplicação? Para alguns, a resposta pode parecer clara, mas a coisa vai mais fundo do que aparenta.

Sendo assim, quais são os tópicos que devemos levar em consideração para desenvolver nossa política de senhas? Com essa questão, bolei uma lista de algumas dicas que todo desenvolvedor deve considerar para construir as políticas de senha de sua própria aplicação:

Evite regras estáticas (ou a farsa da senha complexa)

Infelizmente, o que encontramos de mais comum nas políticas de senha existentes hoje em dia é um dos pontos que mais machuca toda a segurança – regras excessivas e estáticas.

A falsa crença de que podemos classificar senhas “boas” em um escopo com definições permanentes, junto com a síndrome dos chimpanzés na Internet, acabou nos levando a complexidades como:

  • Sua senha deve conter pelo menos 8 caracteres
  • Sua senha deve conter pelo menos uma letra maiúscula (A-Z) e um número (0-9)
  • Sua senha deve conter pelo menos um símbolo (@, !, …)
  • Sua senha não pode conter uma sequência de 3 caracteres iguais

Todas essas regras podem parecer boas, e isso é porque elas realmente podem ser boas! Seguindo-as, poderíamos, por exemplo, chegar em uma senha como 8|hOy7!.G\, que é uma senha relativamente boa (apesar de facilmente quebrável em um ataque de força bruta offline).

O grande problema que essas regras ignoram é que os usuários, no geral, não querem memorizar senhas como 8|hOy7!.G\; o usuário é esperto e quer simplicidade.

Assim, se atrapalhamos de cara a usabilidade de nossa aplicação, obrigando o usuário a seguir nossas regras estritas, é quase certo que ele encontrará uma brecha nisso tudo para fazer do jeito dele. Seguindo essas mesmas regras, chegamos em uma senha como Senh@123, que é terrivelmente insegura, como já discutimos no post sobre criação de boas senhas.

Pensando nisso, devemos sempre levar em conta a Regra de Usabilidade do AviD“segurança às custas da usabilidade vem às custas da segurança”. Não adianta desconsiderar o aspecto humano se nossa aplicação é feita para humanos!

À medida do possível, então, devemos abandonar essas políticas de senha estáticas e proibitivas, que, no geral, não contribuem com a segurança da aplicação (chegando a atrapalhar, muitas vezes) e ainda irritam o usuário. Mas será que não precisamos de nenhuma regra, então?

Adote um limite mínimo no comprimento das senhas

Já vimos que limites restritivos não são muito legais nas políticas de senha. Afinal, devemos permitir qualquer senha? Bem, assim voltaríamos para o ponto que começamos, não é?

Liberdade para o usuário conseguir criar sua senha é importantíssimo, mas a liberdade total pode levar a senhas tão inseguras como abc, 01, dentre outras aberrações.

Considerando isso, podemos voltar àquela famosa tirinha do xkcd, que também discutimos no post sobre criação de boas senhas.

Na tirinha, correto cavalo bateria grampo ganha de Tr0ub4dor&3 por dois motivos: o aspecto humano (no quesito memorização), que já vimos que é importante, e o próprio aspecto de segurança – Tr0ub4dor&3, por mais complexa que pareça, é menos segura.

Nesse caso, a segurança se baseia, principalmente, no comprimento da senha, o que pode ser demonstrado através da matemática.

Pensando em dois exemplos, podemos construir duas funções matemáticas para calcular quantas possibilidades de senha existem, um com uma senha de n caracteres, mas que devem estar no grupo de letras do alfabeto (ou seja, 26 possibilidades, como corretocavalobateriagrampo):

possibilidades = 26^numero_de_caracteres

e outro com uma senha de 11 caracteres fixos (como Tr0ub4dor&3) que podem ser de um grupo de n caracteres distintos:

possibilidades = caracteres_possiveis^11

Como o crescimento exponencial é mais acelerado que o crescimento polinomial, montando ambas as funções em um gráfico, temos o seguinte:

Crescimento exponencial vs crescimento polinomial

Com o gráfico, podemos facilmente ver que, por volta de 6 caracteres (de opção, na função vermelha, e de tamanho, na função azul), o crescimento da função azul, com variação de tamanho, é bem mais rápido. Isso indica, com clareza, que o tamanho da senha é um fator que não pode ser desconsiderado!

Sabendo disso, será que não seria melhor impormos uma restrição quanto a isso?

A princípio, podemos definir um tamanho mínimo que uma senha de nosso usuário deve ter, para garantir uma segurança mínima. Esse número vai de acordo com o que nós achamos necessário.

Considerando que quanto maior, mais seguro, mas mais difícil para o usuário e, quanto menor, menos seguro, mas mais fácil para o usuário – então um mínimo entre 10 e 15 pode ser o ideal.

Ok, definimos um tamanho mínimo. Devemos também definir um tamanho máximo, para padronizar?

Não limite um tamanho máximo de senha

Sempre me disseram para delimitar um tamanho máximo que uma senha pode ter, mas eu nunca entendi muito bem. Depois de muito perguntar, consegui algumas respostas.

“Muito espaço para ocupar no banco de dados”

A primeira explicação que me deram foi que se não limitássemos o tamanho das senhas dos usuários, poderíamos acabar com o banco de dados lotado, por conta de alguma(s) senha(s) excessivamente longa(s).

A questão que essa explicação desconsidera é de que jamais podemos armazenar uma senha em texto plano!

Todas as senhas em nosso sistema precisam ser criptografadas, para serem armazenadas como hashes no banco de dados. Assim, mesmo em caso de vazamento de nossos dados, as senhas ainda estarão, de certa forma, protegidas.

O ponto é que os resultados de uma função hash, por definição, sempre terão o mesmo comprimento. O bcrypt, por exemplo, sempre gera um hash de 60 caracteres. Desse modo, não precisamos nos preocupar com o tamanho que o usuário decida para a senha – pelo menos não por conta disso.

“Mas e esse monte de requisições?”

Outra questão que me trouxeram foi a respeito do perigo de sobrecarga do servidor – “e se alguém tentar enviar uma senha de 2 GB?“.

Realmente, esse tipo de atitude por parte do cliente é preocupante e pode causar problemas para o servidor, em forma de ataques de negação de serviço, mas será que esse perigo está só nas senhas?

Repare que assim como alguém pode enviar uma senha de 2 GB para o servidor, também pode enviar qualquer outro valor de entrada, com qualquer outro significado semântico, tendo esse mesmo tamanho.

Ou seja, essa questão não pode ser resolvida com políticas de senha, mas, principalmente, com definições e fortificações no próprio servidor.

A limitação do bcrypt

O terceiro e último ponto que me trouxeram tinha o foco em uma função hash específica – o bcrypt, que é um dos algoritmos mais recomendados para esse tipo de uso.

A questão é que o bcrypt, por definição, tem um limite de 72 caracteres. Por isso que diversos sites e serviços online tem essa limitação explícita, como é o caso do GitHub:

Limite de tamanho de senha no GitHub

Mas e aí? Devemos impedir nossos usuários de usarem senhas mais longas (e, portanto, possivelmente mais seguras) por conta de uma limitação do algoritmo de criptografia? Ou será que devemos abandonar esse algoritmo de ótima reputação? O ideal seria não precisar fazer nenhum dos dois… mas como?

Se conseguirmos transformar a senha longa do usuário em uma string menor que 72 caracteres, isso será resolvido.

Como existem diversas funções hash por aí, poderíamos, inclusive, usar uma delas para isso! O SHA-256, por exemplo, transforma a senha em um hash de 32 caracteres. Assim, então, podemos passar esse hash para o bcrypt. Isso resolveria o problema do limite de tamanho do bcrypt sem quebrar, de forma alguma, a segurança do sistema.

Apesar de algumas pessoas utilizarem o SHA-256 como função hash principal, ele não é considerado seguro sozinho, por ter um tempo de computação muito rápido.

Bacana! Definimos algumas regras em relação ao tamanho das senhas, mas isso é o suficiente? O que mais podemos organizar claramente nas políticas de senha?

Não se prenda à tabela ASCII

Já cuidamos das questões relacionadas ao tamanho da senha, mas quais são os outros fatores que também importam? Afinal, uma senha como 订ش导书✌︎🚖池, de 7 caracteres, pode ser segura, mesmo que curta? A resposta é sim! Por usar caracteres variados do padrão Unicode, que contém mais de 137 mil caracteres ao total, ela é uma senha segura, já que existem muito mais combinações!

Infelizmente, se tentarmos agora em diversos sites e serviços online atuais, provavelmente acabaremos nos deparando com várias mensagens do tipo: “somente caracteres ASCII permitidos“. Mas por quê? Qual o sentido disso?

Tirando o fator da síndrome do chimpanzé, não há muito o que defender nessa regra. Entendendo o básico de Unicode e suas codificações, e fazendo o certo na hora de armazenar as senhas – ou seja, usando o bcrypt devidamente, como é recomendado -, não deve haver problema nenhum em usar caracteres Unicode.

Assim, a dica é para abandonarmos esse modo ASCII de pensar e melhorarmos a segurança de nossas aplicações simplesmente sendo menos restritivos e permitindo caracteres Unicode nas senhas!

Cada vez mais está ficando claro o quanto regras estáticas e restritivas podem ser prejudiciais nas políticas de senha. Mas quais são as alternativas?

Calcule a força da senha dinamicamente

Já entendemos que tentar garantir a força das senhas de nossos usuários com regras estáticas não funciona, podendo até prejudicar a segurança da aplicação. Então como podemos calcular a força de uma senha?

O ideal parece ser tentar garantir a força das senhas dinamicamente – isto é, com alguma espécie de cálculo para cada senha separada. Mas como?

É difícil apontar para uma senha e dizer com certeza que ela é segura. Por outro lado, muitas vezes conseguimos com clareza distinguir que uma senha é, de fato, fraca!

Sendo assim, poderíamos verificar se a senha enviada pelo usuário é fraca, checando se ela é construída a partir de algum padrão calculável ou até se faz parte de alguma lista de senhas mais utilizadas.

Mas, hum… Vamos ter que criar esse algoritmo do zero? Felizmente, não! Os desenvolvedores do Dropbox vieram com um projeto de estimador de força de senhas, baseado nos próprios crackers de senha utilizados pelos hackers, bastante seguro, flexível e simples de usar – o zxcvbn!

O zxcvbn consegue reconhecer padrões como de repetição, de datas e de sequências, além de testar a senha para vários dicionários, como de nomes comuns e palavras populares em inglês.

O resultado que o zxcvbn nos passa também é de grande ajuda, chegando até a conter um feedback verbal de como a senha pode ser melhorada, para senhas consideradas bem ruins.

Podemos testar o zxcvbn com uma senha que parece difícil de memorizar e ver se é de fato uma senha segura: P@55w0rD123.

Esse algoritmo olha nossa senha e nos retorna um JSON com a análise. No JSON temos alguns pontos bem interessantes como o tempo que demoraria para essa senha ser quebrada com ataques online e offline:


  "crack_times_display": {
    "online_throttling_100_per_hour": "11 days",
    "online_no_throttling_10_per_second": "43 minutes",
    "offline_slow_hashing_1e4_per_second": "3 seconds",
    "offline_fast_hashing_1e10_per_second": "less than a second"
  }

Outras informações bacanas são a nota (score) que o zxcvbn dá para a senha, que varia de 0 (muito fácil e arriscada) até 4 (difícil e forte), e o feedback verbal com sugestões e avisos:

  
"score": 1,
  "feedback": {
    "warning": "This is similar to a commonly used password.",
    "suggestions": [
    "Add another word or two. Uncommon words are better.",
    "Predictable substitutions like '@' instead of 'a' don't help very much."
    ]
  }
}

Em relação a essa senha, o zxcvbn avisou que ela é muito comum e ainda deu duas dicas – de adicionar uma ou duas palavras, preferencialmente incomuns, e de que substituições previsíveis como @ no lugar de a não ajudam muito. Assim, o usuário estará ainda mais bem instruído para criar uma boa senha!

Legal! Desse modo, o zxcvbn pode nos ajudar imensamente. Mas e se tentarmos outras combinações? Por exemplo, passando meu próprio e-mail (yan.orestes@alura.com.br):


  "crack_times_display": {
    "online_throttling_100_per_hour": "centuries",
    "online_no_throttling_10_per_second": "centuries",
    "offline_slow_hashing_1e4_per_second": "centuries",
    "offline_fast_hashing_1e10_per_second": "centuries"
  },
  "score": 4,

Olha só! Eu usei meu próprio email e o zxcvbn identificou como uma senha boa e que demoraria séculos (literalmente) para quebrar! Bom, ele realmente não tinha como saber meu email… mas e aí?

Considere os casos locais

O zxcvbn é de incrível utilidade e pode funcionar muito bem, mas confiar totalmente no uso padrão dele pode trazer alguns grandes problemas para a gente, como acabamos de ver.

O motivo disso é claro – não há como o zxcvbn saber, nativamente, informações internas da aplicação ou do usuário.

Além disso, os desenvolvedores dessa ferramenta se concentram, no geral, nos Estados Unidos. Por essa razão, os dicionários com os quais as senhas são testadas têm maior relevância para usuários nativos na língua inglesa, o que muitas vezes não será nosso caso. Então o que podemos fazer?

Uma alternativa para esse problema é fazer nossos próprios testes para senhas locais, além do zxcvbn, considerando a língua mãe da maior parte dos usuários, as informações sobre a aplicação, e até as informações dos usuários (como o email ou o username).

Na verdade, podemos até usar uma própria funcionalidade do zxcvbn, que nos permite enviar junto à verificação nosso próprio dicionário de senhas, que auxiliará na estimativa da ferramenta. Para isso, é só usar o parâmetro user_inputs. Olha só a diferença agora:


  "crack_times_display": {
    "online_throttling_100_per_hour": "1 minute",
    "online_no_throttling_10_per_second": "less than a second",
    "offline_slow_hashing_1e4_per_second": "less than a second",
    "offline_fast_hashing_1e10_per_second": "less than a second"
  },
  "score": 0,
  "feedback": {
    "warning": "",
    "suggestions": [
    "Add another word or two. Uncommon words are better."
    ]
  }
}

Agora sim! Já conseguimos um bom progresso, mas ainda podemos fazer mais. Considerando uma senha ideal, sabemos que uma ferramenta que pode facilitar muito são os gerenciadores de senha. Que atitudes podemos tomar, como desenvolvedores, para aumentar o suporte a esse instrumento?

Dando suporte a gerenciadores de senha

A princípio, pode ser intuitivo pensar que o usuário deve sempre digitar sua senha, afinal ela tem que ser lembrada.

Entretanto, no post sobre criação de boas senhas, concluímos que, no geral, a melhor senha é justamente aquela que não conseguimos lembrar – uma senha aleatória, em conjunto com um gerenciador de senha seguro.

Apesar disso, vemos muitos sites hoje em dia que chegam até a bloquear a função de colar da área de transferência, mais conhecida como clipboard, dificultando bastante o livre uso desse tipo de senha. E por que isso?

Um argumento que vem à tona, às vezes, é de que o clipboard pode estar comprometido, inseguro.

De fato, pode, mas se assumirmos isso, também precisamos considerar que o teclado pode estar comprometido com um keylogger, e que até o movimento do mouse pode estar sendo capturado por um malware no computador do nosso usuário. E aí?

Fica claro que precisamos de algum limite de confiança com o usuário e seu computador. Bloquear a ação de colar texto, enfim, extrapola esse limite.

Obrigando o usuário a digitar a senha, o estamos forçando ou a lembrá-la (o que exigiria uma senha teoricamente mais fraca que uma aleatória e longa), ou a anotá-la em algum ambiente que é possivelmente inseguro – não vale a pena, nem faz sentido!

No geral, enquanto pudermos dar liberdade aos usuários, daremos!

Não imponha políticas de expiração de senha

Era muito comum, há algum tempo, que as senhas tivessem datas de expiração – o usuário deveria alterar a senha antes dessa data ou a conta seria bloqueada.

Para muita gente isso era uma medida que aumentava a segurança. Cada vez mais, estamos deixando esse hábito para trás, entendendo sua (no geral) falta de sentido.

Aviso de expiração de senha

Rapidamente, conseguimos pensar em uma situação onde obrigar o usuário a alterar a senha de tempos em tempos não é útil – tentativas de invasão por força bruta online.

Se o atacante não tiver a senha do usuário e estiver tentando se autenticar com um amontoado de senhas diferentes, a probabilidade de que a próxima senha a ser tentada seja a correta continua a mesma independentemente de o usuário alterar ou não. Isso é certo, mas e se o atacante conseguir acesso interno ao sistema?

Em uma situação dessas, o atacante poderia entrar no nosso banco de dados e, assim, capturar os hashes das senhas dos usuários.

Sabendo o algoritmo de hash usado, com um ataque de força bruta offline é bem possível que, eventualmente, o hacker conseguisse descobrir as senhas que estavam armazenadas no banco de dados. E aí, o que aconteceria?

Se a senha continuar a mesma, o hacker vai rapidamente conseguir acessar a conta de nossos usuários. Assim, parece óbvio que podemos cortar o acesso (seja precipitado ou prolongado) do hacker se obrigarmos o usuário a mudar a senha periodicamente, certo? Bom… na realidade, não é bem assim.

O ponto principal que quebra a idealização do conceito de senha periódica é, como muitas outras vezes, o fator humano!

Quando forçamos o usuário a mudar sua senha a cada determinado tempo, podemos imaginar que ele irá bolar uma senha forte e completamente diferente da anterior, mas dificilmente isso é o que vai acontecer.

O usuário é esperto e quer conforto no uso da aplicação, e isso é algo que não podemos nos dar ao luxo de ignorar (lembrando da Regra de Usabilidade do AviD!).

Assim, no geral, o que vemos são mudanças irrelevantes e que enfraquecem a senha do usuário – em abril, senha04, em maio, senha05, em junho, senha06. Frequentemente, são mudanças facilmente calculáveis que acabam atrasando muito pouco o atacante.

Além do mais, caso o hacker já tenha conseguido acesso à conta de um usuário, mudar a senha não diminui as chances de alguma backdoor ter sido preparada, de modo que o acesso seja contínuo mesmo com a mudança de senha. Considerando isso tudo, geralmente não há sentido em forçar essa mudança periódica.

De todo o modo, tratar como se um atacante já tivesse acesso completo ao nosso sistema também não é correto. A possibilidade sempre existe e é por isso que devemos combatê-la com segurança, mas assumir que já é certo acaba por desestimular a proteção nessa camada.

Afinal, se um atacante está há quase 90 dias (ou seja qual for o prazo de expiração de senha) com acesso ao nosso sistema, ou à conta de algum usuário, talvez o problema não esteja só na senha escolhida, mas em nossa segurança geral e, principalmente, em como nos comunicamos com nossos usuários.

Seja sincero com o usuário

Apesar de todos os nossos esforços, imprevistos podem acontecer. Contanto que lidemos bem, de acordo com a situação específica, e nos preparemos para evitar futuros imprevistos do tipo, estamos tratando o problema da melhor forma. Agora, além disso, uma coisa é importantíssima – contar a verdade.

Com todo o escândalo recente envolvendo o Facebook, fica claro o quão prejudicial pode ser para uma marca, para uma comunidade e para os próprios usuários, esconder informações do público.

Assim, caso o sistema de nossa aplicação fosse invadido, por exemplo, é essencial que possamos dizer isso para nossos usuários, mesmo (e especialmente) se a questão já foi resolvida.

No geral, é importante manter uma relação de sinceridade com os usuários de nossa aplicação, mesmo não se tratando de ocasiões tão sérias como um tremendo vazamento de dados, ou uma invasão ao sistema.

Um exemplo simples (e, mesmo assim, real) pode ser tirado a partir do ponto Não limite um tamanho máximo de senha. Nele, usamos de exemplo o GitHub, que limita o tamanho da senha para 72 caracteres e deixa essa limitação explícita.

Entretanto, há alguns sites (alguns ainda bem grandes, como o Discord ou o GitLab), que também sofrem dessa limitação, mas escondem do usuário! Como escondem? Eles truncam a senha para que ela tenha no máximo 72 caracteres.

Faça o teste você mesmo – em um desses sites, defina uma senha aleatória maior que 72 caracteres.

A princípio, não haverá problema nenhum. Depois tente logar com apenas os 72 primeiros e voilà, você estará dentro da sua conta sem ter enviado a senha que definiu.

Apesar de prático, é uma atitude bastante negativa que pode afetar a confiança do usuário quanto à segurança da aplicação.

A dica aqui é clara – se possível, conte a verdade. Se não, talvez seja o caso de evitar essa ação.

Entenda os limites de segurança da senha

Em 2004, Bill Gates, o fundador da Microsoft, predisse a extinção das senhas, alegando que elas não conseguiriam vencer o desafio de manter informações críticas em segurança.

Já fazem 14 anos e a senha continua sendo a ferramenta mais utilizada quando se trata de segurança e autenticação. E aí, qual o sentido disso tudo?

Em primeiro lugar, senhas são confortáveis. Para desenvolvedores e usuários, é uma ferramenta simples de usar e de entender, que consegue cumprir, até certo nível, o que promete – segurança.

Apesar disso, e sem precisar entrar em muitos detalhes, a segurança das senhas é limitada – no final, toda senha é quebrável através da força bruta, por menos prático que esse processo seja.

Além disso, muitas vezes nem precisamos de muita tecnologia para descobrir a senha de alguém, como o xkcd tira sarro na tirinha 538 sobre segurança:

Tirinha 538 do xkcd

Entendendo que senhas podem não ser tão seguras, estamos um passo à frente de qualquer ataque à nossa aplicação. Basta, agora, focar em implementar alternativas.

Se livrar completamente das senhas talvez não seja uma boa escolha, considerando que, mesmo muito antiga, ainda está em processo de amadurecimento.

Entretanto, quando possível, é importante avaliar o uso de ferramentas alternativas, como biometria, que também tem seus problemas, ou até algumas mais simples, como autenticação em múltiplos fatores, em que se usa uma senha, mas só ela não basta.

Além disso, existem técnicas que grandes serviços, como Facebook e Google, já usam, que ajudam a garantir a autenticidade de um login, ou seja, que tentam verificar se é você mesmo quem está entrando com sua senha. Para isso, monitora-se algumas informações dos logins usuais, como local, IP e navegador, para que se compare com as do login atual.

Quando entramos em nossa conta do Facebook em um computador que nunca usamos, por exemplo, provavelmente vamos receber um alerta, o que já ajuda bastante a garantir a segurança para o usuário.

Colaborando com o usuário em prol de uma melhor segurança

Acima de tudo, o importante é conquistar o usuário e o ajudar a agir em favor da segurança de sua conta e de nossa aplicação, seja com senhas, ou além, o que pode ser uma tarefa bem difícil.

Invadir demais as decisões e opções do usuário e o forçar a tomar certas atitudes simplesmente não funciona. Dar liberdade total, porém, pode também não ser a melhor escolha. Com essas dicas, espero que obtenha um bom resultado em relação à segurança da aplicação, assim como eu obtive!

O que achou dessas dicas? Deixe aqui seu comentário com a sua opinião e vamos continuar o debate ;)!

The post Algumas dicas importantes sobre políticas de senha appeared first on Blog da Caelum: desenvolvimento, web, mobile, UX e Scrum.

Segurança: detectando bibliotecas vulneráveis com Snyk

$
0
0

Hoje em dia desenvolver uma aplicação do zero é uma tarefa bem complicada. Além de se preocupar com as regras de negócio da aplicação em si, precisamos nos preocupar com coisas mais “técnicas“, tais como: banco de dados, transações, protocolos, servidores, configurações, etc.

É bem provável que a aplicação precise de algumas funcionalidades comuns, como envio de e-mails, geração de relatórios e gráficos e integração com serviços externos.

Já discutimos aqui no blog anteriormente que desenvolvedores de software não são pagos para programar, mas sim para gerar “dinheiro” para a organização, seja por meio da criação de novos produtos que vão gerar novas receitas ou pela automatização de processos que vai simplificar o trabalho e com isso reduzir custos. Ou seja, o software é um meio para se resolver problemas de negócio.

E isso é algo que vemos, em parte, acontecer naturalmente no processo de desenvolvimento de um software. Por exemplo, quando precisamos acessar um banco de dados em uma aplicação, não é comum que tenhamos que implementar manualmente o seu protocolo de comunicação específico. Geralmente utilizamos alguma biblioteca que já faz esse trabalho para nós, abstraindo assim toda a sua complexidade, e nos poupando tempo, dinheiro e algumas dores de cabeça.

O mesmo é válido para as diversas outras necessidades que eu havia citado, como o envio de e-mails e a geração de relatórios. Existem diversas bibliotecas prontas, nas mais variadas linguagens de programação, que já implementam e abstraem tais necessidades.

Sendo assim, é comum utilizarmos dezenas de bibliotecas nas aplicações que desenvolvemos. Algumas delas são pagas, mas a grande maioria é gratuita e muitas delas inclusive são open source, permitindo que qualquer pessoa possa contribuir com ajustes e melhorias.

Mas uma preocupação que às vezes não nos atentamos ao utilizar tais bibliotecas é em relação à segurança. Será que essas bibliotecas são seguras? Será que elas possuem vulnerabilidades conhecidas?

Segurança em aplicações é um assunto bastante discutido hoje em dia. Existem movimentos como o DevSecOps, que visam em construir o mindset de que segurança não deveria ser uma área separada, mas sim estar presente na organização como um todo, onde todos são responsáveis por ela.

Muitos desenvolvedores de software se preocupam bastante com segurança em suas aplicações. Isso é bastante visível quando vemos algumas práticas e técnicas sendo utilizadas em suas aplicações, tais como:

  • Controle de autenticação e autorização
  • Uso de protocolos seguros, como o HTTPS
  • Não utilização de algoritmos de hashing inseguros, como o MD5 e o SHA2
  • Não armazenamento de senhas em plain-text no banco de dados
  • Proteção contra os ataques mais comuns, como SQL Injection, XSS e CSRF

Mas todas essas práticas e técnicas estão focadas em proteger a aplicação em si, ou seja, garantir que o código que escrevemos está seguro.

Mas quando utilizamos bibliotecas de terceiros em nossa aplicação, não há como garantir que quem as desenvolveu também teve essas mesmas preocupações. Se houverem vulnerabilidades graves em tais bibliotecas, todo esse trabalho em proteger nossa aplicação pode ir por água abaixo.

Uma prática importante de segurança a ser seguida é a de sempre manter as bibliotecas que utilizamos em nossas aplicações atualizadas, pois as novas versões costumam corrigir vulnerabilidades de segurança.

Outra prática de segurança muito importante é a de sempre verificar se não existem vulnerabilidades conhecidas nas bibliotecas que estamos utilizando em nossas aplicações. Inclusive, utilizar componentes com vulnerabilidades conhecidas é listada pelo OWASP como uma das dez vulnerabilidades mais comuns em aplicações Web.

Mas aí vem uma questão: Como fazer isso?

Você pode fazer esse processo de verificação manualmente, por exemplo, acessando o site do Common Vulnerabilities and Exposures e pesquisando por vulnerabilidades conhecidas nas bibliotecas que suas aplicações estiverem utilizando.

Uma outra maneira, mais eficiente, é utilizar alguma ferramenta que faz esse processo de maneira automatizada. Uma dessas ferramentas é o Snyk.

 

Snyk é uma ferramenta que automatiza o processo de detecção e correção de vulnerabilidades nas dependências de uma aplicação. Inicialmente a ferramenta tinha como público alvo aplicações Node.js, fazendo verificações nas bibliotecas disponibilizadas via NPM, mas hoje ela já suporta diversas outras linguagens, como Java, PHP, Ruby, Python, dentre outras.

Snyk também pode ser integrado ao build do projeto, podendo ser configurado para interromper o processo caso identifique que alguma biblioteca utilizada pela aplicação possui vulnerabilidades.

Há também a possibilidade de integração com ferramentas de CI/CD, como Jenkins, Team City, Travis, etc., além de também se integrar com serviços de repositórios de projeto, como GitHub, Bitbucket e GitLab.

Para utilizar o Snyk é necessário criar uma conta e isso pode ser feito nessa página. O Snyk possui alguns planos que são pagos, porém existe o plano gratuito no qual está inclusa a integração com o GitHub e também o monitoramento e alertas contínuos em seus projetos.

Monitoramento de projetos

Após criar sua conta e efetuar o login, é possível adicionar seus projetos que estão hospedados no GitHub/GitLab/Bitbucket à sua conta do Snyk, para que ele possa fazer o monitoramento quanto à existência de dependências com vulnerabilidades.

Na tela de Dashboard do Snyk serão listados os projetos que possuem vulnerabilidades e quantas cada um possui.

 

Toda vulnerabilidade encontrada pelo Snyk é classificada em um nível de severidade, que pode ser Low, Medium ou High. Essa classificação leva em consideração o impacto da vulnerabilidade e também sua facilidade de exploração.

Ao detalhar um projeto que contenha vulnerabilidades, o Snyk mostra os detalhes de cada uma delas e também como fazer para corrigi-las.

 

Uma coisa bacana do Snyk é que ele possui uma funcionalidade que pode criar um Pull Request no projeto, para corrigir as vulnerabilidades. Porém, nem todas as vulnerabilidades ele conseguirá criar o Pull Request de correção =/

Integrando o Snyk ao build de uma aplicação Java

Outra funcionalidade bacana do Snyk é sua integração ao processo de build de uma aplicação. No caso de aplicações Java, é comum a utilização de ferramentas de build e gerenciamento de dependências, como o Maven ou o Gradle, e o Snyk possui plugins que se integram com tais ferramentas.

No caso de aplicações que utilizam o Maven, basta adicionar o plugin do Snyk ao arquivo pom.xml do projeto.

<plugin>
  <groupId>io.snyk</groupId>
  <artifactId>snyk-maven-plugin</artifactId>
  <version>1.1.1</version>
  <executions>
    <execution>
      <id>snyk-test</id>
      <phase>test</phase>
      <goals>
        <goal>test</goal>
      </goals>
    </execution>
    <execution>
      <id>snyk-monitor</id>
      <phase>install</phase>
      <goals>
        <goal>monitor</goal>
      </goals>
    </execution>
  </executions>
  <configuration>
    <apiToken>${SNYK_API_TOKEN}</apiToken>
    <failOnSeverity>medium</failOnSeverity>
    <org></org>
  </configuration>
</plugin>

Será necessário informar o seu API TOKEN, que pode ser obtido na tela de configurações da sua conta do Snyk.

 

Agora, ao executar o build do projeto, o Snyk vai verificar suas dependências, buscando por vulnerabilidades conhecidas e poderá interromper o processo de build, dependendo de como você configurou o plugin.

 

Essas são apenas algumas das principais funcionalidades do Snyk. Vale a pena dar uma conferida em todos os recursos que essa ferramenta incrível possui, visando em melhorar a segurança de suas aplicações.

E você, se preocupa com vulnerabilidades nas dependências de seus projetos? Utiliza o Snyk ou outra ferramenta similar? Conte-nos sua experiência! 🙂

The post Segurança: detectando bibliotecas vulneráveis com Snyk appeared first on Blog da Caelum: desenvolvimento, web, mobile, UX e Scrum.

Publicando SpringBoot e Frontend em produção

$
0
0
Ao criarmos uma aplicação nova hoje em dia, temos vários suportes aos devs no ambiente de desenvolvimento. IDEs e gerenciadores de deploy, como maven/gradle no Java ou npm no front, facilitam bastante a carga da aplicação. Usando ainda o SpringBoot, a facilidade de subir um projeto é muito mais simplificada, bastando rodar uma classe principal contendo o método main que faz o restante das coisas para nós (como baixar um tomcat e deixar o WAR no ar). Agora, pensando em ambiente de produção, as coisas são um pouco mais complicadas. Usando frontend Angular, não podemos simplesmente rodar “ng serve –prod”, ou com Java e SpringBoot, rodar “java -jar meuprojeto.jar”. Se entrarmos com ssh e rodarmos esses comandos, travaríamos o console. Existe a opção de deixar esses comandos rodando em background, mas no caso de precisar reiniciar a máquina (remotamente), esses comandos não estaria mais em execução. Em produção, com ambiente Linux, devemos pensar em configurações mais robustas, e por isso, um pouco mais complexas. Vamos mostrar aqui como fazer isso de forma, digamos, manual (sem Docker). No frontend é interessante instalar um servidor de página com Nginx. Já no caso do backend podemos baixar e configurar na mão um servidor com o Tomcat. Como estamos usando SpringBoot, podemos deixar ele fazer esse trabalho para nós, mas ao menos vamos configurar um serviço para rodar nossa aplicação. Para fins deste post, vamos chamar de “harbor-frontend” um projeto feito em Angular6 e NPM, e “harbor-backend” outro projeto feito em Java com SpringBoot e Maven, ambos vão ser configurados em uma máquina remota Linux Ubuntu. Cada linha de configuração deste post deve ser executado individualmente. Ainda, irei usar o editor de texto “vim”, mas use qual achar melhor.

Envio dos Projetos

Sendo primeiro deploy dos seus projetos ou atualização, temos que decidir como enviar os projetos atualizados para o seu servidor remoto Linux. De forma simples, podemos enviar um ZIP do projeto frontend e um WAR/JAR do projeto backend, ou então, clonar os projetos na máquina servidora e atualizá-los com GIT. Por ser mais prático, irei fazer a segunda opção. Logue remotamente no seu servidor Linux. Se a sua máquina também for Linux/Mac, use o SSH no seu terminal. Caso seja Windows, use a interface Putty.

Preparando o Servidor

Após entrar remotamente, atualize seu repositório de apps do Ubuntu:
sudo apt-get update
Vamos então iniciar as instalações. Baixe e instale o GIT:
sudo apt-get install git
git --version (para verificar se está ok)
Baixe e instale o node. No momento da escrita deste post, ao tentar instalar direto pelo apt-get, virá a versão 6 do node. Para instalar a versão 8 em diante, devemos configurar o PPA, adicionar o caminho do node 8 e instalar libs extras.
curl -sL https://deb.nodesource.com/setup_8.x -o nodesource_setup.sh
sudo bash nodesource_setup.sh
sudo apt-get install nodejs
sudo apt-get install build-essential
npm --version (para verificar se está ok)
Vamos instalar o servidor de página web para o projeto frontend (vamos configurá-lo mais adiante):
sudo apt-get install nginx
sudo systemctl status nginx (para verificar se esta ok)
Agora, vamos baixar e instalar o JDK 8
sudo add-apt-repository ppa:webupd8team/java
sudo apt-get update
sudo apt-get install oracle-java8-installer
java -version (para verificar se está ok)
E instalar o Maven.
sudo apt-get install maven
mvn --version (para verificar se está ok)
Por fim, vamos então clonar os dois projetos do repositório git para a máquina remota. Estando na home do seu usuário, vamos baixar os dois projetos individualmente:
git clone <url-harbor-frontend>
git clone <url-harbor-backend>

Mexendo no projeto Frontend

Precisamos deixar o seu projeto pronto para a publicação em produção. Entre na raiz do projeto front e faça:
npm install
ng build --prod
Isso compila, prepara e gera o diretório “dist” com todo o necessário para rodar em produção, já com minificações dos arquivos e outras melhorias. Bom, com tudo pronto podemos publicar o projeto frontend de duas formas. A primeira é copiar todo o conteúdo do diretório “dist” do seu projeto para o diretório executor padrão do nginx, que é o “/var/www/html”. No entanto, para cada atualização, temos que compilar o projeto e copiar denovo o diretório “dist”. Já a segunda forma seria configurar o nginx apontando-o diretamente para o diretório “dist” de dentro do seu projeto, e dessa forma, bastaria baixar a compilar as atualizações que o nginx já está com as páginas atualizadas. Vamos da segunda forma e para fazer isso edite o conteúdo do arquivo de configuração do nginx.
sudo vim /etc/nginx/sites-available/default
Colocando estas configurações:
server {
        listen 80 default_server;
        listen [::]:80 default_server;

        root <path-diretorio>/harbor-frontend/dist/harbor-frontend;

        index index.html

        server_name <ip-ou-dominio-externo>;

        location / {
                try_files $uri $uri/ /index.html;
        }
}
Vamos a algumas explicações:
  • A ideia desse arquivo nginx é configurar portas e caminhos para acesso externo.
  • Primeiro dissemos que nossa porta é 80.
  • Depois indicamos que essa porta 80 configurada será direcionada internamente para o diretório físico indicado no “root”. Veja que esse diretório é aquele “dist” do nosso projeto frontend.
  • Apontamos qual é o arquivo index da nossa aplicação.
  • Indicamos qual é o IP externo ou domínio que nossa aplicação estará funcionado. Isso é importante para o Angular reconhecer certinho nossos arquivos.
  • E indicamos que a localização raiz “/” aponta ao index. Isso também é importante para o Angular funcionar.
Como mudamos um arquivo de configuração, temos que reiniciar o serviço nginx:
sudo systemctl restart nginx
Pronto! Agora acesse o endereço do seu servidor (IP ou o nome) na porta 80 (porta padrão) e veja as páginas aparecendo. 🙂 Para atualizar o projeto frontend, basta mexer dentro do projeto, pois o nginx está apontando diretamente para lá. Assim, vá a raiz do projeto front e faça os comando em sequência:
git pull <url-harbor-frontend> 
npm install
ng build --prod
Não é necessário reiniciar o serviço nginx, basta acessar e tudo estará atualizado.

Mexendo no projeto Backend

No Java, como estamos usando SpringBoot, bastaria criar o JAR do projeto e executá-lo em linha de comando. Mas, vamos fazer isso como um serviço do Linux, podendo iniciar, parar, reiniciar ou ver o status. Primeiro, vamos construí-lo entrando na raiz do projeto back e fazendo:
mvn clean install
Será criado o diretório “target” que contém o JAR compilado e pronto para executar. Mas, para rodá-lo, precisamos dar permissão de execução a este arquivo:
chmod +x harbor-backend.jar
Agora, vamos configurar um serviço Linux apontando para esse JAR utilizando systemd. Para isso, criamos um arquivo dentro de “/etc/systemd” com o nome do nosso projeto e com extensão “.service”:
vim /etc/systemd/system/harbor-backend.service
Com o seguinte conteúdo:
[Unit]
Description=Harbor Backend Spring Boot
After=syslog.target
After=network.target[Service]
User=harbor
Type=simple

[Service]
ExecStart=/usr/bin/java -jar <path-projeto>/target/harbor-backend.jar
Restart=always
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=harbor-backend

[Install]
WantedBy=multi-user.target
Vamos a algumas explicações:
  • A ideia central desse arquivo é apontar ao JAR do seu projeto baixado.
  • Em “ExecStart” você deve apontar o caminho completo do seu JAR.
Depois de criado o arquivo, reinicie os serviços do Linux:
systemctl daemon-reload
E agora, podemos usar este comando para ver o status do nosso serviço java:
sudo systemctl status harbor-backend
Podemos ainda iniciar (start), parar (stop) e reiniciar (restart) caso necessário. Para atualizar o projeto backend, basta mexer dentro do projeto, pois o serviço criado está apontando diretamente para lá. Assim, vá a raiz do projeto backend e faça os comando em sequência:
sudo systemctl stop harbor-backend
git pull <url-harbor-backend>
mvn clean install
sudo systemctl start harbor-backend
Feito isso seu projeto Java está no ar!! \o/ ……. mas, temos um problema 🙁 A porta padrão iniciada no Tomcat pelo SpringBoot é a 8080 (ou a que você configurou no application.properties) e normalmente os servidores não estão com essa porta aberta. Em tese, se tentar acessar seu servidor na porta 8080, retornará um 404, apesar de tudo funcionando corretamente com o Java. Podemos resolver mexendo nas configurações do seu servidor (dentro da Amazon ou Azure por exemplo) e habilitar a porta 8080. Essa é a forma mais fácil, mas teremos uma porta não padrão pública acessível. Seria interessante se somente a porta padrão 80 estivesse disponível ao mundo externo (não só questão de segurança, mas alguns clientes do seu site podem ter bloqueios de porta na rede). No entanto, já estamos ocupando a porta 80 com o nosso servidor frontend nginx e não é possível deixar dois aplicativos usando a mesma porta. Para nossa felicidade, o nginx possui um esquema chamado Proxy Reverso. Com ele, podemos configurar padrões de URL para ser redirecionado internamente para outro endereço (da mesma máquina ou não) ou mesmo um diretório local. Dessa forma, vamos fazer com que tudo que chegue na porta 80 no padrão de URL /backend/ possa ser direcionado localmente para a porta 8080. Editando o arquivo “/etc/nginx/sites-available/default” vamos adicionar as linhas logo abaixo do fechamento do “location /” (ainda dentro das chaves do “server”)
location /backend/ {
    proxy_set_header Accept-Encoding "";
    proxy_buffering off;
    proxy_pass http://127.0.0.1:8080/;
}
Reinicie o serviço nginx e pronto! Ele está redirecionando tudo que chega na porta externa 80 onde na URL contem o padrão “/backend/” para a porta interna 8080. 🙂 Caso você tenha banco de dados, não esqueça de instalá-lo no servidor e deixá-lo disponível para o seu Java.

URL Externas

Com o serviço Java e o nginx no ar, agora teremos dois padrões de URL públicas disponíveis:
  • www.exemplo-harbor.com.br -> projeto frontend
  • www.exemplo-harbor.com.br/backend/ -> redirecionamento interno para tomcat java 8080
Não se esqueça de configurar no seu projeto frontend nas propriedades de produção o endereço backend correto. Outro detalhe é que caso o seu projeto Java possua um contexto configurado (como ao invés de acessar “localhost:8080/” você acessa “localhost:8080/projeto/”), deve ser especificado essa raiz do contexto lá no redirecionamento nginx (na entrada proxy_pass).

Automatização de Ambiente

Podemos automatizar os passos de publicação em produção de forma mais avançada configurando um servidor de integração contínua com o Jenkins, onde ele é acionado após um commit do desenvolvedor. O Jenkins faria os passos descritos nesse post sobre atualizar o projeto (git) e executar o comando de deploy (maven e npm). Podemos ainda melhorar as configurações jogando os projetos front e back em containers Docker, que faria as mesmas coisas mas de forma incremental e padronizada.

Mas diz aí, como você publica seus projetos em produção? Não deixe de conferir nossas turmas presenciais de Java e Web, além das aulas de Java na plafatorma Alura.

The post Publicando SpringBoot e Frontend em produção appeared first on Blog da Caelum: desenvolvimento, web, mobile, UX e Scrum.

Sensitive Data Exposure

$
0
0

Frequentemente vemos notícias de que hackers invadiram algum sistema de alguma empresa, expondo dados sensíveis de seus usuários, tais como documentos pessoais e cartões de crédito.

Um caso recente aconteceu aqui mesmo no Brasil com uma empresa de e-commerce, onde dados como nome completo, CPF e data de nascimento de milhares de seus clientes foram expostos na internet.

Toda aplicação precisa lidar com dados de seus usuários, sendo que alguns deles são considerados como sendo sensíveis e por isso deveriam estar bem protegidos para o caso de um possível acesso indevido.

O grande problema é que desenvolvedores somente consideram dados de cartão de crédito como sendo uma informação sensível, tendo então uma preocupação maior com sua manipulação e armazenamento.

Inclusive existe uma norma chamada PCI-DSS (Payment Card Industry – Data Security Standard), que deve ser seguida por empresas de pagamento online e lojas de e-commerce que lidam com dados de cartão de crédito de seus clientes.

Porém, nem toda aplicação tem funcionalidades relacionadas com pagamentos online e precisará lidar com dados de cartão de crédito. Mas isso não significa que ela não lide com dados sensíveis.

Ninguém gostaria que seus dados pessoais, como documentos, endereços e telefones, fossem expostos na internet para qualquer um acessar. Esse tipo de informação também deveria ser considerado como sendo sensível e deveria ser trafegado e armazenado de maneira segura.

OWASP Top 10

O Open Web Application Security Project, conhecido como OWASP, é uma comunidade aberta iniciada em 2001, com o objetivo de capacitar as organizações sobre como desenvolver, adquirir, operar e manter aplicações confiáveis, em relação à segurança. O projeto oferece gratuitamente documentos, ferramentas, fóruns e estudos sobre segurança em aplicações Web.

Um dos documentos mais populares produzidos pelo OWASP é o Top 10, que é uma lista elaborada baseada em estudos, contendo os 10 principais e mais críticos riscos existentes em aplicações Web. O documento descreve de maneira detalhada os riscos, mostra exemplos de como eles funcionam e também ensina como se prevenir contra eles.

Na última atualização do Top 10, que saiu em 2017, existe um item chamado Sensitive Data Exposure, que justamente fala sobre o risco que existe em aplicações de terem seus dados sensíveis expostos.

Para minimizar esse risco, existem algumas boas práticas que devem ser seguidas pela equipe de desenvolvimento da aplicação.

Boas práticas para lidar com dados sensíveis

Existem algumas boas práticas que devem ser seguidas ao lidar com dados sensíveis em uma aplicação, diminuindo assim o risco deles serem expostos de maneira indevida.

Classifique os dados da aplicação

Classifique todos os dados que a aplicação vai gerenciar. Além de analisar do ponto de vista de negócios da aplicação, pesquise também por normas e leis que classificam dados como sendo sensíveis.

Evite armazenar dados sensíveis

Discuta se realmente é necessário para a aplicação armazenar os dados sensíveis, buscando por outras alternativas quando possível. Um exemplo de alternativa seria o armazenamento temporário.

Utilize criptografia

Sempre utilize criptografia ao armazenar os dados classificados como sensíveis, evitando que eles sejam armazenados em plain text no banco de dados. Utilize também criptografia ao trafegar esses dados pela rede, com o auxilio de protocolos seguros.

Evite algoritmos fracos

Ao utilizar criptografia, certifique-se de utilizar algoritmos atualizados e que são considerados como sendo fortes. O mesmo é válido para os algoritmos de hashing que são utilizados para armazenar as senhas dos usuários.

Evite a utilização de cache

Embora cache seja uma técnica que ajuda bastante com performance, evite o utilizar em requisições que trafegam dados sensíveis.

Quer aprender mais sobre segurança em aplicações Web? Conheça nosso curso focado em como executar pentesting em aplicações Web e também sobre como identificar e corrigir as vulnerabilidades no código fonte, seguindo as boas práticas do desenvolvimento seguro: Curso Segurança em Aplicações Web.

E você, costuma discutir sobre dados sensíveis e criptografia com sua equipe de desenvolvimento? Já utilizou alguma técnica para lidar com esse tipo de informação?

Compartilhe nos comentários sua experiência sobre o assunto 🙂

The post Sensitive Data Exposure appeared first on Blog da Caelum: desenvolvimento, web, mobile, UX e Scrum.

Viewing all 24 articles
Browse latest View live