paint-brush
Minha jornada de 4 anos para construir uma rede social como desenvolvedor solopor@mariostopfer
651 leituras
651 leituras

Minha jornada de 4 anos para construir uma rede social como desenvolvedor solo

por Mario Stopfer2022/10/18
Read on Terminal Reader
Read this story w/o Javascript

Muito longo; Para ler

Visa dar aos criadores de conteúdo a capacidade de se envolver com sua comunidade e contribuir para o processo de criação de conteúdo, ao mesmo tempo em que podem monetizar seu esforço, tudo em um só lugar. A ideia de construir uma plataforma de mídia social surgiu do desejo de não apenas criar conteúdo como indivíduos, mas também se envolver com a comunidade. A própria plataforma, se quisesse estar disponível para o maior número de pessoas possível, precisaria ser um site e com quase nenhuma experiência em desenvolvimento web, nada disso parecia alcançável. Pensar que isso não seria fácil seria um eufemismo do século.
featured image - Minha jornada de 4 anos para construir uma rede social como desenvolvedor solo
Mario Stopfer HackerNoon profile picture
0-item


Esta é uma história, uma exposição completa, do meu projeto solo de desenvolvimento, Comunidades imersivas - uma plataforma de mídia social para criadores de conteúdo, que iniciei no início de 2018 e concluí em 2022.


Espero que sirva de guia para quem está iniciando um grande projeto ou está no meio da construção de um e precisa de motivação para continuar.

A ideia

No início de 2018, vozes fracas começaram a surgir. As pessoas começaram a expressar sua opinião sobre o estado da web e das mídias sociais em geral.


Algumas pessoas estavam insatisfeitas com o Facebook e suas políticas de privacidade do usuário, outras reclamavam do YouTube e suas práticas de monetização, enquanto outras ainda voltavam sua opinião contra as próprias plataformas onde expressavam sua opinião, como o Medium.com.


Mas como seria a alternativa para tudo isso?

O Projeto Solo-Dev – História em formação

A ideia de construir uma plataforma de mídia social surgiu do desejo de dar aos criadores de conteúdo a capacidade, não apenas de criar conteúdo como indivíduos, mas também de se envolver com sua comunidade e contribuir para o processo de criação de conteúdo, podendo monetizar seu esforço ; tudo em um só lugar.


A plataforma deve ser de uso gratuito e qualquer pessoa deve poder participar. Escusado será dizer que os criadores, que investem tempo no seu trabalho, que educa e entretém pessoas em todo o mundo, também devem poder viver daquilo que gostam de fazer.


Isso resolveria todos os problemas que enfrentamos hoje online? Não, mas é uma alternativa, e é isso que conta. É uma semente, que pode com o tempo, se transformar em algo lindo.


Como tecnólogo, também me perguntei se tudo isso poderia ser feito por uma única pessoa.


Seria realmente possível para uma única pessoa enfrentar o desafio e fornecer uma plataforma robusta de nível empresarial que as pessoas adorariam usar e, em caso afirmativo, o que seria necessário para fornecer tal sistema?


Para ser completamente honesto, eu não tinha uma resposta para essas perguntas, mas decidi aceitar o desafio.


Naqueles primeiros dias de 2018, eu tinha cerca de 6 anos de experiência profissional como desenvolvedor de software, que era principalmente .NET de back-end e algum WPF no front-end.


Também moro em Tóquio, para onde me mudei há menos de 3 anos, e fazer horas extras era a norma; a ideia de assumir um trabalho adicional, para todos os efeitos, não era razoável, para dizer o mínimo.


A própria plataforma, se quisesse estar disponível para o maior número de pessoas possível, precisaria ser um site e com quase nenhuma experiência em desenvolvimento web, nada disso parecia alcançável.


O fato de parecer impossível foi exatamente o motivo pelo qual decidi começar mais cedo ou mais tarde. Seria tudo isso uma gigantesca perda de tempo? Só o tempo diria, mas quanto mais cedo eu começasse, mais cedo descobriria a resposta.


Então, em 1º de fevereiro de 2018 , comecei a planejar.

2018 - O Plano

A primeira coisa que decidi fazer foi dizer a mim mesmo que pensar que isso não seria fácil seria o eufemismo do século. Eu nunca tinha feito nada assim antes, então literalmente não tinha ideia no que estava me metendo.


Isso significava nenhum hobby ou qualquer coisa parecida com uma vida até que isso fosse concluído. Trabalhar horas extras no meu emprego de tempo integral e depois voltar para casa e trabalhar neste projeto vai se tornar a nova norma.


Eu tiraria férias e viajaria em algum momento? Sim, mas eu teria que passar essas férias principalmente trabalhando também.


Se aprendi alguma coisa com minha experiência anterior, é que ser organizado é o que faz seu projeto crescer ou fracassar e o mantém no caminho certo. Quanto mais você preparar e projetar antecipadamente, menos tipo de trabalho de tentativa e erro precisará fazer mais tarde.


Pensar e planejar são certamente mais fáceis do que construir e consomem menos tempo, então quanto mais eu faço na fase de planejamento, menos eu teria que descobrir durante a fase de desenvolvimento real, que requer mais recursos em termos de tempo.


Recursos eram algo que eu não tinha, então tive que compensar com planejamento e design. Eu também sabia que a web era o lugar para ir quando eu travasse, mas decidi que não usaria o Stack Overflow para este projeto e faria novas perguntas.


O raciocínio por trás dessa decisão foi bastante simples. Visto que haverá muito a aprender aqui, se eu apenas for pedir a alguém para resolver todos os meus problemas, não ganharei nenhuma experiência.


Quanto mais o projeto avança, mais difícil se torna e não terei adquirido nenhuma experiência para enfrentá-lo sozinho.


Portanto, decidi apenas pesquisar na web as respostas já existentes, mas não fazer novas perguntas para resolver meus problemas. Assim que o projeto fosse concluído, eu poderia me envolver com o Stack Overflow novamente, mas para esse objetivo de desenvolvimento específico, estaria fora dos limites.


Eu utilizaria a abordagem OOAD para projetar o sistema que queria construir. O sistema me diria como cada parte funciona e como ela interage com outras partes do sistema. Além disso, eu extrairia regras de negócios que posteriormente implementaria no código.


Comecei então a fazer anotações e percebi que havia dois pontos principais nos quais eu deveria me concentrar:


  • Projeto de design
  • pilha de tecnologia


Como eu sabia que o dia tinha apenas 24 horas, e a maior parte delas eu passaria no trabalho ou viajando, tive que otimizar meu tempo com cuidado.

Projeto de design

Para a parte de Design do Projeto, para maximizar meu tempo, decidi ver quais produtos já funcionavam bem e usá-los como fonte de inspiração.


Claramente, o sistema precisa ser rápido e acessível a todos e como não tenho tempo para escrever código para múltiplas plataformas, a web foi a resposta.


Então me virei para o design. A Apple, conhecida por suas práticas de design bem aceitas, foi uma fonte imediata de inspiração. Em seguida, recorri ao Pinterest e decidi que esse era o design mais simples possível que funcionava bem. A ideia por trás da minha decisão era o velho ditado.


O conteúdo é rei. —Bill Gates, 1996.


Isso me deu a ideia de remover o máximo possível de detalhes de design desnecessários e focar na apresentação do conteúdo. Se você pensar sobre isso, as pessoas irão ao seu site uma vez porque ele tem um bom design, mas não voltarão a menos que você tenha um bom conteúdo.


Isso teve o efeito de reduzir o tempo necessário para projetar o front-end.


O próprio sistema tinha que ser simples. Cada usuário deve ser capaz de criar e possuir sua própria comunidade. Outros usuários devem poder ingressar como membros e escrever artigos para contribuir com conteúdo para esta comunidade. Esse recurso seria inspirado na Wikipedia, onde muitos usuários podem editar o mesmo artigo.


Se houver muitas pessoas envolvidas em um determinado tópico, isso nos diz que o que temos em mãos é uma comunidade e, como tal, deve ser separada do resto.


No que diz respeito aos recursos, os usuários precisariam ser capazes de escrever não apenas artigos regulares e conectá-los como a Wikipedia, mas também escrever resenhas, o que exigiria um tipo diferente de artigo.


Assim, os artigos regulares seriam chamados de “Interesses” e seriam de natureza factual e informativa, com qualquer pessoa podendo editá-los.


Por outro lado, as “Comentários” seriam baseadas em cada interesse, e somente o autor do comentário poderia editá-lo.


Resumindo, as pessoas podem escrever colaborativamente sobre um filme, digamos “The Matrix”, e editar esse artigo sempre que quiserem. Eles poderiam adicionar qualquer informação factual ao artigo que desejassem.


Então, cada usuário poderia escrever sua própria crítica desse filme. Naturalmente, o artigo original mostraria uma lista de todas as resenhas escritas para este filme.


Ficou claro para mim que eu também tinha que adicionar mais dois recursos importantes. A primeira seria uma opção de busca que buscaria artigos em cada comunidade. O outro recurso eram as recomendações, que os usuários receberiam com base no que gostassem quando rolassem até o final do artigo.


Cada usuário também deve poder postar atualizações curtas ou “Atividades” em seu perfil, o que funcionaria como um Mural no Facebook. Este foi o esboço básico que fiz antes de começar a pensar nas tecnologias que poderia usar para realmente entregar o projeto.

A pilha de tecnologia

A segunda coisa em que me concentrei foi o Tech Stack. Como já decidi que iria construir um projeto baseado na web, decidi anotar todas as tecnologias populares e modernas mais usadas na época. Eu então escolheria aqueles que tecnicamente fossem mais próximos do que já usei em minha carreira para gastar o mínimo de tempo possível aprendendo novas tecnologias.


Além disso, a ideia que mais me levou a pensar durante essa fase foi tomar decisões de design que exigissem que eu escrevesse o mínimo de código possível, economizando tempo.



Depois de uma extensa pesquisa, estabeleci-me no seguinte Tech Stack principal:



Além disso, ao escolher os serviços SaaS, eu economizaria ainda mais tempo, pois não precisaria implementar esses recursos sozinho. Os serviços que decidi foram os seguintes.



Essas foram as principais tecnologias que tive que aprender o mais rápido possível para começar a trabalhar no projeto. Todo o resto teria que ser aprendido ao longo do caminho.


Neste ponto, comecei a aprender novas tecnologias. Para os principais e mais importantes, comecei a ler os seguintes livros.



Decidi que trabalharia em meu emprego de período integral durante o dia, mas as noites eram um jogo justo. Quando chegava em casa, podia estudar até dormir. No que diz respeito ao sono em si, eu seria reduzido para 6 horas para ganhar mais tempo para estudar.


Inicialmente, previ que precisaria de apenas um ano para construir este projeto. Então, depois de quase um ano aprendendo novas tecnologias, era dezembro de 2018 e acabei de terminar o material de leitura principal, tendo escrito exatamente 0 linhas de código.

O Desenvolvimento Começa

Em dezembro de 2018, finalmente comecei a desenvolver. Eu configurei o Visual Studio Code e comecei a construir meu ambiente de desenvolvimento.


Ficou claro para mim desde o primeiro dia que não conseguiria manter todos os servidores necessários para um projeto tão grande. Não apenas do ponto de vista técnico, mas também do lado do orçamento. Assim, tive que encontrar uma solução.


Felizmente, encontrei a solução na forma de DevOps e na abordagem Serverless para infraestrutura de back-end.


Ficou imediatamente claro para mim, mesmo antes de essas abordagens se espalharem como estão hoje, que se podemos descrever algo sucintamente com código, também podemos automatizá-lo, economizando tempo e recursos.


Com o DevOps, eu unificaria e automatizaria o desenvolvimento de front-end e back-end, enquanto com o Serverless, removeria a necessidade de manutenção do servidor e também reduziria o custo de operação.


Essa filosofia claramente seguia o meu pensamento e a primeira coisa que decidi configurar foi um pipeline de CI/CD com o Terraform.


O design consistia em 3 ramos, Desenvolvimento, UAT e Produção. Eu trabalharia todos os dias e, quando o trabalho estivesse concluído, simplesmente confirmaria as alterações na ramificação de desenvolvimento no AWS CodeCommit e isso acionaria o AWS CodeBuild para criar meu projeto.


Eu manteria um executável do Terraform no repositório, tanto para macOS, para testes locais, quanto para Linux, para compilações no CodeBuild.


Uma vez iniciado o processo de construção, o executável do Terraform seria invocado dentro do CodeBuild e pegaria os arquivos de código do Terraform, construindo assim minha infraestrutura na AWS.


Todas essas partes teriam que ser conectadas e automatizadas, o que fiz usando o AWS CodePipeline, que moveria o código do CodeCommit para o CodeBuild toda vez que eu fizesse um commit.


Isso me ajudaria a manter meu código e minha infraestrutura sincronizados. Sempre que eu estivesse pronto para seguir em frente, simplesmente mesclaria a ramificação de Desenvolvimento ao UAT ou o UAT à Produção para sincronizar meus outros ambientes.

2019 - Sucessos do COVID-19

Assim que terminasse o pipeline de CI/CD para o back-end, passaria a configurar o site real localmente para começar a desenvolver o front-end.


A etapa inicial de configuração do front-end foi criar um projeto baseado no Aurelia. A decisão de usar Aurelia e não React, que foi e ainda é a escolha mais popular para um framework JavaScript, foi por causa do padrão MVVM .


O padrão MVVM é usado com destaque em aplicativos de desktop WPF com os quais tive experiência. Portanto, aprender React levaria mais tempo do que simplesmente desenvolver o que eu já sabia.


Por outro lado, a decisão de usar o Aurelia e não o Angular ou o Vue foi baseada na filosofia por trás do Aurelia, que é – fazer com que o framework saia do seu caminho. Em outras palavras, não há Aurelia em Aurelia.


O que você está usando basicamente, ao desenvolver com o Aurelia, é HTML, JavaScript e CSS, com alguns recursos adicionais, como vinculação de dados a atributos, com os quais eu já estava familiarizado.


Portanto, a decisão foi final. Em seguida, vindo do mundo C# que é digitado estaticamente, decidi usar TypeScript em vez de JavaScript.


Em seguida veio o WebPack. Havia uma clara necessidade de dividir o aplicativo em partes que facilitariam o carregamento lento. Como eu já sabia que haveria muitos recursos no aplicativo, era imperativo construí-lo como um SPA que pudesse carregar peças sob demanda.


Caso contrário, a experiência do usuário seria inaceitável.


Para facilitar a configuração do WebPack, decidi adicionar o Neutrino.JS à mistura e usar sua interface fluente para configurar o WebPack.


Era bem sabido que a navegação na web móvel estava em ascensão mesmo em 2019. Para estar pronto para a web moderna, a abordagem de desenvolvimento foi definida da seguinte forma.



Isso foi facilitado por duas tecnologias principais – Tailwind CSS e PWA. Na hora de estilizar, para simplificar ainda mais o sistema, adicionei o Tailwind CSS, que acabou sendo uma das melhores decisões que tomei, já que é mobile-first por padrão.


Além disso, é baseado em utilitário, portanto, tem alta capacidade de reutilização, exatamente o que eu estava procurando.


Além disso, como eu estava tentando oferecer uma experiência nativa, mas não tinha tempo para criar aplicativos nativos, decidi ir para a próxima melhor coisa – um aplicativo que pode ser instalado diretamente do navegador e funcionar offline.


Isso é o que os Progressive Web Apps (PWA) visam fornecer ao usuário, mas uma configuração manual seria muito propensa a erros e demorada, por isso decidi usar o Google WorkBox , que possui os recursos de instalação, offline e cache do Service Worker construídas em.


Uma vez que o núcleo foi configurado, era hora de levá-lo para um passeio online. Ficou claro que o site deveria estar acessível a qualquer pessoa o tempo todo, interrupções não pertencem a um sistema moderno, então decidi configurar o sistema da seguinte maneira.


Em primeiro lugar, os arquivos HTML e JS serão servidos de um balde AWS S3. Para disponibilizá-lo globalmente com baixa latência, ele será liderado pela AWS CloudFront CDN Network.


Um pequeno contratempo, neste caso, foi quando também decidi usar o Serverless Framework porque configurar as funções do AWS Lambda era mais fácil do que com o Terraform, então introduzi uma nova tecnologia que tive que cuidar.


Feita a configuração, comprei os domínios no AWS Route 53, que usei para testes e o domínio de produção – https://immersive.community .


A ideia por trás do nome vem de uma rede baseada na comunidade com o mesmo nome - Mighty Networks . Eu simplesmente decidi pela palavra "Imersivo" já que, naquela época, ela começou a ser uma tendência no Google.


Uma vez que „immersive.networks“ e „immersive.communities“ já estavam disponíveis, optei por „immersive.community“.


Agora que eu tinha o front-end lançado, era hora de começar a trabalhar no banco de dados. Embora eu estivesse acostumado com bancos de dados relacionais baseados em SQL no passado, eles eram claramente muito lentos para este projeto específico, então decidi usar um banco de dados NoSQL. Escolhi o AWS DynamoDB por causa de sua oferta sem servidor.


Para acessar o banco de dados, escolhi o AWS AppSync, que é uma implementação GraphQL gerenciada e também sem servidor.

Um sistema multilocatário

Neste ponto, era hora de começar a resolver um dos maiores problemas que enfrentei, a saber:


Como permitir que os usuários participem de várias comunidades, mas mantenha os dados privados ou restritos, em cada comunidade, separados uns dos outros?


A maneira mais fácil de resolver esse problema seria criar vários bancos de dados, mas isso tem limitações claras porque, em algum momento, ficaríamos sem bancos de dados que poderíamos criar.


Acontece que cada conta da AWS tem limitações em quantos recursos você pode criar, então essa não era uma solução viável.


Eu finalmente resolveria esse problema atribuindo uma coluna de tipo a cada entrada no banco de dados do DynamoDB. Cada usuário teria seu tipo definido como „user“, enquanto cada comunidade seria simplesmente definida como „web“.


Eu indicaria então que um usuário ingressou em uma comunidade adicionando uma nova linha onde a chave desta linha é designada como „user#web_user#web“. Como o usuário e o nome da comunidade seriam exclusivos, essa chave também seria exclusiva, portanto, o usuário não poderia ingressar na comunidade várias vezes.


Se eu quiser executar uma ação que só pode ser realizada se um usuário ingressar em uma comunidade, eu simplesmente usaria as funções de pipeline fornecidas pelo AppSync, que permitem consultar várias linhas no DynamoDB.


Em seguida, eu consultaria e verificaria se o usuário é membro de uma comunidade e, somente se for, permitiria que o usuário executasse a ação.


Isso resolveu o problema de multilocação , mas um dos maiores problemas a resolver estava chegando.

Arquitetura altamente disponível

Nem é preciso dizer que um sistema de nível empresarial é construído com tolerância a falhas e alta disponibilidade em mente. Isso permitiria que os usuários continuassem usando o sistema, mesmo que houvesse falhas em alguns de seus componentes.


Se quisermos implementar tal sistema, devemos fazê-lo com redundância em mente.


Minha pesquisa me levou à solução ideal neste caso, que é uma arquitetura Active-Active Highly Available . Acontece que a maioria dos serviços na AWS já está altamente disponível, mas o próprio AppSync não. Assim, decidi criar minha própria implementação.


A web não oferecia uma solução para esse problema, então tive que construir a minha. Comecei a pensar globalmente. Ou seja, meus visitantes viriam de regiões diferentes e, se eu colocasse meu AppSync nos EUA, os visitantes da Ásia teriam uma latência maior.


Resolvi o problema de latência e alta disponibilidade da seguinte maneira. Decidi criar 10 APIs AppSync diferentes em todas as regiões disponíveis naquele momento. Atualmente, as APIs estão localizadas nos EUA, Ásia e Europa.


Além disso, cada API precisa estar conectada ao banco de dados DynamoDB correspondente, localizado na mesma região. Assim, criei mais 10 tabelas adicionais do DynamoDB.


Felizmente, o DynamoDB oferece um recurso de tabela global que copia os dados entre as tabelas do DynamoDB conectadas e as mantém sincronizadas.


Agora, independentemente de onde um usuário grava no banco de dados, um usuário em uma região diferente poderá ler as mesmas informações, depois que os dados forem sincronizados.


A questão que surgiu agora foi a seguinte.


Como os usuários seriam encaminhados para a API mais próxima? Não apenas isso, mas se fosse o caso de uma API falhar, como encaminharíamos imediatamente a chamada para a próxima API disponível?


A solução surgiu na forma de funções CloudFront e Lambda@Edge . É um recurso incrível do CloudFront que pode acionar funções do Lambda@Edge na região onde o chamador está localizado.


Deve ficar claro que, se soubermos onde o usuário está localizado, podemos escolher a API, dentro da função Lambda@Edge, com base na origem da chamada.


Além disso, também podemos obter a região da função Lambda@Edge em execução, permitindo-nos escolher a API do AppSync nessa mesma região. A primeira etapa para implementar essa solução foi fazer proxy de chamadas do AppSync por meio do CloudFront.


Portanto, agora, as chamadas seriam feitas diretamente para o CloudFront em vez do AppSync.


Em seguida, tive que extrair a chamada HTTP dos parâmetros do CloudFront dentro da função Lambda@Edge. Depois de obter a região e a consulta do AppSync, que foi extraída dos parâmetros do CloudFront, eu faria uma nova chamada HTTP para a API do AppSync correspondente.


Quando os dados retornassem, eu simplesmente os passaria de volta para o CloudFront por meio da função Lambda@Edge. O usuário obteria então os dados solicitados.


Mas ainda não resolvemos o requisito Ativo-Ativo. O objetivo agora era detectar o ponto em que uma API está indisponível e depois mudar para outra. Resolvi esse problema verificando o resultado da chamada do AppSync. Se não foi uma resposta HTTP 200, a chamada claramente falhou.

Eu simplesmente escolheria outra região em uma lista de todas as regiões disponíveis e faria uma chamada para a próxima API do AppSync nessa região. Se a chamada for bem-sucedida, retornamos o resultado e, se falhar, tentamos a próxima região até obter sucesso.


Se a última região também falhar, simplesmente retornamos o resultado com falha.


Isso é simplesmente a implementação Round Robin da arquitetura Active-Active Highly Available. Com esse sistema agora em vigor, implementamos os três recursos a seguir:


  • Latência global baixa
  • Balanceamento de carga baseado em região
  • Alta disponibilidade ativa-ativa


Claramente, temos baixa latência, em média, para cada usuário global, já que cada usuário será roteado para a região mais próxima de onde está invocando a chamada para a API. Também temos balanceamento de carga baseado em região, porque os usuários serão roteados para várias APIs em sua região.


Por fim, temos uma Alta Disponibilidade Ativa-Ativa, pois o sistema permanecerá funcional mesmo que alguma de suas APIs ou bancos de dados falhem, pois os usuários serão encaminhados para a próxima API disponível.


Na verdade, não seria suficiente simplesmente lidar com alta disponibilidade para as APIs. Eu queria tê-lo para todos os recursos, incluindo os arquivos HTML e JavaScript fornecidos pelo CloudFront.


Desta vez, usei a mesma abordagem, mas criei 16 baldes AWS S3. Cada bucket atenderia aos mesmos arquivos, mas estaria localizado em regiões diferentes.


Nesse caso, quando o usuário visita nosso site, o navegador faz várias chamadas HTTP para HTML, JS, JSON ou arquivos de imagem. O Lambda@Edge teria, neste caso, que extrair a URL que estava sendo chamada no momento.


Uma vez que eu tivesse a URL, eu teria que determinar o tipo de arquivo desse arquivo e fazer uma nova chamada HTTP para o bucket S3 correspondente na região.


Desnecessário dizer que, se a chamada for bem-sucedida, retornaremos o arquivo, enquanto se falhar, usaremos o mesmo sistema de roteamento anterior, fornecendo assim também um sistema Ativo-Ativo Altamente Disponível.


Com este sistema agora em vigor, atingimos outro marco e colocamos outra peça fundamental para nossa infraestrutura de nível empresarial. Este foi de longe o sistema mais difícil de desenvolver e levou 3 meses para ser concluído.


Acontece que tínhamos mais problemas para resolver e esse sistema se mostraria útil novamente.

Manifesto PWA Dinâmico

O PWA é uma tecnologia da web incrível e será usado por mais sites com o passar do tempo, mas em 2019, as coisas estavam apenas começando.


Como decidi atender a cada comunidade em um subdomínio separado, também queria dar aos usuários a capacidade de instalar seu PWA de marca, com um título e um ícone de aplicativo apropriados.

Acontece que o arquivo PWA Manifest, que define todos esses recursos, não funciona com base em subdomínios. Ele só pode definir um conjunto de valores com base no domínio do qual é servido.


O fato de eu já poder fazer proxy de chamadas HTTP usando CloudFront e Lambda@Edge também foi útil aqui.


O objetivo agora era fazer proxy de cada chamada para o arquivo manifest.json. Em seguida, dependendo de qual subdomínio a chamada está vindo, para obter os dados da comunidade correspondente, que seria o ícone do aplicativo, título etc., preencheríamos dinamicamente o manifest.json com esses valores.

O arquivo seria então fornecido ao navegador e a comunidade seria instalada como um novo aplicativo PWA no dispositivo do usuário.

Movendo-se para o front-end

Depois de descobrir essas etapas cruciais, era hora de começar a trabalhar no front-end. De acordo com o requisito anterior baseado em subdomínio, também tivemos que descobrir como carregar uma comunidade diferente e seus dados com base no subdomínio.


Isso também exigiria o carregamento de diferentes layouts de sites, que seriam usados em cada comunidade.


Por exemplo, a página inicial precisaria listar todas as comunidades disponíveis, enquanto outros subdomínios precisariam listar os artigos de cada uma dessas comunidades.


Nem é preciso dizer que, para resolver esse problema, não podemos simplesmente criar vários sites diferentes do zero. Isso não seria dimensionado, portanto, precisaríamos reutilizar o máximo possível de controles e recursos.


Esses recursos seriam compartilhados entre esses dois tipos de comunidade e seriam carregados somente se fossem necessários. Para maximizar a reutilização do código, defini todos os controles como 4 tipos diferentes.


  • Componentes
  • Controles
  • Páginas
  • Comunidades


Os menores elementos HTML personalizados como <button> e <input> foram definidos como componentes. Em seguida, poderíamos reutilizar esses componentes em controles, que são conjuntos desses elementos menores, por exemplo, o controle de informações do perfil exibiria a imagem do perfil do usuário, nome de usuário, seguidores, etc.


Seríamos então capazes de reutilizar esses elementos em elementos de nível superior, que no nosso caso são — Páginas.


Cada página representaria basicamente uma rota, por exemplo, a página Trending, onde poderíamos ver todas as atividades ou a página Interest onde seria exibido o texto real do artigo. Eu então comporia cada página a partir desses controles menores.


Por fim, os elementos de nível mais alto seriam definidos em Comunidades, com base em seu tipo. Cada elemento da comunidade simplesmente definiria todas as páginas de nível inferior necessárias.


O roteador Aurelia foi útil neste caso, visto que você pode carregar as rotas dinamicamente. A implementação foi realizada da seguinte maneira.


Independentemente do subdomínio, quando o site começa a carregar, registramos as duas ramificações principais, que são implementadas como componentes do Aurelia. Estes representam dois tipos diferentes de comunidades. Em seguida, defini dois tipos de web ou layouts diferentes:


  • Principal
  • Artigo


O tipo “principal” simplesmente representa o layout do site que será carregado quando o usuário acessar a página principal https://immersive.community . Aqui, mostraremos todas as comunidades com todos os controles correspondentes.

Por outro lado, uma vez que o usuário navegue para um subdomínio, precisaríamos carregar um layout diferente. Em outras palavras, em vez de comunidades, carregaríamos artigos e recursos e rotas correspondentes, por exemplo, a capacidade de publicar e editar artigos.


Isso habilitaria ou desabilitaria certas rotas, com base no tipo de comunidade em que estávamos localizados.


Nossa configuração de Aurelia e WebPack divide o JavaScript em pedaços apropriados, de modo que rotas e recursos que não serão necessários não sejam carregados, melhorando assim a velocidade e economizando largura de banda.

Neste ponto, uma vez que determinamos em qual subdomínio estamos localizados, carregaríamos a comunidade e os dados do usuário para esta comunidade específica, tendo assim implementado a solução com sucesso.

O Layout de Alvenaria

Foi meu raciocínio que deveríamos tentar manter o design o mais simples possível. Portanto, como os usuários acessam o site em busca do conteúdo, vamos nos concentrar em exibir o conteúdo, em vez de recursos secundários.


Os artigos precisariam ser exibidos em listas, mas não deveriam parecer obsoletos, portanto, decidi que cada artigo consistiria simplesmente no seguinte.


  • uma foto de capa
  • Título do artigo
  • Categoria do artigo
  • Data em que o artigo foi postado ou editado
  • Perfil do autor


A principal maneira de garantir que a lista de artigos não parecesse obsoleta foi garantir que o usuário pudesse escolher a proporção de cada foto de capa para o artigo. A inspiração veio da forma como o Pinterest exibe seus pins, então cada artigo também teria uma proporção diferente.


Isso exigiu que eu implementasse o layout de alvenaria, que não pode ser escolhido pronto para uso nem no CSS Grid nem no FlexBox.

Felizmente, existem várias implementações úteis de código aberto que experimentei e usei para o layout. Tive que adicionar várias melhorias, como carregar dados paginados e dimensionar com o tamanho da tela.


E depois…


Em novembro de 2019, começaram a aparecer os primeiros sinais de COVID-19 . O mundo logo entrou em turbulência e ninguém tinha ideia do que estava acontecendo, mas isso mudaria o mundo e como interagimos uns com os outros de maneiras que ninguém poderia imaginar.

Logo depois, começaríamos a trabalhar em casa. Isso teria um grande impacto no meu processo de desenvolvimento, já que não precisava mais viajar para trabalhar. Ironicamente, o mundo desabou, enquanto eu tinha o descanso de que precisava!

Interesses e comentários

De volta ao mundo do desenvolvimento, a ideia por trás de escrever artigos sobre comunidades imersivas foi baseada na colaboração. Para tanto, optei pela Wikipédia como base para o esforço colaborativo.


Além disso, sites comunitários como Amino Apps e Fandom.com e o site de blogs HubPages.com também desempenharam seu papel.


Escrever postagens de blog como uma pessoa solteira pode ser um bom começo, mas podemos ir além disso fazendo com que as pessoas escrevam artigos juntas.


Depois de adicionar hiperlinks ao texto e conectar esses artigos escritos por pessoas diferentes, basicamente criamos uma comunidade onde as pessoas se reúnem para se envolver em tópicos de seu interesse.


Eu decidi definir dois tipos de artigos, ou seja,


  • Interesses
  • Avaliações


Os interesses seriam artigos curtos, aprox. 5.000 artigos de caracteres que descrevam de fato qualquer interesse particular que uma pessoa possa ter. Em seguida, cada pessoa poderia escrever uma resenha, com uma classificação desse interesse específico.


A página de interesse principal conteria uma referência a todas as resenhas escritas para esse interesse específico. A principal diferença seria que qualquer pessoa pode editar os Interesses, mas apenas a pessoa que escreveu uma Revisão poderia editá-la, adicionando assim um toque pessoal a cada artigo.


Aqui, nossa decisão anterior de usar o CloudFront para fazer proxy de chamadas do AppSync voltou a nos incomodar. Acontece que o CloudFront oferece suporte apenas a comprimentos de string de consulta de até 8.192 bytes , portanto, não podemos salvar dados maiores do que isso.


No que diz respeito a cada artigo, cada interesse pode ser apreciado e comentado. Assim, os usuários puderam se reunir e discutir como cada artigo será escrito e editado. Além disso, cada Interesse pode ser adicionado à página de perfil do usuário, para acesso rápido.


Depois que todos esses recursos foram implementados, chegou o final do ano. A situação parecia boa e eu tinha certeza de que o projeto seria concluído no próximo ano. Essa suposição não se mostrou precisa, para dizer o mínimo.

2020 - A toda velocidade

O ano começou mais ou menos bem. A economia ainda resistiu um pouco, mas depois de um tempo, começou a cair. Os mercados começaram a responder à pandemia e, da mesma forma, os preços começaram a subir.


O início de 2020 foi o ano em que trabalhei muito, mas não tinha um produto realmente funcional. Ainda havia muito a ser feito, mas eu estava confiante no resultado, então continuei avançando.


No meu trabalho diurno, o horário de trabalho também foi estendido e tivemos que cumprir nossos prazos mais rápido do que o normal. Escusado será dizer que tive de reorganizar a minha agenda e a única forma de poupar mais tempo seria dormir apenas 4 horas por noite.


A ideia era chegar em casa por volta das 18h ou 19h e depois ir direto trabalhar no projeto. Eu poderia trabalhar até 3 ou 4 da manhã e depois dormir. Eu então teria que acordar por volta das 7 da manhã e chegar rapidamente ao meu trabalho diário.


É claro que isso não seria suficiente para dormir todas as noites, mas imaginei que compensaria esse tempo dormindo 12 horas durante os fins de semana. Também agendei todos os dias de férias e feriados para o trabalho.


O novo sistema foi configurado e eu procedi conforme planejado.

O editor de descontos

Nem é preciso dizer que um site de redação de artigos deve ter um editor de texto fácil de usar. Durante o início de 2020, o Markdown surgiu como uma forma muito popular de escrever texto. Decidi que as Comunidades Imersivas teriam de apoiá-lo fora da caixa.


Isso não apenas exigiria que eu escrevesse o Markdown, mas também o exibisse como HTML. A biblioteca Markdown-It seria usada para transformar Markdown em HTML. Mas havia um requisito adicional, então a lista completa de diferentes mídias que devemos exibir é a seguinte.


  • Texto
  • Imagem
  • Vídeo
  • incorpora


Além disso, imagens e vídeos devem ser exibidos como um controle deslizante onde os usuários podem deslizar as imagens como no Instagram. Isso exigiria uma mistura de Markdown e outros elementos HTML.


O editor seria dividido em várias partes onde havia dois tipos de entradas, o campo de texto e o campo de mídia. Cada campo no editor pode ser movido para cima ou para baixo, o que foi bastante fácil de implementar usando Sortable.js .


Quando se trata de campos de entrada, o campo Markdown foi simples o suficiente para criar com um elemento <textarea>. O editor também carrega o Inconsolata Google Font que dá ao texto, que está sendo digitado, a aparência de uma máquina de escrever.

Além disso, para realmente estilizar o texto, foi implementada uma barra que adiciona Markdown ao texto. O mesmo foi feito usando atalhos de teclado usando Mousetrap.js . Agora, podemos facilmente adicionar texto em negrito na forma de tags ** Markdown usando Control+B, etc.


Ao digitar, é natural que o elemento <textarea> se expanda conforme a quantidade de texto aumenta, então usei a biblioteca Autosize.js para implementar esse recurso.


O campo de mídia seria capaz de exibir imagens, vídeos ou iframes que conteriam sites incorporados. O tipo de campo de mídia mudaria com base no tipo de mídia em si. Usei o Swiper.js para implementar o deslizamento entre as imagens.


O componente de vídeo foi implementado usando a biblioteca Video.js .


Os problemas começaram a surgir quando chegou a hora de fazer o upload da mídia. No que diz respeito às imagens, foi fácil usar a API de arquivos do navegador para carregar fotos e vídeos do seu dispositivo. O que eu tive que fazer foi primeiro transformar as imagens, que poderiam estar no formato HEIC em JPEG.


Em seguida, eu os compactaria antes de enviá-los para o back-end. Felizmente, as bibliotecas Heic-Convert e Browser-Image-Compression serviram bem a esse propósito.


Outro problema surgiu quando tive que escolher a proporção correta da imagem e cortá-la antes de fazer o upload. Isso foi implementado usando o Cropper.JS , mas, infelizmente, não funcionou imediatamente no navegador Safari.


Passei muito tempo configurando o CSS apropriado para que a imagem não transbordasse do contêiner. No final, o usuário pode facilmente carregar uma imagem de seu dispositivo, aumentar e diminuir o zoom e também recortar a imagem antes de carregá-la.


Depois que tudo estiver concluído, a mídia será carregada no Cloudinary, que é um serviço de gerenciamento de arquivos de mídia.


Chegou a hora de juntar tudo isso e mostrar ao usuário na forma de artigos. Tive a sorte de Aurelia ter um elemento <compose> que pode carregar HTML dinamicamente.


Portanto, dependendo do tipo de entrada, eu carregaria elementos de mídia ou elementos Markdown, que seriam transformados em HTML.


Esse HTML teria então que ser estilizado com CSS, principalmente as tabelas HTML que eu transformaria dependendo do tamanho da tela. Em telas maiores, as tabelas seriam exibidas em seu layout horizontal regular, enquanto em telas menores, elas seriam exibidas em um layout vertical.


Isso exigiria uma abordagem orientada a eventos que nos diria quando e como o tamanho da tela está mudando. A melhor biblioteca para usar neste caso foi RxJs , que lidava com os eventos de "redimensionamento", e pude formatar a tabela de acordo.

Melhorando a entrada de dados

Voltei então aos artigos. Tive que mudar a maneira como os artigos eram salvos no banco de dados, pois várias pessoas podiam modificar o artigo ao mesmo tempo.


Eu então salvaria o novo artigo como um tipo de artigo inicial, mas os dados reais de cada artigo seriam salvos como uma versão. Eu seria capaz de rastrear qual usuário e quando cada artigo foi alterado.


Isso me permitiu evitar que uma nova versão fosse salva se o usuário não carregasse a versão mais recente primeiro. Além disso, se uma determinada atualização for inadequada, ela poderá ser desativada e uma versão anterior ficará visível novamente. Rascunhos para cada artigo seriam salvos da mesma maneira.


No que diz respeito à entrada de dados real, decidi implementá-la como um pop-up. Os próprios pop-ups não apareceriam simplesmente na tela, mas deslizariam de baixo para cima. Além disso, seria possível deslizar dentro do pop-up.


Para isso, reutilizei a biblioteca Swiper.Js, enquanto todas as outras animações foram feitas usando a biblioteca Animate.CSS .


O pop-up não era simples de implementar porque exigia escala com o tamanho da tela. Assim, em telas maiores, ocuparia 50% da largura da tela enquanto em telas menores ocuparia 100% da largura.


Além disso, em alguns casos, como na lista de seguidores, implementei o scroll para ficar dentro do pop-up. Isso significa que a lista que estávamos rolando não parava no topo, mas desaparecia ao rolar.


Também adicionei mais estilo e esmaeci o plano de fundo e desativei a rolagem ou clique fora do pop-up. Por outro lado, o pop-up Visualização do sistema de edição de artigos se move com a tela.


Isso foi inspirado no aplicativo Atalhos da Apple e em como seus pop-ups aparecem, o que também vale para os botões de pílula e títulos acima dos elementos.

A barra de navegação

Um dos recursos de interface do usuário mais importantes que implementei foi inspirado ainda mais no iPhone, que é sua barra de navegação. Percebi que quase todos os aplicativos móveis têm uma barra de navegação bastante básica, com ícones simples e pequenos que realmente não se encaixam no design geral do aplicativo.


Decidi simplesmente replicar a barra do iOS e usá-la em todo o site. Escusado será dizer que não deve estar sempre visível, mas deve desaparecer quando rolamos para baixo e aparecer quando rolamos para cima.


Quando o usuário está rolando para baixo, assumimos que ele está interessado no conteúdo e não vai sair da página atual, então podemos ocultar a barra.


Por outro lado, se o usuário estiver rolando para cima, ele pode estar procurando uma maneira de sair da página, então podemos mostrar a barra novamente.


Existem quatro botões na barra e permitem ao usuário navegar pelas quatro partes principais do site. O botão Início navega para a página inicial de cada comunidade. O botão Tendências navega para a página Tendências, onde o usuário pode ver todas as atividades recentes que outros usuários postaram.


O próximo botão é o botão Engage, que navega para a lista de todos os recursos e configurações que a comunidade oferece. Por fim, o botão Perfil nos leva à nossa página de perfil.


Também era necessário levar em consideração telas maiores, então a barra realmente se move para o lado direito da tela quando exibida em uma tela grande. Torna-se pegajoso e não se move para lugar nenhum naquele ponto.

Processamento em lote em tempo real

Depois que o trabalho mais importante no front-end foi concluído, era hora de visitar o back-end mais uma vez. Esta parte do sistema provaria ser uma das mais complexas de implementar, mas, em última análise, muito importante e também tornaria bastante fácil prosseguir com outros recursos.


Na Programação Orientada a Objetos , existe um conceito de Separação de Responsabilidades , onde mantemos nossas funções simples e as fazemos fazer apenas uma coisa, a qual elas devem fazer.


Além disso, a ideia da Programação Orientada a Aspectos é especificamente sobre a separação de preocupações, onde precisamos separar a lógica de negócios de outras preocupações transversais.


Por exemplo, salvar um usuário em um banco de dados deve ser naturalmente acompanhado de registro, enquanto o salvamento do usuário está sendo processado. Mas o código para esses dois recursos deve ser mantido separado.


Decidi aplicar esse raciocínio em todos os aspectos e extrair o máximo de recursos da interface do usuário que não são importantes para o usuário e movê-los para o back-end.


No nosso caso, estamos mais preocupados em salvar os dados no banco de dados, que se refere a comunidades, artigos, comentários, curtidas e assim por diante.


Se quisermos acompanhar quantas curtidas um artigo recebe, podemos ter um processo que conte todas as curtidas de cada artigo e as atualize periodicamente.


Como estamos lidando aqui com uma grande quantidade de dados armazenados no banco de dados e potencialmente uma grande quantidade de dados que flui constantemente para o banco de dados, precisaremos empregar o processamento de dados em tempo real para lidar com essa situação.

Escolhi o AWS Kinesis para esta tarefa. O Kinesis é capaz de ingerir grandes quantidades de dados em tempo real e também podemos escrever consultas SQL para consultar e agrupar esses dados quase em tempo real. Por padrão, o Kinesis fará lotes de dados por 60 segundos ou o lote atingirá 5 MB, o que ocorrer primeiro.


Assim, no nosso caso, consultaremos os dados recebidos, ou seja, a criação de novas comunidades, adição ou exclusão de artigos, usuários, atividades, etc., e atualizaremos o banco de dados a cada minuto com novos dados. A questão que surge agora é como colocamos os dados no Kinesis em primeiro lugar?


Nosso banco de dados preferido, o DynamoDB, é realmente capaz de definir gatilhos que são invocados, na forma de funções do Lambda, sempre que os dados são adicionados, removidos ou modificados. Em seguida, capturaríamos esses dados e os enviaríamos ao Kinesis para processamento.


Acontece que uma de nossas decisões anteriores tornaria esse processo um pouco mais difícil de implementar porque não estamos lidando com 1 banco de dados, mas na verdade estamos lidando com 10 bancos de dados.


Portanto, assim que os dados forem adicionados, as funções do Lambda serão invocadas 10 vezes em vez de uma vez, mas precisamos lidar com cada caso porque os dados podem vir de qualquer banco de dados, pois estão localizados em regiões diferentes.


Resolvi esse problema filtrando os dados copiados em oposição aos dados originais que foram adicionados ao banco de dados pelo usuário.


A coluna “aws:rep:updateregion” nos dá essa informação e podemos determinar se estamos lidando com os dados da região onde foram inseridos ou se representam dados copiados.


Uma vez resolvido esse problema, simplesmente filtraríamos, adicionando novos dados ou removendo-os. Além disso, filtraríamos os dados com base em seu tipo, ou seja, estamos lidando com dados que representam uma comunidade, artigo, comentário etc.


Em seguida, coletamos esses dados, marcamos como “INSERT” ou “DELETE” e os passamos para Kinesis. Essas ideias da abordagem Domain-Driven Design são chamadas de Domain Events e nos permitem determinar qual ação aconteceu e atualizar nosso banco de dados de acordo.




Em seguida, voltamos nossa atenção para Kinesis. Aqui tivemos que definir três partes principais do sistema


  • Fluxos de dados do AWS Kinesis
  • AWS Kinesis Firehose
  • Análise de dados do AWS Kinesis


O Kinesis Streams nos permite ingerir dados em tempo real em grandes quantidades. O Kinesis Analytics é um sistema que nos permite realmente consultar esses dados em lotes e agregá-los com base em uma janela de tempo contínua.


Depois que os dados forem agregados, enviaremos cada resultado ainda mais para o Kinesis Firehose, que pode lidar com grandes quantidades de dados e armazená-los em um serviço de destino, que em nosso caso é um bucket S3 no formato JSON.


Quando os dados chegam ao bucket S3, acionamos outra função do Lambda e manipulamos esses dados para atualizar o banco de dados do DynamoDB.


Por exemplo, se 5 pessoas curtiram um Interesse no último minuto, encontraríamos esses dados em nosso arquivo JSON. Em seguida, atualizaríamos a contagem de curtidas para esse interesse e incrementaríamos ou decrementaríamos a contagem de curtidas. Nesse caso, simplesmente o incrementaríamos para 5 curtidas.


Usando este sistema, as estatísticas de cada comunidade seriam atualizadas em um minuto.


Além disso, não precisaríamos escrever e executar consultas complexas quando precisamos exibir dados agregados, pois o resultado exato é armazenado no rápido banco de dados DynamoDB em cada registro, aumentando assim a velocidade da consulta para cada registro.


Essa melhoria é baseada na ideia de localidade de dados

Cloudinário

Agora era hora de começar a implementar serviços de terceiros que lidariam com os recursos que eu precisava, mas era mais fácil comprar uma assinatura do que criar por conta própria. O primeiro serviço que implementei foi o Cloudinary, que é um serviço de gerenciamento de mídia.


Configurei todas as predefinições no Cloudinary para transformar imagens com entusiasmo para os seguintes pontos de interrupção de tela responsivos.


  • 576 pixels
  • 768 pixels
  • 992 pixels
  • 1200 pixels


Esses também seriam pontos de interrupção definidos no Tailwind CSS, onde nosso site estaria em conformidade com diferentes tamanhos de tela para telefones celulares, tablets pequenos, tablets grandes e monitores de computador.


Então, dependendo do tamanho da tela atual, chamaríamos apropriadamente imagens criadas avidamente do Cloudinary usando o atributo scrcset no elemento <image>.


Isso nos ajudaria a economizar largura de banda e reduzir o tempo de carregamento de imagens em dispositivos móveis.


No que diz respeito ao recurso de vídeo, depois de implementado, decidi abandoná-lo porque o preço dos vídeos no Cloudinary era muito caro. Portanto, mesmo que o código esteja lá, o recurso não é usado no momento, mas pode ser disponibilizado posteriormente.


Isso exigirá que eu crie um sistema personalizado na AWS no futuro.

Embed.ly

Decidi usar o Embed.ly para incorporar conteúdo de sites populares como Twitter, YouTube etc.


Infelizmente, isso não funcionou sem problemas, então tive que usar várias técnicas para remover manualmente os scripts do Facebook e do Twitter do site porque eles interfeririam no conteúdo incorporado depois que ele fosse carregado várias vezes.

Algolia

Quando se trata de busca, escolhi Algolia e implementei a busca por comunidades, atividades, artigos e usuários. A implementação do front-end foi bastante simples.


Eu simplesmente criei uma barra de pesquisa que, quando clicada, ocultaria o restante do aplicativo e, enquanto digitamos, exibiria o resultado para o subdomínio específico que estamos navegando no momento.


Uma vez que pressionamos „Enter“, a alvenaria na página inicial exibe os artigos que se encaixam na consulta. Escusado será dizer que também tive que implementar a paginação que carregaria os resultados de forma incremental, para imitar a aparência do Pinterest.


O problema surgiu quando percebi que não havia como realmente pesquisar as atividades a menos que você armazenasse todo o texto em Algolia, o que eu queria evitar. Decidi, portanto, armazenar apenas tags relevantes para cada atividade, mas a questão era como extrair tags relevantes de cada atividade.


A resposta veio na forma de AWS Translate e AWS Comprehend . Como a quantidade de itens que seriam adicionados ao banco de dados seria grande e gostaríamos de adicionar esses dados ao Algolia, poderíamos sobrecarregar a API se fôssemos adicionar cada registro separadamente.


Em vez disso, gostaríamos de manipulá-los em tempo real e em lotes, portanto, voltaríamos a empregar o Kinesis como solução.


Nesse caso, cada adição de um novo item ao banco de dados acionaria uma função Lambda que enviaria esses dados para o Kinesis Data Streams, que por sua vez enviaria os dados para o Kinesis Firehose (sem necessidade de Analytics desta vez) e os armazenaria posteriormente em um balde S3.


Assim que os dados estiverem armazenados com segurança, acionaríamos uma função Lambda que os enviaria para Algolia, mas antes disso precisaríamos processar esses dados.


Em particular, precisaríamos processar atividades, das quais retiraríamos o texto Markdown usando a biblioteca removedor de markdown . Ficaríamos então com texto simples. Assim que tivermos o texto real, podemos prosseguir com a extração das tags relevantes que serão usadas para a pesquisa.


Isso pode ser feito facilmente usando o serviço AWS Comprehend, mas o problema é que ele oferece suporte apenas a alguns idiomas. Assim, se um usuário estiver escrevendo em um idioma não suportado, não poderíamos extrair as tags.


Nesse caso, simplesmente usamos o AWS Translate e traduzimos o texto para o inglês. Em seguida, extraímos as tags e as traduzimos de volta para o idioma original.


Agora, simplesmente armazenamos as tags no Algolia conforme pretendido.

Recombee

Um dos recursos mais importantes do Pinterest é seu mecanismo de recomendação. Depois que o usuário clica em um Pin, ele vê imediatamente a imagem em tamanho real do Pin, enquanto sob a imagem, podemos ver as recomendações que o usuário pode gostar, com base no Pin atual.


Esta é uma ótima maneira de aumentar a retenção de usuários e fazê-los continuar navegando no site. Para implementar esse recurso, que no meu caso teria que mostrar artigos semelhantes aos usuários, escolhi o Recombee — que é um mecanismo de recomendação SaaS.


A implementação foi mais fácil desta vez ao contrário do Algolia, pois reutilizei os mesmos princípios. Visto que precisaremos recomendar comunidades, artigos e atividades, para cada novo item criado por um usuário, eu usaria o Kinesis para agrupar esses itens e enviá-los para o Recombee.


O processo de recomendação é baseado em visualizações, ou seja, toda vez que um usuário vê um artigo, enviamos esta visualização para este usuário específico e o artigo para o Recombee.


Também podemos atribuir outras ações aos itens no Recombee, com base em como o usuário interage com eles. Por exemplo, escrever um novo Interesse seria mapeado para uma Adição de Carrinho para esse Interesse. Se um usuário gostar de um Interesse, isso será adicionado à Classificação.


Se um usuário ingressar em uma comunidade, isso será mapeado para o marcador dessa comunidade.


Com base nesses dados, o Recombee criaria recomendações para os usuários.


No front-end, eu simplesmente pegaria o artigo que o usuário está lendo no momento e obteria os dados de recomendação para esse artigo específico e para o usuário. Isso seria exibido na parte inferior de cada artigo, como uma lista de alvenaria paginada.


Isso daria ao usuário uma lista de artigos em potencial que ele poderia estar interessado em ler.

localizar

Vendo como o site visaria um público global desde o início, tive que implementar a localização também. Para o lançamento inicial, decidi usar 10 idiomas e optei por um serviço SaaS — Locize, que é implementado com base na estrutura de localização i18next .


Precisaremos localizar as palavras com base na quantidade, significando singular ou plural, e também precisaremos localizar o tempo. Visto que estamos exibindo a hora em que cada artigo foi criado ou atualizado pela última vez.


Escolhi o inglês como idioma padrão e traduzi todas as palavras usando o Google Tradutor para outros idiomas, como alemão, japonês etc. Novamente, é muito conveniente que o Aurelia também ofereça suporte à localização.


Depois que todas as traduções foram feitas, importei os arquivos JSON traduzidos para o aplicativo e os dividi com base no tipo de comunidade, para que não carreguemos texto desnecessário que não será usado.


Aurelia então nos permite simplesmente usar modelos e encadernação que traduziriam automaticamente o texto. Mas também usei conversores de valor que formatariam a hora, para mostrar quanto tempo se passou desde que um artigo foi escrito, em vez de mostrar uma data real.


Além disso, tive que formatar os números também, assim, ao invés de mostrar o número 1000, exibiria 1K. Todos esses recursos foram gerenciados por bibliotecas como Numbro e TimeAgo .

Twilio

Um site de comunidade requer comunicação, mas não apenas em público. Requer comunicação privada também. Isso significava que um bate-papo privado em tempo real também deveria ser algo que eu precisava oferecer. Esse recurso foi implementado usando o serviço Twilio Programmable Chat.


Cada usuário pode ter um bate-papo privado com qualquer outro usuário em cada comunidade específica. A implementação de back-end foi fácil de implementar usando as bibliotecas Twilio. No front-end, decidi estilizar o chat com base no Instagram porque tinha um design limpo e simples.

Pré-renderização do SPA

Também escolhi um serviço chamado Prerender para usar na pré-renderização do site para disponibilizá-lo aos rastreadores dos mecanismos de pesquisa. Depois de perceber que o preço poderia ser uma preocupação, decidi construir o sistema de pré-renderização por conta própria.


Para isso, encontrei uma biblioteca chamada Puppeteer , que é uma API Headless do Chrome.


Essa biblioteca poderia ser usada para carregar sites programaticamente e retornar um HTML gerado com JavaScript executado, o que os rastreadores de pesquisa da época não faziam. A implementação carregaria o Puppeteer em uma função do Lambda, que carregaria um site, o renderizaria e retornaria o HTML.


Eu usaria o Lambda@Edge para detectar quando meu usuário era realmente um rastreador e, em seguida, passaria para o Lambda de pré-renderização. Isso foi bastante simples de fazer detectando o atributo "user-agent" nos parâmetros do CloudFront. Na verdade, o Lambda não conseguiu carregar a biblioteca Puppeteer porque era muito grande.


Isso não foi um impedimento, pois encontrei a biblioteca chrome-aws-lambda , que fazia todo esse trabalho imediatamente e seria muito menor, pois usa apenas o núcleo Puppeteer, que era necessário para meus propósitos.


Depois que o sistema foi concluído, os mecanismos de pesquisa já eram poderosos o suficiente e começaram a executar o JavaScript também. Assim, embora eu tenha concluído esse recurso, eu o desativei e simplesmente permito que os mecanismos de pesquisa rastreiem meu site por conta própria.

Listra

Um dos principais recursos das Comunidades imersivas é o esquema de compartilhamento de receita, no qual os usuários compartilham 50% das assinaturas dos membros e da receita de anúncios.


Conforme declarado anteriormente, precisamos permitir que nossos criadores não apenas criem seu conteúdo, mas também gerem receita com ele. A questão agora era como implementar esse sistema. Escusado será dizer que a escolha padrão era Stripe, então procedi da seguinte forma.


Decidi desenhar o sistema de Revenue Sharing baseado em cada comunidade. Dessa forma, um usuário pode criar várias comunidades e ganhar com base em cada comunidade. A receita para cada comunidade viria de duas fontes.


  • Assinaturas de membros
  • Anúncios de autoatendimento


As assinaturas de membros foram as mais fáceis de implementar. Eu criaria três pontos de preço para assinaturas de membros, $ 5, $ 10 e $ 15 mensais. Os membros de cada comunidade podem apoiar o proprietário da comunidade mensalmente e, em troca, nenhum anúncio será exibido.


O sistema de anúncios era baseado nas mesmas assinaturas mensais, mas variava entre $ 100 e $ 1.000 mensais. A empresa que deseja anunciar em uma determinada comunidade pode simplesmente escolher o valor da mensalidade e definir o banner do anúncio.


Supondo que haja vários anunciantes em uma única comunidade, os anúncios seriam escolhidos aleatoriamente a cada carregamento de página ou mudança de rota. A forma como o anunciante pode aumentar a frequência de exibição de seus anúncios, em comparação com outros anunciantes, aumentando o valor do pagamento mensal.


Também precisaríamos mostrar ao anunciante o desempenho de seus anúncios, então usei novamente uma configuração do Kinesis para medir visualizações e cliques. Esse sistema atualizaria as estatísticas como de costume e, em seguida, usei a biblioteca Brite Charts para exibir as estatísticas.


A parte mais importante era o recurso real de compartilhamento de receita. Isso foi simplesmente implementado pelo recurso Stripe Connect . O usuário simplesmente precisa adicionar sua conta bancária e se conectar ao Stripe Express e o sistema terá todas as informações necessárias para enviar pagamentos.



Eu teria então um sistema Lambda programado que receberia todos os usuários diariamente e atualizaria as transações e garantiria que 50% de cada transação (assinatura de membro ou pagamento de anúncio) fosse transferida para o proprietário da comunidade onde o pagamento é feito.

AWS Cognito

O último serviço a ser implementado foi o Auth0 , que ajudaria na autenticação do usuário. Depois de alguma pesquisa, decidi por uma configuração sem senha, baseada em mensagens SMS.


Visto que agora estamos em um mundo que prioriza os dispositivos móveis, fazia sentido renunciar às senhas e basear a autenticação em algo que todos já possuem - o telefone celular.


Acontece que a implementação Auth0 da autenticação sem senha era abaixo do ideal, pois redirecionava para o site deles todas as vezes e se baseava em parâmetros de URL, o que eu queria evitar.


O preço também não escalaria para algo como uma rede social, então decidi criar minha própria implementação usando o AWS Cognito.


Foi bastante conveniente que o Cognito tenha gatilhos que podem ser conectados às funções do Lambda, que é o que usei para acionar a autenticação. As funções do Lambda seriam usadas para coletar dados do usuário durante a inscrição.


Neste ponto, o usuário só precisa fornecer seu número de telefone e seu nome de usuário para se registrar.

Durante o procedimento de login, a função Lambda coletaria o número de telefone do usuário e enviaria uma mensagem SMS, contendo um código de verificação para o usuário, usando o AWS SNS .


O usuário simplesmente digitaria esse código para ser verificado por meio do Cognito e seria redirecionado para sua página de perfil.


Obviamente, uma vez que o usuário seja autorizado e obtenha os dados de validação passados de volta para o front-end, teríamos que criptografá-los antes de podermos armazená-los. Os mesmos dados de autorização são criptografados antes de serem armazenados no back-end.


Além disso, durante cada inscrição e login, armazenamos o IP do usuário.


Mais tarde, descobri que os usuários realmente teriam problemas em fornecer seus números de celular, então decidi substituir o SMS por mensagens de e-mail.


Houve um problema com mensagens duplicadas quando eu queria usar o AWS SES , então mudei para o SendGrid do Twilio para enviar e-mails aos usuários.


Com este sistema concluído, o ano acabou e o projeto que iniciei há 2 anos não estava nem perto de ser concluído. Não havia outra escolha a não ser continuar trabalhando e tentar concluí-lo o mais rápido possível. Mal sabia eu que os maiores desafios ainda estavam por vir.

2021 - Sem fim à vista

Aqui é quando tudo tinha que se encaixar, mas trabalhar como desenvolvedor solo sem nenhum feedback por tanto tempo faz você questionar a direção que o projeto está tomando.


A pergunta que qualquer desenvolvedor que está atualmente no mesmo lugar agora pode se fazer a seguinte pergunta.


Como posso me manter motivado e capaz de continuar, mesmo que não veja um fim à vista?


A resposta é bem simples.


Você simplesmente não deve questionar suas decisões, independentemente de como você se sente sobre o projeto. Você não pode permitir que seu estado emocional atual determine como você agirá.


Você pode não sentir vontade de continuar agora, mas pode sentir vontade mais tarde e com certeza se sentirá mal se realmente desistir.


Portanto, se você desistir, não terá mais o projeto e todo o trabalho terá sido em vão. Portanto, a única coisa a fazer é simplesmente seguir em frente, independentemente do que aconteça.


A única coisa a ter em mente neste momento é que cada recurso fornecido, cada pressionamento do teclado, o aproxima do objetivo.


Durante este projeto, na verdade, mudei de emprego 3 vezes, cada vez sendo bastante envolvente, mas mesmo tendo que ir para entrevistas de emprego, ainda voltava para casa, sentava atrás da minha mesa e continuava trabalhando no meu projeto.


O que você deve se perguntar, se estiver com falta de motivação, é o seguinte.


Se você desistir agora, para onde irá? A única maneira que você pode seguir, depois de desistir, é voltar para onde você veio. Mas você já sabe o que tem lá atrás. Você já sabe como é e não gostou, e é por isso que partiu nesta jornada em primeiro lugar. Então, agora você sabe de fato, que não há para onde voltar. O único caminho que você pode seguir é para frente. E a única maneira de seguir em frente é continuar trabalhando.


Essa é toda a motivação que eu tive durante esse projeto, como já disse, era isso, ou voltar onde já estava, então resolvi seguir em frente.

O Sistema Administrativo

Agora era hora de começar a juntar as coisas e, para começar, decidi implementar o Sistema Administrativo que seria usado para manter cada comunidade. Cada proprietário de comunidade seria capaz de tomar decisões sobre a remoção de conteúdo em sua comunidade.


Isso implicaria que podemos desabilitar anúncios, artigos e atividades e banir usuários se suas ações não estiverem de acordo com as regras de conduta.


O proprietário de cada comunidade também pode conceder direitos de administrador a outros usuários. Mas também precisamos permitir que os administradores da comunidade principal possam administrar todas as outras comunidades.


Além disso, esses administradores seriam capazes de desabilitar completamente outros usuários de todas as comunidades e até mesmo desabilitar a comunidade como um todo.


Para facilitar o trabalho dos administradores, introduzi o sistema de sinalização, onde cada item pode ser relatado aos administradores. Os usuários agora podem relatar qualquer coisa no site que considerem inapropriada.


A validação real das permissões para cada usuário seria decidida no back-end. Eu simplesmente criaria uma função Lambda que seria invocada dentro de cada chamada do AppSync que validaria cada solicitação.


Além disso, o front-end usaria a autorização baseada em roteamento fornecida pelo Aurelia. Eu simplesmente definiria regras que permitiriam ou não permitiriam que o usuário atual prosseguisse para uma determinada rota.


Por exemplo, você não poderá ver seu perfil se tiver sido banido de uma determinada comunidade. Mas esse sistema também pode ser usado para impedir que alguém navegue para uma página de perfil se não estiver logado; em vez disso, eles seriam redirecionados para a página de login.

O painel de análise

Outro recurso que seria útil para os usuários seria a página Analytics Dashboard. Cada proprietário de comunidade pode ver gráficos que exibem exatamente quanta interação está acontecendo em sua comunidade.


Para este caso específico, eu reutilizaria os dados que foram agregados pelo Kinesis e os exibiria com gráficos usando a biblioteca Brite Charts .


Além disso, eu também pegaria os dados do Stripe e exibiria o número de assinantes, anunciantes e ganhos totais desta comunidade.


O único problema que precisava ser resolvido era o design responsivo, ou seja, como exibir gráficos em telas pequenas e grandes. Mais uma vez, usei RxJs para detectar o evento "resize" e aplicar estilo com base nos pontos de interrupção da tela definidos no Tailwind CSS.

A segurança

Um nível adicional de segurança também estava no roteiro e decidi implementar um WAF na frente das minhas distribuições do CloudFront.


Eu usei o AWS Marketplace e me inscrevi no sistema Imperva WAF , que faria o proxy do meu tráfego e permitiria apenas o tráfego que foi validado como seguro.


A solução foi muito fácil de implementar, mas uma vez que o primeiro mês acabou, a conta era demais para pagar, então desconectei o sistema e decidi simplesmente confiar no que o CloudFront tinha a oferecer por padrão.

O redesenho de última hora

Nesse ponto, tive que começar a olhar para tudo o que fiz e começar a consertar os pequenos problemas que ainda restavam. Muitas coisas ainda precisavam ser polidas, mas a maior coisa que precisava ser alterada era a configuração do banco de dados do DynamoDB.


Acontece que minha configuração inicial, que não era a que estou usando agora, não ia escalar bem. É por isso que decidi redesenhá-lo completamente e começar a usar o separador „#“ para indicar a ramificação no identificador do registro.


Anteriormente, eu fazia registros separados e usava o AppSync Pipelines para localizar cada registro relacionado, o que era claramente insustentável. Isso também afetou a configuração do Kinesis e de serviços de terceiros, como Algolia e Recombee.


Por sua vez, foram necessários 3 meses para redesenhar completamente o sistema para funcionar corretamente. Feito isso, eu poderia continuar com os novos recursos novamente.

O verão mais quente já registrado

Os verões em Tóquio são quentes e úmidos. É um grande desafio manter o foco em qualquer coisa que você esteja fazendo, especialmente em julho e agosto.


Naquela época, as Olimpíadas aconteciam em Tóquio e, no dia 7 de agosto, foi relatado que a temperatura mais alta foi registrada na história das Olimpíadas .


Escusado será dizer que ir para o trabalho de comboio já não faria sentido porque o tempo seria demasiado exaustivo, deixando-me exausta e incapaz de trabalhar à noite. Percebi que precisava economizar mais tempo pegando um táxi para o trabalho.


Isso me dava mais tempo para dormir e evitaria que eu ficasse cansado demais para trabalhar quando voltasse para casa.

Notificações em tempo real

O PWA é uma ótima tecnologia e nos oferece uma maneira de enviar notificações aos usuários usando Push Notifications. Decidi que este seria um sistema que também seria necessário e continuei com a implementação.


O sistema de notificação seria implementado com base no usuário que está sendo seguido. Se você estiver seguindo um usuário, quando ele criar uma nova atividade ou um artigo, você precisará ser notificado.


Atualmente, o único problema com as notificações push é que, no momento da redação deste artigo, elas ainda não são suportadas pelo navegador Safari em dispositivos iOS. Em vez de notificações por push nativas, decidi pela API de notificação do navegador.


No back-end, eu criaria uma nova instância do AWS API Gateway e a configuraria para trabalhar com dados em tempo real.


No front-end, eu faria uma conexão usando WebSocket API para o API Gateway. Assim que o usuário que está sendo seguido publicar um novo artigo, esses dados serão enviados ao Kinesis. Novamente, usando o processamento em lote, obtemos todos os usuários que seguem o autor e, em seguida, usamos o API Gateway para enviar as notificações para o front-end.


No front-end, a conexão WebSocket é acionada, que por sua vez usamos para invocar a API de notificação do navegador e exibir a notificação.


Além disso, quando se trata de comentários que os usuários podem escrever em cada artigo, precisamos acompanhar e mostrar ao usuário onde ele está envolvido nas discussões.


Também implementei um indicador não lido que mostraria qual seção de comentários tem novos comentários que o usuário ainda não leu.


Isso seria verificado quando o usuário carregasse o aplicativo sem usar a palavra-chave await ao invocar a chamada do AppSync. Isso garantiria que a execução não esperasse a conclusão da chamada, mas, em vez disso, os dados mais importantes seriam carregados primeiro.


Assim que a chamada retornasse, simplesmente atualizaríamos a interface do usuário e mostraríamos a notificação ao usuário.

Eu também usaria notificações na forma de pop-ups para sinalizar ao usuário quando uma ação foi concluída com sucesso ou não.


Por exemplo, eu criaria uma mensagem pop-up que informaria ao usuário se a atualização do artigo falhou.

Validação de front-end

Vendo como a validação de back-end foi concluída, tivemos que dar ao usuário uma experiência ainda melhor implementando a validação no front-end para dar ao usuário um feedback mais rápido.


Felizmente, o Aurelia possui um plug-in de validação e é implementado adequadamente com uma interface fluida. Isso facilitou bastante a criação de regras de negócios que limitariam, por exemplo, o número de caracteres que o usuário poderia digitar em um campo <input>, para o nome de um artigo.


Eu usaria o sistema de vinculação de propriedades do Aurelia para coletar as mensagens de validação e, em seguida, exibi-las na IU. Eu ainda precisaria incorporar isso ao sistema de localização e garantir que as mensagens fossem exibidas no idioma correto.

Finalizando o trabalho

O resto do ano consistia em trabalhar em detalhes menores. Exigiu que eu criasse coisas como carregar espaços reservados. Decidi especificamente que não queria exibir espaços reservados para carregamento como elementos de tela separados.


Em vez disso, queria indicar ao usuário que um elemento está sendo carregado. É por isso que usei o contorno do elemento que estava sendo carregado e dei a eles uma animação de carregamento transparente. Isso foi inspirado no aplicativo móvel Netflix, que funciona da mesma maneira.


A essa altura, o final do ano chegou e eu estava trabalhando na página inicial principal. Esta página exibiria apenas todas as comunidades que temos atualmente. Felizmente, o sistema baseado em componentes que criei anteriormente facilitou bastante a reutilização da maior parte do código que escrevi, de modo que a tarefa foi concluída rapidamente.


O ano finalmente terminou e fiquei satisfeito com o trabalho que foi feito. Embora o projeto ainda não estivesse concluído, sabia que o sucesso estava ao meu alcance.

2022 — A última milha

Este ano seria o último ano. Eu não sabia se realmente implementaria tudo o que queria, mas sabia que tinha que fazer independentemente do que acontecesse.


Não queria repetir o trabalho durante o verão como fiz no ano passado porque era mais provável que fosse ainda mais quente do que no ano passado.


Minha previsão se tornou realidade e, na verdade, Tóquio teve a temperatura mais quente do verão em 2022, medida nos últimos 147 anos!

O design da página de destino

Comecei projetando a página de destino. A pergunta era a seguinte.


Como quero que meus usuários se sintam quando visitam minha página de destino?


Eu não queria que os usuários sentissem que isso seria um site muito sério, mas sim uma comunidade amigável e colaborativa.


Percebi que ultimamente as landing pages tinham ilustrações ao invés de fotos de pessoas reais, então faria sentido seguir por esse caminho. Por isso decidi por um conjunto de ilustrações que comprei no Adobe Stock .


A página de destino deveria ser simples e também descrever rapidamente tudo o que o site está oferecendo. Isso também precisava ser localizado, então usei o recurso de localização para traduzir todos os títulos e legendas da página de destino que estão em exibição.


A única questão técnica que teve de ser superada foi como introduzir a cor no texto. Felizmente, pude usar o recurso de estilo dentro das definições de tradução e, em seguida, usar o Markdown para gerar dinamicamente o HTML que seria exibido na página de destino.


Os dados necessários, como "Política de Privacidade" e "Termos de Uso", foram adquiridos on-line e traduzidos para vários idiomas usando o Google Tradutor.

Espere o inesperado

Agora era hora de amarrar todas as pontas soltas, então passei o resto do tempo certificando-me de que o registro estava presente em todas as funções do Lamda no back-end. Isso me ajudaria a garantir que, se problemas acontecessem, eu saberia o que estava acontecendo.


Quando eu estava terminando, a Guerra na Ucrânia havia começado. Isso aumentou novamente a incerteza da economia global, mas continuei trabalhando e me mantive focado no objetivo final.

Como não mantive a implementação do PWA atualizada, tive que garantir que todos os recursos estivessem funcionando e, portanto, algum desenvolvimento adicional foi necessário para melhorar o JavaScript e o cache de imagens.


O recurso offline foi finalmente ativado e o aplicativo agora estava se comportando corretamente como um aplicativo offline.


Também tive que mover as alterações que fiz no back-end e espalhar as alterações que fiz no AppSync para outras regiões. Como seria muito complicado fazer isso durante o desenvolvimento, não fiz alterações em outras regiões desde que comecei a desenvolver.


O mesmo vale para os ambientes. Teria perdido muito tempo para construir constantemente todos os três ambientes, então finalmente reservei algum tempo para sincronizar todos os ambientes e mover o código para UAT e Produção.


Por fim, tive que implementar o domínio https://immersive.community, que teria que funcionar sem o subdomínio „www“ e redirecionar para a página inicial corretamente.


A essa altura, estávamos na madrugada do dia 25 de abril de 2022 . Meu projeto de 4 anos finalmente acabou. Criei o primeiro post no site e fui dormir. Eu sabia que finalmente consegui. Não apenas terminei o que pretendia fazer, mas também o fiz antes do verão chegar.

Palavras Finais

Ironicamente, as palavras finais da minha aventura são que este não é o fim, mas apenas o começo. Agora que o sistema está ativo, o conteúdo que precisa ser criado e a promoção e publicidade que serão necessárias para o conhecimento da marca será uma aventura completamente nova.


Mas, o que eu realmente aprendi com este exercício?


Bem, bastante. Em primeiro lugar, posso dizer com confiança que nunca mais faria isso.


Não que eu não goste do resultado, muito pelo contrário, estou muito satisfeito, mas isso é o tipo de coisa que você faz uma vez na vida e não faria sentido tentar se superar, simplesmente para provar que você pode fazer ainda melhor.


Eu queria saber se era possível construir um sistema de nível empresarial como desenvolvedor solo e mostrei que isso pode ser feito com a pilha de tecnologia que temos à nossa disposição.


Mais do que tudo, esta é uma declaração para todo desenvolvedor trabalhando em seu projeto paralelo ou pensando em iniciar um.


Eu recomendaria essa abordagem para outros desenvolvedores por aí? Absolutamente. Não porque seja uma maneira ideal de fazer as coisas, certamente não é.


Não pedir ajuda quando você está preso certamente não é a maneira mais rápida de resolver um problema, mas o que fará é ajudá-lo a descobrir seus limites.


Depois de decidir fazer algo assim e conseguir, saberá que tudo o mais que decidir fazer depois disso será mais fácil de alcançar.


Acredito que minha história irá motivá-lo a terminar tudo o que começou, independentemente de como se sente e mesmo que não veja o fim da estrada que está percorrendo agora, lembre-se de que “lá atrás” não é onde você quer estar. .


Se você achou esta história inspiradora, inscreva-se no meu canal do YouTube, porque começarei a fazer um curso avançado de programação „Full Stack Dev“, onde detalharei toda a tecnologia que usei para criar comunidades imersivas.


O que não abordei neste artigo são os fundamentos filosóficos e as justificativas para a forma como abordei cada problema e as técnicas que usei para analisar e projetar as soluções para cada problema.


Este foi um componente ainda mais importante do que simplesmente conhecer as tecnologias e como usá-las. Como você aborda um problema e seu processo de pensamento, que o leva à solução, é algo que abordarei em profundidade em meus vídeos do YouTube.


Esta será uma ótima maneira para os desenvolvedores aprenderem programação com alguém que criou um sistema do mundo real e está pronto para compartilhar seu conhecimento. Vejo você no YouTube!


Também publicado aqui