sábado, 16 de janeiro de 2016

Mostra ae seu mobile backend!


Calma! Apesar, do título sugestivo, eu garanto que este post nada tem a ver com "trazeiros" que se movem. Piadas à parte, Servidores "backend" para aplicações móveis são a maior dor de cabeça para os desenvolvedores e empreendedores.

Neste artigo, vamos ver quais são os requisitos para um "backend" adequado, assim como várias soluções disponíveis no Mercado, como: Parse, Firebase e Google App Engine (GAE).



Aplicações móveis modernas 


Hoje em dia, praticamente não existem mais projetos de novas apps "standalone", ou seja, sem nenhuma integração com um serviço de processamento de retaguarda, ou "Backend" Server. 

Seja para processamento e envio de notificações "push", para "login" do usuário, ou para envio de conteúdo, todas as apps modernas dependem de um "backend". 

Mas o como fazemos um "backend"? Qual a melhor tecnologia? Como hospedá-lo? Para isto, temos que analisar em detalhes a função de um backend em sua aplicação. Geralmente, o backend é o "patinho feio", ou seja, aquele que escondemos do Cliente, para não assustá-lo com seu custo e complexidade. Ele não tem as características agradáveis da Aplicação móvel, mas é imprescindível para o seu sucesso. 

Um bom "backend" é: 

1) Fácil de criar, manter e recriar:

Os requisitos das aplicações móveis são extremamente mutáveis, pois ela depende muito do gosto pessoal dos usuários. Elas são criadas, alteradas, recriadas muitas vezes, logo, devem ser fáceis de criar e manter. Assim como o código "frontend", o código "backend" deve ser igualmente fácil de criar e manter, dependendo de poucos recursos e tecnologias. Quanto mais simples, mais fácil.

2) Barato de usar:

Teremos que usar recursos servidores na Internet, e todos sabemos que isto não é barato. Talvez optemos por utilizar recursos de nuvem, como o Amazon Web Services ou Rackspace, que cobram por utilização de recursos (memória, CPU, disco e tráfego de rede). Neste caso, queremos o menor custo possível, e o nosso "backend" tem que consumir o mínimo de recursos suficientes para servir à nossa aplicação. Não temos muita margem para esbanjar. 

3) Simples de operar:

Startups não têm muitos recursos para lidar com "overhead" de trabalho. Precisamos de soluções de "backend" que requeiram um mínimo de intervenção possível, e que possam funcionar 24 x 7 (24 horas, 7 dias por semana), sem necessidade de uma "babá" tomando conta.

Alocar um servidor físico na Internet, com toda a operação, backup e manutenção, pode ser inviável para a maioria dos projetos de aplicações móveis.

O exemplo


Vamos imaginar que nós tenhamos que criar uma aplicação móvel com as seguintes características:
  • Autenticação de usuários;
  • Listagem de matérias exclusivas;
  • Deve rodar em Android, iOS e Windows Phone.
Pode parecer simples, mas estas duas funções representam 2 / 3 das funcionalidades requeridas pela maioria das aplicações móveis. Em vários, casos, podem ser TODA a funcionalidade requerida.

Quais seriam as alternativas? Bem, precisamos autenticar os usuários, e, neste caso, precisamos de algum tipo de sistema de banco de dados e mecanismo de "Sign In". E vamos listar matérias para estes usuários, o que também requererá  algum tipo de armazenamento remoto dos "feeds".

Para começar, eu resolvi utilizar um framework Javascript, o Ionic. Eu poderia utilizar o PhoneGap, que também é uma boa opção. Ambos (Ionic e PhoneGap) são baseados no framework Apache Cordova. Mas o que realmente me atraiu para o Ionic foram duas coisas: a integração dele com o Angular.js, e o Ionic Creator.

O uso do Angular.JS elimina a necessidade de lidar diretamente com o DOM da sua página, facilitando o desenvolvimento e diminuindo os riscos de problemas. Porém, o Ionic Creator é o grande "filé mignon". Você modela sua app visualmente, gerando um protótipo funcional a partir da modelagem. Esqueça Web Design (HTML 5 e CSS), pois todo o esqueleto (incluindo o Javascript) é gerado para você automaticamente.

Mas, a pergunta de 1 milhão de dólares é: Por que usar Javascript? Eu só posso pensar em uma resposta: porque não sou idiota. Calma! Não estou xingando as pessoas que preferem usar linguagens nativas das plataformas móveis (Java / ART ou Swift / iOS). É que, para mim, você precisa ter um motivo forte para desenvolver aplicações nas linguagens nativas, por vários motivos. Se você criar apps nativas:
  • Terá que manter várias bases de código-fonte separadas, uma para cada plataforma;
  • Terá problemas diferentes para implementar requisitos nas várias plataformas;
  • Alguns serviços e componentes poderão não existir em uma ou outra plataforma;
  • A aplicação poderá ficar diferente nas várias plataformas.
Quai seria um motivo forte para desenvolver em plataforma nativa? Por exemplo, se você quer usar um recurso exclusivo de uma delas, como o iOS Game Center. Ou se você quiser desenvolver algo com requisitos não funcionais muito fortes, como o desempenho.

Então, eu tenho uma app feita em Javascript, usando o Ionic (HTML 5 + CSS + Javascript + Angular + Cordova), e quero criar um "backend" para usar. Neste momento, é bom analisar com calma, pois a escolha do "backend" pode impactar o seu orçamento e até o seu prazo. O mecanismo de "backend" poderá influenciar o desenvolvimento da sua app e poderá adicionar vários itens ao seu "backlog" de projeto.

Alternativas de "backend"

Eu acredito que existam algumas alternativas para "backend" de aplicação móvel. A mais tradicional é criar um Servidor (REST ou SOAP) e hospedá-lo em um computador ligado à Internet. Muitas empresas fazem assim. Seria o caso de "backend" Próprio, ou seja, uma aplicação desenvolvida por você para uso da sua app.

Uma alternativa muito popular atualmente é o uso de soluções de "Backend as a Service" (BaaS), onde existe uma API que permite armazenar dados e até processar scripts para você, sem a necessidade de criar uma aplicação Servidora para isso.

Finalmente, existem soluções "Platform as a Service" (PaaS), como o Google App Engine, que lhe permitem hospedar uma aplicação, desenvolvida com uma API particular, em sua plataforma remota, sem a necessidade de configurar e administrar uma infraestrutura de TI.

CaracterísticaPróprio BaaS PaaS
Facilidade de manter e criar Pouca Muita Média
Custo Alto Baixo Médio
Complexidade de operar Alto Baixo Médio

Se você quer uma app cuja funcionalidade esteja perto dos 2/3 que eu mencionei, sem dúvida alguma, sua escolha deve ser algum serviço de BaaS, como o Parse ou o Firebase. Porém, se você tem alguma funcionalidade que fique dentro dos 1/3 restantes (ou poderá ter), por exemplo: processamento de regras, interface com serviços de pagamento, streaming de mídia, então, você deveria escolher um provedor PaaS, como o Google Cloud Platform (com o Google App Engine), ou pode contratar um provedor de IaaS, como o Amazon Web Services, e hospedar seu código lá.

Neste exemplo, eu resolvi avaliar estas três alternativas (Próprio, BaaS e PaaS), usando o exemplo descrito anteriormente.

Todo o código-fonte deste artigo está no Github e você pode baixar e rodar tudo. Eis o que existe lá:
  • GaeBackEnd: Solução "backend" escrita para o GAE - Google App Engine (projeto Maven);
  • dropbackend: Solução "backend" própria, escrita em Java com Dropwizard (projeto Maven);
  • mobileApp_DropBackEnd: Aplicação móvel Ionic (só a pasta "www"), para uso com o "dropbackend";
  • mobileApp_ParseBackEnd: Aplicação móvel Ionic (só a pasta "www"), para uso com o BaaS "Parse.com" como "backend";
  • mobileApp_FirebaseBackEnd: Aplicação móvel Ionic (só a pasta "www"), para uso com o BaaS "Firebase" como "backend";
  • mobileApp_GaeBackEnd:  Aplicação móvel Ionic (só a pasta "www"), para uso com o PaaS "Google App Engine" como "backend";
 Como preparar e rodar

Para gerar o "dropbackend", basta usar o Maven para compilar e gerar a aplicação. Para testar basta rodar a classe com método "main", sem necessidade de Servidor de Aplicação.

Para gerar o "GaeBackEnd", você precisa instalar o Google App Engine SDK e, opcionalmente, o Eclipse Plugin. Leia este doc: https://cloud.google.com/appengine/docs/java/

Para gerar as apps Ionic, você precisa instalar o Ionic, e depois criar aplicações, substituindo a pasta "www" gerada, pela correspondente do repositório.

A aplicação "mobileApp_GaeBackEnd" foi criada usando o plugin "cordova-plugin-googleplus", com base neste artigo do Ionic, logo, após gerar a app e adicionar a(s) plataforma(s) desejada(s), você deverá seguir as instruções para instalar o plugin em cada plataforma do seu projeto.

Comparação de custo


"backend" Próprio

O custo de desenvolver, manter e hospedar seu "backend" próprio depende de alguns fatores:
  • Mão de obra especializada em serviços (REST ou SOAP);
  • Custo da hospedagem;
Vamos considerar apenas a hospedagem de um "backend", por 30 dias, 24 horas por dia, servindo a cerca de 30 usuários simultâneos. Na Amazon AWS, este custo seria aproximadamente US$ 14,00 (uma instância t2-small, com 5GB de armazenamento) http://calculator.s3.amazonaws.com/index.html.

"backend" Google App Engine

O cálculo do Google App Engine é um pouco mais complexo. Eu selecionei 4 instâncias por hora (a instância padrão é muito pequena), 5GB de Cloud Storage, 1 GB de tráfego e 5 GB de Datastore. Resultou em um total de US$ 7,23 por mês (https://cloud.google.com/products/calculator/#id=30a61f30-727e-49aa-8896-1dda151b2b16).

"backend" BaaS usando Parse.com

O nível gratuito do Parse.com (30 requests por segundo), já atenderia nesse primeiro momento. As características são: 20 GB de armazenamento, 1 Cloud Job. Se o número de usuários fazendo requests simultâneos aumentar, por exemplo, para 40, passaríamos pagar US$ 100,00.

"backend" BaaS usando Firebase

O Firebase tem um nível gratuito bem interessante: 100 conexões simultâneas, 10 GB de transferência. Porém, só permite 1 GB de armazenamento. O próximo nível que aumenta o armazenamento para 10 GB, custa U$ 49,00 por mês.

Comparando tudo...

É difícil comparar os serviços, com cobranças tão diferentes. Porém, eu acredito que os níveis apresentados da AWS e da GAE (Google App Engine), sejam compatíveis com os níveis gratuitos do Parse.com e do Firebase, embora seja mais barato escalar com AWS e GAE do que com os outros.

Comparação de Funcionalidade

Antes de decidir qual tipo de "backend" quer utilizar, é necessário saber quais são as funcionalidades disponíveis, de modo a saber se sua opção atenderá aos seus requisitos.

Armazenamento

No GAE (Google App Engine) e no AWS, há inúmeras possibilidades de armazenamento, desde ORBs (como o Datastore do GAE), até bancos relacionais (como o Amazon Aurora).

O Parse armazena Objetos e possui uma API de query fantástica. Além disso, é capaz de armazenar GeoPoints e fazer consultas de geolocalização. Não há como armazenar bancos relacionais, mas você pode gravar arquivos nele.

O Firebase armazena os dados como objetos JSON, dentro de uma única árvore de objetos. Possui alguns recursos simples de consultas.

Lógica Server-side

O GAE e o AWS são os mais versáteis, afinal de contas, você é quem escreve a aplicação servidora.

O Parse permite armazenar "Cloud Code", utilizando Javascript e uma API especial.

O Firebase não permite armazenar e processar lógica Server-side. Você pode usar o sistema de regras dele (Rules) para automatizar alguma coisa, calcular colunas etc. Mas é só isso. Se quiser código server-side, tem que hospedar seu código em algum provedor.

Autenticação de usuários

O GAE permite autenticar usuários através de suas contas Google, usando OAuth 2.0. Embora você possa criar sua própria base de autenticação, o esquema dele é muito mais seguro e estável. A única restrição é que o usuário deve possuir uma conta Google, o que, com a proliferação dos dispositivos Android, não é exatamente um problema.

O AWS Cognito é um serviço específico para armazenar dados de apps móveis e prover gerenciamento de identidade. Você pode se autenticar usando contas da Google, Facebook, Twitter etc.

O Parse possui um esquema de autenticação baseado em "username" e "senha", validando também o email do usuário. É possível integrar a autenticação do Parse com a de outros serviços, com o Facebook.

O Firebase possui um esquema de autenticação baseado em "email" e "senha", mas possui API para autenticar com Facebook, Twitter, Github e Google.

Sem dúvida, "backends" usando GAE são os que apresentam maior funcionalidade embutida, embora seja possível fazer as mesmas coisas com AWS. O Firebase é o que apresenta menor funcionalidade e o caso médio é o Parse.

Comparação de simplicidade operacional

Sem dúvida alguma, O AWS é o que apresenta menor simplicidade, pois, por ser um IaaS (infrastructure as a service), você tem que administrar o ambiente. O GAE é mais simples, pois é PaaS. Ele isola você de problemas operacionais.

Porém, o Parse e o Firebase são os mais simples, sendo que o Firebase é o campeão de simplicidade.

Comparação de facilidade de criar e manter

Vou apresentar a vocês como foi o desenvolvimento de cada uma das soluções, mostrando onde está o código e como foi a configuração.

"backend" Próprio

Nesta solução, eu resolvi criar um RESTful service auto-servido, com o Dropwizard. O Jetty embutido dispensa o uso de Servidores de aplicação, como o JBoss, por exemplo. Isso me permite economizar muito em recursos computacionais, criando uma solução mais leve e simples de operar e manter.

Eu utilizei MongoDB como banco de dados, por ser rápido, simples de usar e administrar.

Eu tive que criar um código Servidor, que é o RESTful service, além da app cliente, que é a aplicação móvel Ionic.

Sem querer entrar em detalhes do Servidor, existem duas classes de Resources, cada uma administrando um tipo de entidade. Tenho um "LoginResource", que permite criar novos usuários:


@Path("/create")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public class LoginResource {
 private String dbUrl;
 private DrbDao dao;
 public LoginResource(String dbUrl) {
  this.dbUrl = dbUrl;
  this.dao = new DrbDao(dbUrl);
 }
 @POST
 @Timed
 public Response insereLogin(LoginCredential login) {
  int httpStatus = 200;
  String saida = "";
  if(dao.insertLogin(login)) {
   saida = "{\"status\" : \"success\", \"message\" : \"new user added\"}";
  }
  else {
   httpStatus = 400;
   saida = "{\"status\" : \"fail\", \"message\" : \"user already exists\"}";
  }
  return Response.status(httpStatus).entity(saida).build();
 }
}

E tenho um "RestResource", que efetua o Login e retorna as notícias:


 @POST
 @Timed
 public Response processaLogin(LoginCredential login) {
  int httpStatus = 200;
  NewsFeed nf = null;
  if (dao.getLogin(login)) {
   nf = getNewsFeed();
  }
  else {
   nf = new NewsFeed();
   nf.setStatus(NewsFeed.FAIL);
   nf.setMessage("User not authenticated");
   nf.setData(new ArrayList<NewsLine>());
   httpStatus = 403;
  }
  return Response.status(httpStatus).entity(nf.toString()).build();
 }
 private NewsFeed getNewsFeed() {
  NewsFeed nf = new NewsFeed();
  nf.setStatus(NewsFeed.SUCCESS);
  nf.setMessage("OK");
  nf.setData(dao.getNewsFeed());
  return nf;
 }

E minha aplicação Cliente (Ionic) efetua requests ao Servidor para criar usuários, logar e buscar notícias:


.controller('signInCtrl', [
        '$state', '$scope', '$rootScope', '$http', 'GetBackEnd', // <-- controller dependencies
        function ($state, $scope, $rootScope, $http, GetBackEnd) {
          //*** begin controller code
          $scope.userData = {};
          $scope.loginUser = function() {
              var req = {
                method: 'POST',
                url: GetBackEnd.url + '/api/login',
                headers: {
                 'Content-Type': 'application/json'
                },
                data: $scope.userData
              }
              $http(req).then(
                function(response) { // Success
                  alert("Login Ok!");
                  //response.data is the entity. "data" is my array
                  $rootScope.news = response.data.data;
                  $state.go('tabsController.news', {});
                },
                function(response){ // Error
                  alert("Login error: " + response.status);
                });
              };
          //*** end controller code
}])
.controller('registerCtrl', [
          '$state', '$scope','$http', 'GetBackEnd',
          function($state, $scope, $http, GetBackEnd) {
          $scope.userData = {};
          $scope.register = function() {
              var req = {
                method: 'POST',
                url: GetBackEnd.url + '/api/create',
                headers: {
                 'Content-Type': 'application/json'
                },
                data: $scope.userData
              }
              $http(req).then(
                function(response) { // Success
                  alert("New user Ok!");
                  $state.go('signIn', {});
                },
                function(response){ // Error
                  if(response.status == 400) {
                    alert("User exists with this email");
                  }
                  else {
                    alert("Login error: " + response.status);
                  }
                });
          };
}])

 Código cliente / Código servidor.

O Desenvolvimento desta solução envolveu dois itens de trabalho (Servidor e Cliente), além de exigir especialização em Servidores REST, além do Ionic. Eu classificaria essa opção como dificuldade média.


app rodando 



Falta proteger o recurso!

Mas não é só isso! Deixei alguma coisa faltando de propósito. Sabe o que é? Não há controle de autenticação! Após fazer logon, seria necessário devolver um "token", para ser acrescentado nos requests subsequentes (Header "Authorization"). No Dropwizard, fazemos isso desta forma: https://dropwizard.github.io/dropwizard/0.6.2/manual/auth.html

Eu deixei isso de fora para mostrar como é complicado desenvolver esquemas próprios de autenticação de usuários.

"backend" Google App Engine (GAE)

Usar o GAE não é uma tarefa para os fracos! É bastante complexo e a documentação, apesar de prolixa, é pouco objetiva, carecendo de exemplos simples e práticos. É quase tão difícil de entender quanto a documentação do AWS. Eu acho que eles pensam que "quantidade" é "qualidade", e criam toneladas de documentações, sem analisar se elas cumprem seu objetivo, que é ensinar a utilizar o produto.

Eles têm um "hello world" de 5 minutos (que leva isso mesmo), porém, cria apenas um Servlet, e não é o que necessitamos. Queremos criar um "backend" mobile. Existe outro tutorial, mais demorado, que faz isso.

Para começar, a Google força muito a barra para usarmos o Android Studio, e parece que tudo funciona com ele. De minha parte, eu queria usar o Eclipse, assim como 90% dos desenvolvedores Terrestres. Sofri muito para instalar o plugin para Eclipse, junto com a IDE, e fazer tudo funcionar.

Enfim, depois de alguma dificuldade, consegui seguir o tutorial de criação de "backend" com o GAE, usando o Eclipse e o "Development Server", que permite testar sem criar instâncias reais (que são pagas).

O GAE tem uma arquitetura bem interessante, criando classes que expõem métodos, chamadas de "Cloud Endpoints", de maneira semelhante ao JAX-RS.

O código servidor é bem simples, e está no diretório "GaeBackEnd".


Autenticação de usuários

Um dos fatores que simplificou muito o código servidor foi o uso da autenticação "OAuth 2.0", com a conta Google do usuário. Isso me exime de criar e administrar bases de contas e senhas. Simplesmente, o usuário se autentica usando sua conta Google, e isso é administrado pela Google. O OAuth apenas me retorna um "token" de acesso, que eu deverei incluir nos meus requests subsequentes.

Código servidor simples

Eis um trecho do código servidor que processa os requests (classe "NewsFeed")

@Api(name = "gaebackend",
 backendRoot = "http://localhost:8888/_ah/spi", // I am using http, but it should be HTTPS
    version = "v1",
    scopes = {Constants.EMAIL_SCOPE}
 ,
    clientIds = {Constants.WEB_CLIENT_ID, Constants.ANDROID_CLIENT_ID, Constants.IOS_CLIENT_ID}
)
public class NewsFeed {
  private static DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
  static {
  Query q = new Query(Entities.KIND_METADATA_KIND);
  Filter nameFilter =
    new FilterPredicate(Entity.KEY_RESERVED_PROPERTY, 
           Query.FilterOperator.EQUAL,
          Entities.createKindKey("newsLine"));
  q.setFilter(nameFilter);
  int contagem = datastore.prepare(q).countEntities(FetchOptions.Builder.withLimit(1));
  if (contagem == 0) {
   // Lets add some news:
   NewsArticle na = new NewsArticle("02/01/2016 7:21", "Breaking news #1");
   Entity entity = new Entity("newsLine");
   entity.setProperty("newsDate", na.getNewsDate());
   entity.setProperty("newsHeadLine", na.getNewsHeadLine());
   datastore.put(entity);
  }
  }
  // Parameter "User" requires that the client is authenticated
  public List<NewsArticle> listArticles(User user) {
   Query qNews = new Query("newsLine");
   PreparedQuery pq = datastore.prepare(qNews);
   List<NewsArticle> articles = new ArrayList<NewsArticle>();
   for (Entity result : pq.asIterable()) {
    NewsArticle na = new NewsArticle((String) result.getProperty("newsDate"), 
                               (String) result.getProperty("newsHeadLine"));
    articles.add(na);
  }
   return articles;
  }
Com a anotação "@Api", eu defino a minha API exposta, indicando seu nome, versão e quais tipos de clientes pode acessá-las. Você precisa criar tipos de cliente OAuth (Client IDs) na Google Developer Console e anotá-los na classe "Constants", antes de usar o GAE.

Código cliente (app Ionic)

Bem, aqui o sofrimento foi maior! Ao analisar a documentação, você pode notar que é possível usar GAE Endpoints em várias plataformas: Javascript, Android e iOS. No código-fonte, você pode ver que eu criei uma app Web para testar o Endpoint. Então, fui todo feliz tentar usar a mesma API em minha app Ionic, que, afinal de contas, é uma app Javascript. Deu tudo errado! Perdi muito tempo até perceber que a biblioteca recomendada (https://developers.google.com/api-client-library/javascript/reference/referencedocs) só funcionaria com Websites, hospedados em Navegadores comuns, e não dentro de uma "WebView", como eu queria.

Eu fiz tudo funcionar, mas sem autenticação (tive que comentar o parâmetro "user" dentro do método "listArticles". Pois a autenticação Google não funcionava. 

A solução foi usar um "plugin" Cordova: cordova-plugin-googleplus, criado por Eddy Verbruggen, que me permitiu autenticar com a conta Google, dentro de uma app Ionic, sem problema algum. Para isto, você precisa fazer separadamente para cada plataforma (Android e iOS), e tem que instalar o plugin diferentemente, cadastrando também os IDs necessários na Google Developers Console.

Isso resolveu a questão da autenticação Google, só que eu não poderia usar a biblioteca Javascript do GAE, logo, eu teria que forjar meus próprios requests, usando o módulo $http, do Angular.js.

O código cliente (mobileApp_GaeBackEnd) tem um trecho de código no "Controller", que faz a autenticação, armazena o Token OAuth, e depois forja um request para a lista de artigos, enviando o token no header HTTP "Authorization":

// This is using GAE Local Development Server.
var bk = {
      'url': 'http://localhost:8888/_ah/api/gaebackend/v1/newsarticle',
    };
function getArticles($scope, $http, accToken) {
      var req = {
        method: 'GET',
        url: bk.url,
        headers: {
         'Content-Type': 'application/json',
         'Authorization': 'Bearer ' + accToken
        }
      }
    $http(req).then(
      function(response) {
          if(response.status == 200) {
            $scope.news = response.data.items;
            $scope.$apply();
          }
          else {
            alert("Error listing news: " + response.status);
          }
      });
}
angular.module('app.controllers', [])
.controller('oBPMobileCtrl', ['$state', '$scope', function($state, $scope) {
    $scope.lista = function() {
      $state.go('tabsController.news', {});
    }
}])
/*
{"items":[{"newsDate":"02/01/2016 7:21","newsHeadLine":"Breaking news #1"}],"result":{"items":[{"newsDate":"02/01/2016 7:21","newsHeadLine":"Breaking news #1"}]}}
*/
.controller('newsCtrl', [
          '$state', '$scope','$http', 'UserService', '$ionicLoading',
          function($state, $scope, $http, UserService, $ionicLoading) {
              $ionicLoading.show({
                template: 'Logging in...'
              });
              window.plugins.googleplus.login(
                {},
                function (user_data) {
                  UserService.setUser({
                    userID: user_data.userId,
                    name: user_data.displayName,
                    email: user_data.email,
                    picture: user_data.imageUrl,
                    accessToken: user_data.oauthToken,
                    idToken: user_data.idToken
                  });
                  $ionicLoading.hide();
                  getArticles($scope, $http, UserService.accessToken);
                },
                function (msg) {
                  $ionicLoading.hide();
                }
              );
   $scope.logout =  function(){
    $ionicLoading.show({
     template: 'Logging out...'
    });
    //google logout
    window.plugins.googleplus.logout(
     function (msg) {
            alert(msg);
      $ionicLoading.hide();
      $state.go('oBPMobile');
     },
     function(fail){
      console.log(fail);
     }
    );
   }
}])

Tudo funcionou muito bem! O cliente é desviado para a autenticação e autorização na Google, e depois volta para a listagem de artigos:

Autenticação google

Listagem dos artigos

Conclusão

Você tem que desenvolver 2 itens de backlog: a app Ionic e a aplicação GAE, assim como na solução de "backend" próprio. Porém, a API GAE tem um "setup" mais demorado, e exige mais conhecimento específico, além disso, o código da app Ionic fica um pouco mais complicado. Eu a classificaria como mais difícil que a alternativa de criar um "backend" prório, porém, existem outras vantagens em usar o GAE como solução, como: robustez, quantidade de serviços, segurança e baixo preço.

"backend" usando BaaS Parse

Realmente, é muito fácil utilizar um "backend" BaaS. O Parse tem uma API muito boa e requer quase nenhum "setup".

Crie uma conta ou faça login com sua conta do Facebook, Github ou Google. Crie uma app, crie suas classes de dados e, se quiser, entre com alguns dados nelas. Pronto! Seu "backend" está configurado. Ele tem um tutorial muito bom, o qual não demora mais de 10 minutos.

Código-fonte cliente

O diretório "mobileApp_ParseBackEnd" tem a app Ionic que usa o Parse. Eu carrego o script do Parse na página "index.html":

<script src="js/parse-1.6.14.min.js"></script>

Ou posso acessar a partir do CDN deles.  Depois, é só colocar o código necessário no Controller:


.controller('signInCtrl', [
        '$state', '$scope', '$rootScope', '$http', 'GetBackEnd', // <-- controller dependencies
        function ($state, $scope, $rootScope, $http, GetBackEnd) {
          //*** begin controller code
          $scope.userData = {};
          $scope.loginUser = function() {
                GetBackEnd.initialize();
                Parse.User.logIn($scope.userData.username, $scope.userData.password, {
                  success: function(user) {
                    $state.go('tabsController.news', {});
                  },
                  error: function(user, error) {
                    alert("User login error: " + error.code);
                  }
                });
              };
          //*** end controller code
}])
.controller('registerCtrl', [
          '$state', '$scope','$http', 'GetBackEnd',
          function($state, $scope, $http, GetBackEnd) {
            $scope.userData = {};
            $scope.register = function() {
              GetBackEnd.initialize();
              var user = new Parse.User();
              user.set("username", $scope.userData.username);
              user.set("password", $scope.userData.password);
              user.set("email",    $scope.userData.email);
              user.signUp(null, {
                success: function(user) {
                          $scope.user = user;
                          alert("Success Creating User Account ");
                          $state.go('signIn', {});
                },
                error: function(user, error) {
                  alert("Error signing up: " + error.code + " " + error.message);
                }
            });
          }
}])
.controller('newsCtrl', [
          '$state', '$scope','$http', 'GetBackEnd',
          function($state, $scope, $http, GetBackEnd) {
            GetBackEnd.initialize();
            var query = new Parse.Query("news");
            $scope.news = [];
            query.find({
              success: function(news) {
                for (var i=0; i<news.length; i++) {
                  var newsLine = {};
                  newsLine.newsDate = news[i].get("newsDate");
                  newsLine.newsHeadLine = news[i].get("newsHeadLine");
                  $scope.news.push(newsLine);
                }
                // This is asynchronous code, we need to refresh the view:
                $scope.$apply();
              },
              error: function(news, error) {
                alert("Error getting news: " + error.message);
              }
            });
}])

Lembrando que o Parse autentica os usuários pelo "username" e "password", porém, verifica se o email já existe.

Usar o Parse é extremamente simples, e, se for necessário, você pode hospedar Server-side code também.

Você deve configurar quais tipos de classes os usuários podem acessar. Este é o controle de autorização, e você faz isso tudo via Web.

"backend" usando BaaS Firebase

O Firebase é tão simples de utilizar quanto o Parse. Você pode logar com sua conta Google, criar uma aplicação e começar a criar seus dados. Depois, pode criar regras de acesso a eles. Eu recomendo seguir o tutorial deles, que é muito fácil.

Código-fonte cliente

O código-fonte da app Ionic está na pasta "mobileApp_FirebaseBackEnd". De maneira semelhante ao Parse, tive que referenciar a biblioteca cliente deles:

<script src="https://cdn.firebase.com/js/client/2.3.2/firebase.js"></script>

O acesso aos dados armazenados no Firebase, incluindo o Login, é feito no Controller




.controller('signInCtrl', [
        '$state', '$scope', '$rootScope', '$http', 'GetBackEnd', // <-- controller dependencies
        function ($state, $scope, $rootScope, $http, GetBackEnd) {
          //*** begin controller code
          $scope.userData = {};
          /*
            As Firebase does not store username, we decided to remove the
            "register.html" template, and join both controllers here.
          */
          $scope.loginUser = function() {
            var ref = new Firebase(GetBackEnd.url);
            ref.authWithPassword({
              email    : $scope.userData.email,
              password : $scope.userData.password
            }, function(error, authData) {
              if (error) {
                alert("Login Failed!", error);
              } else {
                alert("Authenticated successfully with payload:", authData);
                $state.go('tabsController.news', {});
              }
            });
          };
          $scope.register = function() {
            var ref = new Firebase(GetBackEnd.url);
            ref.createUser({
              email    : $scope.userData.email,
              password : $scope.userData.password
            }, function(error, user) {
              if (error) {
                alert("Error creating user:", error);
              } else {
                alert("Successfully created user account with uid:", user.uid);
                $scope.loginUser();
              }
            });
          };
          //*** end controller code
}])
.controller('newsCtrl', [
          '$state', '$scope','$http', 'GetBackEnd',
          function($state, $scope, $http, GetBackEnd) {
            var ref = new Firebase(GetBackEnd.url);
            $scope.news = [];
            ref.on("value", function(snapshot) {
              var newsArray = snapshot.val().news;
              for (var i=0; i<newsArray.length; i++) {
                  var newsLine = {};
                  newsLine.newsDate = newsArray[i].newsDate;
                  newsLine.newsHeadLine = newsArray[i].newsHeadLine;
                  $scope.news.push(newsLine);
                }
                $scope.$apply();
            }, function (errorObject) {
              console.log("The read failed: " + errorObject.code);
            });
}])

No Firebase, autenticamos o usuário pelo "email" e "senha". E podemos configurar regras para determinar quais dados os usuários podem acessar. O Firebase não hospeda Server-side code.

Então, qual "backend" devo usar?

Acredito que essa decisão dependa de algumas perguntas básicas:
  1. Minha funcionalidade está dentro dos 2 / 3? Se sim, considere usar um BaaS;
  2. Eu tenho um orçamento limitado? Se sim, certamente use um BaaS, pois eles possuem níveis gratuitos que lhe permitirão iniciar sem despesas;
  3. Eu tenho necessidade de mais funcionalidade! Considere usar o Google App Engine. Fazer o seu próprio "backend" e hospedá-lo em algum provedor, é arriscado e mais caro. 
Mas eu tenho necessidade de enviar notificações "push"!

O Parse tem um serviço de "push", para várias plataformas, e custa bem barato. Para 1.000.000 de mensagens enviadas a aparelhos diferentes por mês, o Parse não cobra nada.

O Firebase não tem suporte a notificações. No GAE, você pode embutir isso no seu código hospedado.

Mas eu tenho necessidade de consultas por localização geográfica!

O Parse permite armazenar GeoPoints, (latitude e longitude), atribuindo-os às instâncias de objetos. Depois, você pode fazer consultas para saber quais locais estão mais próximos da localização do usuário.

O Firebase pode ser usado com o GeoFire, que é um conjunto de bibliotecas que permitem armazenar a localização do usuário e fazer consultas geográficas.

Já a Google nem se fala... O GAE tem como armazenar localização geográfica e fazer consulas geoespaciais.


Comece simples

Startups não devem começar esbanjando dinheiro. Se você está criando uma app e ainda não tem o financiamento garantido, é melhor adequar seus requisitos a uma solução BaaS, do que investir muito tempo e dinheiro codificando "backends".









Nenhum comentário:

Postar um comentário