71
PEDROFRANCESCHI @pedroh96 [email protected] github.com/pedrofranceschi Node.js: serious business

Node.js: serious business

Embed Size (px)

DESCRIPTION

Por que e como montamos um meio de pagamentos inteiramente em Node.js? Nessa palestra, falarei sobre o que aprendemos construindo um projeto relativamente grande em Node. Primeiro, os motivos que nos levaram a usá-lo no Pagar.me e porque Node foi a ferramenta certa para nós. Depois, sobre como montamos uma infraestrutura que une Node.js com tecnologias diferentes (MongoDB, MySQL e ElasticSearch) para usar a melhor parte de cada uma delas e ganhar versatilidade e escalabilidade. Também abordarei os cuidados com código (patterns, code style, modules, etc), testes, integração contínua e segurança na stack que precisamos ter para criar algo grande com Node. Além disso, contarei um pouco sobre ele em produção, incluindo as ferramentas que usamos para rodar e monitorar nossa aplicação e a infraestrutura por trás dela. O objetivo é mostrar quando, como e porque utilizar o Node, e provar até onde ele pode chegar.

Citation preview

Page 1: Node.js: serious business

PEDROFRANCESCHI @pedroh96

[email protected] github.com/pedrofranceschi

Node.js: serious business

Page 2: Node.js: serious business

• O problema

• Por que e quando usar Node.js?

• Problemas de Node.js

• Node.js “the right way”

• Repensando a infraestrutura

• Deployment (Continuous Integration) e monitoramento

• Conclusões…

Assuntos

Page 3: Node.js: serious business

O problema

Page 4: Node.js: serious business

“Montar um gateway de pagamentos amigável para desenvolvedores e

empreendedores”

Page 5: Node.js: serious business

“A forma mais simples de receber pagamentos online”

Page 6: Node.js: serious business

API RESTful

Dashboard Angular.js

RubyNode.js

Python Java

.NET

PHPC#

Page 7: Node.js: serious business

• Simplicidade: RESTful + JSON

• Ambiente de testes isolado e decente

• Segurança sem comprometer simplicidade

• Uptime de 99,9%

• Escalabilidade

Premissas da API

Page 8: Node.js: serious business

Por que Node.js?

Page 9: Node.js: serious business

Antes...

Page 10: Node.js: serious business
Page 11: Node.js: serious business

Por que Node.js?

Request de transação

API RESTful

Sistema antifraude

fraude

legítimaAdquirente (Cielo, RedeCard, etc)

10.000 ms

3.000

ms

sucesso/erro

erro

Page 12: Node.js: serious business

Por que Node.js? (no nosso caso)

• Requests externos demorados (>1.000ms)

• I/O intenso em banco de dados

• Totalmente assíncrono, single thread e event-based

• Pouco processamento (sem blocking de CPU)

Page 13: Node.js: serious business

Por que Node.js? (no nosso caso)

“Mas é só usar threads em qualquer linguagem!..”

Não.

Page 14: Node.js: serious business

Por que Node.js? (no nosso caso)

1.000 requests por segundo com conexão a uma API externa + I/O no DB = 1.000 threads por segundo

… se cada request leva em média 10 segundos …

Em 9 segundos, teremos 9.000 threads

Isso escala? :P :P :P

Page 15: Node.js: serious business

Por que Node.js? (no nosso caso)

Agora, em Node.js…

Page 16: Node.js: serious business

Por que Node.js? (no nosso caso)

1.000 requests por segundo com conexão a uma API externa + I/O no DB = 1 thread

… se cada request leva em média 10 segundos …

Em 9 segundos, teremos 1 thread

Isso escala? Sim.#eventloopFTW

Page 17: Node.js: serious business

I/O síncrona

Aplicação

Sistema operacional

I/Oretorno

I/O bloqueante (aplicação travada)

I/Oretorno

I/O bloqueante (aplicação travada)

I/Oretorno

I/O bloqueante (aplicação travada)

Page 18: Node.js: serious business

I/O “assíncrona” com threads

Aplicação

Sistema operacional

I/O

Thread

callback

I/O bloqueante (thread travada)

I/O

Thread

callback

I/O bloqueante (thread travada)

I/O

Thread

callback

I/O bloqueante (thread travada)

Page 19: Node.js: serious business

O segredo do Node.js

JavaScript (event loop)

V8

libuv

Sistema operacional……………………………………

operações assíncronas em nível de OS

…………………………………………………………………………

I/Ocallback

I/OI/O

callbackcallback

Page 20: Node.js: serious business

Node.js escala em I/O bloqueante, não em utilização de CPU

(threads são boas em processamento paralelo)

Page 21: Node.js: serious business

Se I/O bloqueante não for um problema, Node.js não tem tantas vantagens.

Page 22: Node.js: serious business
Page 23: Node.js: serious business

Problemas de Node.js

Page 24: Node.js: serious business

Código assíncrono (race conditions, callback hell, testes assíncronos, etc)

db.query("SELECT a FROM users WHERE ...;", function (err, result1) { db.query("SELECT b FROM users WHERE ...;", function (err, result2) { db.query("SELECT c FROM users WHERE ...;", function (err, result3) { db.query("SELECT d FROM users WHERE ...;", function (err, result4) { db.query("SELECT e FROM users WHERE ...;", function (err, result5) { console.log("Finished."); }); }); }); }); });

Um ótimo exemplo do que não fazer: callback hell.

Problemas de Node.js

Page 25: Node.js: serious business

Problemas de Javascript: bizarrices e facilidade em não seguir padrões e orientação a objetos.

> 0.1+0.2 0.30000000000000004 !> typeof NaN 'number' !> NaN === NaN false

Cortesia do wtfjs.com

Problemas de Node.js

Page 26: Node.js: serious business

Exceptions não tratadas matam o processo.var name = “Pedro Franceschi"; !console.log("Tamanho do primeiro nome: " + name.split(" ")[0].length); console.log("Tamanho do segundo nome: " + name.split(" ")[1].length);

Problemas de Node.js

$ node test.js Tamanho do primeiro nome: 5 Tamanho do segundo nome: 10

Page 27: Node.js: serious business

Exceptions não tratadas matam o processo.var name = “Pedro"; !console.log("Tamanho do primeiro nome: " + name.split(" ")[0].length); console.log("Tamanho do segundo nome: " + name.split(" ")[1].length);

Problemas de Node.js

$ node test.js Tamanho do primeiro nome: 5 !/private/tmp/test.js:4 console.log("Tamanho do segundo nome: " + name.split(" ")[1].length); ^ TypeError: Cannot read property 'length' of undefined at Object.<anonymous> (/private/tmp/test.js:4:61) at Module._compile (module.js:456:26) at Object.Module._extensions..js (module.js:474:10) at Module.load (module.js:356:32) at Function.Module._load (module.js:312:12) at Function.Module.runMain (module.js:497:10) at startup (node.js:119:16) at node.js:901:3

Page 28: Node.js: serious business

Problemas de Node.js

• Single thread (escalar horizontalmente e verticalmente com múltiplas instâncias)

• Leaks de memória difíceis de detectar devido a código mal feito (variáveis globalizadas, closures, etc)

• Programadores front-end mexendo em back-end (“é tudo Javascript!!”)

• Existe a 4 anos, porém ainda é beta (v0.10.22)

Page 29: Node.js: serious business

Node.js “the right way”

Page 30: Node.js: serious business

Modules

Node.js “the right way”

var PI = Math.PI; !exports.area = function (r) { return PI * r * r; }; !exports.circumference = function (r) { return 2 * PI * r; };

circle.js

var circle = require('./circle.js'); !console.log( 'The area of a circle of radius 4 is ' + circle.area(4));

main.js

Page 31: Node.js: serious business

Evitar callback hells com async (https://github.com/caolan/async)

Node.js “the right way”

Não tente fazer isso em casa

db.query("SELECT a FROM users WHERE ...;", function (err, result1) { db.query("SELECT b FROM users WHERE ...;", function (err, result2) { db.query("SELECT c FROM users WHERE ...;", function (err, result3) { db.query("SELECT d FROM users WHERE ...;", function (err, result4) { db.query("SELECT e FROM users WHERE ...;", function (err, result5) { console.log("Finished."); }); }); }); }); });

Page 32: Node.js: serious business

Node.js “the right way”

async.parallel({ result1: function(callback) { db.query("SELECT a FROM users WHERE ...;", function(err, result1){ callback(err, result1); }); }, result2: function(callback) { db.query("SELECT b FROM users WHERE ...;", function(err, result2){ callback(err, result2); }); }, result3: function(callback) { db.query("SELECT c FROM users WHERE ...;", function(err, result3){ callback(err, result3); }); } }, function(err, results){ console.log("Finished."); })

Assim é bem melhor :)

Evitar callback hells com async (https://github.com/caolan/async)

Page 33: Node.js: serious business

Node.js “the right way”

describe('Array', function(){ describe('#indexOf()', function(){ it('should return -1 when the value is not present', function(){ [1,2,3].indexOf(5).should.equal(-1); }) }) ! describe(‘#indexOf() after one second', function(){ before(function(done){ setTimeout(function(){ done(); }, 1000); }); ! it('should return -1 when the value is not present', function(){ [1,2,3].indexOf(5).should.equal(-1); }) }) });

Testes (JavaScript quebra) (https://github.com/visionmedia/mocha e https://github.com/visionmedia/should.js/)

Page 34: Node.js: serious business

Node.js “the right way”

• Seguir e manter um code style (dica: Google JavaScript Style Guide)

• Tratamento de erros consistente (processos morrem)

Page 35: Node.js: serious business

Node.js “the right way” utils

• Express.js: lightweight HTTP framework

• Sequelize: ORM de MySQL, PostgreSQL, sqlite3, etc.

• Mongoose: ORM de MongoDB

• Commander: wrapper de command line

• Vim: melhor editor de texto :P

Page 36: Node.js: serious business

“Inovação” em padrões de código não é inovação.

(99,9% das vezes)

Page 37: Node.js: serious business

“Follow the patterns”

Page 38: Node.js: serious business

Repensando a infraestrutura

Page 39: Node.js: serious business

Use o melhor de cada banco de dados.

Page 40: Node.js: serious business

Infraestrutura do Pagar.me

Router

api.pagar.me

Servidor da API (Node.js)

ElasticSearchElasticSearch

MySQL (transações e dados relacionais)

MySQL (transações e dados relacionais)

MongoDB (dados de clientes e não relacionais)

Ambiente de testes (sandbox dos clientes)

Ambiente de produção

Servidor da API (Node.js)

Page 41: Node.js: serious business

Por que tantos bancos?

• Separar dados de teste (sandbox dos clientes) dos dados de produção

• MySQL: dados relacionais (transações, assinaturas, planos, cartões, etc.)

• MongoDB: dados não-relacionais (informações do cliente, usuários de uma conta, etc.)

• ElasticSearch: indexação/buscas ultra-rápidas (expondo uma engine de buscas poderosa para os clientes)

• Não há porque se prender a uma tecnologia quando cada uma delas resolve uma parte do seu problema

Page 42: Node.js: serious business

Um parênteses...

Page 43: Node.js: serious business

ElasticSearch

• “Open Source Distributed Real Time Search & Analytics”

• Construído em Java em cima do Apache Lucene (engine robusta de busca e indexação em Java)

• API RESTful (POST para inserir (indexar) dados num “model”, GET para buscá-los)

• Just works and scales (sem instalação, basta iniciar o servidor; suporte a clusters out of the box)

• Nosso uso: indexar/buscar responses (incluindo metadata e nested objects) de models do MySQL

Page 44: Node.js: serious business

ElasticSearch

Transaction Customer Address Phone

amount name customer_id customer_id

payment_method email street ddd

card_last_digits document_number neighborhood number

customer_id document_type id id

address_id id

phone_id

3 joins para montar o objeto de resposta

{ "object": "transaction", "status": "paid", "date_created": "2013-11-21T02:27:10.000Z", "amount": 1000, "installments": 1, "id": 35, "card_holder_name": "PEDRO FRANCESCHI", "card_last_digits": "5592", "card_brand": "visa", "payment_method": "credit_card", "antifraud_score": null, "subscription_id": null, "customer": { "object": "customer", "document_number": "12388451908", "document_type": "cpf", "name": "Pedro Franceschi", "email": null, "born_at": null, "date_created": "2013-11-11T05:24:52.000Z", "id": 1 }, "address": { "object": "address", "street": "Av. Brigadeiro Faria Lima 2941", "street_number": "2941", "neighborhood": “Itaim Bibi", "city": "São Paulo", "state": "SP", "zipcode": "01452000", "country": "Brasil", "id": 1 }, "phone": { "object": "phone", "ddi": "55", "ddd": "21", "number": "88888888", "id": 1 }, "metadata": null }

Page 45: Node.js: serious business

ElasticSearch $ curl -X POST http://0.0.0.0:9200/pagarme/transactions/35 -d ‘ { "object": "transaction", "status": "paid", "date_created": "2013-11-21T02:27:10.000Z", "amount": 1000, "installments": 1, "id": 35, "card_holder_name": "PEDRO FRANCESCHI", "card_last_digits": "5592", "card_brand": "visa", "payment_method": "credit_card", "antifraud_score": null, "subscription_id": null, "customer": { "object": "customer", "document_number": "12388451908", "document_type": "cpf", "name": "Pedro Franceschi", "email": null, "born_at": null, "date_created": "2013-11-11T05:24:52.000Z", "id": 1 }, "address": { "object": "address", "street": "Av. Brigadeiro Faria Lima 2941", "street_number": "2941", "neighborhood": “Itaim Bibi", "city": "São Paulo", "state": "SP", "zipcode": "01452000", "country": "Brasil", "id": 1 }, "phone": { "object": "phone", "ddi": "55", "ddd": "21", "number": "88888888", "id": 1 }, "metadata": null }’

Transaction Customer Address Phone

amount name customer_id customer_id

payment_method email street ddd

card_last_digits document_number neighborhood number

customer_id document_type id id

address_id id

phone_id

ElasticSearch indexa o JSON já montado pelo seu id em um índice (model) específico.

Page 46: Node.js: serious business

ElasticSearch$ curl -X GET http://0.0.0.0:9200/pagarme/transactions/35 { "_index": "pagarme", "_type": "transaction", "_id": "35", "_version": 2, "exists": true, "_source": { "object": "transaction", "status": "paid", "date_created": "2013-11-21T02:27:10.000Z", "amount": 1000, "installments": 1, "id": 35, "card_holder_name": “PEDRO FRANCESCHI", "card_last_digits": "5592", "card_brand": "visa", "payment_method": "credit_card", "antifraud_score": null, "subscription_id": null, "customer": { "object": "customer", "document_number": "12388451908", "document_type": "cpf", "name": "Pedro Franceschi", "email": null, "born_at": null, "date_created": "2013-11-11T05:24:52.000Z", "id": 1 }, "address": { "object": "address", "street": "Av. Brigadeiro Faria Lima 2941", "street_number": "2941", "neighborhood": “Itaim Bibi", "city": "São Paulo", "state": "SP", "zipcode": "01452000", "country": "Brasil", "id": 1 }, "phone": { "object": "phone", "ddi": "55", "ddd": "21", "number": "88888888", "id": 1 }, "metadata": null, } }

Transaction Customer Address Phone

amount name customer_id customer_id

payment_method email street ddd

card_last_digits document_number neighborhood number

customer_id document_type id id

address_id id

phone_id

Para retornar o objeto, basta realizar um GET pelo seu id (o objeto é retornado dentro de “_source”)

Page 47: Node.js: serious business

ElasticSearch (buscando…)

$ curl -X GET ‘http://localhost:9200/pagarme/transaction/_search’ -d ' { "query": { "bool": { "must": [ { "text": { "payment_method": "credit_card" } }, { "text": { "address.zipcode": "01452000" } }, { "range": { "amount": { "gt": 1000 } } } ] } } }'

Page 48: Node.js: serious business

ElasticSearch

• NÃO é um banco de dados. É um indexador.

• Evita I/O no DB (salva JSONs construídos a partir das queries no DB)

• Permite buscas em cima dos próprios objetos que são retornados para os clientes (padroniza o que é exposto nos responses independente da estrutura do DB)

• Dashboard em tempo real a partir das indexações

• Dica: crie um script para indexar seu DB (indexar em tempo real funciona mas pode quebrar)

Page 49: Node.js: serious business

Deployment

Page 50: Node.js: serious business

Nível 1

$ node server.js

n00bz… Processo não roda em background

Page 51: Node.js: serious business

Nível 2

$ git pull && npm install && node server.js &

Opa… Agora tem Git, update das dependências pelo NPM e o processo roda em background

Page 52: Node.js: serious business

Nível 3

$ git pull && npm install && nohup node server.js

Nohup roda o processo mesmo depois do logout do SSH

Page 53: Node.js: serious business

Nível 4

$ git pull && npm install && service node-server restart

Um serviço é responsável por rodar e reiniciar o processo e salvar os logs do processo

Page 54: Node.js: serious business

Nível 5

$ service node-server restart

Agora o servidor de CI lida com o Git e as dependências

Servidor de Continuous Integration (CI)

+

Page 55: Node.js: serious business

Nível 6

$ pm2 reload all

Strider é o servidor de CI e o pm2 reinicia o processo on-the-fly, sem perder nenhum request

Strider (servidor de CI)

+

Page 56: Node.js: serious business

pm2 (https://github.com/Unitech/pm2)

• Roda e gerencia os processos do Node.js (mantém processo rodando para sempre)

• Reload no código on-the-fly (zero downtime)

• Multi-thread e clusterização sem alterar uma linha de código

• Monitoramento e gerenciamento dos logs

• API RESTful + interface web

Page 57: Node.js: serious business

Strider (https://github.com/Strider-CD/strider)

• Servidor de CI 100% em Node.js

• Integração com Github

• Monitoramento dos testes em tempo real pelo browser (com Socket.io)

• Envio de emails com erros nos testes

• Suporte a testes de regressão, custom scripts de deployment, etc.

Page 58: Node.js: serious business

Monitoramento

Page 59: Node.js: serious business

Saber de problemas antes dos clientes e ser transparente quando eles acontecerem.

Page 60: Node.js: serious business

Para saber dos problemas… !

Pingdom + PagerDuty

Page 61: Node.js: serious business

Para ser transparente sobre os problemas… !

Status Page + Twitter de status

Page 62: Node.js: serious business

Para monitorar possíveis problemas… !

Librato

Page 63: Node.js: serious business
Page 64: Node.js: serious business

Conclusões…

Page 65: Node.js: serious business
Page 66: Node.js: serious business

… mas …

Page 67: Node.js: serious business
Page 68: Node.js: serious business

JavaScript

Page 69: Node.js: serious business

Overall…

Page 70: Node.js: serious business

PEDROFRANCESCHI @pedroh96

[email protected] github.com/pedrofranceschi

Obrigado! :)

Page 71: Node.js: serious business

PEDROFRANCESCHI @pedroh96

[email protected] github.com/pedrofranceschi

Node.js: serious business