Arquitetura de Software

Flux – descubra o motivo do sucesso dessa arquitetura em grandes empresas

Olá Web Developers!

Você já deve ter ouvido falar na Arquitetura Flux. Ela está sendo cada vez mais requisitada no mercado. Mas por que? Será só mais uma modinha entre desenvolvedores ou realmente há um valor em trabalhar com Flux? Vamos ver melhor aqui neste post!

Qual o problema a ser resolvido?

Já faz um bom tempo que paramos de fazer aplicações criando views inteiras de uma só vez e começamos a pensar mais em componentização para permitir a melhor reutilização dos elementos de nossa interface.

O problema começa no momento em que precisamos distribuir os dados da nossa aplicação para nossos componentes, ou então fazer com que os componentes enviem algum dado ou ação para que outro componente responda.

Isso sem contar que cada componente pode guardar seus próprios dados. Caso um dado esteja presente em vários componentes diferentes você vai ter um certo trabalho para garantir que ele esteja sempre atualizado em todos os lugares, evitando inconsistências.
Um exemplo simples: uma lista de produtos e um formulário de produtos. Ao selecionar um produto da lista ele é enviado ao formulário para você editar. Ao fazer a alteração é preciso garantir que o produto que está no formulário também seja atualizado na lista.

Este é um exemplo simples, mas em sistemas mais complexos há a possibilidade de um mesmo dado estar presente em bem mais lugares. Conforme o sistema for crescendo isso vai dando mais trabalho, até chegar a um ponto em que alguém vai esquecer de arrumar algo em algum lugar ou fazer uma nova funcionalidade que só altera o dado em um lugar específico, deixando o sistema com problemas.

Como resolver este problema?

Para evitar o problema de dados espalhados por diferentes lugares e uma bagunça na comunicação entre componentes foi pensada a seguinte ideia: vamos colocar os dados da aplicação em um único lugar!

Dessa maneira só precisamos alterar nossos dados em um único lugar, garantindo que todos os componentes da aplicação sempre estarão com o mesmo dado.

Isso também resolve o problema de comunicação entre componentes. Se os componentes se comunicarem a partir de um lugar central, não importa a estrutura dos nossos componentes e nem da nossa View.

Como é a Arquitetura Flux?

React - Trabalhando com Arquitetura Flux
Curso de React - Trabalhando com Arquitetura Flux
CONHEÇA O CURSO

A Arquitetura Flux, diferente do que muitos pensam, não é uma biblioteca. É apenas uma arquitetura, ou seja, ela indica como devemos estruturar nossa aplicação. Ela separa totalmente a View dos dados.

Na parte da View estão os componentes. Eles sempre passam dados de pai para filho, fazendo um fluxo de dados unidirecional. Nunca que um filho pode influenciar no pai.
Como a lógica e os dados não estão aqui, podemos alterar nossa View sem medo de estragar nossa aplicação.

Já os dados e a lógica para modificar estes dados ficam todos centralizados no que chamamos de Store. Como ela está totalmente separada da View podemos criar toda a lógica da nossa aplicação sem encostar em uma única linha de HTML ou CSS.

Como o Flux funciona?

Basicamente o dividimos em 3 partes:

  • Dispatcher
  • View
  • Store

View

Como vimos antes, a View é simplesmente os componentes visuais que são apresentados, ou seja, tudo o que aparece na tela para o usuário. Se tem algum formulário ou botão, HTML em geral, estamos falando da View.

Store

A Store, como também dito anteriormente, é onde ficam os nossos dados e lógica da aplicação. Então a View consome os dados presentes na Store. E é junto à Store que ficam as funções que alteram esses dados.
Então na Store podemos ter, por exemplo, uma lista de produtos. A View recebe essa lista da Store. Caso a gente precise apagar um produto, essa função ficará junto à Store, nunca em algum componente. Quando a lista for alterada, a View será atualizada automaticamente nos pontos necessários.
Essas funções que modificam dados não são acessíveis pela View, elas ficam limitadas à Store.

Dispatcher

Mas se não podemos executar funções dentro dos componentes, como indicar, por exemplo, que queremos apagar um produto da lista?

É aí que entra o Dispatcher! Pense no Dispatcher como uma central de comunicação.

Na View podemos indicar no clique de um botão: “Dispatcher, quero apagar esse produto aqui!”.
Então o Dispatcher comunica à Store: “ei, apague este produto!”.

A Store vai ter todos os dados (nesse caso, a lista de produtos), e também a lógica necessária para apagar o produto em questão. Lista alterada! Ao ter alguma alteração, a Store sempre emite um aviso dizendo que seus dados foram alterados. Assim, a View recebe os novos dados, e eles serão propagados por todos os seus componentes, os quais sabem exatamente o que e onde deve ser atualizado.

Então o Dispatcher não possui nenhuma inteligência. Ele apenas repassa para a Store o que precisa ser feito.
Se um componente chamar o Dispatcher para uma função que não existe na Store nada vai acontecer.

React - Redux e Redux-Saga
Curso de React - Redux e Redux-Saga
CONHEÇA O CURSO

Actions

Mas como garantir uma boa comunicação entre as necessidades da View e as funções da Store, já que o Dispatcher não tem inteligência?

No nosso exemplo de tirar um dos produtos da lista, imagine a bagunça se um componente indica que quer “remover”, outro diz que quer “apagar” e outro “deletar”.
Como saber com qual palavra a Store está trabalhando? Mesmo que a gente padronize, podemos acabar escrevendo errado sem querer.

E quanto a Store: ela vai receber o produto inteiro e pegar o ID dele ou ela vai receber apenas o ID?

Para solucionar este problema podemos adicionar aqui o que chamamos de Actions. Elas são simplesmente objetos que usamos para padronizar a comunicação entre View e Dispatcher.

Conclusão

A Arquitetura Flux simplifica muito na organização de nossas aplicações, além de ser ótima para sistemas que tendem a crescer muito, pois é facilmente escalonável.

Também temos implementações do Flux que simplificam e nos dão ainda mais poder, mudando a sua experiência no modo de desenvolver sistemas. No começo pode parecer estranho e mais complicado de entender, mas garanto que se aprender do modo certo você vai considerar usar em seu próximo projeto.

Para saber mais, confira nossos cursos de Arquitetura Flux e de Redux e Redux-Saga.

Node.js: por que você deve conhecer essa tecnologia?

Você provavelmente já ouviu falar de Node.js: a tecnologia figurou na Stack Overflow Developer Survey de 2017 entre as mais amadas entre desenvolvedores (em segundo lugar, com 62.1%) e a que os devs mais querem trabalhar (em primeiro lugar, 22.2%). A pesquisa do Stack Overflow também demonstrou que Node.js vem crescendo de forma impressionante: de 2013 a 2017, passou de 8% a 26% em popularidade.

Dentre os principais motivos desse crescimento está a comunidade dedicada ao Node.js. Por ser uma tecnologia open source, o Node.js mantém uma comunidade ativa e muito presente: é possível acompanhar (e contribuir) via GitHub, auxiliar com a documentação ou mesmo produzir tutoriais. No Brasil, já existem duas grandes comunidades dedicadas ao Node no Google+ e Facebook, além dos Meetups espalhados por diversas cidades, que são organizados pelos próprios desenvolvedores e interessados. Além disso, em 2016 também rolou a primeira Nodeconf BR, evento dedicado a discutir essa tecnologia.

Nesse post vamos falar um pouco sobre o que é Node.js, em quais casos é melhor utilizá-lo e que projetos já adotaram. Mas e a primeira pergunta, que não quer calar: o que é Node?

O que é o Node.js?

Nem linguagem, nem framework: Node.js é um runtime de JavaScript que leva a renderização e processamento do código JavaScript para o lado do servidor, desvinculando-o totalmente do browser, possibilitando que você desenvolva aplicações de rede rápidas e estáveis.

Desenvolvido sobre o motor JavaScript V8 – engine criada pelo Google e utilizada nos navegadores Chrome e Chromium (open source), Node.js é direcionado a eventos (event driven) e o processamento de requisições I/O (input e output) é não-bloqueante, garantindo estabilidade e pouco consumo de recursos. O desenvolvimento da tecnologia teve como objetivo “fornecer uma maneira fácil para construir aplicações escaláveis”, conforme o site oficial.

Criado em 2009 por Ryan Dahl, o Node.js, apesar de recente, já é utilizado por grandes empresas como LinkedIn, Groupon e PayPal (mais para frente você poderá ver mais sobre como essas empresas estão usando Node). Entre as principais vantagens está justamente o uso de JavaScript: uma linguagem versátil que possui uma curva de aprendizado relativamente baixa, além de ser uma das linguagens mais populares na atualidade.

Quando devemos utilizar o Node.js?

Levando em consideração esses pontos positivos, pode ser fácil se empolgar e já começar a querer mudar todos os seus projetos para Node. Apesar de estar fazendo a diferença para muita gente, Node.js não é a solução para todos os problemas. Existem casos em que optar por essa tecnologia é uma boa, mas outros em que ela talvez não te ajude tanto.

Por isso, Node é a pedida caso você necessite:

Criar aplicações de chat e mensagens instantâneas:

Apps como chats e ferramentas de colaboração multi-usuários tornam-se um dos melhores usos para Node: alto tráfego e uso intenso de dados em tempo real rodando em diferentes dispositivos. Como o Node.js consegue lidar muito bem com comunicação assíncrona e já consegue lidar com WebSockets (através de bibliotecas como Socket.IO), ele se mostra como uma das melhores opções neste tipo de cenário. Um exemplo de chat construído usando Node.js é este, que usa Socket.io e Express;

Criar APIs que sejam facilmente escaláveis:

Também por decorrência da abordagem não-bloqueante do Node.js, ele pode ser uma excelente opção quando for necessário criar APIs que sejam escaláveis de maneira rápida e simples. Em tempos onde a criação de aplicações SPA (Single Page Application) é muito comum, a questão da escalabilidade e do tempo de resposta de APIs (RESTful ou não) torna-se importantíssima; já que, nesse tipo de cenário, existe uma grande possibilidade de que o I/O de rede cresça consideravelmente e, portanto, o servidor tenha que lidar com uma quantidade de requisições maior ao mesmo tempo sem perder performance ou consumir grandes quantidades de recursos (como memória e processador) do servidor. O Node.js já foi criado exatamente para resolver esse tipo de situação;

Dados em streaming:

Outro bom uso do Node.js é para dados em streaming: novamente, por causa da sua abordagem de I/O não bloqueante. O Node.js é uma opção que economiza muito tempo e recursos para processar dados de fontes diversas. Na verdade, em quaisquer situações onde haja uma carga muito grande de leitura e escrita de dados (como acontece com streaming), o Node.js conseguirá entregar um resultado muito satisfatório com menos esforço, tanto no sentido de codificação, como no sentido de hardware necessário para suporte;

IoT:

Uma pesquisa realizada pela Node.js Foundation em 2016 mostrou que 96% dos envolvidos em projetos de IoT estão usando JavaScript/Node para o desenvolvimento. E os desenvolvedores de IoT também são os mais experientes, com 40% dos participantes tendo mais de 10 anos de experiência.

Outros casos de uso para o Node.js que podem ser interessantes:

Criação de dashboard para monitoramento de aplicações, criação de sistemas para corretores de ações e até mesmo backend para jogos!

Agora, é melhor não usar Node.js se…

Sem dúvida, a utilização de Node pode trazer muitos benefícios, especialmente em termos de performance, mas existem casos em que não vale a pena adotar essa tecnologia. Por exemplo, para projetos que precisam de muitos recursos de CPU, Node.js não é a melhor escolha, já que as vantagens do modelo de I/O não bloqueante e eventos lineares são perdidas nesse caso.

Se você, portanto, está criando algoritmos de machine learning, ou se a aplicação a ser construída demanda regras de negócio complexas que normalmente seriam resolvidas com orientação à objetos, Node também não é a melhor opção por causa de características do próprio JavaScript.

O Node.js também tem uma limitação importante: não é possível utilizar multi-threading com ele, pelo menos não da maneira tradicional – se você quiser trabalhar com multithreading “de verdade” no Node.js, precisará de bibliotecas adicionais. Apesar de o Node.js trabalhar sob o aspecto não-bloqueante, é importante ressaltar que todo o código JavaScript que é convertido para código de máquina é executado em uma única thread.

O Node.js até possui algumas bibliotecas que permitem a junção entre processos concorrentes em um único cluster, simulando o multithreading. Porém, mesmo com estas bibliotecas, o compartilhamento de informações entre os diferentes processos que estão sendo executados de maneira concorrente é algo muito complexo de ser realizado. No final, o Node.js é ótimo para multi-processos, mas não para multithreading. Sendo assim, se você precisa de paralelismo, onde cada uma das unidades de trabalho precisa compartilhar informações entre si, o Node.js pode não ser uma opção tão interessante.

Projetos e empresas que utilizam Node.js

No seu Guia para Convencer seu Chefe a adotar Node.js, Felix Geisendörfer fala que muitas vezes a barreira para adotar uma nova tecnologia open source é a dúvida sobre o comprometimento a longo prazo com a manutenção. No caso do Node, essa questão foi respondida com a Node Foundation e o apoio de grandes empresas, como Microsoft, Joyent e IBM, que garantem o desenvolvimento da tecnologia ao longo dos anos.

A maior prova de que Node.js está aqui para ficar é a quantidade de grandes projetos que já adotam a tecnologia:

LinkedIn: O LinkedIn foi uma das primeiras grandes empresas a adotar Node.js em 2011. O app mobile lançado pela rede social que usava Node conseguiu aumentos de performance com um uso muito menor de recursos. Além disso, de acordo com Kiran Prasad, líder de desenvolvimento mobile do LinkedIn na época, o projeto foi desenvolvido de forma muito rápida;

PayPal: O PayPal mudou parte de suas aplicações de Java para Node.js em 2013 ao perceber que, com Node e a possibilidade de escrever tudo em JavaScript, eles poderiam ganhar mais produtividade e performance. O PayPal inclusive criou o Kraken.js, hoje open source, uma layer de convenções e configurações criada para dar mais estrutura a projetos criados em Express;

Groupon: O Groupon mudou do Ruby on Rails para o Node.js ao perceber que a aplicação monolítica que tinham estava difícil de manter e escalar. Além disso, após realizarem aquisições na Europa e América do Sul, era importante para a empresa ter uma plataforma de desenvolvimento para toda as equipes de desenvolvimento, independente da localização. Além dos ganhos de performance nas páginas do site, o Groupon também percebeu um aumento na velocidade da entrega de novas features por parte dos times de desenvolvimento;

Ghost: A plataforma Ghost de blogging que hoje também é voltada ao jornalismo independente, roda sob o Node.JS. Desde à sua criação, já foram feitos quase dois milhões downloads. Ela é open source e mantida por uma organização sem fins lucrativos.

Tecnologias utilizadas em conjunto com Node.js

Em 2016, a *Node.js Foundation, *em parceria com a Linux Foundation, criou uma pesquisa sobre o uso de Node.js, que foi respondida por 1760 pessoas ao redor do mundo. A pesquisa demonstrou quais são as principais tecnologias utilizadas junto com o Node.js:

  • MongoDB é a primeira escolha para banco de dados. Dois terços dos participantes afirmaram que MongoDB é o banco utilizado com suas aplicações Node, pois ambas são tecnologias não-bloqueantes que aguentam um alto throughput de I/O.

  • Containers são a tecnologia perfeita para rodar aplicações em Node.js. Quase metade dos participantes está usando container. Dentre as opções, Docker é a principal escolha – 73% de quem usa alguma tecnologia baseada em containers, está usando Docker.

Na Umbler, você consegue hospedar uma aplicação Node.js usando tecnologia de Containers e tem MongoDB disponível para criar com as melhores ferramentas.

Você já utiliza Node.js? Tem interesse em usar? Conte para a gente nos comentários o que você acha dessa tecnologia.

Node.js Completo
Curso de Node.js Completo
CONHEÇA O CURSO

Introdução aos princípios SOLID

Muito provavelmente você já ouviu falar dos tais princípios SOLID enquanto você estudava orientação a objetos. Mas, afinal de contas, para quê servem e onde podemos utilizá-los? Qual é a real importância destes princípios na programação orientada a objetos? O que posso obter de vantagens se eu utilizar os tais princípios SOLID?

Java - Collections - Parte 1
Curso de Java - Collections - Parte 1
CONHEÇA O CURSO

O que são os princípios SOLID?

É importante frisarmos que os princípios SOLID não constituem uma linguagem, nem uma tecnologia, nem um framework, nem uma biblioteca, nem uma arquitetura, nem um paradigma. Os princípios SOLID são apenas… princípios, haha. Eles constituem algumas constatações que podem fazer você programar mais próximo da orientação a objetos.

Estes princípios surgiram no início dos anos 2000 e foram definidos pelo engenheiro de software Robert Martin (também conhecido como Uncle Bob), sendo que a sigla que tornou estes princípios tão populares foi criada por Michael Feathers.

Os princípios SOLID tentam definir de maneira efetiva as bases para que a orientação a objetos seja efetivamente aplicada no mundo real, trazendo a possibilidade da construção de aplicações e sistemas que sejam fáceis de se manter e evoluir com o passar do tempo – este é um dos grandes desafios da arquitetura e engenharia de software até hoje!

Quais são estes princípios?

SOLID na verdade é um acrônimo que denomina cinco princípios:

  • Single Responsability Principle ou Princípio da Responsabilidade Única;
  • Open/closed Principle* ou Princípio do aberto/fechado;
  • Liskov Substitution Principle ou Princípio da Substituição de Liskov;
  • Interface Segregation Principle ou Princípio da Segregação de Interfaces;
  • Dependency Inversion Principle ou Princípio da Inversão de Dependências.

Não se preocupe no momento com cada um deles em especial… Nos próximos artigos, abordaremos cada um deles, inclusive com exemplos práticos. 😉

Mas, para quê eu deveria me atentar aos princípios SOLID?

Essa é uma pergunta que pode ser interessante. De fato, você não é obrigado a seguir os princípios SOLID necessariamente para programar com uma linguagem orientada a objetos (embora, sem querer, você vá acabar usando pelo menos um dos princípios em algum momento).

Mas, não tem sentido também não nos atentarmos a estes princípios se eles visam nos guiar para aplicarmos a Orientação a Objetos corretamente, obtendo manutenibilidade e extensibilidade do nosso código da maneira correta. Se estamos nos propondo a utilizar uma linguagem orientada a objetos, eu particularmente acho importante que nós tenhamos a capacidade de absorver o que é proposto pelo paradigma para aplicá-lo da maneira mais correta possível. Sendo assim, por que não entender os princípios SOLID?

Eu gosto de ilustrar o porquê de nos atentarmos a eles com a ilustração abaixo:

As ferramentas acima estão, nitidamente, sendo utilizadas de forma incorreta, não? Pois é… Mas quantas vezes nós não escrevemos código, mesmo que sem querer, que no final tínhamos uma situação parecida com essa? O código funcionava? Até funcionava… Da melhor maneira possível? Não necessariamente. Era fácil dar manutenção no código? Aí já era outra história, hahaha.
Os princípios SOLID nos auxiliam a justamente evitar situações como as da imagem acima dentro do nosso código.

Os princípios SOLID são a salvação do universo?

Não, não são. Por várias vezes, você vai se encontrar no dilema de ou aplicar corretamente algum dos princípios ou tornar o seu código mais claro. Você até mesmo provavelmente irá chegar na situação de ou aplicar os princípios SOLID ou entregar o projeto no prazo determinado! Mas não, eles não constituem a salvação do seu software.

Existe também o fato de que você provavelmente não vá utilizar somente orientação a objetos em seu projeto. Assim como os princípios SOLID, a orientação a objetos não resolve todos os problemas de arquitetura e engenharia de software do mundo.

É por isso que temos outros paradigmas de desenvolvimento que podem ser muito mais úteis em determinadas situações, como o paradigma orientado a eventos, o paradigma funcional (este que vem ganhando muito mais popularidade a cada ano que passa) e o paradigma orientado a aspectos. Cada um deles é bom em resolver determinada situação e ruim para resolver outras. O legal é quando chegamos a um nível de maturidade onde conseguimos enxergar o melhor de cada um destes paradigmas e conseguimos aplicá-los em conjunto.

Mas, voltando aos princípios SOLID, se você provavelmente não vai utilizar somente orientação a objetos, isso quer dizer que você também não utilizará somente os princípios SOLID.

Nos próximos posts, iremos analisar e entender cada um dos cinco princípios SOLID. Verificaremos para que cada um deles serve, que problemas eles resolvem e, inclusive, teremos exemplos de implementações com “código de verdade”.

Até o próximo post da série! 😉

C# (C Sharp) - ASP.NET MVC
Curso de C# (C Sharp) - ASP.NET MVC
CONHEÇA O CURSO

Segurança em SOA: Como proteger os web services?

No último artigo sobre SOA, vimos sobre a Arquitetura Orientada a Serviços e como ela funciona. Vimos que uma implementação dessa arquitetura pode ser realizada com qualquer tecnologia baseada em web, mas geralmente a SOA é implementada utilizando Web Services, já que estes são fáceis de implementar e distribuir. Estes mesmos web services também são responsáveis por expor, através de uma rede, um determinado serviço. Sendo assim, eles precisam de uma atenção especial quanto à segurança. Como saber se meus web services estão realmente protegidos?

HTTP - Fundamentos
Curso de HTTP - Fundamentos
CONHEÇA O CURSO

Serviços

Antes de qualquer coisa vamos retomar à definição sobre o que é um serviço. Para que se possa ter uma real governança em TI em uma empresa é necessário saber que isso não é atingido do dia para a noite. É um processo de longo prazo, por isso exige muita cautela dos processos como um todo, mantendo um alinhamento estratégico entre o negócio e a TI. O Negócio e a TI precisam trabalhar juntos!

Um dos autores mais famosos deste segmento, Thomas Erl, escreveu o livro SOA: Princípios do Design de Serviços, onde ele aborda 8 “regras” para definir um serviço. Nesse livro, ele enumera o que considera essencial para fazer uso da SOA “de verdade”. São eles:

Serviços são reutilizáveis

Essa é talvez uma das principais regras. Para isso acontecer, é necessária a interação da TI e do negócio. Quanto mais um serviço for genérico e puder ser reaproveitado, melhor!

Serviços compartilham um contrato formal

Todo serviço deve vir acompanhado de um “contrato”, onde ele deve informar o que o serviço faz e como ele se comunica.

Serviços possuem baixo acoplamento

Baixo acoplamento significa que certas implementações de um serviço podem ser modificadas, evoluídas e até mesmo substituídas, sem afetar negativamente os consumidores deste serviço.

Serviços abstraem a lógica

Serviços não devem expressar as regras de negócio. Os serviços SOA devem guardar os detalhes de sua implementação, até mesmo caso seja preciso modificar a lógica do negócio, não comprometendo as obrigações do serviço escritos no seu contrato.

Serviços são capazes de se compor de outros serviços

A composição também é uma forma de reutilização. Sendo assim, um serviço pode muito bem chamar um outro serviço para executar a sua tarefa.

Serviços são autônomos

Um serviço também deve ser autônomo, ou seja, apenas ele é suficiente para executar sua lógica.

Serviços evitam alocação de recursos por longos períodos

Quando um serviço é reutilizado, devemos tomar alguns cuidados. Não se deve criar muitas instâncias de um mesmo serviço, pois isso pode sobrecarregar a infra-estrutura. Para isso não acontecer, o serviço deve evitar reter informações específicas em uma determinada atividade.

Serviços devem possuir a capacidade de serem descobertos

O que eu quero dizer é que a capacidade dos serviços serem descobertos significa a visibilidade deles.

Para que isso ocorra, o contrato deve ser bem descrito, evitando que novos requerimentos resultem em serviços redundantes.

Agora que já entendemos um pouco melhor sobre como nossos serviços devem se comportar, vamos a um outro ponto do nosso artigo.

Imagine que uma empresa esteja fazendo o uso de SOA. O que pode acontecer se ela estiver utilizando um web service onde suas informações estão trafegando pela rede sem proteção???

Isso mesmo: acessos indevidos e possíveis interceptações das informações que trafegam pela infraestrutura dos serviços SOA!

Sendo assim, a necessidade de se proteger os recursos envolvidos com a aplicação da SOA no ambiente de TI é uma necessidade real.

Conheça o WS-Security: Segurança para Web Services

A tecnologia WS-Security (Web Services Security) é um padrão que tem a intenção de apoiar a SOA no sentido de prover segurança quando os dados são trocados. O WS-Security disponibiliza um sistema rico para prover segurança, tanto em confidencialidade quanto em autenticação.

Apesar disso, o WS-Security não funciona sozinho. Ele funciona em conjunto com as especificações WS-Policy e WS-SecurityPolicy. Essas especificações têm por objetivo, respectivamente, estabelecer políticas gerais a respeito de segurança, qualidade de serviço, confiabilidade; além de estabelecer quais são as políticas de segurança aplicáveis a um determinado serviço.

Na figura abaixo, podemos ver a estrutura de segurança que é fornecida pelo WS-Security, fazendo também uso da estrutura do WS-Policy.

Esses conceitos são baseados em cinco requisitos comuns de segurança: identificação, autenticação, autorização, confidencialidade e integridade.

Se um solicitante quiser acessar os serviços em segurança, primeiramente ele deve fornecer informações que expressem a sua origem. O WS-Security armazena essas informações em um cabeçalho padronizado, cujo ponto é referido como um token.

A autenticação requer a prova de que a mensagem que está sendo entregue ao destinatário é realmente do remetente que alega ser, fornecendo uma prova de que sua identidade reivindicada é verdadeira.

Após a autenticação, o destinatário pode precisar determinar se o solicitante está autorizado a fazer o que ele tenta fazer. Isto é chamado de autorização. Já a confidencialidade está diretamente ligada com a proteção e privacidade do conteúdo da mensagem, onde a mensagem deve ser mantida como confidencial e nenhum serviço ou mensagem não autorizada deve ver seu conteúdo.

Por último, temos a integridade, que garante que a mensagem não foi alterada desde a sua saída do remetente, garantindo que a mensagem permaneceu intacta a partir do momento de transmissão para o ponto de entrega.

Percebe a necessidade de se proteger as estruturas SOA em uma organização? Os benefícios adquiridos são muitos. Fazendo o uso correto das normas de segurança, a empresa poderá se beneficiar com o uso da SOA sem se preocupar com a integridade de suas informações, ponto de preocupação este que é de certa forma recorrente em organizações que iniciam o processo de adoção de uma arquitetura SOA.

Django - Desenvolvimento de APIs REST
Curso de Django - Desenvolvimento de APIs REST
CONHEÇA O CURSO

Você sabe o que é Arquitetura Orientada a Serviços (SOA)?

Quando estamos lidando com aplicações a nível empresarial, é muito comum ouvirmos sobre uns tais de Serviços SOA. É comum até mesmo ouvirmos os termos “barramento SOA” ou “fachada SOA”. E tudo isso dentro do que geralmente chamam de “arquitetura SOA”. Mas, o que é tudo isso? Será que estamos falando simplesmente de web services?

Afinal, o que realmente vem a ser SOA?

Uma solução fundamentada em SOA geralmente possui uma arquitetura baseada em padrões para a criação de uma infraestrutura de TI, visando simplificar as relações entre sistemas distintos, aperfeiçoando seu funcionamento e facilitando a incorporação de novos elementos. Sendo assim, caso haja mudanças nas necessidades do negócio, estes fatores permitem que a empresa responda a isso de forma rápida.

Essa exposição de regras de negócio é realizada basicamente através dos famosos web services, pois são eles que determinam os padrões e acabam especificando essa infraestrutura de TI que foi citada. A vantagem de termos essa estrutura em uma organização é justamente a flexibilidade que os web services trazem. Para isso ficar mais claro, vamos utilizar o seguinte exemplo:

Imagine que você tem uma infinidade de softwares que devem ser capazes de fazer a inserção de um novo cliente na base de dados de sua empresa. Cada um destes softwares é mantido por um prestador de serviços diferente e, pior ainda, cada um destes softwares foi escrito em uma linguagem diferente. Para piorar mais um pouquinho: sua organização tem uma série de validações que precisam ser realizadas antes de permitir a inserção desse novo cliente, sendo mandatório que todos os softwares realizem essas validações da maneira correta.

Para muitas empresas este é um cenário caótico. São sistemas que utilizam linguagens diferentes e desenvolvidos por pessoas diferentes fazendo a interação direta com o negócio da sua organização, além de existirem regras de validação a serem seguidas para garantir o sucesso e efetividade do negócio.

Desenvolvedor Java Júnior
Formação: Desenvolvedor Java Júnior
A formação Desenvolvedor Java nível Júnior da TreinaWeb tem como objetivo fornecer uma introdução ao desenvolvimento através do Java e todo o ecossistema para desenvolvimento da Oracle. Nesta formação, são abordados tópicos como o desenvolvimento da capacidade analítica, o paradigma orientado a objetos, a preparação do ambiente de desenvolvimento para o Java através do Eclipse e o controle de versão de código através do Git e do GitHub. Além disso, também são abordados aspectos mais essenciais da linguagem e estruturas importantíssimas dentro do ecossistema do Java, como a Stream API, que auxilia a lidar com coleções de dados; a implementação das estruturas de dados mais tradicionais como listas, filas e pilhas; e a API de coleções.
CONHEÇA A FORMAÇÃO

Sendo assim, como lidar com um cenário tão divergente?

Esse é exatamente o cenário perfeito para a utilização da arquitetura SOA, pois temos ativos de negócio envolvidos em um ambiente completamente heterogêneo. E todo mundo precisa conversar entre si.

Pensando em uma arquitetura voltada a serviços, nós poderíamos resolver isso de maneira muito fácil. Poderíamos criar um web service chamado “IncluirCliente”. Este web service será responsável por fazer todas as validações do cliente antes de o inserir na base de dados. Assim, caberia aos demais softwares simplesmente consumir esse serviço da maneira adequada.

Agora as coisas ficaram muito mais simples. Conseguimos centralizar o nosso ativo de negócio (no caso, o cliente e a sua inserção), garantindo que o fluxo de negócio dele sempre ocorra da maneira correta (temos certeza que o cliente sempre vai ser validado da maneira correta).

Também garantimos reutilização com esse web service: para qualquer software que necessite incluir clientes na base da organização, bastará a utilização deste web service. Sendo assim, facilitamos a relação entre os sistemas distintos da organização, pois qualquer linguagem é capaz de consumir um web service, o que coloca um pouco de ordem neste nosso ambiente completamente heterogêneo.

Por fim, garantimos extensibilidade pois, se quisermos um dia alterar a regra de negócio de validação do cliente ou mesmo acrescentar novas etapas do negócio, basta modificarmos o web service. Todos os softwares que consomem este web service serão automaticamente “atualizados”. Esta é a arquitetura SOA!

Mas então é só eu criar web services para que eu tenha uma arquitetura SOA?

Não, não basta criarmos e expormos web services para dizer que estamos utilizando uma arquitetura orientada a serviços. Perceba que temos um problema que vai muito além da questão técnica de criar ou não um web service em uma determinada linguagem. Temos a barreira do negócio. O modelo de negócio da organização fica exposto nos serviços quando estamos utilizando SOA. Por isso, é essencial uma completa integração entre o setor de negócios e o setor de tecnologia. Essa é outra intenção da utilização da arquitetura SOA: implementar tecnologia de ponta para o negócio da empresa, tornando-a mais competitiva no mercado.

Temos ainda várias outras questões envolvidas nesse processo: como fica a segurança dos dados trafegados, já que serviços SOA certamente receberão dados sensíveis para o negócio? Como saber o que pode ser exposto e o que não pode ser exposto? Como conseguir ter governança de TI de verdade em um ambiente orientado a serviços?

Nos próximos posts dessa série, iremos discutir mais sobre estes pontos. Até lá!

REST não é simplesmente retornar JSON: indo além com APIs REST

É até comum, de certa forma, ouvirmos alguém falar que construiu uma API REST porque acabou disponibilizando um endpoint que retorna alguma informação no formato JSON. Mas isso, infelizmente, é um equívoco. Criar uma API REST nada tem a ver com simplesmente retornar algum JSON.

Neste post, vamos discutir sobre os conceitos de REST e JSON e verificar que, apesar de serem conceitos muito íntimos hoje, tecnicamente um não não tem nada a ver com o outro.

HTTP - Fundamentos
Curso de HTTP - Fundamentos
CONHEÇA O CURSO

Mas, afinal de contas, o que é REST?


Hmmm, pelo jeito é algo arquitetural…

REST é um acrônimo para REpresentational State Transfer, ou seja, Transferência de Representação de Estado. O REST é, no final das contas, um estilo arquitetural que podemos utilizar ou não em nossas aplicações.

O conceito do REST foi criado pelo norte-americano Roy Fielding. Roy é também um dos principais responsáveis pela especificação técnica do protocolo HTTP. Sim, esse mesmo protocolo que você está utilizando nesse exato momento para visualizar esta página em nosso blog. A idéia do REST é utilizar de maneira mais eficiente e em sua plenitude as características do protocolo HTTP, principalmente no que diz respeito à semântica do protocolo. O resultado disso ao final das contas é, além da utilização mais “correta” do protocolo, um trânsito de informações mais eficiente e, por consequência, mais rápido.

Mas quais são as características do protocolo HTTP?

O protocolo HTTP, por decorrência de sua arquitetura, possui algumas características bem marcantes:

  • É um protocolo cliente-servidor: o protocolo HTTP se caracteriza por uma conexão feita entre uma máquina cliente e uma máquina servidora. Quando você acessa o nosso blog, por exemplo, você está fechando uma via de comunicação entre a sua máquina com o nosso servidor. É através dessa conexão que seu browser baixa o HTML, o CSS, o JavaScript e as imagens necessárias para renderizar a página solicitada;

  • A comunicação entre o cliente e o servidor é feita através de “mensagens HTTP”: o tráfego de dados entre um cliente e um servidor é feito através de porções de informação chamadas mensagens. Em uma “rodada” de comunicação, nós temos duas mensagens envolvidas: a mensagem enviada pelo cliente solicitando algo, chamada de request; e a resposta do servidor para a solicitação realizada, chamada de response. Estes dois componentes são essenciais para que a comunicação ocorra seguindo o protocolo HTTP e não é possível ter um request sem que depois haja um response e vice-versa;

  • O protocolo HTTP por definição não é “keep alive”: por padrão, uma conexão HTTP só dura até o momento em que o ciclo request-response é concluído. Logo após este ciclo, a conexão é automaticamente encerrada, ou seja: a conexão morre. Caso algum novo conteúdo precise ser trafegado entre o cliente e o servidor, uma nova conexão é aberta. Hoje, até existe maneiras de se modificar esse comportamento utilizando-se algumas flags na requisição (uma destas flags se chama justamente keep-alive), mas é importante destacar que o protocolo originalmente não foi planejado para essa finalidade;

  • O protocolo HTTP é utilizado de maneira assíncrona na maioria dos clientes: requisições HTTP são assíncronas por definição do lado dos clientes. Isso quer dizer que, se você precisa disparar duas requisições para baixar dois conteúdos distintos, o cliente pode disparar essas requisições ao mesmo tempo, sendo que cada uma delas pode ser respondida em um tempo diferente. O protocolo HTTP foi concebido para funcionar desta forma. Inclusive, quando você renderiza uma página, você pode verificar este comportamento observando a aba “Network” do Web Inspector se você utiliza browsers baseados no WebKit/Blink, como o Chrome:

Na figura acima, temos a inspeção do carregamento da página inicial do nosso blog. Cada um dos componentes listados abaixo do gráfico representa um componente que foi solicitado ao servidor, ou seja: houve um ciclo de requisição HTTP para a obtenção de cada um daqueles componentes, sendo que cada ciclo deste foi transmitido dentro de uma conexão própria ao servidor. O gráfico acima mostra o momento que a requisição foi disparada, bem como quanto tempo o servidor demorou para devolver a response correspondente.

Veja que no gráfico acima, podemos notar que o browser dispara várias requisições ao mesmo tempo, sendo que cada uma tem sua resposta em seu devido tempo. Isso prova que o protocolo HTTP é tratado de maneira assíncrona. Esse processo, inclusive, é chamado de pipelining.

Se você já lidou com AJAX por exemplo, já deve ter ouvido falar sobre o termo callback. Nós precisamos apelar para callbacks (ou promisses ou observables) justamente por causa desta característica do protocolo HTTP: nós precisamos de uma resposta do servidor, mas, pelo cliente fazer a requisição de maneira assíncrona, nós não sabemos exatamente quando essa resposta vai chegar… Por isso criamos callbacks (ou promisses ou observables) para serem executados quando o ciclo request-response for finalmente concluído.

  • As conexões no protocolo HTTP são independentes: como as conexões são abertas e fechadas a medida que algum conteúdo precisa ser trafegado, as conexões acabam sendo independentes umas das outras. Junto à característica assíncrona do protocolo HTTP, isso torna tecnicamente inviável que uma conexão possa “se comunicar” com alguma outra que esteja em curso, ou mesmo conhecer quais outros ciclos request-response estão em curso em determinado momento;

  • O protocolo HTTP é “stateless”: por decorrência dos três últimos pontos, nós afirmamos que o protocolo HTTP é stateless, ou seja, ele não guarda estado das requisições. Mais uma vez, isso é inviável, já que as conexões HTTP são independentes, assíncronas e, principalmente, por não serem keep alive. Se a conexão é imediatamente fechada após sua utilização, como podemos guardar alguma informação sobre ela? É exatamente por essa característica do protocolo HTTP que acabamos utilizando técnicas (como os cookies) para tentar guardar alguma informação necessária, como o usuário que está logado em uma aplicação por exemplo.

  • O protocolo HTTP é semântico: os recursos que podem ser disponibilizados por um servidor HTTP (como um página, por exemplo) podem ser acessados através de URIs (Unique Resource Identifier), que podem ser “traduzidas” para URLs (Unique Resource Locator). O grande ponto é que um servidor Web pode disponibilizar não somente páginas, ele também pode, por exemplo, fazer um upload de um arquivo. Para traduzir o que deve ser feito no servidor, o protocolo HTTP disponibiliza algo que nós chamamos de verbo ou método HTTP. A idéia é que esse verbo, associado ao request, indique o que deve ser feito no servidor.

Nós temos vários verbos HTTP, mas os principais, de maneira sucinta, são:

1) GET: indica que um recurso será recuperado do servidor. Por exemplo, quando você solicita uma página pelo seu browser;

2) POST: indica que um recurso será inserido ou criado no servidor. Um upload de um novo arquivo, por exemplo;

3) PUT: indica que um recurso será atualizado no servidor. Seria equivalente a um update em uma base de dados;

4) DELETE: indica que um recurso será removido do servidor. Seria o equivalente a um delete em uma base de dados.

Isso quer dizer que nós podemos invocar uma mesma URL (ou URI) em uma requisição HTTP, porém, dependendo da atribuição do verbo HTTP, a requisição irá desempenhar uma tarefa diferente no servidor. O verbo HTTP acaba determinando a semântica – ou significado/intenção – da requisição HTTP.

E o JSON? Onde ele entra na jogada?

O JSON (JavaScript Object Notation) não é um protocolo de transporte de informações como o HTTP. Ele é somente uma forma bem leve de representação e troca de informações. Ele tem a única função de levar informações de um lado para o outro. Nós podemos utilizar o JSON para transportar informações entre mensagens HTTP.

JSON x XML


Mas calma! A “treta” não precisa começar, rs

O XML também é uma forma de representação de informações. Porém, é uma forma mais “pesada” e verbosa de representação, já que preza pela “legibilidade” das informações a serem representadas.

Veja, por exemplo, um fragmento de um XML:

<clientes>
    <cliente>
        <id>1</id>
        <nome>TreinaWeb Cursos</nome>
        <idade>10</idade>
    </cliente>
</clientes>

A mesma informação acima poderia ser representada facilmente com o JSON abaixo:

"clientes" : [
    {
        "id" : 1,
        "nome" : "TreinaWeb Cursos",
        "idade" : 10
    }
]

Veja que, apesar de não ser tão explícita, a forma de representação com o JSON é muito mais direta e simples do que através do XML. Perceba também a quantidade de caracteres utilizados em cada uma das representações… Isso é um ponto muito importante! Como o JSON utiliza menos caracteres que o XML, ele também vai ocupar menos bytes dentro de um response com relação ao XML e, por consequência, o download de um response que contenha dados no formato JSON será mais rápido do que um response com as mesmas informações no formato XML. Essa é uma das principais justificativas para os desenvolvedores preferirem utilizar JSON do que XML para o intercâmbio de informações.

Então, o REST e o JSON possuem responsabilidades completamente diferentes!?


Me desculpa se isso foi um balde de água fria… 🙁

Sim, exatamente esse é o ponto! REST é um conceito arquitetural muito complexo, mas que no fim visa tirar vantagem de todas as características do protocolo HTTP, que é um protocolo de transporte. O JSON é somente uma forma de representar informações que precisam ser transportadas de um lado para outro. Sendo assim, podemos utilizar o protocolo HTTP para fazer o transporte de dados entre um cliente e um servidor.

Para conseguir utilizar o protocolo HTTP da forma correta, nós podemos adotar uma arquitetura baseada no REST. Agora, para fazer a representação das informações que precisam ser transportadas através do protocolo HTTP em uma arquitetura REST, nós podemos utilizar o JSON.

Mas então, eu posso ter uma API REST que responde XML?


Isso pode ser interessante…

Absolutamente, sim! Você, neste caso, só estará alterando a forma como as informações serão representadas nas requisições HTTP. Lembre-se: as responsabilidades do HTTP, do REST, do JSON e do XML são completamente diferentes. Isso, inclusive, é um trunfo da arquitetura REST: a representação das informações e o modo de transporte destas são completamente desacopladas.

Inclusive, é muito comum que APIs aceitem dados tanto no formato XML quanto no formato JSON, além de também responderem nestes dois formatos. As linguagens modernas hoje praticamente oferecem suporte nativo ao formato JSON, o que faz com que a adoção deste seja mais popular. Mas muitos sistemas, principalmente os sistemas legados, ainda são fundamentados no formato XML. Por isso é interessante que as APIs respondam nos dois formatos. Inclusive, as APIs definem a forma como vão fazer a leitura das informações com base no formato com que estas estão representadas, bem como definem o formato de dados a ser utilizado para a resposta, em uma etapa chamada content negociation.

“Perceba a petulância do protocolo HTTP!”


Do clássico meme “Percebe, Ivair, a petulância do cavalo!”

E ele é petulante com muita razão, haha. Afinal, toda a web hoje é fundamentada nele. Qualquer transporte de informação que for necessário entre aplicações hoje em dia passará pelo HTTP. Perceba como as características dele justificam muitas coisas que nós sem querer fazemos no automático quando estamos desenvolvendo soluções baseadas na Web.

Eu costumo dizer que entender os princípios básicos do protocolo HTTP é importantíssimo para qualquer desenvolvedor Web hoje em dia. Quando o desenvolvedor sabe como que as coisas “funcionam por baixo dos panos”, ele se preocupa mais com a maneira como ele vai desenvolvedor, além de entender bem melhor o porquê de algumas coisas serem do jeito que são.

Agora, deixa eu aproveitar o momento para fazer um “jabá”, hahaha: sabia que temos aqui no TreinaWeb um curso sobre o protocolo HTTP? Se você se interessar:

HTTP - Fundamentos
Curso de HTTP - Fundamentos
CONHEÇA O CURSO

Como se preparar para falhas em alguma região da AWS

Na terça-feira de carnaval, vários sites e aplicativos móveis que utilizam a AWS (Amazon Web Services) enfrentaram uma brusca interrupção na disponibilidade do serviço. A Web ficou, por algumas horas, “quebrada”. Para você ter ideia da dimensão do problema, foram afetados, de alguma forma, Trello, Quora, Wix, Giphy, Slack, Dropbox, Instagram, Vine e até mesmo o Github. Rumores eram de que toda a AWS estava offline mas, na verdade, os problemas afetaram apenas os servidores da região us-east-1 (N. Virginia), que é apenas uma das 14 regiões que a AWS possui.

C Intermediário
Curso de C Intermediário
CONHEÇA O CURSO

Segundo a própria Amazon a raiz do problema aconteceu no S3, o serviço de storage da empresa, um dos mais antigos e que serve como base para uma série de outros serviços da AWS, como o Elastic Block Store de volumes do EC2 que utiliza persistentemente os servidores do S3.

Por não afetar a zona sa-east-1 (localizada em São Paulo), a mais utilizada por serviços do Brasil e também devido a maioria dos desenvolvedores estarem pulando carnaval (ou não) o problema acabou não sendo tão divulgado.

Aqui no TreinaWeb, a nossa arquitetura está toda na AWS, especificamente na região de São Paulo (sa-east-1), a ideia é ter o mínimo de latência (apesar dessa ser uma das regiões mais caras de toda a AWS, você sabe, impostos praticados no Brasil e tudo mais). Da nossa parte, não tivemos nenhum problema. No entanto, o nosso gateway de pagamento usava alguns serviços daquela região americana e tivemos alguns contra-tempos limitados ao checkout. Mas, os alunos não deixaram de acessar os cursos e usufruir dos serviços do site.

O histórico, da identificação até a resolução do problema:

1) 11:49 AM PST We can confirm increased query failure rates when running queries and executing DDL statements in the in US-EAST-1 Region.

2) 1:59 PM PST We are starting to see recovery when running SQL queries in the US-EAST-1 region. We continue to see elevated error rates when executing DDL statements in the US-EAST-1 region.

3) 2:12 PM PST We continue to see recovery when running SQL queries in the US-EAST-1 region. We are also starting to see recovery when executing DDL statements in the US-EAST-1 region.

4) 3:25 PM PST We are able to execute SQL queries normally in the US-EAST-1 region. We continue to see increasing recovery when executing DDL statements in the US-EAST-1 Region.

5) 3:44 PM PST Between 9:37 AM and 3:23 PM PST we experienced increased error rates when running SQL queries and executing DDL statements in the US-EAST-1 Region. The issue has been resolved and the service is operating normally.

Por fim:

“The issue has been resolved and the service is operating normally.”

alt

Por mais que tudo tenha sido restabelecido, a Amazon apenas mitigou o problema. E isso não é nenhum demérito. Ela focou os esforços em fazer as coisas voltarem a funcionar. O que não falta é expertise para que, nos próximos dias, eles resolvam, de fato, os verdadeiros problemas estruturais/lógicos que lhes acometeram.

Esse tipo problema, especialmente na AWS, não é regra, é exceção. Mas, como nada está imune, o que podemos fazer para evitar que nossos aplicativos fiquem fora do ar em casos como esse? Abaixo duas válidas opções.

Active-Active ou Active-Passive usando Route53

O termo Active-Active não é especifico da AWS, na verdade ele está relacionado a alta disponibilidade. Basicamente, significa que o trafego é direcionado para um nó que está online ou balanceado entre outros que permanecem ativos, quando uma falha é identificada. Para usar essa solução é necessário uma cópia ativa da sua infraestrutura em outra região. O problema é que para a maioria das aplicações de pequeno porte isso é inviável, devido ao alto preço de ter a aplicação “espelhada” em mais de uma região.

A solução Active-Passive é parecida, porém um pouco mais em conta. Nela as instâncias redundantes só são colocadas completamente online caso o nó primário falhe. A desvantagem está no tempo extra após a queda do nó primário até as instâncias redundantes estarem prontas.

A AWS possui uma ferramenta chamada Route53 que ajuda a direcionar o tráfego automaticamente em caso de falha. Ela possui duas funções adicionais, uma chamada Health Check e outra chamada Traffic Polices. A primeira verifica se está tudo funcionando e a segunda permite definir o que fazer caso encontre algum problema. Essas duas configurações permitem implementar as estratégias acima descritas.

Replicação entre regiões

Caso você não queira usar as opções Active-Active ou Active-Passive, pode-se utilizar apenas a opção replicação entre regiões, nela todos os objetos e metadados como ACLs e tags são armazenados automaticamente em uma outra região escolhida na hora da ativação da opção. Isso pode ser suficiente caso aconteça algum problema no armazenamento da região principal.

Delphi 10 Seattle Básico
Curso de Delphi 10 Seattle Básico
CONHEÇA O CURSO

Concluindo

Apesar da AWS ser um dos serviços mais confiáveis do mercado, sempre que tratamos de softwares que precisam da alta disponibilidade é necessário pensar em alternativas que mitiguem bruscas interrupções, para evitar a perda de dinheiro, sem contar a credibilidade da sua empresa.

Caso queira saber como implementar os recursos acima citados, veja nesse excelente artigo So AWS Went Down. Here’s How You Can be Prepared If It Happens Again

Fontes complementares: theverge e datacenterdynamics

C# (C Sharp) Avançado
Curso de C# (C Sharp) Avançado
CONHEÇA O CURSO

Não seja um profissional “intolerante”

Todo profissional da área de TI já viu ou até fez parte de uma discussão com algum amigo ou colega de trabalho sobre o quão uma determinada tecnologia/linguagem/plataforma pode ser melhor do que a outra. É fácil darmos exemplos deste tipo de discussão: “Java vs .NET”, “Windows vs Linux”, “Android vs iOS”… Os exemplos são vários.

Essa discussão, obviamente, é muito bem-vinda! Uma discussão saudável é um dos melhores meios para aprendermos coisas novas e mudarmos paradigmas de pensamento. Mas, e quando a discussão torna-se, digamos, “acalorada” demais? E quando alguém simplesmente diz que a tecnologia/linguagem/plataforma que a pessoa em questão utiliza é melhor que todas as outras simplesmente por que a pessoa diz que é assim e pronto? Nesse momento, estamos diante de um profissional intolerante, pelo menos tecnicamente falando: é o famoso “xiita”. O final de uma discussão com um profissional intolerante? Geralmente, o festival de frases como “funciona porque é Linux”, “Java é que é linguagem de gente grande”, e por aí vai.

Discussões com “xiitas” acabam em nada

Esse tipo de discussão sempre acaba mais no emocional. No final, acabamos por ter pouca argumentação técnica e muito achismo e convicção pessoal. Não é que cada um não possa ter sua opinião e defendê-la, mas estamos em uma área onde é necessário que se prove o que se fala para que se possa ter um juízo correto. Porém, o profissional que é intolerante quase nunca vai dar valor a uma argumentação realmente técnica e, no final, ainda vai ficar com a opinião de que “o que importa é o que ele fala”. Uma pena, pois talvez uma grande oportunidade de aprendizado pode ter sido deixada para trás.

Oportunidades são deixadas para trás

Essa falta de flexibilidade por muitas vezes faz o profissional perder verdadeiras oportunidades de aprendizado e crescimento. Imagine a seguinte situação: você tem a possibilidade de participar de um super projeto legal, envolvendo várias pessoas ao redor do mundo, em uma empresa mundialmente reconhecida na área de TI e com uma excelente remuneração… Enfim, uma oportunidade para mudar uma vida! O grande ponto é que você curte desenvolver em Java, mas o projeto é em… .NET. E agora? Você deixaria a oportunidade única passar ou aceitaria o desafio?

Parece ser uma decisão relativamente fácil de ser tomada, mas não pra quem é inflexível. E, por incrível que pareça, não seria difícil de acreditar que alguém recusou a oportunidade. “Ah, recusei porque era .NET, C# não presta.”… Certamente esse seria provavelmente uma resposta que poderíamos com uma certa facilidade ouvir de alguém que preferiu recusar a oportunidade em questão. Parece absurdo, não? E, de certa forma é, mas infelizmente esse exemplo de intransigência também é comum. Mas sabemos que oportunidades que passam por nós raramente voltam e o arrependimento pode bater depois…

Twig - PHP Template Engine
Curso de Twig - PHP Template Engine
CONHEÇA O CURSO
Tecnologia da Informação serve para agregar valor ao negócio

Aqui vale aquela máxima: Tecnologia da Informação serve para agregar valor ao negócio, os deixando mais rápidos, eficientes, dinâmicos e adequados à realidade de velocidade extrema que temos hoje em dia com o menor custo operacional. Esse é o objetivo de qualquer projeto de software, no final. E isso, muitas vezes, quer dizer que o cliente não quer saber muito se a solução a ser desenvolvida utilizará Java, .NET, Ruby ou até mesmo Cobol. Ele quer que a solução seja eficiente e se adeque à sua realidade de negócio. Não adianta, por exemplo, utilizar Ruby em uma empresa onde o Java predomina. E essa comparação serve para qualquer linguagem/framework/tecnologia.

A ferramenta a ser utilizada é quem tem que na maioria das vezes se adaptar ao cliente, e não o contrário. E, justamente pela heterogeneidade entre os diferentes ambientes de negócio, não dá para cravarmos uma “bala de prata” para todos os cenários. Existem clientes onde .NET será mais adequado, outros onde o Java será mais adequado, outros onde até o Delphi será mais adequado à realidade de negócio do cliente! Aqui, o “xiitismo” acaba não tendo muita vez…

Esse tipo de discussão é muito chato!

Convenhamos: esse tipo de discussão na verdade é improdutiva e chata demais, justamente porque não leva a lugar algum de fato. Fora que, de fora, os participantes acabam por parecer profissionais que não sabem lidar com opiniões e experiências divergentes… Isso quando a pessoa não chega a parecer de fato uma criança birrenta!
Ninguém é obrigado a aceitar a opinião de outra pessoa como se fosse a verdade absoluta (por isso que uma discussão sadia e produtiva é sempre muito bem-vinda!). Agora, temos que ter maturidade e aceitar o fato de que alguém pode ter experiências e ideais diferentes dos nossos. E o fato de alguém pensar diferente não significa que existe alguém inferior ou errado na história na maioria das vezes. São somente… Diferenças! Temos que aprender a conviver com elas. E isso vale também para nós como profissionais da área de TI.

Se as discussões são chatas, os envolvidos automaticamente passam a ser chatos também

Profissionais intolerantes são automaticamente classificados como chatos e até mesmo muitas vezes arrogantes. Isso acontece porque simplesmente ninguém consegue conviver com uma pessoa que é completamente intransigente e não aceita que podem existir outras maneiras de se enxergar as coisas. Quando há algum fator emocional envolvido, a convivência pode até ser tolerada. Mas, como estamos falando do âmbito profissional e de maneira específica à nossa área de TI (mesmo que isso tudo não sirva somente para quem trabalha com tecnologia), muitas vezes isso acaba não valendo.

A certeza na área de TI é muito temporal

Temos sempre que nos lembrar que a nossa área é completamente mutante. Tecnologias novas sempre estão surgindo, frameworks novos e diferentes são lançados todos os dias praticamente… Essa interessante mutabilidade (que é o que pra mim faz a nossa área ser tão legal) também traz uma consequência: praticamente nenhuma verdade é absoluta por muito tempo.

Linguagens e frameworks nascem e são deixados de lado praticamente com a mesma velocidade com que foram adotados. Até algum tempo atrás, ninguém pensava em utilizar JavaScript do lado do servidor, até aparecer o Node.js… Delphi era considerada uma linguagem moderna e de ponta, hoje nem sequer é considerada (para mim, em alguns cenários, de maneira injusta) ao se desenvolver um novo projeto… Ninguém pensava em desenvolver aplicações mobile híbridas com HTML, CSS e JavaScript… Temos sempre que nos lembrar: o que sabemos hoje pode ser considerado completamente antiquado e ultrapassado daqui alguns anos. Sendo assim, pra quê perder tempo e energia com intransigência? Linguagens, frameworks e arquiteturas se foram, se vão e continuarão indo embora.

Cabe a nós nos desprendermos das convicções pessoais “intocáveis” e corrermos atrás.

Essa discussão não é uma bobeira ou qualquer coisa assim por envolver aspectos humanos

Infelizmente, é comum na área de TI encontrarmos pessoas que acham que basta ter um super conhecimento e ser alguém que manje horrores do aspecto técnico é mais do que suficiente. Mas não, isso não é suficiente.
Nós não somos máquinas, nem vivemos como se fôssemos. O ser humano é social por definição e, por isso, vive através de interações humanas. É impossível uma pessoa viver completamente reclusa de outras pessoas. Nós precisamos de contato para viver.

Se isso vale para a vida de maneira geral, por que não serviria para o aspecto profissional? Do que adianta termos um profissional super capacitado tecnicamente se este profissional não consegue se comunicar e interagir com os outros profissionais ao redor? A capacitação técnica, em algum momento, não vai ser suficiente por si só. Habilidades sociais hoje, além de necessárias como sempre foram, são também ferramentas profissionais. É preciso saber conversar, ouvir e conviver com outras pessoas, que por muitas vezes pensarão de maneira diferente e enxergarão as coisas por outra óptica. Isso é uma ferramenta de sobrevivência não somente profissional, mas também pessoal.

Mas as coisas não precisam ser chatas…

Não estou tentando pregar regras de convivência profissionais e pessoais, nem tenho esse direito, na verdade. Muito menos estou aqui para julgar. Isso é apenas uma reflexão sobre uma situação muito comum na área de Tecnologia da Informação. Lógico que aquela tiração de sarro e aquela “zueira” com o amigo que programa em alguma linguagem ou utiliza algum framework é super bem-vinda. Lógico que nós podemos ter a nossa opinião, inclusive temos o direito (e até mesmo o dever) de defendê-la. Nós, por muitas vezes, também acabamos por ser intransigentes e passamos dos limites… E isso é normal.

Aqui, a intenção foi falar sobre aquelas pessoas que adotam a intolerância como uma filosofia de vida. Falamos sobre pessoas que acham que ninguém pode pensar de maneira diferente e/ou discordar. E o ponto maior não é a pessoa ser chata ou qualquer coisa assim: o cerne da questão é a pessoa não perceber que na verdade ela está se prejudicando… O grande ponto é a pessoa não perceber que ela poderia ser um profissional muito melhor se deixasse essa falta de flexibilidade de lado e abrisse a mente para tudo que ela poderia aprender se ouvisse com mais atenção o que os outros têm a dizer! o/

E você? Concorda com este post? Já passou por alguma situação onde já precisou trabalhar com um profissional intransigente? Sinta-se à vontade para comentar com a gente nos comentários! =)

C++ Básico
Curso de C++ Básico
CONHEÇA O CURSO

Para que servem os métodos ToString(), Equals() e GetHashCode()?

Muitas pessoas costumam se confundir com três métodos que qualquer classe possui no .NET e no Java: os métodos ToString(), Equals() e GetHashCode(). Os dois primeiros são mais ou menos claros até certo ponto, mas o último vive causando confusão e pânico entre desenvolvedores Java e C#, ainda mais na hora em que necessitamos sobrescrevê-los… Que tal entendermos de uma vez por todas para que estes três métodos servem?

Importante: os conceitos que veremos aqui servem tanto para a plataforma Java quanto para a plataforma .NET! 😀

C# (C Sharp) - Introdução ao ASP.NET Core
Curso de C# (C Sharp) - Introdução ao ASP.NET Core
CONHEÇA O CURSO
Por que toda classe tem estes métodos?

No Java e no .NET, toda classe tem um ancestral por “padrão”: a classe Object. Dessa maneira, qualquer objeto de qualquer classe que criarmos em Java ou .NET, pelo Princípio da Substituição de Liskov, também será uma instância de Object. E a classe Object já expõe por padrão pelo menos os três métodos que estão em discussão.

Java Básico
Curso de Java Básico
CONHEÇA O CURSO
O método ToString()

Talvez este seja o método que é mais claro com relação ao seu propósito. Seu objetivo é trazer uma representação textual de uma instância de um objeto.
Essa representação textual de um objeto vem a ser muito útil principalmente em situações de debugging e de logging. Isso ocorre porque os métodos de saída para o streamming padrão (os famosos System.out.print[ln]() ou Console.Write[Line]()), assim como os principais métodos de praticamente todas as APIs de log (métodos como o debug() e info()) sempre chamam por padrão o método ToString() de instâncias de objetos que tenham sido passadas para eles.

O exemplo abaixo está escrito em C#, mas o princípio é exatamente o mesmo para o Java também, rs.

Imagine o código abaixo:

namespace TreinaWeb.Exemplo
{
    public class Pessoa
    {
        public string Nome { get; set; }
    }
}
// ...
Pessoa p = new Pessoa { Nome = "TreinaWeb" };
Console.WriteLine(p);
// ou
Console.WriteLine(p.ToString());

Perceba que não foi realizada nenhuma sobrecarga do método ToString(), o que faz com que a chamada deste método para a classe Pessoa encaminhe a chamada para a classe ancestral, ou seja: na verdade será chamado o método Object.ToString().

Não se esqueça: ==todo e qualquer objeto/classe sempre vai ter o método ToString() por causa da herança da classe Object==. Se a própria classe não implementa este método, a chamada será encaminhada para o ToString() “padrão”, ou seja, o método ToString() da classe Object.

No caso do C#, a saída do código abaixo seria a representada abaixo, independente da chamada explícita ao método ToString() ou não:

TreinaWeb.Exemplo.Pessoa

A saída tanto para a chamada explícita ao método ToString() como para a chamada ocultando-se a chamada é a mesma. Isso ocorre porque os método Console.Write[Line]() chama o método ToString() da instância repassada como parâmetro por padrão. 😉

O que o .NET emite como saída nesses casos é um elemento conhecido como Full Qualified Name, ou simplesmente FQN. Apesar de o nome assustar, ele é muito simples: trata-se somente do “nome completo” e único da classe, que é composto por namespace + nome da classe.

Se tivéssemos o código equivalente em Java, teríamos uma saída similar à abaixo:

TreinaWeb.Exemplo.Pessoa@1033203

A saída é muito similar ao FQN do .NET, com o acréscimo deste número precedido por @. Costumam dizer por aí que este número logo após o @ é a posição de memória do objeto. Daqui a pouco vamos ver que não é “beeem” assim, hehe.

O grande ponto é que essa saída fornecida pelo método ToString() não tem nenhuma utilidade prática para nós, tanto no streamming padrão de saída, quanto em um arquivo de log. Daí vem a necessidade da sobrescrita deste método.

A idéia é que o método ToString() forneça uma representação simplificada e direta do estado do objeto em questão. Uma maneira de atingir este objetivo é fazer com que a saída forneça o valor atual dos atributos do objeto.

Poderíamos sobrescrever o método ToString() da nossa classe Pessoa da seguinte maneira:

namespace TreinaWeb.Exemplo
{
    public class Pessoa
    {
        public string Nome { get; set; }

        public override string ToString()
        {
            return string.Format("Pessoa [Nome = {0}]", this.Nome);
        }
    }
}

Nossa saída agora, tanto em Java quanto em .NET, seria a mesma abaixo:

Pessoa [Nome = TreinaWeb]

Agora temos uma representação “palpável” e que faça sentido para nós. Essa é com certeza uma representação muito melhor do que as representações padrão da classe Object.

O método Equals()

Antes de falarmos do método Equals(), precisamos relembrar um pouco sobre manipulação de memória, principalmente sobre stack e heap.

Se você quer relembrar como funciona a manipulação de memória, tanto em Java quanto em .NET, você pode ver este nosso artigo, onde tratamos sobre manipulação de memória, stack, heap, value-types e reference-types.

Quando criamos um objeto da classe Pessoa, este objeto será armazenado na memória heap:

Pessoa minhaPessoa = new Pessoa();

Alocação de memória: reference-type

Não se esqueça de que o compilador não acessa os objetos na heap de maneira direta por questões de performance. Sendo assim, o acesso a esse objeto armazenado na heap é feito através de uma referência dentro da stack para o objeto minhaPessoa, apontando onde na memória heap que este objeto está de fato guardado! Sim: estamos falando de ponteiros.

Acesso à memória: reference-types

É importante entendermos estes conceitos para entendermos melhor como o método Equals() funciona.

Quando comparamos objetos, é considerada uma boa prática utilizarmos o método Equals() para fazer a comparação de igualdade. E, quando a classe a qual os objetos em questão pertencem não sobrescreve o método Equals(), o método Object.Equals() será chamado.

Vamos verificar o código abaixo, considerando ainda a classe Pessoa:

namespace TreinaWeb.Exemplo
{
    public class Pessoa
    {
        public string Nome { get; set; }
    }
}
// ...
Pessoa p = new Pessoa { Nome = "TreinaWeb" };
Pessoa p2 = new Pessoa { Nome = "TreinaWeb" }; 

Console.WriteLine(p.Equals(p2));

Faria muito sentido se esse código fornecesse como saída true, afinal, os objetos aparentemente são iguais, certo? Mas esse código irá produzir false, apesar de o nome nos dois objetos serem iguais… Por que isso acontece?

Aí entra em cena a relação entre o método Object.Equals() e a maneira como a memória é manipulada no Java e no .NET.

A cada vez que utilizamos a keyword new, nós estamos instruindo o compilador a reservar um espaço na memória heap e criar um ponteiro na stack para que seja possível acessar esta área da heap. Sendo assim, no código acima, são criadas duas áreas de memória na heap distintas. Estas áreas são gerenciadas, respectivamente, pelos ponteiros p e p2.

O que ocorre quando utilizamos o Equals() baseado na implementação de Object.Equals() é que este por padrão verifica se os ponteiros ==apontam para a mesma área de memória na heap!== Como temos dois objetos instanciados de maneira distintas (chamamos o new para cada um dos ponteiros), nós temos também duas posições de memória distintas para cada um dos objetos p e p2. Por isso, por padrão, temos como resposta false para o código acima.

Agora, vamos imaginar o código abaixo:

namespace TreinaWeb.Exemplo
{
    public class Pessoa
    {
        public string Nome { get; set; }
    }
}
// ...
Pessoa p = new Pessoa { Nome = "TreinaWeb" };
Pessoa p2 = p; 

Console.WriteLine(p.Equals(p2));

Agora sim! Perceba que para p2 nós não instruímos o compilador a criar uma nova área de memória na heap, e sim o instruímos a fazer com que p2 aponte para a mesma posição de memória na heap para a qual p aponta. Desta maneira, neste último código, obteremos true como resposta quando utilizamos a implementação do método Equals() baseada em Object.Equals().

Agora, não faz muito sentido para nós esta implementação do método Equals() baseado em Object.Equals(). Para nós, faria muito mais sentido que o método Equals() na primeira situação deste tópico retornasse true, afinal, ambos os objetos Pessoa possuem o mesmo nome. Por isso, é importantíssimo para nós sobrescrevermos de maneira adequada o método Equals().

Antes de partirmos para a sobrecarga, é importante entendermos algumas premissas para o método Equals():

  • Se ambos os objetos que estão sendo comparados apontam para a mesma posição de memória, é mandatório que Equals() retorne true, baseado na implementação de Object.Equals();
  • x.Equals(x) sempre tem que retornar true;
  • x.Equals(y) sempre tem que retornar o mesmo que y.Equals(x). Este princípio é conhecido como princípio de simetria;
  • Se x.Equals(y) e y.Equals(z), z.Equals(x) tem que retornar true também. Isso ocorre por decorrência do princípio da simetria;
  • x.Equals(null) sempre será falso. O método Equals() por definição não pode retornar exceções do tipo NullReferenceException ou NullPointerException. Isso faz sentido: se um ponteiro é nulo, na verdade ele não aponta para nenhuma área da heap e, portanto, é impossível fazer uma comparação coerente entre os objetos.

Tendo todos estes princípios em vista, temos 3 pontos importantes a serem observados quando sobrescrevermos o método Equals() para que este atenda a todos estes requisitos:

  • Precisamos ver se um dos participantes da chamada do método Equals() é nulo;
  • Precisamos ver se os dois objetos apontam para a mesma área de memória;
  • Precisamos comparar o estado interno dos participantes da chamada do Equals().

Poderíamos sobrescrever o método Equals() da nossa classe Pessoa() da seguinte maneira, afim de que a nossa sobrecarga atenda aos requisitos essenciais:

namespace TreinaWeb.Exemplo
{
    public class Pessoa
    {
        public string Nome { get; set; }

        public override bool Equals(Object obj)
        {
            // Verificando se o segundo participante está nulo
            if (obj == null)
            {
                return false;
            }
            Pessoa p2 = obj as Pessoa;
            // Verificando se o cast foi realizado com sucesso. 
            // Caso não foi, obj nem é um objeto do tipo Pessoa 
            // e automaticamente o método tem que retornar false
            // NOTA: o operador de cast "as" retorna
            // null caso o cast não seja possível
            if (p2 == null)
            {
                return false;
            }
            // Vamos agora verificar se ambos apontam para a mesma posição 
            // de memória utilizando Object.Equals()
            if (base.Equals(obj))
            {
                return true;
            }
            // Agora comparamos o estado interno dos objetos!
            return this.Nome == p2.Nome;
        }
    }
}

Agora nós temos o método Equals() devidamente sobrescrito e respeitando todas as condições necessárias. Considerando esta sobrecarga, se chamarmos o código abaixo…

Pessoa p = new Pessoa { Nome = "TreinaWeb" };
Pessoa p2 = new Pessoa { Nome = "TreinaWeb" }; 
Pessoa p3 = p2;

Console.WriteLine(p.Equals(p2));
Console.WriteLine(p2.Equals(p3));
Console.WriteLine(p.Equals(p3));

… obteremos true em todas as saídas, o que faz muito sentido!

No caso específico do C#, nós ainda poderíamos utilizar p == p2, o que causaria também a chamada de Object.Equals(). Por isso, no C#, temos a possibilidade de fazermos a sobrescrita também de operadores. Nesta situação, precisaríamos sobrescrever o operador == para a classe Pessoa para que tenhamos tudo “nos trilhos” e não tenhamos duas implementações distintas de igualdade. Da mesma maneira, acaba sendo prudente sobrescrever também o operador !=.
Nosso código poderia ficar como está abaixo:

namespace TreinaWeb.Exemplo
{
    public class Pessoa
    {
        public string Nome { get; set; }

        public static bool operator ==(Pessoa a, Pessoa b)
        {
            // Fazemos o cast para Object
            // para evitar StackOverFlowException:
            // o código cairia em loop infinito
            // chamando de maneira encadeada
            // o método Equals() sobrescrito
            // e o operador == sobrescrito! 😉
            if ((((object)a) == null) || (((object)b) == null))
            {
                return false;
            }
            return object.Equals(a, b) || a.Nome == b.Nome;
        }

        public static bool operator !=(Pessoa a, Pessoa b)
        {
            return !(a == b);
        }

        public override bool Equals(Object obj)
        {
            // Verificando se o segundo participante está nulo
            if (obj == null)
            {
                return false;
            }
            Pessoa p2 = obj as Pessoa;
            // Verificando se o cast foi realizado com sucesso. 
            // Caso não foi, obj nem é um objeto do tipo Pessoa 
            // e automaticamente o método tem que retornar false
            if (p2 == null)
            {
                return false;
            }
            // Vamos agora verificar se ambos apontam 
            // para a mesma posição de memória utilizando Object.Equals()
            if (base.Equals(obj))
            {
                return true;
            }
            // Agora comparamos o estado interno dos objetos!
            return this.Nome == p2.Nome;
        }
    }
}
O método GetHashCode() ou hashCode()

Por fim, temos o famigerado método GetHashCode() ou hashCode(). Este é um dos métodos que causam mais confusão nos desenvolvedores.

O hash code é um número inteiro que é gerado de maneira única para cada objeto que esteja alocado em memória. É como se ele fosse um ID único para cada objeto que esteja sob domínio da CLR ou da JVM.

E onde este código único é utilizado? Aí é a grande sacada! Ele é utilizado principalmente dentro de coleções com a finalidade de melhoria da performance. A JVM e a CLR utilizam o hash code internamente para localizar objetos em coleções de maneira mais rápida. E daí também vem sua relação direta com o método Equals().

Uma das maneiras que os ambientes de execução do .NET e do Java utilizam para ver se um determinado objeto existe dentro de uma coleção é comparando os hash codes dos objetos pertencentes à coleção com o hash code do objeto a ser localizado. E, se um objeto é localizado dentro de uma coleção, é porque ele é igual ao elemento dentro de alguma posição da coleção em questão. Percebe a relação entre GetHashCode() e Equals()?

Por isso, vale a máxima abaixo para o método GetHashCode():

Se x.Equals(y) e x e y são objetos da mesma classe, x.GetHashCode() == y.GetHashCode() tem que obrigatoriamente retornar true.

A sobrescrita do Equals() impacta diretamente na implementação do GetHashCode() e vice-versa por causa dessa relação entre os dois. E é por isso que as IDEs emitem warnings quando você, por exemplo, sobrescreve o método Equals() e não sobrescreve o método GetHashCode() e vice-versa.

Agora, um ponto interessante: nem a CLR e nem a JVM garantem a situação inversa! Isso quer dizer que dois objetos, deste que de tipos (ou classes) diferentes, podem por coincidência retornar o mesmo hash code!

Se x é do tipo A e y é do tipo B (ou seja: x e y são objetos de classes diferentes), x.Equals(y) irá retornar por definição false; porém, pode ser que x.GetHashCode() == y.GetHashCode() retorne true.

Essa situação, apesar de ser incomum, pode acontecer. Ela é chamada de colisão. A colisão, quando ocorre, geralmente acontece por causa de sobrescrita equivocada do método GetHashCode().

O método padrão Object.GetHashCode() tem uma implementação um pouco complexa. Se estivermos falando de .NET, a chamada a Object.GetHashCode() irá ser convertida para uma chamada de baixo nível para ObjectNative::GetHashCode. Se estivermos falando de Java, a chamada de Object.hashCode() irá converter a posição de memória onde o objeto está alocado para uma representação numérica, adotando esta representação como sendo o hash code. Inclusive, lembra-se da implementação padrão do método toString() no Java? Pois então… Aquele número estranho que vem depois do @ é o hash code do objeto! =)

Existe um outro ponto muito importante com relação ao método GetHashCode():

Se GetHashCode() tem relação direta com Equals(), um mesmo objeto sempre deverá retornar o mesmo hash code, da mesma maneira que seu método Equals() sempre retornará o mesmo resultado quando haver uma comparação entre dois objetos.

Aí entra um problema grave: a implementação padrão vinda de Object.GetHashCode() não retorna o mesmo hash code para o mesmo objeto. Na verdade, a cada chamada ao método padrão Object.GetHashCode(), um novo hash code será invocado. Isso faz cair por terra o ganho de performance que a utilização de GetHashCode() poderia trazer… Daí vem a necessidade de sobrescrevermos corretamente este método em nossas classes.

Uma técnica geralmente utilizada para conseguirmos sobrescrever corretamente o método GetHashCode() é somar os hash codes de todos os atributos da classe e multiplicar por um número primo. Isso reduz bastante as chances de haver algum tipo de colisão.

Sendo assim, poderíamos sobrescrever o método GetHashCode() da classe Pessoa da seguinte maneira:

namespace TreinaWeb.Exemplo
{
    public class Pessoa
    {
        public string Nome { get; set; }

        public override int GetHashCode()
        {
            // 17 é um número primo! 😉
            return this.Nome.GetHashCode() * 17;
        }
    }
}

O método GetHashCode() auxilia na performance dentro de coleções porque é muito mais simples para os compiladores comparar dois números inteiros do que chamar o método Equals() para cada elemento que faça parte da coleção, sendo que a implementação do Equals() pode ser um pouco complexa e lenta.

Sendo assim, quando o compilador precisa localizar um objeto dentro de uma coleção, ele faz uma iteração em cada elemento que faça parte e faz a comparação com o objeto a ser localizado da seguinte maneira:

  • A primeira comparação é feita através do hash code dos objetos envolvidos. Se eles forem diferentes, o compilador para o trabalho por aqui, já que se dois objetos são iguais (ou seja, o método Equals() com os dois objetos deveria retornar true), o hash code de ambos também deveria ser igual;
  • Caso os hash codes sejam iguais, o compilador levanta a hipótese de estar havendo uma colisão. Então, ele chama o método Equals() para confirmar se os objetos são iguais ou não. Se eles forem iguais, o compilador considera que encontrou o objeto dentro da coleção. Caso não, o compilador avança para o próximo item da coleção e reinicia o processo de comparação.

Consegue perceber como o hash code pode acelerar o processo de manipulação de coleções? Ele ajuda o compilador a evitar a chamada ao método Equals(), que pode ser lento, de maneira desnecessária! o/

O método GetHashCode() é importantíssmo para coleções. Desde coleções mais básicas, como ArrayList; até coleções mais complexas, como Dictionary<TKey, TValue> ou Map<K, U>, se utilizam deste método. O Dictionary<TKey, TValue>, inclusive, utiliza este método para localizar se existem chaves duplicadas ou não.

Os métodos ToString(), Equals() e GetHashCode() são importantíssimos!

Esperamos que você tenha percebido melhor a importância desses métodos para os desenvolvedores. Muitos costumam não dar a devida importância para a sobrescrita correta destes métodos, o que pode ocasionar problemas bem críticos no código em determinadas situações (principalmente quando falamos de serialização de objetos e ambientes de alta concorrência). Você, inclusive, pode acompanhar um problema decorrente da sobrescrita e utilização incorretas destes métodos em um ambiente real neste post do StackOverflow.

Tem alguma dúvida? Quer discutir sobre algum determinado ponto? Quer expor uma situação pela qual você já passou no seu dia-a-dia como desenvolvedor que envolvia a utilização destes métodos? Compartilha com a gente nos comentários! Vamos discutir sobre este assunto! o/

Até o próximo post! =)

C# (C Sharp) - Introdução ao ASP.NET Core
Curso de C# (C Sharp) - Introdução ao ASP.NET Core
CONHEÇA O CURSO

Por que a arquitetura do meu software deu errado?

Atualmente, muitos desenvolvedores estão dando um pouco mais de relevância para algo que não era tão levado em consideração há pouco tempo atrás: a arquitetura do código. Muitas profissionais de TI estão dando cada vez mais valor para a qualidade do código que é produzido, ao invés de simplesmente se preocuparem em “fazer funcionar”. E isso é excelente! =)

Agora, também é notório que muitas pessoas estão aplicando a arquitetura no código que elas constroem de maneira equivocada, quer seja porque aquela arquitetura não era muito adequada à situação, ou porque houve um overhead arquitetural, ou por falta de preocupação com outros fatores além da arquitetura utilizada no código ou até mesmo pela simples falta de domínio. E qual é o resultado disso? Softwares ruins, difíceis de testar e até mesmo com baixa legibilidade, indo completamente contra tudo que arquitetura de software tenta alcançar.

Por que será que esse tipo de coisa acaba acontecendo? Vamos ver algumas possíveis razões.

Java - Criação de aplicações web com Spring Boot
Curso de Java - Criação de aplicações web com Spring Boot
CONHEÇA O CURSO
Esquecimento de que produzir um software não é simplesmente escrever código

Alguns desenvolvedores dão muita atenção a somente… codificar! Porém, produzir um software não consiste somente em produzir código. Muitos se esquecem que existe a parte de testes de software, a parte de configuração do ambiente de execução do software, a parte de validação do funcionamento do software, entre vários outros fatores. Se você vai, por exemplo, construir uma aplicação web, você ou outra pessoa deverão em algum momento configurar servidores de aplicação, firewalls, permissões de acesso e outras coisas. Tudo isso, no final, impacta até mesmo na maneira como você irá realizar a codificação da sua aplicação. O mesmo ocorre com relação aos testes: se você quiser criar um projeto que possa ser testado de verdade, você precisará se preocupar com a maneira como você produz seu código, para que seja possível realizar os testes até mesmo nos menores níveis (como os testes unitários). Também podemos considerar conceitos como granularidade e até mesmo complexidade de algoritmos.

Outro exemplo: você precisa criar um software de missão crítica que irá realizar intercâmbio de informações com o meio externo. Será que aqui, ao invés de utilizar o padrão JSON para fazer este intercâmbio de informações, não caberia o bom e velho SOAP? Perceba que até mesmo esse tipo de situação pode impactar na maneira como você vai montar a arquitetura de seu software, impactando de maneira direta até mesmo na codificação.

Aplicação incorreta de conceitos

Isso é algo comum se tratando de arquitetura e pode até ser “mal de desenvolvedor”: quando aprendemos algo novo, queremos logo sair aplicando em nossos projetos. O grande problema disso é que é bem complicado conseguirmos aplicar um conceito novo em um projeto que tenha uma certa complexidade e da maneira correta, assim, “logo de cara”. Além disso, geralmente nem avaliamos se a situação com a qual nos deparamos é realmente a ideal para aplicar aquele novo conceito que estamos loucos para mostrar para todo mundo que já “dominamos”, quer seja esse conceito uma nova arquitetura (como DDD, por exemplo) ou mesmo algum design pattern. O resultado disso: um código conceitualmente incorreto, com várias falhas de implementação, de manutenção e testes muito complicados e que ninguém sente orgulho em dizer que participou da respectiva codificação. O mais complicado dessa situação: o prazo do projeto vai fatalmente apertar e no final, para cumprimento do prazo, você estará apelando para “gambiarras” que vão contra a própria arquitetura que você tinha imaginado inicialmente.

Eu já tive esse tipo de experiência na pele… Por várias vezes precisei trabalhar em um projeto em que a impressão era que o responsável simplesmente resolveu aplicar todos os design patterns existentes! No final, o que era para facilitar a manutenção e tornar o código mais legível, causou o efeito completamente contrário. O código tinha um custo muito alto de manutenção e era muito mais complexo do que deveria ser. E por que isso aconteceu? Porque vários conceitos de design patterns que estavam sendo utilizados foram aplicados de maneira completamente equivocada. O efeito foi justamente o contrário do que era esperado. Faltou talvez um pouco de conhecimento e bom senso a quem desenvolveu a arquitetura do projeto inicialmente.

É comum aprendermos conceitos de arquitetura de código e querermos aplicá-los de maneira imediata em nossos projetos. Mas, um mantra que acaba ficando é: domine-o primeiro. Estude-o bastante, aplique-o em projetos menores e de menor relevância inicialmente, para que você possa de fato dominar o que você acabou de aprender. Depois, avalie com frieza se realmente é necessário aplicar o conceito em questão. Não adianta você aplicar uma série de conceitos em um software que não exige um código tão complexo. É como se você estivesse “matando uma mosca com um tiro de bazuca”. Não é porque você acabou de aprender algo que você precisa aplicar a novidade em todos os seus projetos e em todas as situações. Se caso for viável e prudente a aplicação de um conceito em seu código, utilize o ciclo de refactoring para realizar esta alteração! =)

Falta de alinhamento de conhecimento entre a equipe

Isso também, infelizmente, é muito comum… Dificilmente você irá desenvolver um software sozinho: você estará inserido em uma equipe. E isso fatalmente irá causar um desalinhamento de conhecimento entre os componentes da equipe, afinal, é normal sabermos mais que alguém e alguém saber mais que a gente. E aqui está uma grande armadilha.

É importante definir um estilo de arquitetura que esteja congruente com o nível de conhecimento da equipe.

Por exemplo: existe um padrão arquitetural que está ficando cada vez mais popular, que é o DDD (Domain Driven Design). O grande ponto é que, para um desenvolvedor conseguir entender e aplicar corretamente os conceitos previstos pelo DDD, ele tem que ter um certo domínio de, no mínimo, orientação a objetos… A chance de as coisas darem erradas se você tentar codificar um projeto baseado no DDD com uma equipe mais iniciante ou que não conheça muito bem orientação a objetos é muito grande. Neste caso, a atitude mais sensata é utilizar um estilo arquitetural mais simples, como por exemplo uma arquitetura baseada em domínios anêmicos (por mais que esta deturpe alguns conceitos de orientação a objetos, rs).

Isso não quer dizer que você não deve estimular a equipe a aprender novos padrões arquiteturais e expandir os conhecimentos, muito pelo contrário. O grande ponto é que isso não deve ser feito com o projeto que está sendo desenvolvido. Ao invés disso, a criação de grupos de estudos paralelos ao desenvolvimento do projeto é, por exemplo, uma excelente idéia. Pode ser que a equipe não possa aplicar a arquitetura desejada no projeto atual, mas certamente estará preparada e entenderá a necessidade de se aplicar uma arquitetura mais elaborada nos próximos projetos.

Muitas vezes, o simples é melhor

De nada adianta pensarmos em uma arquitetura super mirabolante se ela não puder ser implementada com sucesso, ou se ela se demonstrar complexa demais para a situação a qual ela tenta resolver. Aqui, cabe o velho ditado: “menos é mais”. Código não tem que ser algo complexo de se entender e complicado de lidar. Muito pelo contrário: quando mais simples, melhor vai ser a arquitetura do código, desde que a arquitetura consiga atender os requisitos do projeto.

Aqui, cabe um exemplo muito legal: o do StackOverflow. Esse site que certamente você já visitou e usou se você trabalha com desenvolvimento possui uma arquitetura super simples. E nem por isso ele deixa de atender milhões de usuários espalhados pelo mundo. Não é porque o projeto é enorme que sua arquitetura precisa ser a mais complexa do mundo.

O StackOverflow é construído em cima de .NET, SQL Server e Redis. Para que você tenha idéia, nem mesmo frameworks ORM mirabolantes eles utilizam: eles apelam para o velho e eficiente ADO.NET (em algumas partes com o auxílio do Dapper). Se você é desenvolvedor .NET, sabe que aqui não temos nada maluco ou complexo do ponto de vista técnico. Só que funciona muito bem! A arquitetura é simples, direta e eficiente. Não foi necessário nenhum tipo de complicação para fazer o StackOverflow funcionar! =)

Se estiver curioso sobre a arquitetura do StackOverflow, você pode ver maiores detalhes neste link aqui.

Preciosismo do responsável pela arquitetura

Como geralmente não existe um “cargo” específico para arquiteto de software, geralmente o desenvolvedor com mais experiência e conhecimento acaba exercendo este papel. E isso é muito bom, porque ele terá uma oportunidade muito legal para repassar todo seu conhecimento e experiência para o restante da equipe ao definir a arquitetura dos projetos que a mesma vai desempenhar. O grande problema é quando essa pessoa se fecha em seus conhecimentos, se indispondo a ouvir opiniões da equipe ou a aprender coisas novas.

É importante sempre lembrarmos que a área de desenvolvimento de software é muito dinâmica. E isso logicamente ocorre também com arquitetura de software. É fácil percebermos isso: há pouco tempo atrás, era o paradigma procedural quem imperava. Aí, menos tempo atrás ainda, veio o paradigma orientado a objeto. Hoje, temos uma explosão do paradigma funcional, por causa da popularização do JavaScript e de outras linguagens funcionais, como o F#. As coisas estão sempre mudando. Novos conceitos estão sempre surgindo, e continuarão a surgir. Conceitos antigos caem em desuso, e continuarão caindo. Essa mutabilidade é que dá a graça e faz a área de desenvolvimento de software em geral ser tão desafiadora.

Quem assume o papel de arquiteto precisa estar antenado nestas mudanças. Na verdade, qualquer profissional de TI precisa sempre estar a par dessas novidades. Mas, no caso do arquiteto, isso cai mais na situação de que ele precisa ter consciência de que o que ele pensa e fala não é uma verdade absoluta, pois as coisas estão sempre mudando. Preciosismo em arquitetura de software nunca será bem-vindo.

É complicado falar de “certo” e “errado” em arquitetura de software

É importante salientar que este post não tem a menor intenção de ser um “juiz” e dizer que tudo que você faz hoje está “certo” ou “errado” com relação a arquitetura de software. Eu penso que, tratando-se de arquitetura, isso não existe. Têm-se o “melhor” ou “mais aplicável” e o “menos melhor” ou “menos aplicável”. E esse enquadramento pode depender de uma série de fatores externos, como o nível de conhecimento da equipe, plataforma a ser utilizada e até mesmo o tempo disponível para execução do projeto. É aí que entra o conhecimento e até mesmo um certo feeling por parte do responsável por definir a arquitetura do projeto de software em questão. Também é necessário lembrar que, mais importante que aplicar um conceito arquitetural todo rebuscado em um projeto, querendo ou não, o mais importante é entregá-lo. É para isso que existem os ciclos de refactoring.

Os pontos deste post tratam apenas de minha opinião pessoal. Posso estar certo ou errado… Você pode concordar comigo ou não… E é justamente isso que torna tão legal conversar e discutir sobre arquitetura de software. o/

Concorda com os pontos deste post? Discorda? Quer acrescentar alguma coisa? Tem uma opinião diferente sobre algum ponto? Ou ainda tem uma experiência que gostaria de compartilhar? “Bora” conversar aí embaixo nos comentários! 😀

Até o próximo post!

Ruby on Rails - Testes unitários
Curso de Ruby on Rails - Testes unitários
CONHEÇA O CURSO