Executando e orquestrando múltiplos requests em API's diferentes com Node.JS

Para executar um cenário para orquestrar e executar diversos requests em API’s diferentes (viacep.com.br, ibge.gov.br e webservicex.net/ConvertArea.asmx com SOAP XML) com bluebird.

Para executar online:
http://code.runnable.com/WVmIJXik-5dIakbr/bluebird-for-node-js-for-async-cheerio-soap-xml-parser-terminal-and-promisses

package.json:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{
"name": "lab",
"version": "1.0.0",
"description": "labs",
"main": "index.js",
"author": "André Bassi",
"license": "ISC",
"dependencies": {
"async": "^2.5.0",
"bluebird": "^3.5.0",
"cheerio": "^1.0.0-rc.1",
"cheerio-tableparser": "^1.0.1",
"iconv-lite": "^0.4.18",
"inquirer": "^3.1.1",
"lodash": "^4.17.4",
"request": "^2.81.0",
"soap": "^0.19.2",
"xml-parser": "^1.2.1"
}
}

index.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
'use strict';
const Promise = require('bluebird'),
request = Promise.promisifyAll(require("request")),
soap = require("soap"),
parse = require('xml-parser'),
cheerio = require('cheerio'),
cheerioTableparser = require('cheerio-tableparser'),
iconv = require('iconv-lite'),
inquirer = require('inquirer'),
dateRegex = /^(\d{5})\-?(\d{3})$/;
// Step 1: A partir do CEP de Barueri descobriremos o código do IBGE
// e o nome cidade de forma dinâmica via
// https://viacep.com.br/ws/06440260/querty/
var step1_getIBGEDataFromCEP = function (cep) {
var options = {
headers: { 'user-agent': 'Mozilla/5.0' },
url: 'https://viacep.com.br/ws/' + cep + '/querty/'
};
return new Promise(function (resolve, reject) {
request.get(options, function (error, response, body) {
if (!error && response.statusCode === 200) {
if (body === 'erro=true') {
reject('step1_getIBGEDataFromCEP - ' + error);
} else {
var ibgeCode = body.split('&')[7].replace('ibge=', '');
resolve(ibgeCode);
}
}
else {
reject('step1_getIBGEDataFromCEP - ' + error);
}
});
});
};
// Step 2: Com o código do IBGE retornaremos o número
// de habitantes e outros dados
// via http://www.ibge.gov.br
var step2_getCityDataFromCode = function (code) {
var options = {
headers: { 'user-agent': 'Mozilla/5.0' },
url: 'http://www.ibge.gov.br/home/geociencias/areaterritorial/area.php?nome=&codigo=' + code.replace('ibge=', ''),
encoding: null
};
return new Promise(function (resolve, reject) {
request.get(options, function (error, response, body) {
if (!error && response.statusCode === 200) {
var cheerioOptions = {
normalizeWhitespace: false,
xmlMode: false,
decodeEntities: true
};
body = iconv.decode(body, 'ISO-8859-1');
var $ = cheerio.load(body, cheerioOptions);
cheerioTableparser($);
var data = [];
var arrayTable = $("table").parsetable(false, false, true);
arrayTable[0].forEach(function (d, i) {
var row = {
"Codigo_UF": $("<div>" + arrayTable[0][i] + "</div>").text(),
"UF": $("<div>" + arrayTable[1][i] + "</div>").text(),
"Codigo_Municipio": $("<div>" + arrayTable[2][i] + "</div>").text(),
"Municipio": $("<div>" + arrayTable[3][i] + "</div>").text(),
"Area": $("<div>" + arrayTable[4][i] + "</div>").text()
};
data.push(row);
})
resolve(data.splice(1));
}
else {
reject('step2_getCityDataFromCode - ' + error);
}
});
});
};
// Step 3: Com o nome da cidade realizaremos uma consulta
// da previsão do tempo para o dia atual no webservice
// http://api.openweathermap.org/
var step3_getWeatherForecast = function (address) {
var options = {
headers: { 'user-agent': 'Mozilla/5.0' },
url: 'http://api.openweathermap.org/data/2.5/weather?q=' + address[0].Municipio + '&appid=dade4abc3d546cf5afee6cda56cb3995',
json: true
};
return new Promise(function (resolve, reject) {
request.get(options, function (error, response, json) {
if (!error && response.statusCode === 200) {
resolve(json);
}
else {
reject('step3_getWeatherForecast - ' + error);
}
});
});
};
// Step 4: Ainda com o código do IBGE retornaremos a área da
// unidade territorial http://cidades.ibge.gov.br
var step4_getNumberCityDataFromCode = function (code) {
var options = {
headers: { 'user-agent': 'Mozilla/5.0' },
url: 'http://cidades.ibge.gov.br/xtras/perfil.php?codmun=' + code[0].Codigo_Municipio
};
return new Promise(function (resolve, reject) {
request.get(options, function (error, response, body) {
if (!error && response.statusCode === 200) {
var $ = cheerio.load(body);
cheerioTableparser($);
var data = [];
var arrayTable = $("table").parsetable(false, false, false);
arrayTable[0].forEach(function (d, i) {
var row = {
"Populacao2010": $("<div>" + arrayTable[1][1] + "</div>").text(),
"Populacao2016": $("<div>" + arrayTable[1][0] + "</div>").text(),
"Gentilico": $("<div>" + arrayTable[1][5] + "</div>").text()
};
data.push(row);
})
resolve(data.splice(data.length - 1));
}
else {
reject('step4_getNumberCityDataFromCode - ' + error);
}
});
});
};
// Step 5: Através da informação fornecida da área da unidade territorial
// faremos um cálculo qualquer com de um webservice em asmx com SOAP
// que retorna xml: http://www.webservicex.net/ConvertArea.asmx?WSDL;
var step5_getUnitDataFromSOAP = function (param) {
var options = {
headers: { 'user-agent': 'Mozilla/5.0' },
wsdl: 'http://www.webservicex.net/ConvertArea.asmx?WSDL'
};
return new Promise(function (resolve, reject) {
soap.createClient(options.wsdl, function (err, client) {
client.AreaUnit.AreaUnitSoap.ChangeAreaUnit({ AreaValue: param[0].Area.replace(',', ''), fromAreaUnit: 'squarekilometer', toAreaUnit: 'squarecubit' }, function (error, response) {
if (!error) {
var result = parse(client.lastResponse);
resolve(result.root.children[0].children[0]["children"][0].content);
}
else {
reject('step5_getUnitDataFromSOAP - ' + error);
}
});
});
});
};
// Step 6: Realizamos as requests de todos os passos apartir do
// promisses e vamos montando um retorno em JSON customizado
function execute_steps(param) {
var cep = param; //process.argv.slice(2);
step1_getIBGEDataFromCEP(cep).then(function (result1) {
return Promise.all([result1,
step2_getCityDataFromCode(result1).then(function (result2) {
return Promise.all([result2,
step3_getWeatherForecast(result2).then(function (result3) {
return Promise.all([result3,
step4_getNumberCityDataFromCode(result2).then(function (result4) {
return Promise.all([result4,
step5_getUnitDataFromSOAP(result2).then(function (result5) {
return Promise.all([result5]);
})
]);
})
]);
})
]);
})
]);
}).then(function (results) {
var json = {
"cep": cep,
"municipio": results[1][0][0].Municipio,
"uf": results[1][0][0].UF,
"ibge": {
"codigo": results[1][0][0].Codigo_Municipio,
"censo": results[1][1][1][0][0],
},
"coordenada": {
"longitude": results[1][1][0].coord.lon,
"latitude": results[1][1][0].coord.lat
},
"temperatura": {
"atual": Math.round(results[1][1][0].main.temp - 273.15),
"minima": Math.round(results[1][1][0].main.temp_min - 273.15),
"maxima": Math.round(results[1][1][0].main.temp_max - 273.15),
},
"area_km2": results[1][0][0].Area,
"metro_cubico": results[1][1][1][1][0]
}
console.log(JSON.stringify(json, null, 4));
}).catch((error) => {
console.log(error);
});
}
// ------------------------------------------------------------------------
// Então vamos realizar testes e validação com o CEP de Barueri por padrão
var questions = [{
type: 'input',
name: 'cep',
message: 'Digite o CEP:',
default: '04653-055',
validate: function (value) {
var pass = value.match(dateRegex);
if (pass) {
return true;
} else {
return 'Entre com um CEP válido';
}
}
}];
inquirer.prompt(questions).then(function (answers) {
console.log('Processando...');
execute_steps(answers.cep);
});

Sugestão de Arquitetura Serverless para Microservices na AWS Amazon

Define por “Arquitetura sem servidor” e aplicativos sem servidor estão ganhando força, também apresentado ao mercado como FaaS (Function as a Service) ou Plataforma de Função como Serviço.
A plataforma Serverless permite aos desenvolvedores ficarem livres do trabalho de gerenciar a infraestrutura de servidores enquanto estão realizando suas atividades. É claro que a operação de criação e execução dos aplicativos deles continua seguindo o mesmo fluxo de processamento em máquinas virtuais.

O que é Lambda?
  • O Amazon Web Services (AWS) Lambda permite que você execute JavaScript (Node.js) em ambiente de nuvem Amazon (praticamente) infinitamente escalável sem ter instâncias de VM provisórias ou outra “orquestração”; Tudo é dinamicamente automático, portanto, se você tiver 1 usuário ou 1 bilhão você paga pelo uso.
Plano de negócio da AWS:
  • A AWS está efetivamente interrompendo seus próprio negócio com o Lambda. Em vez de nos forçar a pagar as instâncias EC2 em incrementos fixos e ter monitoramento / dimensionamento complexo, a AWS criou uma maneira muito mais simples de criar e executar micro-serviços.
Serviços AWS para o desenvolvimento:
  • Micro Serviços através do AWS API GATEWAY com AWS Lambda seguindo a arquitetura serverless e auto escalável de acordo com a demanda de requests;
  • Segurança com AWS Cognito Identity compatível com OpenID Connect, onde fornece uma camada de identidade sob o protocolo OAuth 2.0;
  • Amazon SQS para serviço de armazenamento e processamento das filas;
  • Caso o negócio exija persistência, o No-SQL da AWS, o DynamoDB poderá ter essa responsabilidade;
  • Para monitoramento é recomendado o uso do serviço da AWS CloudWatch;
  • Para envio de email através do micro-serviço podemos utilizar o Amazon SES;
  • Para armazenamento de arquivos, redimensionamento de images para otimização do processamento no mobile do lado do servidor, se necessário, recomendamos o uso do serviço Amazon AWS S3

===================================================

Boas práticas para o desenvolvimento:

Caching:

Controle de Acesso:

Versionamento:

Generate SDK for an API:

Deploying:

Monitoramento:

================================================

Recomendações para o Mobile:
Testes em dispositivos Físico:

https://aws.amazon.com/pt/device-farm/?p=tile

Push Notification:

https://aws.amazon.com/pt/pinpoint/?p=tile

Versionamento do código com Git:

https://aws.amazon.com/pt/codecommit/?p=tile

http://docs.aws.amazon.com/pt_br/codecommit/latest/userguide/getting-started.html

Monitoramento de performance:

https://aws.amazon.com/pt/xray/?p=tile

=====================================================

Análise Técnica para o desenvolvimento em NodeJS:
Framework Claudia.js:
  • Atua como um facilitador de implantação de projetos do Node.js para a AWS Lambda e o Gateway da API. Automatiza todas as tarefas de implantação e configuração propensas a erros, isso significa que você pode começar facilmente com os microservices Lambda e se concentrar em resolver problemas importantes de negócios em vez de lidar com fluxos de trabalho de implantação da AWS. Altamente recomendado pelo Martin Fowler em seu site. Se compararmos com seu principal concorrente o serverless, notamos que o mesmo tem 233 issues abertas de 1.886 encerradas no github e contra 0 issues de 79 encerradas do framework Claudiajs, o qual comprova que os desenvolvedores e comunidade estam sempre trabalhando nas correções de erros, ajustes e melhorias onde se torna um detalhe importante na decisão o uso do framework Claudiajs como um facilitador para o AWS Lambda.

  • Maiores detalhes: https://vimeo.com/156232471

Android 7.1 - Build on Docker Container

A vantagem de se usar um container do docker para realizar o build é na minha opinião que temos sempre um ambiente configurado e encapsulado em uma imagem para usarmos a hora que desejarmos, mesmo que outras versões forem lançadas.

Como não fazemos persistência, teremos esse ambiente sempre operacional para realizar as tarefas de build em versões anteriores caso seja necessário em algum momento.

Se caso você é novo em docker, recomendo que acesse o site https://mundodocker.com.br e conheça mais sobre.

Para que você construa sua imagem para o Android 7.1 (Nogaut) na API 25, se certifique que já esteja com o docker-machine instalado e em operação em seu MAC/Linux:

Dockerfile

É onde toda a mágica acontece em uma automação com docker, podemos comparar a todos os comandos que executamos manualmente na instalação e configuração desse ambiente.

Se preferir realizar o download dos arquivos, faça o clone do Gist abaixo:
1
$ git clone https://gist.github.com/andrebassi/8b07b5501347215e755a3bbee6466866 build
Ou então crie um arquivo denominado Dockerfile e coloque as instruções abaixo e siga os passos:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
FROM ubuntu:14.04
MAINTAINER André Bassi "andrebassi@gmail.com"
# Sets language to UTF8 : this works in pretty much all cases
ENV LANG en_US.UTF-8
RUN locale-gen $LANG
ENV DOCKER_ANDROID_LANG en_US
ENV DOCKER_ANDROID_DISPLAY_NAME mobileci-docker
# Never ask for confirmations
ENV DEBIAN_FRONTEND noninteractive
# Updating & Installing packages
RUN apt-get update \
&& apt-get dist-upgrade -y \
&& apt-get install -y \
autoconf \
build-essential \
bzip2 \
curl \
gcc \
git \
groff \
lib32stdc++6 \
lib32z1 \
lib32z1-dev \
lib32ncurses5 \
lib32bz2-1.0 \
libc6-dev \
libgmp-dev \
libmpc-dev \
libmpfr-dev \
libxslt-dev \
libxml2-dev \
m4 \
make \
ncurses-dev \
ocaml \
openssh-client \
pkg-config \
python-software-properties \
rsync \
software-properties-common \
unzip \
wget \
zip \
zlib1g-dev \
--no-install-recommends \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# Install Java
RUN apt-add-repository ppa:openjdk-r/ppa \
&& apt-get update \
&& apt-get -y install openjdk-8-jdk \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# Environment variables
ENV ANDROID_HOME /usr/local/android-sdk
ENV ANDROID_SDK_HOME $ANDROID_HOME
ENV ANDROID_NDK_HOME /usr/local/android-ndk
ENV JENKINS_HOME $HOME
ENV PATH ${INFER_HOME}/bin:${PATH}
ENV PATH $PATH:$ANDROID_SDK_HOME/tools
ENV PATH $PATH:$ANDROID_SDK_HOME/platform-tools
ENV PATH $PATH:$ANDROID_SDK_HOME/build-tools/23.0.2
ENV PATH $PATH:$ANDROID_SDK_HOME/build-tools/24.0.0
ENV PATH $PATH:$ANDROID_NDK_HOME
# Export JAVA_HOME variable
ENV JAVA_HOME /usr/lib/jvm/java-8-openjdk-amd64/
# Add build user account, values are set to default below
ENV RUN_USER mobileci
ENV RUN_UID 5089
RUN id $RUN_USER || adduser --uid "$RUN_UID" \
--gecos 'Build User' \
--shell '/bin/sh' \
--disabled-login \
--disabled-password "$RUN_USER"
# Install Android SDK
RUN wget https://dl.google.com/android/android-sdk_r24.4.1-linux.tgz \
&& tar -xvzf android-sdk_r24.4.1-linux.tgz \
&& mv android-sdk-linux /usr/local/android-sdk \
&& chown -R $RUN_USER:$RUN_USER $ANDROID_HOME \
&& chmod -R a+rx $ANDROID_HOME \
&& rm android-sdk_r24.4.1-linux.tgz
ENV ANDROID_COMPONENTS platform-tools,android-25,build-tools-25.0.3,extra-android-m2repository,extra-google-m2repository
# Install Android tools
RUN echo y | /usr/local/android-sdk/tools/android update sdk --filter "${ANDROID_COMPONENTS}" --no-ui -a \
&& chown -R $RUN_USER:$RUN_USER $ANDROID_HOME \
&& chmod -R a+rx $ANDROID_HOME
# Install Android NDK
RUN wget http://dl.google.com/android/repository/android-ndk-r12-linux-x86_64.zip \
&& unzip android-ndk-r12-linux-x86_64.zip \
&& mv android-ndk-r12 /usr/local/android-ndk \
&& chown -R $RUN_USER:$RUN_USER $ANDROID_NDK_HOME \
&& chmod -R a+rx $ANDROID_NDK_HOME \
&& rm android-ndk-r12-linux-x86_64.zip
# Support Gradle
ENV TERM dumb
ENV JAVA_OPTS "-Xms4096m -Xmx4096m"
ENV GRADLE_OPTS "-XX:+UseG1GC -XX:MaxGCPauseMillis=1000"
# Creating project directories prepared for build when running
# `docker run`
ENV PROJECT /project
RUN mkdir $PROJECT
RUN chown -R $RUN_USER:$RUN_USER $PROJECT
WORKDIR $PROJECT
USER $RUN_USER
RUN echo "sdk.dir=$ANDROID_HOME" > local.properties

Configurando a docker-machine :

1
$ docker-machine create -d virtualbox --virtualbox-disk-size "200000" --virtualbox-memory 4096 --virtualbox-hostonly-cidr "192.168.90.1/24" default

Construindo a imagem:

1
2
3
$ eval "$(docker-machine env default)"
$ docker build -t ab/android-build-environment .

Realizando o Build:

Simplesmente devemos estar no diretório onde se encontra os sources de seu projeto compatível com o Nogoaut - API 25 e executar o build através de um container que será processado em execução interativa.

Faça as seguintes instruções:

1
2
3
4
$ cd /path/do/projeto
$ docker run -i -u 0 -v $PWD:/project -t ab/android-build-environment /bin/bash ./gradlew assembleRelease

Jenkins:

Tanto em um JOB ou um Pipeline, temos a necessidade de injetar as variáveis de ambiente, com isso podemos utilizar um plug-in de sua escolha para a injeção e o bom funcionamento do build.

Variáveis de Ambiente:

1
2
3
4
5
6
# system config
DOCKER_MACHINE_IP=192.168.90.100
DOCKER_TLS_VERIFY=1
DOCKER_HOST=tcp://192.168.90.100:2376
DOCKER_CERT_PATH=/Users/andrebassi/.docker/machine/machines/default
DOCKER_MACHINE_NAME=default

Lembrando que o ip 192.168.90.100 é do meu docker-machine, criada anteriormente.

Para você encontrar essa informação execute:

1
$ docker-machine ip

Shell Script - JOB:

Na preferência na execução de um JOB FreeStyle, você pode configurar as variáveis de ambiente e em seguida executar o seu build conforme descrito:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/bin/bash -x
set -e
# system config
export DOCKER_MACHINE_IP=192.168.90.100
export DOCKER_TLS_VERIFY=1
DOCKER_TLS_VERIFY=1
export DOCKER_HOST=tcp://$DOCKER_MACHINE_IP:2376
DOCKER_HOST=tcp://$DOCKER_MACHINE_IP:2376
export DOCKER_CERT_PATH=/Users/$(whoami)/.docker/machine/machines/default
DOCKER_CERT_PATH=/Users/$(whoami)/.docker/machine/machines/default
export DOCKER_MACHINE_NAME=default
DOCKER_MACHINE_NAME=default
# Execute gradle in docker
/usr/local/bin/docker run -u 0 -v $PWD:/project ab/android-build-environment /bin/bash gradlew assembleRelease

Jenkinsfile - Pipeline:

1
2
3
4
5
6
7
8
9
node('node-mac'){
stage('checkout') {
git url: "git@10.243.126.164:android/NetEmpresa.git", branch: 'develop'
}
stage('build') {
sh "/usr/local/bin/docker run -u 0 -v ${WORKSPACE}:/project ab/android-build-environment /bin/bash gradlew assembleRelease"
}
}

Lembrando que esse post é exclusivamente criado para realizar build para o Android 7.1 na API 25 com sua documentação disponível em https://developer.android.com/about/versions/nougat/android-7.1.html.

Mas você pode customizar e adaptar para outras versões de acordo com sua necessidades.

O dia-a-dia com o GIT

GIT é um sistema que registra as mudanças feitas em um arquivo ou um conjunto de arquivos ao longo do tempo de forma que você possa recuperar versões específicas e trabalhar em equipe de forma distríbuida com qualquer tipo de arquivo.

No ambiente corporativo o ideal é o uso do Git-Flow que se trata de um conjunto de ferramentas para te auxiliar no ciclo do seu trabalho e na entrega, conforme demonstra na imagem abaixo:

Alguns links para Referências: