domingo, 4 de janeiro de 2015

Rompendo o paradigma com Dart, da Google



Nada como começar um ano novo com uma linguagem de programação também nova. O Bom programador traz para você a nova linguagem criada pela Google, para desenvolvimento de aplicações Web: Dart.


Neste artigo, vamos fazer um breve "tour" pela linguagem Dart, desenvolvendo uma aplicação que já fizemos com MEAN Stack. Minha intenção não é ensinar a linguagem Dart, mas mostrar suas principais características, com um belo exemplo de uso.


Mais uma linguagem de programação?


Sim. Mais uma linguagem de programação. E, antes que se revolte, deixe-me contar dois "causos": um sobre o incrédulo e outro sobre o visionário.


O incrédulo
Nos idos dos anos 80, eu tinha um amigo que era bem cético quanto aos micro computadores. Ele acreditava que eram apenas "brinquedos" e que nada iria mudar, pois as empresas precisavam mesmo é de computadores "mainframe".
Mesmo com o surgimento das redes locais, e das BBS, ele continuou mantendo sua postura reacionária. Quanto a Internet surgiu, e todos nós começamos a trabalhar com ela, meu amigo continuava com sua postura.
Resultado: Ficou desempregado! E, com muito custo, teve que correr atrás do prejuízo.


O visionário
Nos idos de 2004, eu estava conversando com um grande amigo meu, que era realmente visionário. Falávamos sobre o crescimento da Google, que havia desbancado grandes players do mercado, como: Altavista e Yahoo. Ele me disse a seguinte frase: "Em 5 anos, você estará programando em Google".
Dito e feito! Em 2009, eu estava programando aplicações para Smartphones Android, da Google.


Moral da história
NUNCA DUVIDE DA GOOGLE!
Quando se trata de tecnologia oriunda da Google, ninguém deve subestimar!


Dart: Uma nova plataforma de desenvolvimento

Segundo a Wikipedia , o objetivo da linguagem Dart (https://www.dartlang.org/) é substituir o Javascript como linguagem de desenvolvimento Web, na plataforma da Web Aberta. Ela foi apresentada na conferência GOTO, em 2011.

A plataforma Dart é composta por: Interpretador (Dart VM), transpiller para Javascript, Editor (Dart Editor) e bibiotecas servidoras, de I/O não bloqueante (dart:io).

É uma nova linguagem de programação, que pode ser traduzida (pelo transpiller) para Javascript, rodando nos navegadores mais modernos. Em breve, os navegadores darão suporte direto à Dart VM.

Quem usa essa isso?
Muita gente! O site da linguagem tem uma página dedicada a mostrar quem está usando Dart. Além da própria Google, o que já é muita coisa, temos: SAP, CodeShip, Netflix e Adobe, que estão usando de alguma forma a linguagem Dart.

Qual é o jeitão dela?
Eu diria que Dart é uma deliciosa mistura de Java com Javascript, que procura manter o que há de melhor em cada uma dessas linguagens. Ela tem a estrutura e a orientação por classes do Java, mas sem as "frescuras", enquanto mantém boa parte da flexibilidade e informalidade do Javascript, mas de maneira organizada.

Eis algumas das características da linguagem Dart, de acordo com a Wikipedia:
  • Baseada em classes;
  • Herança simples;
  • Orientada a objetos;
  • Sintaxe estilo "C";
  • Optional typing;
  • Suporta: interfaces, classes abstratas, generics e anotações;
Na verdade, o Dart apresenta muitas das características que o Javascript 6 (ECMAScript 6) terá, como:
  • Arrow functions;
  • Classes;
  • Escopo de bloco;
  • Promises ("Future", no Dart).
Você pode programar de maneira muito parecida com Javascript, por exemplo:

  db = new Db("mongodb://127.0.0.1/banco");
  db.open()
      .then((ok) {
        var pathToBuild = join("./build/web");
        print(pathToBuild);
        staticFiles = new VirtualDirectory(pathToBuild);
        staticFiles.allowDirectoryListing = true;
        staticFiles.directoryHandler = (dir, request) {
                var indexUri = new Uri.file(dir.path).resolve('index.html');
               staticFiles.serveFile(new File(indexUri.toFilePath()), request);
        };        
        HttpServer
            .bind(InternetAddress.ANY_IP_V4, PORT)
            .then((server) {
              server.listen((HttpRequest request) {
                print(">> " + request.uri.path);
                switch (request.method) {
                  case "GET": 
                    process(request);
                    break;
                  default: methodNotAllowed(request);
                }
              }, 
              onError: msgErro);
            });
  });

 Ou pode criar e utilizar tipos e estruturas parecidas com as do Java (ou C++):

library estilos;

import 'package:angular/angular.dart';
import 'package:angular/application_factory.dart';
import 'package:TesteMongoDB/component/estilocomponent.dart';

class MyAppModule extends Module {
  MyAppModule() {
    bind(Estilocomp);
  }
}

void main() {
  applicationFactory()
      .addModule(new MyAppModule())
      .run();
}

Um arquivo-fonte em Dart
Um programa Dart é armazenado em um arquivo, com extensão ".dart", e pode conter:
  • Comando "import": Permite importar classes e elementos criados em bibliotecas Dart;
  • Função "main": Ponto de entrada de todo programa Dart. Você pode pensar nele como um "constutor" da aplicação;
  • Variáveis globais (top level): Declaradas fora de qualquer função ou classe;
  • Funções globais (top level): idem;
  • Classes;
import
O comando "import" permite importarmos bibliotecas para uso em nosso código. Essas bibliotecas podem ser nativas da linguagem Dart (usam o prefixo: "dart:") ou podem ser pacotes desenvolvidos por nós ou importados pelo gerenciador de pacotes "pub". Eis alguns exemplos:
  • import 'dart:io';
    • Importa a bilioteca "io", que faz parte da linguagem Dart;
  • import 'package:http_server/http_server.dart';
    • Importa o pacote "http_server", indicando qual é o arquivo a ser importado;
  • import 'package:path/path.dart';
    • Importa outro pacote, indicando o arquivo a ser importado;
  • import 'dart:convert' show UTF8, JSON;
    • Importa apenas os objetos "UTF8" e "JSON", do pacote "dart:convert";
  • import 'package:mongo_dart/mongo_dart.dart';
    • Importa um pacote baixado usando o "pub";
Assim como o Node.js usa o NPM, o Dart usa o "pub" para importar pacotes feitos por terceiros.

Função "main"
É o ponto de entrada do código, agindo como um iniciador ou construtor da aplicação. Se for uma aplicação console, por exemplo, é o que será executado pela Dart.VM. Um exemplo de função "main":
class MyAppModule extends Module {
  MyAppModule() {
    bind(Estilocomp);
  }
}

void main() {
  applicationFactory()
      .addModule(new MyAppModule())
      .run();
}

variáveis globais
São definidas fora de qualquer função ou classe:

final PORT = 3000;
Db db;
var staticFiles;

Note que Dart aceita declarações com ou sem tipo, e que possui alguns dos modificadores da linguagem Java, como o "final", que indica que o valor da varíavel não será alterado.

Funções globais
São funções declaradas fora de classes e que podem ser utilizadas por qualquer código dentro do arquivo:

void msgErro(erro) {
    print("ERRO: " + erro);
}

As funções podem retornar um valor ou não.

Classes
Classes são declaradas de forma simples:

class MyAppModule extends Module {
    MyAppModule() {
        bind(Estilocomp);
     }
}

Não existe o modificador "private". Se você quiser declarar um membro privado, então deve prefixar seu nome com "_" (underscore).

Instalando a plataforma Dart

É bem simples e prático. Basta você ir ao site principal: https://www.dartlang.org/, baixar e descompactar.

Se você usa Linux (Ubuntu ou outro), então tem um site especifico ensinando a baixar os pacotes: https://www.dartlang.org/tools/debian.html

Se você usa Mac OSX, pode instalar com o Homebrew.

Dart Editor
O Dart Editor é a IDE da linguagem Dart, criada com base em componentes do Eclipse:

O Dart Editor é o Eclipse sem "frescuras". É muito mais simples e fácil de usar, e, ainda por cima, permite depurar seu código independentemente de onde esteja sendo executado, seja dentro de um Browser ou como servidor independente.

Para iniciar o Dart editor basta rodar o executável, que fica na pasta da instalação do Dart.

Conhecendo a plataforma Dart

Dart permite desenvolver aplicações que rodam:
  • Em um Navegador;
  • Standalone (console);
  • Como um servidor Web;
Com Dart, podemos criar aplicações modernas, codificando tanto a parte cliente Web, como o Servidor REST usando a mesma linguagem. Para aplicações cliente, o pacote: "dart:html" permite criar requests HTTP, e para aplicações Servidoras, o pacote: "dart:io" permite criar servidores assíncronos, de thread único, muito semelhante ao Node.js.

As comparações com o MEAN stack são inevitáveis. Para camada cliente temos:
  • Angular Dart: A implementação do framework MVW Angular, da Google, para a linguagem Dart. Aliás, é mais avançado que a versão Javascript, sendo a base para o Angular 2.0;
  • Scripts Web Dart: Podem ser invocados diretamente pelas páginas HTML. Atualmente, só o navegador "Dartium" (um porte do Chrome que inclui a Dart.VM) podem rodar diretamente;
  • Scritps Servidores Dart: Usando a biblioteca "dart:io", podem criar sockets passivos e tratar requisições usando um único Thread e um "loop" de eventos, como o Node.js.
Supote à programação assíncrona
A maioria das funções de I/O do Dart já operam de forma assíncrona, logo, você enfilera os pedidos de I/O, aguardando a resposta. Para facilitar a vida, a maioria das operações de I/O retorna "Futures".

Future é um ADT (Abstract Data Type) da Dart, semelhante à Promise, usada no Node.js e a ser implementada no ECMAScript 6.

Um exemplo de uso de Future:
HttpServer
.bind(InternetAddress.ANY_IP_V4, PORT)
.then((server) {
  server.listen((HttpRequest request) {
    print(">> " + request.uri.path);
    switch (request.method) {
      case "GET": 
        process(request);
        break;
      default: methodNotAllowed(request);
    }
  }, 
  onError: msgErro);
});

O método "bind()", do objeto "HttpServer", retorna uma Future. Logo, nós usamos o seu método "then()" para colocar o código a ser executado assim que a operação assíncrona "bind" seja executada.

Um exemplo turbo

Para exemplificar bem o uso da linguagem Dart, nada melhor do que compará-la com algo conhecido, como o MEAN Stack.

No meu curso de MEAN Stack, tem uma aplicação feita na sessão 4 (Angular.js), que é bem simples e interessante:

Se você baixou o curso de MEAN Stack, então já tem o banco criado. Se não, então baixe o MongoDB (veja aqui como fazer) e crie um banco chamado "banco", com uma coleção:

{
    "nome" : "< nome do estilo musical >",
    "pais" : "< País do estilo musical >"
}

O projeto Dart está em https://github.com/cleuton/DartDemo e você pode baixá-lo clonando o repositório Git ou baixando um arquivo Zip.

No Dart Editor, selecione o menu "File / Open Existing Folder..." e indique o caminho do diretório baixado.

Temos algumas pastas e arquivos no projeto:
  • TesteMongoDB: Pasta principal;
    • packages: Pasta dos pacotes baixados pelo "pub";
    • pubspec.yaml: Descritor da aplicação, com a lista de bibliotecas das quais ela depende;
    • pubspec.lock: Associa as versões baixadas à sua aplicação;
    • lib: Onde ficam as bibliotecas públicas, criadas na sua aplicação;
    • web: Onde ficam os arquivos que podem ser baixados pelo Cliente Web;
O arquivo "pubspec.yaml" parece muito com o "package.json", das aplicações Node.js. A grande diferença é o "pubspec.lock".

Para declarar dependências, escrevemos no arquivo "pubspec.yaml" e depois rodamos "pub get", ou então o Dart Editor fará isso automaticamente para nós.

Assim que uma dependência é baixada, ela é declarada com sua versão no arquivo "pubspec.lock". Toda vez que atualizarmos o arquivo, o "pub" verificará se ela existe no "pubspec.lock", garantindo que a mesma versão seja sempre baixada. Isso evita incompatibilidades com versões futuras.

Servidor REST
Nossa aplicação usa um servidor REST, criado em Dart. O código dele está no arquivo: "EstiloRestServer.dart". Ele trata as seguintes rotas:
  • Estáticos: Tudo o que vier com um nome de arquivo na URI. Ele vai servir os arquivos estáticos que estiverem dentro da pasta "/web";
  • "/estilos": Ele vai gerar um objeto JSON com a lista de estilos encontrada no MongoDB.

 O código do servidor:

import 'dart:io';
import 'package:http_server/http_server.dart';
import 'package:path/path.dart';
import 'dart:convert' show UTF8, JSON;
import 'package:mongo_dart/mongo_dart.dart';

final PORT = 3000; 

Db db;
var staticFiles;

main() {
  db = new Db("mongodb://127.0.0.1/banco");
  db.open()
      .then((ok) {
        var pathToBuild = join("./build/web");
        print(pathToBuild);
        staticFiles = new VirtualDirectory(pathToBuild);
        staticFiles.allowDirectoryListing = true;
        staticFiles.directoryHandler = (dir, request) {
                var indexUri = new Uri.file(dir.path).resolve('index.html');
               staticFiles.serveFile(new File(indexUri.toFilePath()), request);
        };        
        HttpServer
            .bind(InternetAddress.ANY_IP_V4, PORT)
            .then((server) {
              server.listen((HttpRequest request) {
                print(">> " + request.uri.path);
                switch (request.method) {
                  case "GET": 
                    process(request);
                    break;
                  default: methodNotAllowed(request);
                }
              }, 
              onError: msgErro);
            });
  });
}

void process(request) {
  if(request.uri.path == '/estilos') {
    // Request para a lista de estilos
    DbCollection dbEstilos = new DbCollection(db, 'estilo');
    List estilos = new List();
    dbEstilos.find().forEach((Map mEstilo) {
          estilos.add(mEstilo);
        }).then((v) {
        request.response.headers.set("Access-Control-Allow-Origin",
                             '*');           
        request.response.headers.contentType
               = new ContentType("application", "json", charset: "utf-8");
        request.response.write(JSON.encode(estilos));
        request.response.close();
    });
  }
  else {
    staticFiles.serveRequest(request);      
  }
}

void methodNotAllowed(request) {
  request.response.statusCode = HttpStatus.METHOD_NOT_ALLOWED;
  request.response.write("Unsupported request: ${request.method}.");
  request.response.close();
}

void msgErro(erro) {
  print("ERRO: " + erro);
}

Antes de rodar o projeto, selecione o menu "Tools / Pub Build", para gerar a versão Javascript do site. Isto vai compilar todo o código Dart, gerando uma pasta "build" com o código cliente totalmente em Javascript. Desta forma, o projeto rodará em qualquer navegador. Você pode copiar essa pasta "build" para um Servidor, colocando o arquivo "EstiloRestServer.dart" nela.
Para subir o Servidor é só usar o Dart SDK e executar o programa "EstiloRestServer.dart". Ele se encarregará de servir os arquivos estáticos.

Se estiver usando o Dart Editor, basta selecionar o arquivo "EstiloRestServer.dart" e, no menu de contexto (botão direito), selecionar "run".

Abra um navegador e digite a url: "http://localhost:3000".

Tudo começa na função "main". De início, abrimos uma conexão com o Banco MongoDB, o que retorna uma Future. Então, nós preparamos o nosso diretório virtual, para servir arquivos estáticos:
var pathToBuild = join("./build/web");
  staticFiles = new VirtualDirectory(pathToBuild);
    staticFiles.allowDirectoryListing = true;
    staticFiles.directoryHandler = (dir, request) {
            var indexUri = new Uri.file(dir.path).resolve('index.html');
           staticFiles.serveFile(new File(indexUri.toFilePath()), request);
    };  

Apontamos para a pasta "./build/web", que é onde estarão os arquivos cliente, já convertidos para Javascript. Especificamos que, se o request for para um diretório ("/") o arquivo "index.html" será retornado.

Até agora, não abrimos o socket passivo, coisa que fazemos com:
HttpServer
        .bind(InternetAddress.ANY_IP_V4, PORT)
        .then((server) {
          server.listen((HttpRequest request) {
            print(">> " + request.uri.path);
            switch (request.method) {
              case "GET": 
                process(request);
                break;
              default: methodNotAllowed(request);
            }
          }, 
          onError: msgErro);
        });

Abrimos um socket passivo, associando-o a todos os endereços IP V 4 do servidor, e à porta declarada na variável "PORT" (3000). O método "listen" permite associarmos um "callback", que será invocado sempre que chegar um novo request HTTP.

Então, testamos se o método foi "GET" e processamos o request. O processamento é simples:
void process(request) {
  if(request.uri.path == '/estilos') {
    // Request para a lista de estilos
    DbCollection dbEstilos = new DbCollection(db, 'estilo');
    List estilos = new List();
    dbEstilos.find().forEach((Map mEstilo) {
          estilos.add(mEstilo);
        }).then((v) {
        request.response.headers.set("Access-Control-Allow-Origin",
                             '*');           
        request.response.headers.contentType
               = new ContentType("application", "json", charset: "utf-8");
        request.response.write(JSON.encode(estilos));
        request.response.close();
    });
  }
  else {
    staticFiles.serveRequest(request);      
  }
}

Se for para o caminho "/estilos", então fazemos uma consulta à coleção de estilos no MongoDB, montando uma Lista de estilos. Ao final, fechamos a resposta do tipo "application/json", enviando a lista codificada como objeto JSON.

Caso contrário, mandamos servir como arquivo estático.

Cliente AngularDart
Eu simplesmente adoro o Angular! Na versão Dart, ele é o máximo! Então, eu usei o AngularDart para criar a parte cliente do site. Para começar, temos um arquivo HTML simples ("/web/index.html"):
<!DOCTYPE html>
<html>
<head>
</head>
<body>
  <div>
    <lista-estilos></lista-estilos>
  </div>
  <script type="application/dart" src="main.dart"></script>
  <script data-pub-inline src="packages/browser/dart.js"></script>
</body>
</html>

Se é uma aplicação Angular, cadê o "ng-app"? O gato comeu! Não preciso mais disso! Note que eu criei um tag especial: "<lista-estilos>" e vou associá-lo a um Componente Angular.

No final, eu carrego o programa "main.dart", que é o "code behind" da página, e o arquivo de conversão "dart.js", para compatibilidade com outros navegadores (diferentes do Dartium).

Componente Angular
Um componente Angular é uma mistura de código e HTML, que é injetado dentro de um TAG específico. Quando a página for renderizada, o tag "<lista-estilos>" será substituído pelo HTML gerado pelo meu componente. Esse componente está dentro de "/lib/component", e é composto por um arquivo Dart e um trecho de código HTML (um template). Eis o arquivo Dart:
library estilocomponent;
import 'package:angular/angular.dart';
import 'dart:html';
import 'dart:convert' show UTF8, JSON;

@Component(
    selector: 'lista-estilos',
    templateUrl: 'estilo.html',
    exportExpressions: const["estilos"])
class Estilocomp {
  List<String> estilos = new List<String>();
  
  Estilocomp() {
    _loadData();
  }
  
  void _loadData() {
    var url = "http://127.0.0.1:3000/estilos";
    var request = HttpRequest.getString(url).then((String texto) {
      List data = JSON.decode(texto);
      data.forEach(pegaNome); 
    });
  }
  
  void pegaNome(estilo) {
    estilos.add(estilo['nome']);
  }
}

 A anotação "@Component" transforma a classe "Estilocomp" em um componente AngularDart. Isso faz com que ele seja instanciado e injetado no escopo da aplicação Web. Note que informei na anotação:
  • selector: O Tag que está associado a este componente;
  • templateUrl: O template html para este componente utilizar;
  • exportExpressions: O que este componente exportará para o escopo da página HTML;
O construtor da classe Estilocomp invoca a função "_loadData()", que faz um request HTTP para o caminho "/estilos", do nosso servidor. Como eu coloquei o endereço fixo, isso só rodará se for no Localhost. Você pode passar o endereço IP por parâmetro ou mesmo através de variável de ambiente.

O construtor preenche a Lista "estilos" com o nome de cada estilo encontrado.

O template
O template desse componente é "estilo.html":
<h3>Estilos</h3>
<ul>
  <li ng-repeat="estilo in estilos">
    {{estilo}}
  </li>
</ul>

Se você já conhece Angular.js, está familiarizado com o "ng-repeat". Eu monto uma lista de "<li>" com o nome de cada estilo. Lembre-se que a coleção "estilos" foi exportada dentro do componente, logo, ela contém o nome de cada estilo.

Resultado
O resultado é muito parecido com o da versão original, feita em MEAN Stack:

Análise preliminar da plataforma Dart

Dart é uma promessa interessante. Só o fato de ter sido criada pela Google já merece a minha atenção, embora eu tenha algumas ressalvas, gostei muito da experiência com ela.

Pontos positivos
  1. A linguagem Dart é simples, fácil, versátil e poderosa. Uma mistura deliciosa de Java e Javascript, sem os pontos negativos de cada uma;
  2. A plataforma integrada funciona muito bem, e o Dart Editor é bem agradável de usar;
  3. A biblioteca de I/O não bloqueante e de servidor Web é muito rápida, permitindo a criação de aplicações preparadas para o problema C10k, sem necessidade de "Containers" ou servidores "beberrões";
  4. Dart se integra muito bem com o Angular, dispensando vários outros frameworks, como o próprio jQuery;
  5. Atende ao meu requisito de "uma só linguagem", de ponta a ponta, pois você só precisa programar em Dart;
  6. Tem um ecossistema crescente, com um poderoso gerenciador de bibliotecas (o "pub");
  7. É utilizada pela Google.
Pontos negativos
  1. O suporte à Dart pelos navegadores ainda não é uma realidade. A tradução em Javascript ainda deve continuar por muito tempo;
  2. Toda a cultura Javascript deve ser substituída por Dart, o que exige um alto comprometimento com o futuro dessa linguagem;
  3. O ecossistema Dart ainda é pequeno, se comparado com o ecossistema Javascript;
  4. Dart ainda não é um padrão da Web Aberta.
Eu acho que Dart é um boa alternativa, se comparado com soluções de alta complexidade acidental, como o Java EE. Porém, perde, se comparado com o MEAN Stack.

De qualquer forma, é uma plataforma completa e com suporte da Google, logo, vale a pena conhecer e experimentar, apesar do Technology Radar de Outubro de 2012 tenha classificado como: "Hold" (esperar), ainda vale a pena conhecer a linguagem.

Se você tem medo dos frameworks Open source Javascript, que passam por mudanças "loucas" a cada nova versão, a Dart pode ser uma alternativa mais confiável.