3 Âmbar: Serviço Multiplataforma De FotoMemórias
3.2 Desafios computacionais
3.3.1 Modelo de domínio
Os conceitos presentes na plataforma e refletidos, consequentemente, no serviço e nas aplicaçõescliente vêm de um entendimento dos elementos e
problemas no domínio e de uma solução projetada para eles, formando um modelo do domínio (Figura 4). Esse modelo termina por determinar as funcionalidades e recursos da plataforma e aplicações desenvolvidas a partir dela. Figura 4: Modelo de domínio da plataforma Fonte: o autor Neste modelo, a foto é um conceito central. Cada foto é representada na plataforma como a associação de uma imagem às descrições dadas pelos
usuários, e os metadados extraídos da imagem automaticamente pela plataforma. As descrições de uma foto permitem que os usuários registrem
as memórias associadas a ela. Essas descrições são representadas na plataforma através de um conjunto de “atributos” textuais, com significados determinados, e uma coleção de marcadores (descrições associadas a
pontos na imagem).
Os atributos escolhidos remetem a anotações encontradas no verso de fotografias impressas. O “título” identifica e resume o que a foto representa, o “período” e o “lugar” descrevem respectivamente, quando e onde teriam ocorrido o evento registrado na foto. E o atributo “descrição” permite adicionar detalhes sobre o conteúdo ou o contexto da foto. Há ainda um atributo para descrever os autores da foto.
As descrições de uma foto incluem também uma coleção de marcadores. Cada marcador consiste em uma descrição textual que é associada a um ponto específico da imagem. Eles simplificam a descrição de pessoas, animais ou objetos retratados na foto.
A separação da descrição de uma foto nesses diversos atributos, ao invés de utilizar uma descrição textual única, dá maior flexibilidade a representação dessas descrições na interface das aplicaçõescliente. Por exemplo, alguns desses atributos podem ser omitidos ou representados com características diferentes (como fonte, tamanho e posicionamento).
Os metadados da foto são informações extraídas da própria imagem. O núcleo extrai um subconjunto de metadados que tem utilidade para o desenvolvimento das aplicações ou para o uso das fotos em si. Outros, entretanto, podem estar presentes nas imagens. Os metadados utilizados podem conter informações sobre a imagem em si ou sobre o contexto em que ela foi capturada, como dados sobre a câmera, geolocalização e o timestamp (data e hora) da captura.
Alguns desses dados podem, parcialmente, se sobrepor a informações presentes nas descrições, como as informações de tempo e lugar. Entretanto, enquanto os metadados podem ser mais precisos, as descrições permitem uma visão de mais alto nível e também mais pessoal. Por exemplo, invés de utilizar coordenadas de GPS ou um endereço para descrever um local, um usuário pode utilizar o nome com que ele se refere àquele lugar, como “casa dos meus pais” ou “campus da universidade”.
A plataforma utiliza o conceito de álbuns como uma forma de
organizar conjuntos de fotos relacionadas. Álbuns são identificados, para os usuários, através de um nome e uma imagem de capa.
Além de agrupar fotos, álbuns podem conter outros álbuns. Isto permite organizar as fotos utilizando diferentes níveis de detalhamento. Por sua vez, um mesmo álbum ou foto pode estar contido em diferentes outros álbuns. Dessa forma, usuários podem aplicar diferentes critérios de classificação, sem ter de lidar com fotos ou álbuns duplicados.
A plataforma provê ainda um outro nível para o agrupamento de fotos, os contextos. Os contextos permitem separar grupos de fotos e álbuns
compartilhados entre diferentes conjuntos de usuários. Isto é, para cada contexto pode haver um conjunto diferente de usuários que pode acessálo.
Para tanto, contextos permitem associar níveis de permissão a
usuários. O menor nível, o de leitura, permite a visualização do contexto e de seu conteúdo. O segundo nível (escrita) adiciona a permissão para alterar o conteúdo do contexto. Isto é, modificar, remover ou adicionar fotos e álbuns. O último nível inclui permissões para gerenciar o contexto. Isto inclui adicionar e remover usuários e modificar as permissões deles.
Além das permissões por usuário, um contexto possui um nível de visibilidade, que define o nível de permissão padrão, naquele contexto, para outros usuários da mesma instância do serviço. A visibilidade pode ser “privada”, que restringe o acesso apenas aos usuários com permissões definidas, “Visível para outros” ou “Editável por outros”, que permite que outros usuários visualizem ou editem o contexto, respectivamente.
Para ajudar no uso colaborativo das fotomemórias, a plataforma registra também um conjunto de “eventos”, que representam alterações
feitas pelos usuários nos elementos de um contexto, tais como adição e remoção de fotos. Esses registros são importantes para permitir a usuários identificarem as mudanças realizadas por outros usuários em um contexto compartilhado.
Cada usuário é representado na plataforma em função de sua
identidade. Do ponto de vista do sistema, esta corresponde, essencialmente, aos seus dados de autenticação. Mas, para que os usuários possam reconhecer uns aos outros, os dados de usuário incluem também um nome e foto de perfil.
3.3.2 Núcleo
O núcleo é o componente de software responsável por oferecer o serviço da plataforma. Ele provê uma API RESTful como interface do serviço
para as aplicaçõescliente. O núcleo está organizado em uma arquitetura em camadas (Figura 5) e foi implementado como uma biblioteca em C++. Além de facilitar eventuais expansões, essa estrutura também potencializa o reuso do serviço, já que uma instância do serviço é facilmente criada através do uso dessa biblioteca. Figura 5: Arquitetura do núcleo Fonte: o autor A camada de domínio reúne as entidades que representam o modelo de domínio da plataforma. Essas entidades são utilizadas por todas as outras camadas do núcleo. A camada de persistência é responsável pelo armazenamento de dados. Para tanto, esta camada é dividida em dois módulos. O primeiro realiza a interação com o banco de dados (BD) e expõe uma interface de mais alto nível. Já o segundo é composto por um conjunto de DAOs (Data Access Objects), que interagem com o primeiro módulo para realizar a persistência das entidades do domínio. A camada de serviços é responsável por realizar as operações que são oferecidas pelo serviço da plataforma e garantir que as regras de negócio sejam cumpridas. Essa camada provê ainda componentes responsáveis pela manipulação de imagens e configurações do serviço. A camada da API provê uma interface RESTful para os clientes do serviço. Esta camada é composta por controladores, que proveem as diferentes operações expostas pela API.
Estes controladores utilizam de um conjunto de componentes de serialização que realizam a conversão entre as classes que representam as entidades do domínio e um formato que pode ser exposto ou recebido pela API. Essa camada abrange também componentes responsáveis por tratar elementos do protocolo HTTP e aspectos da comunicação em rede.
Além dos componentes presentes no núcleo, aplicações precisam prover outros elementos para permitir o uso do serviço: uma interface que permita aos usuários controlar o serviço (pelo menos iniciálo e finalizálo); e componentes para realizar a configuração do serviço de acordo com as características da plataforma de uso e da aplicação.
Para implementar as camadas do núcleo foram escolhidas tecnologias e bibliotecas que fossem portáveis e tivessem um uso reduzido de recursos computacionais, de modo a permitir o uso da solução em um conjunto amplo de dispositivos e plataformas. Assim, por exemplo, o SQLite21 foi utilizado pois é uma solução leve para persistência de dados. Além disso, ele provê uma engine SQL que executa no mesmo processo da aplicação, através de uma biblioteca. Isso evita a dependência a outros processos ou serviços, o que contribui para tornar o núcleo do Âmbar autocontido. A biblioteca Civetweb22 também contribui para isso. Ela provê um servidor HTTP(S) que pode ser embarcado junto com a aplicação, em um mesmo processo.
Assim, o núcleo pode ser adaptado a diferentes dispositivos, sistemas operacionais e linguagens de programação. As tecnologias, bibliotecas e a linguagem adotadas permitem que o serviço seja utilizado mesmo em dispositivos com recursos de hardware limitados, tais como, dispositivos móveis (e.g. tablets e smartphones) ou single board computers (e.g. Raspyberry Pi23 e, possivelmente, até algumas versões de Arduino que executam Linux, como o Arduino Yún24) e através dos principais sistemas operacionais da atualidade. É possível, ainda, portar o núcleo para diferentes linguagens de programação, utilizando bindings. Através do
21sqlite.org.
22github.com/civetweb/civetweb. 23raspberrypi.org.
projeto swig25, por exemplo, é possível construir bindings de C++ para mais de 20 linguagens.
O desenvolvimento do núcleo como uma biblioteca permite ainda embarcar o serviço junto a uma aplicaçãocliente em um mesmo software. Além de poder simplificar a implantação dessas aplicações, isto possibilita a criação de soluções “auto contidas”, isto é, que não dependam de serviços externos e, portanto, possam ser utilizadas mesmo sem conectividade. O acesso direto ao núcleo pode também simplificar a administração pelos usuários, pois a aplicação possui um acesso privilegiado aos dados e ao próprio serviço.
O uso embarcado do núcleo facilita também o desenvolvimento de appliances, visto que é possível oferecer, em um único dispositivo com propósito específico, tanto uma interface direta para os usuários, quanto uma instância do serviço. Consequentemente, isso permitiria a outras aplicações controlarem remotamente a instância do serviço que está embarcada.
3.3.2.1 API do serviço
A API é a interface que expõe o serviço implementado no núcleo para as aplicaçõescliente. A API utiliza o protocolo HTTP para comunicação com as aplicaçõescliente e adota uma arquitetura RESTful. O uso desse estilo de arquitetura simplifica a representação do modelo do domínio na API, e permite também criar um modo uniforme de interagir com as entidades representadas.
Em arquiteturas RESTful, as capacidades do serviço são representadas através de resources, que podem ser acessados por meio de URLs (Universal Resource Locators). Ações podem ser executadas sobre esses resources através de um conjunto restrito de verbos. Em serviços que utilizam o protocolo HTTP, esses verbos são mapeados para os métodos do protocolo26, tais como, GET, POST e DELETE.
25swig.org. 26restfulapi.net
As rotas de uma API RESTful correspondem à parte da URL que permite localizar um resource dentro de um servidor. Elas apresentam uma estrutura hierárquica que permite representar relações entre conceitos.
A rota base da API do serviço, a “raiz”, apresenta informações sobre a instância do serviço, como a versão da API e do núcleo e um identificador para usuários dessa instância. Como a API utiliza versionamento semântico27, isso permite que usuários e sistemas de terceiros identifiquem o serviço que está sendo executado e se o mesmo é compatível com a aplicaçãocliente.
Versionamento semântico, é uma forma de indicar através do número de versão de um software informações sobre sua compatibilidade com outras versões. Para isso é utilizado um formato com três números separados por pontos (‘X.Y.Z’), onde o primeiro número identifica mudanças sem retro compatibilidade, o segundo número marca adição de features com retro compatibilidade e o terceiro número representa correções.
A raiz da API também lista o conjunto de rotas acessíveis através dela, o que funciona como autodocumentação. Cada uma dessas rotas representa uma coleção de entidades de um mesmo tipo. Elas seguem um “arquétipo” de resouce (collection). Esses arquétipos atuam como padrões de design, mas aplicados a modelagem de APIs RESTful. Outros três arquétipos são identificados por Massé (2012): document, store e controller.
Cada membro de uma coleção pode ser acessado através de uma “sub rota” dela, utilizando um identificador único da entidade ou, em alguns casos, um nome que referencia a entidade de forma relativa a outros dados da requisição. A rota “/users/me”, por exemplo, permite acessar dados do próprio usuário que fez a requisição. Essas entidades seguem o arquétipo “document”.
Propriedades de uma entidade podem, por sua vez, ser acessadas através de “subrotas” da entidade, utilizando o nome da propriedade. Algumas dessas propriedades podem ser também coleções, sob as quais o mesmo padrão de acesso, definido anteriormente, se aplica.
Dessa forma, é possível identificar um padrão geral para as rotas da API (Figura 6). Isto é, na base da API são referenciadas as diferentes coleções. Abaixo delas é possível acessar as entidades ou itens das coleções que podem ter um conjunto de propriedades. A interação com essas coleções e entidades da API também é feita de modo uniforme, de acordo com o tipo da rota (Tabela 3). Cada coleção provê operações para listar e adicionar itens, através dos métodos GET e POST, respectivamente. Já a atualização ou remoção de cada item é feita através da rota que o identifica, utilizando os métodos POST e DELETE, respectivamente. Esta rota também pode ser utilizada para acessar dados de uma entidade, usando o método GET. Figura 6: Estrutura geral de rotas da API e alguns exemplos /<coleção>/<entidade>/<propriedade> /photos/1/image /contexts/123/photos Fonte: o autor A interação com as propriedades de uma entidade, dependendo de sua semântica, pode ocorrer de forma similar a uma entidade ou coleção. Em ambos os casos, uma propriedade não pode ser removida, apenas alterada ou lida.
Os dados trocados entre as aplicaçõescliente e a API são representados no formato JSON28 (JavaScript Object Notation), exceto quando são utilizados tipos específicos, como imagens.
Tabela 3: Comportamento geral para interagir com rotas da API Tipo da
rota Método Operação Exemplos de rota
Coleção GET Lista todos os membros /events/contexts
POST Adiciona membro
Entidade GET Obtém entidade /albums/1
/photos/123 POST Altera entidade
DELETE Remove entidade Propriedade
(entidade)
GET Obtém propriedade /users/me/profile-image
POST Altera propriedade
Propriedade (coleção)
GET Lista todos os membros
/photos/1/markers
POST Adiciona membro
Fonte: o autor
Algumas rotas da API permitem o uso de parâmetros na url (Figura 7) para filtrar a coleção de itens retornados ou solicitar mudanças na representação deles.
Nem todas operações feitas através da API podem ser mapeadas diretamente ao padrão descrito. É o caso das operações em lote feitas sobre os álbuns, uma vez que elas atuam sobre os itens de um ou mais álbuns para permitir copiar, mover ou excluir esses itens de forma atômica. Além dos verbos e rotas utilizados, os dados retornados pela API e operações realizadas através dela também são modificados de acordo com o usuário que está autenticado, seguindo as permissões definidas no modelo de dados do serviço. Figura 7: Exemplo do uso de parâmetros em requisições à API. a) /albums/123/photos?query="família" b) /albums/456?expand-items=true
a) representa uma busca por fotos no álbum '123' que contenham o termo "família". Já em b), a representação do álbum '456' é modificada para incluir também dados das fotos e álbuns contidos nele, de forma a reduzir número de consultas.
Fonte: o autor Para se autenticar, clientes requisitam a criação de uma nova sessão, passando suas credenciais de acesso, i.e., login e senha. Após validar as credenciais, o núcleo cria a sessão para aquele cliente e gera um token. Isto é, um identificador da sessão que será utilizado pelo cliente para realizar as requisições futuras.
O token é enviado ao cliente através de um cookie e no corpo da requisição. Isso permite um gerenciamento automático da sessão por clientes que tem suporte a cookies. Mas também dá suporte aqueles que tenham restrições de acesso aos cookies. Esse é o caso de clientes Javascript que executam no navegador. Devido a questões de segurança, o navegador impede que eles acessem cookies que não venham do seu servidor de origem. Também para permitir esse tipo de cliente, a API dá suporte a requisições CORS29 (CrossOrigin Resource Sharing), um protocolo que permite a páginas web acessarem alguns tipos de recursos “restritos” provenientes de domínios diferentes da origem.
Sem essas medidas, clientes Web não poderiam acessar uma instância do serviço em um domínio ou servidor diferente. Isso dificultaria o desenvolvimento desses clientes de forma independente, bem como o seu acesso a diferentes instâncias do serviço.
Para acessar resources não públicos da API, os clientes precisam enviar a cada requisição o token recebido. A partir dele, o núcleo irá verificar as permissões do usuário. Caso o usuário não possua as permissões suficientes ou não esteja autenticado (token expirou, está inválido ou ausente), a API irá retornar uma mensagem de erro. Os status codes (códigos de estado) do protocolo HTTP são utilizados para comunicar a causa do erro. No caso de coleções a que o cliente tenha acesso a apenas uma parte dos itens, o núcleo oculta automaticamente os dados que o cliente não tenha permissão para visualizar. Ao acessar a coleção de contextos, por exemplo, são listados apenas aqueles que o usuário tem pelo menos permissão de leitura. 29 developer.mozilla.org/en-US/docs/Web/HTTP/CORS
3.3.2.2 Aplicações do núcleo
Para prover os serviços da plataforma, o núcleo, por ser uma biblioteca, precisa estar presente em uma aplicação. Essa aplicação é responsável por controlar a execução do serviço, i.e. inicialização e finalização. Além disso, ela irá prover uma interface para os usuários de modo a permitir a configuração do serviço.
3.3.2.2.1 Âmbar Daemon
O Âmbar Daemon é uma aplicação de linha de comando para a execução do serviço Âmbar. Ela foi desenvolvida para executar primariamente em background, i.e. como um daemon, de modo a poder ser implantada em servidores ou dispositivos embarcados. Mas ela também pode ser utilizada em computadores pessoais, e.g. desktops e notebooks, sem interferir nas atividades do usuário. A aplicação é compatível com sistemas Unix, e.g. Linux e MacOS, e com Windows, mas atualmente foi testada apenas no Linux. O Âmbar Daemon utiliza o framework Poco30, que permite a execução dele como um daemon Unix ou como um Windows service, de acordo com o sistema operacional. Ele pode também ser executada em modo interativo, i.e. conectado à linha de comando.
A interface do Âmbar Daemon provê opções para definir as configurações do serviço, e.g. porta utilizada, diretórios para armazenamento de dados, configurações de log, etc. Essas mesmas configurações podem também ser fornecidas através de um arquivo de configuração (utilizando formato JSON). Isso permite persistir as configurações entre execuções. Caso as configurações não sejam informadas, o núcleo utiliza valores padrão.
3.3.2.2.2 Âmbar Android Server
O Âmbar Android Server é um componente criado para permitir 30pocoproject.org
executar o serviço Âmbar em dispositivos Android. Ele é oferecido através de uma biblioteca Android para permitir seu uso integrado a um aplicativo cliente (ver seção 3.3.3.2) ou em uma aplicação isolada.
O componente principal da biblioteca é um serviço Android31 (AmbarAndroidService), que é responsável por configurar, iniciar e finalizar o serviço Âmbar. Para isso, ele interage com o núcleo, com ajuda de outros componentes, utilizando bindings JNI (Java Native Interface).
O AmbarAndroidService provê ainda a integração necessária com a plataforma Android. Ele permite receber comandos de uma aplicação para iniciar ou finalizar o serviço Âmbar, e pode, por sua vez, notificála em relação a mudanças no estado desse serviço. O AmbarAndroidService também apresenta um ciclo de vida, gerenciado pelo Android, que permite a ele reagir, por exemplo, à finalização da aplicação ou serviço, por um usuário ou pelo sistema.
Para reduzir a chance do serviço Âmbar ser finalizada pelo Android enquanto estiver em uso, e.g. devido a baixa memória, e permitir aos usuários identificarem quando ele está ativo, o AmbarAndroidService utiliza o recurso de “foreground service”32 do Android. Esse recurso indica ao Android que o usuário está interagindo ativamente com o serviço. Ele também cria uma notificação que permite aos usuários visualizarem que o serviço está em execução. Essa notificação também inclui um controle que permite ao usuário finalizar o serviço, caso deseje. Quando o serviço é finalizado (pelo usuário ou pelo sistema), ou caso saia de “foreground”, a notificação é removida.