Conteúdo


Desenvolva um serviço de notificação simples com Node.js e MongoDB

Você já tentou orquestrar o comportamento de um conjunto cada vez maior de ferramentas heterogêneas para criar um sistema maior? No meu caso, a equipe de desenvolvimento precisa montar um pipeline de entrega contínua e sequenciar a operação. Uma das opções é usar um serviço de notificação que suporta a criação, sinalização e assinatura de eventos.

Um serviço desse tipo pode ser aplicado a um sistema de desenvolvimento para anunciar a disponibilidade de um novo desenvolvimento. Em seguida, os componentes de recebimento de dados do pipeline podem receber notificações e atuar com base na presença de um novo desenvolvimento. A ação pode incluir o fornecimento proativo de um novo sistema de teste e executar um conjunto de regressão.

Para ser útil, o serviço de notificação não precisa ser interessante.

Eu desenvolvi esse sistema de notificação simples usando o tempo de execução do Node.js por causa do seu suporte para o desenvolvimento rápido de servidores HTTP com uma API semelhante a REST. Eu também optei por usar o MongoDB para o backend. Sua orientação a documentos parecia perfeita para a criação rápida de protótipos e eu não precisava de suporte estrito para propriedades de ACID (atomicidade, consistência, isolamento e durabilidade).

Architecture of the app

Para ver o serviço de notificação em ação, clique em Executar o aplicativo para recuperar um log dos cinco sinais de eventos mais recentes processados pelo serviço de notificação. A maioria dos sinais que você vê foi gerada por nosso sistema de desenvolvimento do produto e está formatada em JSON.

Execute o aplicativo (recupere o log)Obtenha o código

O que é necessário para desenvolver um aplicativo semelhante

Depois de instalar o Node e o MongoDB, você poderá usar o gerenciador de pacotes do Node (npm) para carregar as dependências necessárias. Consulte o arquivo package.json no código que você transferiu por download a partir do DevOps Services para versões específicas dos módulos e das estruturas que ativam esse aplicativo.

Vamos começar!

Etapa 1. Crie as APIs

A API é semelhante ao REST porque os recursos são acessados e modificados usando URLs exclusivas que aplicam verbos HTTP: GET, PUT, POST e DELETE. Qualquer aplicativo com recurso de sistema de mensagens HTTP pode acessar o serviço. Essa abordagem fornece uma interface limpa para ferramentas futuras, baseadas em navegador.

Os recursos primários gerenciados pelo serviço são eventos e assinaturas. Há uma API disponível para as ações de criar, ler, atualizar e excluir.

Baseei essa API na estrutura Expresso , porque ela inclui um conjunto robusto de recursos para o desenvolvimento de aplicativos da web e porque é muito fácil de usar, se você segue as convenções. Veja o código do módulo principal server.js do aplicativo que configura o roteamento para solicitações recebidas direcionadas a eventos:

console.log ('registering event routes with express');
app.get('/events', event.findAll);
app.get('/events/:id', event.findById);
app.post('/events', event.addEvent);
app.put('/events/:id', event.updateEvent);
app.delete('/events/:id', event.deleteEvent);

Esse mesmo padrão básico é repetido para as assinaturas:

console.log ('registering subscription routes with express');
app.get('/subscriptions', sub.findAll);
app.get('/subscriptions/:id', sub.findById);
app.post('/subscriptions', sub.addSubscription);
app.put('/subscriptions/:id', sub.updateSubscription);
app.delete('/subscriptions/:id', sub.deleteSubscription);

Além dessas ações básicas para eventos e assinaturas, há duas APIs adicionais importantes, especificamente:

  • Uma API para sinalizar eventos:
    app.post('/signals', signal.processSignal);
  • Uma API para recuperar um log de sinais recentes:
    app.get('/signallog', signallog.findRecent);

Etapa 2. Trabalhe com o backend

O MongoDB fornece um armazenamento persistente para o serviço. Eu trabalho diretamente com documentos de backend (coleções) em vez de usar um mapeador de objetos. O driver nativo do Node.js fornece toda a capacidade necessária.

Os dois recursos primários — eventos e assinaturas — mapeiam diretamente para as coleções de banco de dados. O acesso a elas é objetivo. Por exemplo, uma solicitação GET para o recurso de eventos tem como resultado uma chamada a event.findAll , conforme o definido na seção de código anterior. A função findAll é definida no módulo events.js:

exports.findAll = function(req, res) {
   mongo.Db.connect(mongoUri, function (err, db) {
      db.collection('events', function(er, collection) {
         collection.find().toArray(function(err, items) {
            res.send(items);
            db.close();
         });
      });
   });
}

A função findAll simplesmente se conecta ao banco de dados, obtém uma manipulação para a coleção de eventos, retorna todos os elementos e, em seguida, fecha a conexão. Todas as solicitações à API são manipuladas de forma semelhante e muito direta. No exemplo a seguir, a solicitação de evento de exclusão é manipulada pela função deleteEvent do módulo events.js:

exports.deleteEvent = function(req, res) {
   var id = req.params.id;
   mongo.Db.connect(mongoUri, function (err, db) {
      db.collection('events', function(err, collection) {
         collection.remove({'_id':new BSON.ObjectID(id)}, {safe:true}, function(err,
result) {
            res.send(req.body);
            db.close();
         });
      });
   });
}

Como antes, é estabelecida uma conexão e uma manipulação de coleção é obtida. Em seguida, o elemento de coleção é removido com base no ID fornecido e a conexão é fechada.

Etapa 3. Envie notificações

Além de realizar as ações de criar, substituir, atualizar e excluir em relação aos serviços gerenciados, esse serviço também envia mensagens para os terminais de assinatura adequados quando os eventos são sinalizados. Essa primeira versão do serviço fornece notificações por email. As próximas seções do código rastreiam todo o processo, de ponta a ponta.

Os sinais são recebidos por meio da rota de sinais. Pode-se ver no módulo server.js que essa rota é manipulada com a função processSignal no módulo de sinais:

app.post('/signals', signal.processSignal);
exports.processSignal = function(req, res) {
   var signal = req.body;
   console.log('Processing Signal: ' + JSON.stringify(signal));

  mongo.Db.connect(mongoUri, function (err, db) {
   db.collection('subscriptions', function(err, collection) {
     collection.find().toArray(function(err, items) {
      matches = _.filter(items, function(sub){return sub.eventTitle == signal.eventTitle});
      _.each(matches, function (sub) {processMatch(sub, signal)});
      res.send(matches);
    });
  });
});

Como antes, é estabelecida uma conexão ao banco de dados e uma manipulação referente à coleção associada é obtida. Em seguida, filtramos para obter todas as assinaturas com um título de Evento correspondente. Em seguida, cada elemento desse subconjunto de e assinaturas correspondentes é processado pela função processMatch .

Você percebeu que eu usei _.filter e_.each? Isso veio de uma biblioteca muito legal chamada Underscore, que fornece funções auxiliares excelentes muito conhecidas pelos usuários de linguagens orientadas a objetos como Ruby.

A função processMatch simplesmente atribui valores adequados para uma mensagem de email e chama a função mailer.sendMail :

function processMatch(subscription, signal) {
   opts = {
      from: 'Simple Notification Service',
      to: subscription.alertEndpoint,
      subject: subscription.eventTitle + ' happened at: ' + new Date(),
      body: signal.instancedata
   }
   // Send alert
   mailer.sendMail(opts);
}

A função sendMail também é objetiva porque eu uso outra biblioteca chamada NodeMailer. É possível ver que a função sendMail apenas utiliza alguns recursos do NodeMailer para enviar a notificação por email. Ela basicamente inicializa um objeto de transporte com valores de autenticação e, em seguida, especifica o conteúdo da mensagem (assunto, corpo, endereço, etc) e inicializa um envio:

exports.sendMail = function (opts) {
   var mailOpts, smtpTransport;

   console.log ('Creating Transport');

   smtpTransport = nodemailer.createTransport('SMTP', {
      service: 'Gmail',
      auth: {
         user: config.email,
         pass: config.password
      }
});

// mailing options
mailOpts = {
   from: opts.from,
   replyTo: opts.from,
   to: opts.to,
   subject: opts.subject,
   html: opts.body
};

console.log('mailOpts: ', mailOpts);

console.log('Sending Mail');
// Send mail
smtpTransport.sendMail(mailOpts, function (error, response) {
   if (error) {
      console.log(error);
   }else {
      console.log('Message sent: ' + response.message);
   }
   console.log('Closing Transport');
   smtpTransport.close();
   });

}

Etapa 4. Teste o aplicativo

Eu abordei a principal funcionalidade do serviço de notificação. Em seguida, irei abordar rapidamente o teste automatizado.

O Mocha para teste é outra estrutura excelente do ecossistema do Node.js. A combinação do Mocha com complementos como o supertest para as coisas de HTTP e oshould para asserções fornece testes funcionais legíveis para a API. Veja um fragmento do móduloeventtests.js que verifica a API de eventos readbyid :

it('should verify revised News flash 6 event', function(done) {
   request(url)
      .get('/events/'+ newsFlash6id)
      .end(function(err, res) {
         if (err) {
            throw err;
         }
         res.should.have.status(200);
         res.body.should.have.property('title');
         res.body.title.should.equal('News flash 6 - revised');
         done();
      });
});

Essa sintaxe e esse formato são muito legíveis e eu espero que a manutenção seja relativamente fácil.

Minha suíte de testes de regressão funcional inclui testes para cada API, e eu estruturei uma pasta de testes que inclui todos os testes. O Mocha reconhece essa convenção. Para executar a suíte de testes, emita o comando mocha a partir da raiz do projeto.

Como mostra a figura a seguir, a saída do conjunto de testes é concisa se não há falhas:

Screen capture of test output
Screen capture of test output

Meu processo atual é executar esse conjunto de regressão localmente (e com frequência) conforme eu faço alterações. Eu sempre o executo antes de enviar as alterações por push para o controle de fonte. Uma instância desse serviço está executando na plataforma emergente IBM Cloud™ e, se você clicou em Executar o aplicativo, você interagiu com essa instância e vê um log de sinais de eventos recentes.

Basicamente, eu tenho dois ambientes hoje: meu sistema de desenvolvimento local, onde eu faço alterações e testes, e o sistema de produção no IBM Cloud. Por enquanto, esse arranjo funciona bem, mas a próxima etapa é criar outro ambiente de preparação hospedado no IBM Cloud. Um novo ambiente de preparação pode permitir a execução do conjunto de regressão em um ambiente mais semelhante à produção e abrir opções como o teste automático e implementação na produção depois de alterações na fonte. Quando eu realizar essa etapa, com certeza escreverei no blog sobre ela.

Considere a possibilidade de desenvolver um serviço de notificações simples semelhante a esse, para gerenciar eventos em uma ampla variedade de ferramentas. Tente e nos conte como foi!


Recursos para download


Temas relacionados

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=80
Zone=Software livre, Cloud computing
ArticleID=980802
ArticleTitle=Desenvolva um serviço de notificação simples com Node.js e MongoDB
publish-date=08152014