Amazon Web Services: Integrando serviços AWS com Android

Veja nesse artigo como desenvolver soluções que são integradas em Android junto com os serviços da Amazon Web Services (AWS).

Fique por dentro

O artigo é útil para a criação de um sistema com alta escalabilidade, baixa manutenção e controle de custos. Com ele você poderá desenvolver uma aplicação simples se utilizando de serviços da AWS para otimizar seus serviços.

Veja que o artigo não se aprofundará nos serviços AWS, mas apenas em suas possibilidades de utilização via Android. Sua utilização por outras plataformas também é possível e se utilizam de muita semelhança, porém, com particularidades de cada sistema.

Neste artigo vamos apresentar diversos serviços de fácil utilização e escalabilidade que podem potencializar sua experiência com uso de facilidades disponibilizadas em nuvem para seu aplicativo. Utilizaremos um dos principais servidores de nuvem da atualidade, disponibilizado pela Amazon, o AWS para criar integração e comunicação diretamente com um aparelho Android.

AWS é uma sigla para Amazon Web Services, que propõe de uma forma bem simples prover serviços altamente escaláveis para ajudar no desenvolvimento de aplicações robustas. Em 2011, um datacenter da AWS foi construído em nosso país, o que nos dá mais uma vantagem interessante, em especial relacionado à latência de comunicação.

O que faz a AWS ser uma opção interessante para muitos sistemas são seus serviços especializados, que é também o que lhe diferencia da maioria dos “provedores de nuvem”. De um modo geral, eles possuem altíssima escalabilidade com uma manutenção mínima e ainda conseguem ser entregues por um custo baixo. Alguns desses serviços são: banco de dados, DNS, sistema de filas, sistema de busca, armazenamento, serviços https, entre outros.

Os serviços AWS são independentes de plataforma, sua implementação é baseada em comunicação por web services, porém uma série de APIs especializadas para plataformas são criadas para facilitar sua utilização.

É interessante notar que a maioria dos serviços possuem um nível gratuito de utilização, que é muito útil para testes e dependendo do tamanho da aplicação, com a utilização dos serviços corretos pode-se deixar o lado servidor completamente sem custo.

Os serviços fornecidos nem sempre estão disponíveis em todas as regiões, desta forma nem sempre um serviço que existe nos datacenters nos Estados Unidos existe nos datacenters do Brasil. Entretanto, a Amazon não restringe o uso de serviços disponibilizados em outra região que não a mais próxima, mas claro a latência de acesso será maior.

Também é importante notar que os preços cobrados diferem entre as regiões. Assim, recomendo analisar qual o real impacto em sua aplicação e verificar o custo-benefício, ou até mesmo dividir a utilização entre as regiões.

Comunicação

Um grande desafio no desenvolvimento de aplicativos para dispositivos móveis está justamente na comunicação com outros dispositivos/internet. Apesar deste não ser o foco do artigo, este é um ponto muito importante ao se desenvolver um aplicativo utilizando estes tipos de serviços, em especial em nossas redes 3G aqui no Brasil, com vários problemas sinal e velocidade de conexão.

Para lidar com estes problemas existem uma série de padrões e tratamentos, sendo recomendado a utilização destes para evitar comportamentos inesperados:

Com estes tratamentos relativamente simples, o ganho na usabilidade e estabilidade será muito grande em aplicações que fazem constante comunicação através da internet.

Protocolo de Segurança

Para ter acesso aos seus recursos na nuvem AWS, é necessária uma identificação que é composta por uma chave de acesso e uma senha. Ambas são geradas pelo sistema IAM que gerencia o acesso aos serviços. Esse acesso estará atrelado a uma regra de acessos, que serve para limitar as operações que podem ser executadas (muito importante ser bem configurada, para evitar brechas), sendo estas regras de fácil configuração através do console AWS.

Nota: Para limitar ainda mais o acesso, também pode-se gerar uma sessão em cima deste acesso. Desta forma, ele terá tempo de validade para o acesso e você não terá que expor a sua chave de acesso. Em contrapartida, você precisará de um servidor para gerenciar a criação de sessões.

Também é configurável a comunicação utilizando protocolo HTTPS, porém o seu uso vai acarretar em um overhead maior na comunicação com o servidor, gerando um maior consumo de banda (servidor e aparelho) e utilização de CPU.

Consequentemente, também trará um maior gasto de bateria, especialmente se o aplicativo utilizar esta comunicação frequentemente. Assim, é importante avaliar a necessidade da utilização para a sua aplicação, ou utilizar apenas nas comunicações que requerem maior segurança.

Riscos de se prender aos serviços

Apesar de a Amazon ter demonstrado muita confiança a seus clientes nos últimos anos, inclusive otimizando estruturas para baixar custos e repassar para clientes, não recomendo se prender completamente a sua estrutura para que você não fique vulnerável a mudanças de mercado.

A maioria dos serviços fornecidos pela AWS é exclusivo dela, com isso é muito importante o desenvolvimento em camadas e a utilização de padrões como Factory e DAO que possam deixar até um certo nível esta dependência em relação serviços menos acoplada de forma que seja mais fácil fazer ajustes em uma possível mudança.

Lembrando também que temos o serviço de servidor convencional, onde você pode contratar uma máquina e realizar a instalação e comunicação que você desejar, porém o dimensionamento é um pouco menos dinâmico e necessita de mais configurações do que nos serviços especializados.

Serviços

Apesar da grande variedade de serviços, vamos passar por apenas alguns que são utilizados na maioria dos projetos:

Muitos destes serviços possuem integração entre eles realizada pela Amazon, o que facilita alguns aspectos do desenvolvimento.

Conhecendo os serviços na prática

Para começar a construir um projeto com integração com a AWS você terá que ter em mãos a SDK para sua plataforma, no nosso caso Android. A SDK para o Android tem uma dinâmica um pouco diferente do que normalmente se tem em uma API, em especial pelo seu tamanho. Para evitar inflar excessivamente sua APK, esta SDK foi desenvolvida de forma modular, onde você só precisará importar o que realmente vai utilizar.

É necessário adicionar um usuário através do serviço IAM no console AWS. Feito isso, será gerado uma accessKey e uma secretKey que utilizaremos para fazer a comunicação com os serviços. Não há necessidade de se criar uma senha para este usuário, porém é importante configurar a permissão conforme desejada (ela poderá ser alterada posteriormente).

SDK

O SDK AWS é relativamente fácil de se utilizar, bem modular e com uma boa gama de configurações para que possamos adaptá-lo o melhor possível às nossas necessidades.

Algo que você vai reparar rapidamente é o fato dela ter um alto nível de orientação a objetos. Assim, para cada operação você acabará tendo que criar objetos novos para novas requisições. Para quem não está acostumado, isto pode parecer um pouco chato no início, mas com o tempo e com seu projeto crescendo você acaba por descobrir as vantagens desta abordagem.

Outro aspecto interessante da API é fato dela utilizar muita exceção de tempo de execução (RuntimeException), sendo possível evitar crashes indevidos.

Este artigo vai se basear na utilização do SDK AWS de mais alto nível. Observe que em toda a SDK temos três níveis de uso:

Mesmo este artigo sendo voltado ao desenvolvimento Android, a API desenvolvida para outras plataformas, como Java e iOS são muito semelhantes, inclusive em nomenclatura de métodos e seguem os mesmos conceitos de utilização.

IAM

Demonstraremos aqui duas forma simples para se ter acesso aos serviços AWS, porém é importante ressaltar que a prática de se manter as chaves de acesso dentro do código é de grande risco. A primeira forma seria utilizando diretamente as chaves em código.

Para isso, temos uma classe dentro do pacote STS chamada BasicAWSCredentials, ela recebe dois parâmetros, a sua accessKey e secretKey que estarão relacionados ao usuário que você criou pelo console AWS (citado anteriormente), com estas credenciais você passa a ter acesso a todos os recursos dentro das permissões que você definiu.

Uma segunda forma, ainda simples, seria criar um servidor que fique responsável por esse processo, sendo que no servidor você inicia o processo exatamente da mesma forma mencionada no parágrafo anterior, porém você adiciona uma passo, onde você gera um token que terá uma validade finita e quais as regras de acesso podem ser limitadas diretamente na criação deste token. Observe como realizar a criação do token na Listagem 1.

BasicAWSCredentials credentials = new BasicAWSCredentials(ACCESS_KEY, SECRET_KEY); Action allActions = new Action() { public String getActionName() { return FULL_PERMISSION; } }; Policy policy = new Policy().withStatements(new Statement(Effect.Allow) .withActions(allActions).withResources(new Resource(ALL_RESOUCES))); GetFederationTokenRequest request = new GetFederationTokenRequest() .withDurationSeconds(timeoutSeconds).withName(USER_NAME).withPolicy(policy.toJson()); AWSSecurityTokenServiceClient tokenClient = new AWSSecurityTokenServiceClient(credentials); GetFederationTokenResult result = tokenClient.getFederationToken(request); Credentials resultCredentials = result.getCredentials();
Listagem 1. Criação de token

Na primeira linha de código temos o método que cria as credenciais baseadas na accessKey e secretKey definidas por constantes. Em seguida é utilizada a classe builder Policy para construir as permissões específicas deste token. Neste caso estamos dando permissão total (porém, ainda é limitado às permissões do usuário atrelado).

Em seguida, utilizando mais um builder, completamos as informações para definição do token incluindo tempo de duração, a qual usuário este token estará atrelado e as permissões. Por fim, fazemos a requisição do token diretamente ao serviço AWS, que nos retorna as credenciais conforme requisitado, que é o objeto resultCredentials que poderá ser utilizado para acessar os recursos AWS.

SimpleDB

Esse é o único serviço que ainda não se encontra visível através do console AWS, porém você encontra uma série de plugins para navegadores, eclipse ou aplicativos para poder ter um controle visual sobre suas tabelas.

A lógica deste banco é bem simplificada, apesar de sua implementação ser muito diferente de um banco convencional. Para a criação de tabelas e inserção de dados seguimos o fluxo padrão de requisição de web service através da SDK fornecida.

Um ponto muito interessante a ser notado na inserção de dados é o fato de que as colunas são totalmente dinâmicas. Ao criar uma tabela você não define nenhuma das colunas e durante a inserção dos dados as colunas são criadas dinamicamente.

Como este banco suporta campo multivariado, é bom estar atento à forma como se está inserindo os dados para obter o resultado desejado. Você pode utilizar SQL para buscas, porém ela é um pouco diferente do convencional (veja o endereço na seção Links).

Conforme pode-se observar na Listagem 2, a classe AmazonSimpleDBClient serve para realizar a comunicação com o SimpleDB. A partir dela você poderá realizar chamadas para executar operações com este serviço. Passamos por parâmetro um objeto chamado credentials, este objeto é o mesmo criado no tópico IAM e ele é necessário para que os serviços nos permitam o acesso com a nossa conta.

Observe que criamos um objeto de configuração onde definimos alguns parâmetros interessantes que podem nos ajudar a otimizar a comunicação com o serviço perante as necessidades que nossa aplicação venha a ter. É importante ficar claro que este não é um objeto utilizado exclusivamente para o SimpleDB, ele também funciona com diversos outros serviços.

AmazonSimpleDB sdb; ClientConfiguration clientConfiguration = new ClientConfiguration(); clientConfiguration.withSocketTimeout(10000); clientConfiguration.withProtocol(Protocol.HTTP); clientConfiguration.withMaxErrorRetry(7); sdb = new AmazonSimpleDBClient(credentials, clientConfiguration);
Listagem 2. Configuração do SimpleDB

Para a criação de uma tabela, que é chamada de domínio, você utilizará o objeto sdb criado na última listagem chamando o método createDomain() e tudo que você precisa é passar o nome do domínio como parâmetro para que ele seja criado.

Simple Queue Service (SQS)

Sistema de gerenciamento de filas, seu conceito é bem básico, um processo alimenta uma fila e outro pegas estes elemento na mesma ordem que eles foram colocados. Para a comunicação com o serviço, deve-se utilizar a classe AmazonSQSClient. Para criar dinamicamente uma fila é necessário apenas passar o nome da fila a ser criada, porém é importante armazenar a URL que será retornada durante a criação, pois esta será utilizada para o envio e consumo das mensagens.

As mensagens serão enviadas através do método sendMessage(), que recebe a URL da fila e a mensagem em uma String. Podemos definir a quantidade máxima de mensagens a serem buscadas na requisição, sendo que o limite mínimo fica pela quantidade existente na fila.

Na Listagem 3 estamos recebendo uma única mensagem, pegando o conteúdo dela e descartando a mensagem no servidor. Também durante a requisição da mensagem pode-se passar um parâmetro através do método setVisibilityTimeout() que fará com que a mensagem não fique visível para outras requisições por um tempo definido.

Desta forma, você pode se certificar do tratamento da mensagem antes da exclusão completa dela do servidor.

ReceiveMessageRequest request = new ReceiveMessageRequest(queueUrl); request.setMaxNumberOfMessages(1); Message message = sqs.receiveMessage(request).getMessages().get(0); String body = message.getBody(); sqs.deleteMessage(new DeleteMessageRequest(queueUrl, message.getReceiptHandle()));
Listagem 3. Recebendo mensagem na Fila

Simple Storage Service (S3)

Este é um serviço possui uma cara de sistema de arquivos e é utilizado para armazenar grandes massas de dados de uma forma completamente dinâmica e otimizada. Por se tratar de um grande volume de dados, a sua utilização é um pouco mais complexa comparada com os serviços citados até aqui.

É muito importante tomar o cuidado também de tratamentos no lado Android quanto à utilização de banda e bateria, pois este serviço pode envolver tarefas longas e de utilização intensiva de rede.

A Listagem 4 demonstra a forma mais simples de envio de arquivo. Pode ser utilizado em qualquer arquivo, porém, a partir de um certo tamanho de arquivo você passa a ter problemas e riscos de perda de conexão. Com algumas configurações de conexão você pode melhorar esta estabilidade (em especial de timeout).

AmazonS3 s3 = new AmazonS3Client(credentials); s3.putObject(new PutObjectRequest(bucketName, fileName, file));
Listagem 4. Envio de arquivo S3

Para resolver completamente esse problema, temos uma segunda forma de envio, onde mandamos o arquivo em partes. Com isso, corre-se o risco de perder apenas partes do processo, sendo necessário o reenvio das partes pendentes.

Além da melhor estabilidade do envio, também podemos nos utilizar desta ferramenta para enviar várias partes em paralelo, com a intenção de otimizar o envio do arquivo (ver Listagem 5).

TransferManager transferManger= new TransferManager(credentials); Upload upload = transferManger.upload(bucketName, fileName, file);
Listagem 5. Envio Multi-Part S3

O objeto Upload permite visualizar o status atual do upload e esperar por eventos relacionados. Já o TrasnferManager será utilizado para abortar conexões e iniciar downloads e uploads.

Quando for listar uma série de arquivos é importante prestar atenção à paginação desta lista para que não perca conteúdo existente. Para isso temos os métodos isTruncated() para saber se existe uma próxima página e listNextBatchOfObjects() para buscar a próxima página. O modo como você vai implementar esta listagem de páginas vai depender muito de sua aplicação, mas tenha em mente que cada requisição de página será uma nova requisição na rede, envolvendo custos e tempo. Veja um exemplo na Listagem 6.

ObjectListing obList = s3.listObjects(bucketName, folderPath); List<S3ObjectSummary> list = obList.getObjectSummaries(); while(obList.isTruncated()) { obList = s3.listNextBatchOfObjects(obList); list.addAll(obList.getObjectSummaries()); }
Listagem 6. Listagem de arquivos

Veja também que o objeto retornado nesta listagem não é o arquivo em si, mas apenas um metadado para os arquivos, para buscar definitivamente o conteúdo do arquivo será necessária uma nova requisição através do método getObject() especificando o arquivo a ser buscado.

Apesar de não existir de fato o conceito "pasta" neste serviço, a utilização de barra no nome do arquivo dá uma sensação próxima ao que costumeiramente utilizamos em um sistema de arquivos convencional.

A deleção de um objeto funciona no mesmo padrão das requisições, através do método deleteObject() você fornece o nome completo do arquivo a ser removido.

Demais serviços

A Amazon tem constantemente apresentado novas soluções no mercado, por isso vale sempre a pena dar uma revisada em seus serviços disponíveis para verificar se não temos algo novo que possa satisfazer ainda melhor a solução que queremos aplicar.

Durante a escrita deste artigo, existiam ainda vários outros serviços que não foram abordados aqui:

Este artigo não cobriu todos os aspectos e nem todas as APIs de serviços disponibilizados pelo AWS, porém, fica evidente a padronização no desenvolvimento da API.

Links:

Confira também

Artigos relacionados