Desenvolvimento de aplicativos com framework de desenvolvimento rápido de aplicativos AllcountJS

Publicados: 2022-03-11

A ideia do Rapid Application Development (RAD) nasceu em resposta aos modelos tradicionais de desenvolvimento em cascata. Existem muitas variações de RAD; por exemplo, desenvolvimento ágil e o Rational Unified Process. No entanto, todos esses modelos têm uma coisa em comum: eles visam gerar valor máximo de negócios com tempo mínimo de desenvolvimento por meio de prototipagem e desenvolvimento iterativo. Para isso, o modelo de Desenvolvimento Rápido de Aplicativos conta com ferramentas que facilitam o processo. Neste artigo, exploraremos uma dessas ferramentas e como ela pode ser usada para focar no valor do negócio e na otimização do processo de desenvolvimento.

AllcountJS é uma estrutura de código aberto emergente construída com o desenvolvimento rápido de aplicativos em mente. Ele é baseado na ideia de desenvolvimento de aplicativos declarativos usando código de configuração semelhante ao JSON que descreve a estrutura e o comportamento do aplicativo. A estrutura foi construída sobre Node.js, Express, MongoDB e depende muito de AngularJS e Twitter Bootstrap. Embora dependa de padrões declarativos, o framework ainda permite maior personalização por meio de acesso direto à API, quando necessário.

AllcountJS como seu framework RAD

Por que AllcountJS como seu framework RAD?

Segundo a Wikipedia, existem pelo menos cem ferramentas que prometem desenvolvimento rápido de aplicativos, mas isso levanta a questão: quão rápido é “rápido”. Essas ferramentas permitem que um aplicativo específico centrado em dados seja desenvolvido em poucas horas? Ou, talvez, seja “rápido” se o aplicativo puder ser desenvolvido em alguns dias ou semanas. Algumas dessas ferramentas afirmam que alguns minutos são suficientes para produzir um aplicativo funcional. No entanto, é improvável que você possa criar um aplicativo útil em menos de cinco minutos e ainda afirmar ter satisfeito todas as necessidades de negócios. AllcountJS não afirma ser tal ferramenta; o que o AllcountJS oferece é uma forma de prototipar uma ideia em um curto período de tempo.

Com o framework AllcountJS, é possível construir um aplicativo com uma interface de usuário gerada automaticamente por temas, recursos de gerenciamento de usuários, API RESTful e um punhado de outros recursos com o mínimo de esforço e tempo. É possível usar o AllcountJS para uma ampla variedade de casos de uso, mas é mais adequado para aplicativos em que você tem diferentes coleções de objetos com diferentes visualizações para eles. Normalmente, os aplicativos de negócios são uma boa opção para esse modelo.

AllcountJS foi usado para construir allcountjs.com, além de um rastreador de projeto para ele. Vale a pena notar que allcountjs.com é um aplicativo personalizado do AllcountJS e que o AllcountJS permite que visões estáticas e dinâmicas sejam combinadas com pouco incômodo. Ele ainda permite que peças carregadas dinamicamente sejam inseridas em conteúdo estático. Por exemplo, AllcountJS gerencia uma coleção de modelos de aplicativos de demonstração. Há um widget de demonstração na página principal de allcountjs.com que carrega um modelo de aplicativo aleatório dessa coleção. Um punhado de outros aplicativos de amostra estão disponíveis na galeria em allcountjs.com.

Começando

Para demonstrar alguns dos recursos do framework RAD AllcountJS, criaremos um aplicativo simples para o Toptal, que chamaremos de Toptal Community. Se você acompanha nosso blog, já deve saber que um aplicativo semelhante foi criado usando o Hoodie como parte de um de nossos posts anteriores. Este aplicativo permitirá que os membros da comunidade se inscrevam, criem eventos e se inscrevam para participar deles.

Para configurar o ambiente, você deve instalar Node.js, MongoDB e Git. Em seguida, instale o AllcountJS CLI invocando um comando “npm install” e execute a inicialização do projeto:

 npm install -g allcountjs-cli allcountjs init toptal-community-allcount cd toptal-community-allcount npm install

A CLI do AllcountJS solicitará que você insira algumas informações sobre seu projeto para preencher previamente o package.json.

AllcountJS pode ser usado como servidor autônomo ou como uma dependência. Em nosso primeiro exemplo, não vamos estender o AllcountJS, então um servidor autônomo deve funcionar para nós.

Dentro desse diretório app-config recém-criado, substituiremos o conteúdo do arquivo JavaScript main.js pelo seguinte snippet de código:

 A.app({ appName: "Toptal Community", onlyAuthenticated: true, allowSignUp: true, appIcon: "rocket", menuItems: [{ name: "Events", entityTypeId: "Event", icon: "calendar" }, { name: "My Events", entityTypeId: "MyEvent", icon: "calendar" }], entities: function(Fields) { return { Event: { title: "Events", fields: { eventName: Fields.text("Event").required(), date: Fields.date("Date").required(), time: Fields.text("Starts at").masked("99:99").required(), appliedUsers: Fields.relation("Applied users", "AppliedUser", "event") }, referenceName: "eventName", sorting: [['date', -1], ['time', -1]], actions: [{ id: "apply", name: "Apply", actionTarget: 'single-item', perform: function (User, Actions, Crud) { return Crud.actionContextCrud().readEntity(Actions.selectedEntityId()).then(function (eventToApply) { var userEventCrud = Crud.crudForEntityType('UserEvent'); return userEventCrud.find({filtering: {"user": User.id, "event": eventToApply.id}}).then(function (events) { if (events.length) { return Actions.modalResult("Can't apply to event", "You've already applied to this event"); } else { return userEventCrud.createEntity({ user: {id: User.id}, event: {id: eventToApply.id}, date: eventToApply.date, time: eventToApply.time }).then(function () { return Actions.navigateToEntityTypeResult("MyEvent") }); } }); }) } }] }, UserEvent: { fields: { user: Fields.fixedReference("User", "OnlyNameUser").required(), event: Fields.fixedReference("Event", "Event").required(), date: Fields.date("Date").required(), time: Fields.text("Starts at").masked("99:99").required() }, filtering: function (User) { return {"user.id": User.id} }, sorting: [['date', -1], ['time', -1]], views: { MyEvent: { title: "My Events", showInGrid: ['event', 'date', 'time'], permissions: { write: [], delete: null } }, AppliedUser: { permissions: { write: [] }, showInGrid: ['user'] } } }, User: { views: { OnlyNameUser: { permissions: { read: null, write: ['admin'] } }, fields: { username: Fields.text("User name") } } } } } });

Embora o AllcountJS funcione com repositórios Git, por questão de simplicidade não o usaremos neste tutorial. Para executar o aplicativo Toptal Community, tudo o que precisamos fazer é invocar o comando AllcountJS CLI run no diretório toptal-community-allcount.

 allcountjs run

Vale a pena notar que o MongoDB deve estar em execução quando este comando for executado. Se tudo correr bem, o aplicativo deve estar funcionando em http://localhost:9080.

Para fazer o login, use o nome de usuário “admin” e a senha “admin”.

Menos de 100 linhas

Você deve ter notado que o aplicativo definido em main.js levou apenas 91 linhas de código. Essas linhas incluem a declaração de todos os comportamentos que você pode observar ao navegar para http://localhost:9080. Então, o que exatamente está acontecendo, sob o capô? Vamos dar uma olhada em cada aspecto do aplicativo e ver como o código se relaciona com eles.

Faça login e inscreva-se

A primeira página que você vê depois de abrir o aplicativo é um login. Isso também funciona como uma página de inscrição, supondo que a caixa de seleção "Inscreva-se" - esteja marcada antes de enviar o formulário.

Faça login e inscreva-se

Esta página é mostrada porque o arquivo main.js declara que somente usuários autenticados podem usar este aplicativo. Além disso, permite que os usuários se inscrevam nesta página. As duas linhas a seguir são tudo o que foi necessário para isso:

 A.app({ ..., onlyAuthenticated: true, allowSignUp: true, ... })

Página de boas-vindas

Após fazer login, você será redirecionado para uma página de boas-vindas com um menu de aplicativos. Esta parte da aplicação é gerada automaticamente, com base nos itens de menu definidos na tecla “menuItems”.

exemplo de página de boas-vindas

Junto com algumas outras configurações relevantes, o menu é definido no arquivo main.js da seguinte forma:

 A.app({ ..., appName: "Toptal Community", appIcon: "rocket", menuItems: [{ name: "Events", entityTypeId: "Event", icon: "calendar" }, { name: "My Events", entityTypeId: "MyEvent", icon: "calendar" }], ... });

AllcountJS usa ícones Font Awesome, então todos os nomes de ícones referenciados na configuração são mapeados para nomes de ícones Font Awesome.

Navegando e editando eventos

Depois de clicar em “Eventos” no menu, você será levado para a visualização de Eventos mostrada na captura de tela abaixo. É uma visão padrão do AllcountJS que fornece algumas funcionalidades CRUD genéricas nas entidades correspondentes. Aqui, você pode pesquisar eventos, criar novos eventos e editar ou excluir os existentes. Existem dois modos desta interface CRUD: lista e formulário. Essa parte do aplicativo é configurada por meio das seguintes linhas de código JavaScript.

 A.app({ ..., entities: function(Fields) { return { Event: { title: "Events", fields: { eventName: Fields.text("Event").required(), date: Fields.date("Date").required(), time: Fields.text("Starts at").masked("99:99").required(), appliedUsers: Fields.relation("Applied users", "AppliedUser", "event") }, referenceName: "eventName", sorting: [['date', -1], ['time', -1]], ... } } } });

Este exemplo mostra como as descrições de entidade são configuradas em AllcountJS. Observe como estamos usando uma função para definir as entidades; cada propriedade da configuração do AllcountJS pode ser uma função. Essas funções podem solicitar que as dependências sejam resolvidas por meio de seus nomes de argumentos. Antes que a função seja chamada, as dependências apropriadas são injetadas. Aqui, “Fields” é uma das APIs de configuração AllcountJS usadas para descrever campos de entidade. A propriedade “Entidades” contém pares nome-valor onde o nome é um identificador do tipo entidade e o valor é sua descrição. Um tipo de entidade para eventos é descrito, neste exemplo, onde o título é “Eventos”. Outras configurações, como ordenação por padrão, nome de referência e similares, também podem ser definidas aqui. A ordem de classificação padrão é definida por meio de uma matriz de nomes e direções de campo, enquanto o nome de referência é definido por meio de uma string (leia mais aqui).

função allcountJS

Esse tipo de entidade específico foi definido como tendo quatro campos: “eventName”, “date”, “time” e “appliedUsers”, os três primeiros dos quais são persistidos no banco de dados. Esses campos são obrigatórios, conforme indicado pelo uso de “required()”. Os valores nesses campos com essas regras são validados antes que o formulário seja enviado no front-end, conforme mostrado na captura de tela abaixo. AllcountJS combina validações do lado do cliente e do lado do servidor para fornecer a melhor experiência do usuário. O quarto campo é um relacionamento que contém uma lista de usuários que se inscreveram para participar do evento. Naturalmente, esse campo não é persistido no banco de dados e é preenchido selecionando apenas as entidades AppliedUser relevantes para o evento.

regras de desenvolvimento allcountjs

Inscrevendo-se para participar de eventos

Quando um usuário seleciona um evento específico, a barra de ferramentas mostra um botão chamado “Aplicar”. Clicar nele adiciona o evento à agenda do usuário. No AllcountJS, ações semelhantes a esta podem ser configuradas simplesmente declarando-as na configuração:

 actions: [{ id: "apply", name: "Apply", actionTarget: 'single-item', perform: function (User, Actions, Crud) { return Crud.actionContextCrud().readEntity(Actions.selectedEntityId()).then(function (eventToApply) { var userEventCrud = Crud.crudForEntityType('UserEvent'); return userEventCrud.find({filtering: {"user": User.id, "event": eventToApply.id}}).then(function (events) { if (events.length) { return Actions.modalResult("Can't apply to event", "You've already applied to this event"); } else { return userEventCrud.createEntity({ user: {id: User.id}, event: {id: eventToApply.id}, date: eventToApply.date, time: eventToApply.time }).then(function () { return Actions.navigateToEntityTypeResult("MyEvent") }); } }); }) } }]

A propriedade “actions” de qualquer tipo de entidade recebe uma matriz de objetos que descrevem o comportamento de cada ação personalizada. Cada objeto possui uma propriedade “id” que define um identificador único para a ação, a propriedade “name” define o nome de exibição e a propriedade “actionTarget” é usada para definir o contexto da ação. Definir “actionTarget” como “single-item” indica que a ação deve ser executada com um evento específico. Uma função definida na propriedade “executar” é a lógica executada quando esta ação é executada, normalmente quando o usuário clica no botão correspondente.

Dependências podem ser solicitadas por esta função. Por exemplo, neste exemplo a função depende de “Usuário”, “Ações” e “Crud”. Quando ocorre uma ação, uma referência ao usuário, invocando esta ação, pode ser obtida requerendo a dependência “Usuário”. A dependência “Crud”, que permite a manipulação do estado do banco de dados para essas entidades, também é solicitada aqui. Os dois métodos que retornam uma instância do objeto Crud são: O método “actionContextCrud()” - retorna CRUD para o tipo entidade “Event” já que a ação “Apply” pertence a ele, enquanto o método “crudForEntityType()” - retorna CRUD para qualquer tipo de entidade identificado por seu ID de tipo.

Dependências CRUD

A implementação da ação começa verificando se este evento já está agendado para o usuário e, caso não esteja, ele cria um. Se já estiver agendado, uma caixa de diálogo é mostrada retornando o valor da chamada “Actions.modalResult()”. Além de mostrar um modal, uma ação pode realizar diferentes tipos de operações de forma semelhante, como “navegar para visualizar”, “atualizar visualização”, “mostrar diálogo” e assim por diante.

execução da ação

Cronograma do Usuário de Eventos Aplicados

Depois de aplicar com sucesso a um evento, o navegador é redirecionado para a visualização “Meus eventos”, que mostra uma lista de eventos aos quais o usuário se inscreveu. A visualização é definida pela seguinte configuração:

 UserEvent: { fields: { user: Fields.fixedReference("User", "OnlyNameUser").required(), event: Fields.fixedReference("Event", "Event").required(), date: Fields.date("Date").required(), time: Fields.text("Starts at").masked("99:99").required() }, filtering: function (User) { return {"user.id": User.id} }, sorting: [['date', -1], ['time', -1]], views: { MyEvent: { title: "My Events", showInGrid: ['event', 'date', 'time'], permissions: { write: [], delete: null } }, AppliedUser: { permissions: { write: [] }, showInGrid: ['user'] } } },

Neste caso, estamos usando uma nova propriedade de configuração, “filtragem”. Assim como em nosso exemplo anterior, essa função também depende da dependência “User”. Se a função retornar um objeto, ela será tratada como uma consulta do MongoDB; a consulta filtra a coleção para eventos que pertencem apenas ao usuário atual.

Outra propriedade interessante é “Vistas”. “View” é um tipo de entidade regular, mas sua coleção do MongoDB é a mesma do tipo de entidade pai. Isso possibilita a criação de visualizações visualmente diferentes para os mesmos dados no banco de dados. Na verdade, usamos esse recurso para criar duas visualizações diferentes para “UserEvent:” “MyEvent” e “AppliedUser”. Como o protótipo das subvisualizações é definido para o tipo de entidade pai, as propriedades que não são substituídas são “herdadas” do tipo pai.

Visualizações

Listando participantes do evento

Depois de se inscrever em um evento, outros usuários podem ver uma lista de todos os usuários que planejam participar. Isso é gerado como resultado dos seguintes elementos de configuração em main.js:

 AppliedUser: { permissions: { write: [] }, showInGrid: ['user'] } // ... appliedUsers: Fields.relation("Applied users", "AppliedUser", "event")

“AppliedUser” é uma visualização somente leitura para um tipo de entidade “MyEvent”. Essa permissão somente leitura é aplicada definindo uma matriz vazia para a propriedade “Write” do objeto permissions. Além disso, como a permissão “Ler” não está definida, por padrão, a leitura é permitida para todos os usuários.

usuário aplicado para meu tipo de evento

Estendendo implementações padrão

A desvantagem típica dos frameworks RAD é a falta de flexibilidade. Depois de criar seu aplicativo e precisar personalizá-lo, você poderá encontrar obstáculos significativos. AllcountJS é desenvolvido com extensibilidade em mente e permite a substituição de todos os blocos de construção internos.

Para conseguir isso, o AllcountJS usa sua própria implementação de injeção de dependência (DI). A DI permite que o desenvolvedor substitua os comportamentos padrão do framework por meio de pontos de extensão e, ao mesmo tempo, permite a reutilização de implementações existentes. Muitos aspectos da extensão do framework RAD são descritos nas documentações. Nesta seção, exploraremos como podemos estender dois dos muitos componentes da estrutura, a lógica do lado do servidor e as visualizações.

Continuando com nosso exemplo Toptal Community, vamos integrar uma fonte de dados externa para agregar dados de eventos. Vamos imaginar que há posts no Toptal Blog discutindo planos para eventos no dia anterior a cada evento. Com o Node.js, deve ser possível analisar o feed RSS do blog e extrair esses dados. Para isso, precisaremos de algumas dependências extras do npm, como “request”, “xml2js” (para carregar o feed RSS do Toptal Blog), “q” (para implementar promessas) e “moment” (para analisar datas). Essas dependências podem ser instaladas invocando o seguinte conjunto de comandos:

 npm install xml2js npm install request npm install q npm install moment

Vamos criar outro arquivo JavaScript, nomeie-o como “toptal-community.js” no diretório toptal-community-allcount e preencha-o com o seguinte:

 var request = require('request'); var Q = require('q'); var xml2js = require('xml2js'); var moment = require('moment'); var injection = require('allcountjs'); injection.bindFactory('port', 9080); injection.bindFactory('dbUrl', 'mongodb://localhost:27017/toptal-community'); injection.bindFactory('gitRepoUrl', 'app-config'); injection.bindFactory('DiscussionEventsImport', function (Crud) { return { importEvents: function () { return Q.nfcall(request, "https://www.toptal.com/blog.rss").then(function (responseAndBody) { var body = responseAndBody[1]; return Q.nfcall(xml2js.parseString, body).then (function (feed) { var events = feed.rss.channel[0].item.map(function (item) { return { eventName: "Discussion of " + item.title, date: moment(item.pubDate, "DD MMM YYYY").add(1, 'day').toDate(), time: "12:00" }}); var crud = Crud.crudForEntityType('Event'); return Q.all(events.map(function (event) { return crud.find({query: {eventName: event.eventName}}).then(function (createdEvent) { if (!createdEvent[0]) { return crud.createEntity(event); } }); } )); }); }) } }; }); var server = injection.inject('allcountServerStartup'); server.startup(function (errors) { if (errors) { throw new Error(errors.join('\n')); } });

Neste arquivo, estamos definindo uma dependência chamada “DiscussionEventsImport”, que podemos usar em nosso arquivo main.js adicionando uma ação de importação no tipo de entidade “Event”.

 { id: "import-blog-events", name: "Import Blog Events", actionTarget: "all-items", perform: function (DiscussionEventsImport, Actions) { return DiscussionEventsImport.importEvents().then(function () { return Actions.refreshResult() }); } }

Como é importante reiniciar o servidor depois de fazer algumas alterações nos arquivos JavaScript, você pode matar a instância anterior e iniciá-la novamente executando o mesmo comando de antes:

 node toptal-community.js

Se tudo der certo, você verá algo como a captura de tela abaixo após executar a ação “Importar eventos do blog”.

Ação Importar eventos do blog

Até aí tudo bem, mas não vamos parar por aqui. As visualizações padrão funcionam, mas às vezes podem ser chatas. Vamos personalizá-los um pouco.

Você gosta de cartas? Todo mundo gosta de cartas! Para fazer uma visualização de cartão, coloque o seguinte em um arquivo chamado events.jade dentro do diretório app-config:

 extends main include mixins block vars - var hasToolbar = true block content .refresh-form-controller(ng-app='allcount', ng-controller='EntityViewController') +defaultToolbar() .container.screen-container(ng-cloak) +defaultList() .row: .col-lg-4.col-md-6.col-xs-12(ng-repeat="item in items") .panel.panel-default .panel-heading h3 {{item.date | date}} {{item.time}} div button.btn.btn-default.btn-xs(ng-if="!isInEditMode", lc-tooltip="View", ng-click="navigate(item.id)"): i.glyphicon.glyphicon-chevron-right |   button.btn.btn-danger.btn-xs(ng-if="isInEditMode", lc-tooltip="Delete", ng-click="deleteEntity(item)"): i.glyphicon.glyphicon-trash .panel-body h3 {{item.eventName}} +noEntries() +defaultEditAndCreateForms() block js +entityJs()

Depois disso, basta referenciá-lo da entidade “Event” em main.js como “customView: “events”.” Execute seu aplicativo e você verá uma interface baseada em cartão em vez da tabular padrão.

Entidade de evento em main.js

Conclusão

Atualmente, o fluxo de desenvolvimento de aplicativos da Web é semelhante em muitas tecnologias da Web, onde algumas operações são repetidas várias vezes. Isso realmente vale a pena? Talvez seja hora de repensar a forma como suas aplicações web são desenvolvidas?

AllcountJS fornece uma abordagem alternativa para estruturas de desenvolvimento rápido de aplicativos; você começa criando um esqueleto para o aplicativo definindo descrições de entidade e, em seguida, adiciona exibições e personalizações de comportamento em torno dele. Como você pode ver, com o AllcountJS, criamos um aplicativo simples, mas totalmente funcional, em menos de cem linhas de código. Talvez não atenda a todos os requisitos de produção, mas é personalizável. Tudo isso faz do AllcountJS uma boa ferramenta para inicializar rapidamente aplicativos da web.