fbpx
Como definir a Arquitetura de um Software
Publicado em: sábado, 1 de mar de 2014

Esse post nasceu de uma thread em um grupo de discussão antigo. Com o passar dos anos, passei a usar esse post como um guia sobre os tradoffs inerentes à escolha e definição da arquitetura de um software. Representa uma visão de meados de 2008, que virou post em 2014 e vem sendo amadurecida no decorrer dos anos. Achei interessante conseguir transcrever esses meus pensamentos sobre o assunto e acho que talvez possa ajudar aqueles que estão se questionando não só sobre o quê, mas como tomar decisões sobre a arquitetura do próximo projeto..

Para que?

Legal quando precisamos pensar em arquitetura, mas é interessante revisitar conceitos e definir a função de uma arquitetura ou mesmo de um sistema? Acredito que um sistema possa ser definido como:

Prover uma funcionalidade ou solução de negócio, usando tecnologia e técnicas de programação, entregando um sistema ou solução eficaz e eficiente.

Assuma consigo uma verdade:

Nada mais é necessário, do que oferecer uma funcionalidade de negócio de forma eficaz e eficiente.

Então quando pensar em incrementar sua arquitetura com algum framework ou padrão, avalie se aquilo aumenta eficácia, ou eficiência, caso contrário corte! Corte severamente! Mas, pense bem no que você considera eficácia e eficiência. Entregar no prazo faz parte da eficácia quando prazo é relevante. Suportar evoluções rápidas faz parte da eficácia também. Ser confiável (menor quantidade de erros) representa eficiência. Todos esses itens demandam pontos de atenção na arquitetura.

Você não usa NHibernate ou Entity Framework porque é bonito! Inevitavelmente eles reduzem performance, mas você ganha em produtividade…! Em contrapartida Dapper, na categoria de Micro ORM ajuda a ter alguma produtividade em relação ao Vanilla ADO.NET.

Não quero confundir, só criar um paradigma que se resolve com a seguinte frase:

Se o que quer adicionar é bonito, mas não traz nem eficácia, nem eficiência, corte! [Lean: “Eliminate waste”]

Corte aquilo que não lhe traga resultados, pois inevitavelmente trará complexidade.

Como?

Na definição de uma arquitetura, temos sempre de pensar no objetivo do projeto, mas não podemos fazer isso sem pensar no caminho a ser percorrido. Para entregar o produto precisamos pensar em:

Equipe

Qual o Skill do seu time, quais os gaps técnicos, qual o nível de motivação da sua equipe em aprender algo novo. Quão reativos são?

Você não vai muito longe se aplicar uma arquitetura muito complexa, na qual os desenvolvedores não conseguem sequer usá-la, quem dirá entendê-la. Da mesma forma como você não vai muito longe se seu time não estiver disposto a aprender. Nesses casos conservadorismo é extremamente relevante. Em contrapartida, times muito jovens tendem a querer testar tudo que é novo, mas eles não querem testar de forma isolada, em casa, eles querem testar no projeto da empresa, e isso é um risco absurdo. Valide toda e qualquer ideia, e não saia fazendo concessões sem garantir que a adoção de algo não tratá efeitos colaterais para o seu projeto. Entenda o perfil do seu time.

Contexto

Qual o cenário atual? Você tem as premissas para respeitar? Necessita de alguma coexistência com um outro projeto que já está em on-going? Em qual nível você precisa se preocupar com retrocompatibilidade? Qual a infraestrutura (servidores, serviços) que você dispõe? Até onde você pode ir? Quanto de dinheiro poderia gastar com algum serviço de terceiros, ou cloud? Estando em uma cloud, quanto pode gastar com recursos?

Só para parafrasear, eu tive limitações graves, agora em 2013, por causa de infraestrutura. Meu parque .NET antigo é todo hospedado em Windows 2003 Server, sim 2003. Só em meados de 2014 terei alguma coisa em 2012 server, portanto estou preso a no máximo usar o framework 4.0. #ficaDica

É preciso entender que o estado da arte, onde não precisamos nos preocupar com custos é em geral utópico na maioria das empresas. Sempre haverá limitadores que precisam ser considerados. Assim, em algum momento pode fazer sentido trocar PaaS por IaaS para algum serviço ou até fazer o processo contra-migratório, da cloud de volta para On-Premise.

Mas lembre-se: Não estou nem sugerindo, nem recomendando esse tipo de transição, muito menos ainda nessa direção. Só estou mostrando exemplos que aconteceram e se mostraram acertos, e claro, é incrível pois quebra todo e qualquer argumento absolutista sobre uma decisão qualquer.

São necessidades inerentes à restrições orçamentárias e você como arquiteto precisa entender bem quais são suas restrições ou limitações, e ser capaz de prover alternativas que sejam eficientes.

Arquitetura

Podemos definir arquitetura, como a estratégia que usaremos para criar, entregar e manter um produto. Entenda por estratégia itens como:

  • Plataforma
  • Frameworks
  • Fluxo de Desenvolvimento
  • Versionamento
  • Organização e Empacotamento do Código
  • Deploy
  • Ferramentas de desenvolvimento e auxiliares
  • Padrões de Projeto
  • Padrões de Design

Todos esses itens fazem parte da arquitetura.

Sobre esse ponto tenho uma história interessante. Assim que cheguei no iMusica a primeira dificuldade que encontrei no processo era o deploy. Não havia no repositório de fontes uma versão fiel à versão de produção de muitos sistemas. Na verdade ter uma versão fiel no repositório era uma exceção. O motivo era o deploy feito a partir das máquinas de desenvolvimento, e esse deploy geralmente era diferencial. Copiava-se o que havia sindo modificado, ou o que achava-se que havia sido modificado.

Com um fluxo de deploy contínuo baseado em 2 builds no jenkins, foi fácil eliminar esse problema. O deploy só acontece via ferramenta. Todos os passos são respeitados e somente builds completos chegam a produção. A escolha do Jenkins se deu por causa da proficiência, minha claro, que iria instalar e configurar o serviço, mas também pelo fato de não termos servidores novos, e o fluxo de aprovação de compras ser extremamente custoso, muitas vezes inviável. O Jenkins caiu como uma luva! Esse é um exemplo de decisão tomada em função da equipe e do contexto. É importante trabalhar com o que se tem!

Voltando ao assunto principal, sem esses pilares, estamos desrespeitando leis universais, não levando em consideração ou o cenário, ou o objetivo do projeto (que leis são essas? Não sei, mas alguma deve estar desrespeitando).

Note que equipe é o primeiro ponto, e arquitetura é o último. A ordem é proposital, e acredito que deva ser priorizada assim. São as pessoas que movem a engrenagem, elas que vão dar manutenção nas aplicações, aprender tecnologias novas para realizar coisas não vistas até então, são elas as responsáveis pelo sucesso de qualquer projeto. O contexto, e nele inclui-se automaticamente a empresa, tem sua medida de responsabilidade, mas são as pessoas que realmente fazem a maior diferença. A arquitetura é mera resultante, é uma direção que aponta o caminho para chegarmos de um ponto a outro, com a equipe que temos e com o contexto que temos. Infraestrutura, suporte, quantidade de profissionais, investimento em contratações, treinamento, isso tudo encaixo na parte contexto, são os MEIOS que a empresa provê para que você realize seu projeto [Lean: “Empower the team”]

Até aqui estamos teorizando muito. Esse é o intuito, mas daqui para frente vamos falar um pouco da prática. O que você pode fazer para desenhar efetivamente uma arquitetura.

O que usar?

Você deve se perguntar, em algum momento do projeto: O que vou usar? Como escolher o melhor design? Quais frameworks, quais padrões, quais paradigmas e princípios usar?

Bom, seja conservador, na medida do possível. Comece endereçando os problemas a pontos da sua arquitetura.

  • Difícil de gerenciar versões: ALM, Gerência de Configuração, Release Management, SemVer
  • Difícil de gerenciar deploy: Continous Delivery, Continous Deployment
  • Alta quantidade de erros / Insegurança ao realizar grandes mudanças: Testes

Tem receita de bolo para quase tudo: JS para interfaces ricas, ORM para facilitar o acesso a dados, DDD para melhorar a forma de construir o negócio em si, ASP.NET WebForms para aplicações CRUD, ASP.NET MVC quando precisa-se de maior controle na UI, ou mesmo pelo padrão, reaproveitamento e testes. Enfim, se você fizer uma matriz com a sopa de letrinhas, vai chegar ao seu design, esbarrando hora ou outra em escolhas entre produtos e implementações de um mesmo padrão, e como não poderia deixar de ser, lembre-se:

Toda escolha, uma renúncia.

Cada cenário denota problemas diferentes, resolva-os primeiro, pois o foco é no objetivo. Com os problemas endereçados, vamos pensar em otimização. Veja quais padrões, frameworks, práticas etc otimizariam a eficiência ou eficácia do seu projeto. Lembre-se sempre da busca por eficiência e eficácia.

Dando a cara a tapa!

Saindo um pouco da abstração, vamos à prática. Deixa eu explicar um pouco de como eu faço. Primeiro, trabalho como arquiteto há alguns muitos anos, tive projetos bons e projetos ruins, mas alguma coisa aprendi nesse tempo:

Seja presente e use o que você desenha.

Essa história de arquiteto ausente, arquiteto astronauta, não existe. Arquiteto que não codifica é cozinheiro que não prova sua comida. O arquiteto precisa usar aquilo que está produzindo ou vendendo, é o mínimo para compreender e conhecer os pontos fortes e fracos da arquitetura, trabalhar as dificuldades, enaltecer as facilidades, e administrar os pontos de abstração. Somente no dia-a-dia descobrimos os maiores gargalos e problemas do dia-a-dia do desenvolvedor, que geralmente descobrimos por acaso, ao esbarramos neles. Trabalho com arquitetura de soluções desde os primeiros anos de minha carreira, seja definindo uma arquitetura completa, algo recorrente somente mais tarde, ou definindo partes, algo que já desde os primeiros anos, já fazia, nos projetos em que atuava. Meu prazer em 2003 era criar componentes JS e VBS para web, eram elementos que otimizavam o tempo de desenvolvimento, entregando muita funcionalidade a baixo custo de desenvolvimento. Sempre trabalhei nas arquiteturas que opinei, mas em 2008 caí na furada de não desenvolver sob aquilo que desenhei. Foi frustrante! E aqui vai uma dica de ouro: Se não houver tempo de codificar, no cronograma do arquiteto, há graves problemas com o dimensionamento da sua alocação, porque boa parte do que você precisará codificar, são mecanismos e facilitadores, quando não, você precisa pegar alguma parte do negócio que use aquilo que você desenhou.

Em 2008 pedi demissão de uma empresa que teimou em me alocar como arquiteto de 7 projetos simultaneamente. Se querem que você trabalhe assim, sugiro que vc procure outra empresa, sério! É desvalorizar demais o tanto que vc estudou, estuda e precisará estudar.

Outra dica:

Você tem 2 ouvidos e 1 única boca. #ficaDica

As pessoas que já trabalharam nos projetos, antigos/atuais, que já vivem uma rotina onde você está chegando, ou mesmo trabalham há anos do teu lado, têm visões diferentes das tuas. Geralmente essas visões são interessantes, proveitosas e ajudam muito a entender dificuldades e gaps, ou das pessoas ou da arquitetura atual, esses papos ajudam na compreensão do projeto e na identificação de seus principais pontos de atenção, sejam eles positivos ou negativos. Endereçar as reclamações comuns e fortalecer os pontos elogiados torna a arquitetura atraente para quem está desenvolvendo. [Lean: “Empower the team”]

Probation

Uma fase não pode ser ignorada, a fase que chamo de probation. São aqueles poucos dias, no início do projeto, onde ninguém te conhece e você precisa mostra a que veio. Essa é uma fase crítica, na qual você precisa dar muita atenção. O mercado está repleto de profissionais medíocres, e arquitetos astronautas ou arquitetos de overview. É comum, ao chegar em uma empresa, ou time, que as pessoas questionem sua maturidade e senioridade, afinal será que você é tudo isso mesmo? Bom, nessa fase de probation você precisa cativar o time a respeito da sua competência, e a melhor forma de fazer isso é buscando momentos em que você possa mostrá-la, sem se abster em nenhum momento da humildade. Entenda: Não saber/conhecer uma tecnologia, produto ou pattern é uma condição temporal, não há tema que não se possa aprender com alguma dedicação em pesquisa/estudo. O conhecimento mais relevante ao arquiteto está na clareza sobre como compor produtos e serviços afim de orquestrar uma composição de design, recursos e serviços de forma a oferecer um melhor desenho arquitetural, pautando suas decisões nos 3 pilares: Equipe, Contexto e Arquitetura. Você não se destaca por conhecer os produtos do Azure, mas por compor soluções que os utilizem, mas também por conhecer alternativas que permitam recriar tais recursos on premise, com abrangência parcial ou mesmo total. É a clareza sobre possíveis arquiteturas, endereçando as questões do contexto ao qual está inserido que fazem de você, um diferencial. Com o passar dos anos, você acaba por entender que cada vez mais são lançados produtos e serviços que são “mais do mesmo”, são aqueles produtos que exercem um papel que diversos outros já exerciam. Isso facilita muito, pois na medida que você faz uma pequena imersão nesse novo assunto, é capaz de encontrar as similaridades e diferenças. Isso enriquece seu leque de opções. Não conhecer algo, também se aplica a você não conhecer algo. E não há problema quanto a isso! Só não minta! Não conhecer algo não é vergonha para ninguém, mas não faça disso muleta. Se encontrou algum tema que não está familiarizado, resolva! Sem mimimi.

E sobre o seus 2 ouvidos e 1 boca, você também tem 2 braços, sim, é verdade! Além de falar menos, e escutar mais, faça mais! Arregace as mangas e mostre que o que você está propondo é possível. Mostre com algo prático, sempre que necessário. As pessoas precisam entender que você não é nem mágico, nem charlatão.

Não se frustre, mudanças são bem vindas!

Não se frustre com uma verdade universal:

Não importa o tamanho do projeto, a qualidade da especificação, o nível de detalhamento, o quão debatido ou esmiuçado foi. Em algum momento algo vai mudar! Se não o projeto, o negócio, ou o requisito. Se nada mudar, ainda assim possivelmente sua visão mudará!

Em algum momento você pensará: Essa parte ficaria melhor de outra forma…

Inevitavelmente algum fator interno ou externo demandará mudanças, ou no código de negócio, ou na arquitetura, ou em ambos.

Assim busco facilitar a vida de quem está no dia-a-dia, criando abstrações, utilitários, patterns que possam promover o refactoring. A arquitetura deve favorecer o refactoring, para que possa ser estimulado. Nada se mantém imutável do início ao fim do projeto, principalmente em projetos grandes. Não importa como sua arquitetura está sendo modelada, favoreça sempre o refactoring, pois o custo de ter de trabalhar em uma solução ou um pedaço do negócio mal modelado é altíssimo.

Como eu promovo as mudanças:

  • Utilizo muito ORM, e geralmente com código gerado. As pessoas podem mudar o banco, a qualquer momento, eu garanto que no mínimo a aplicação é compatível com as mudanças. Mudanças estruturais no banco afetam o build, quebrando-o. A ausência ou mesmo o rename de campos no banco, geram quebras de build. Esses erros só apareceriam em runtime, e esse é um ganho substancial. Ao resolver a quebra de build, você tem uma minima garantia de que as coisas estão coerentes, mas claro, se você adicionar um campo não nulo em uma tabela existente, o build não quebrará, mas, como uso linq em quase 100% do projeto, é fácil identificar quem precisa ser modificado.
  • Uso muito IoC e abstrações: Toda a infraestrutura é abstraída. Redis, Spring, MongoDB, SQL Server, MySQL, RabbitMQ, nada é feito diretamente manipulando providers nativos. Uma troca de tecnologia é muito simples de ser realizada. Ao mesmo tempo, os providers estão disponíveis para utilização, mas a utilização direta, por fora da arquitetura é desencorajada.

Evite débitos técnicos

Uma gambiarra é igual a uma mentira.

Sempre precisará de novas para justificar a primeira.

Por isso, evite manter débitos técnicos por muito tempo. Destrua-os! Muitas vezes nós mesmos os criamos, quando por algum motivo, deixamos de fazer a coisa certa para fazer o hipoteticamente mais rápido. Para minimizar esse tipo de problema, é importante que sua arquitetura suporte e favoreça a realização de mudanças com o menor impacto possível e caso seja inevitável o impacto, que facilite a gestão do que precisa ser adequado.

Overdesign

Lembra do papo sobre eficiência e eficácia? Então, tento sempre garantir o maior número de itens não funcionais na arquitetura. Pra isso não abro mão de alguns padrões, como IoC e DI. Quando falo de abstrações, penso automaticamente de um dos itens do Lean (http://en.wikipedia.org/wiki/Lean_software_development ) “Decide as late as possible”. Muitas das estratégias de deploy, agrupamento, processamento distribuído, deixo para o final do projeto. Mas para isso, preciso ter uma arquitetura que me suporte esse tipo de abstração. Muitos consideram Overdesign, eu chamo de adaptabilidade. As coisas mudam!

Enfim, há uma infinidade de pontos que tento abstrair, para garantir uma codificação de negócio limpa e ao mesmo tempo com garantias de segurança, escalabilidade e confiabilidade.

Simples mas não Simplificado

Mas você pode pensar que a complexidade do código é grande? Não, não é. Tudo isso, que desenho geralmente envolve média complexidade de infraestrutura, para ter uma alta produtividade na utilização.

Abaixo temos o código-fonte de um serviço, usado por uma aplicação Web, Windows Services e outros. Para cada cliente o deploy é diferente (Inline Wcf, Etc). E não tenho dor de cabeça com gestão. Esse código tem garantia de exception replacement, exception management (log em fila RabbitMQ e importação futura para banco), suporte a exceptions para WCF (faltas), ainda, abertura/fechamento automático do contexto do NHibernate (e podem ser anilhados), em outro cenário tenho operações em 2 bancos (mysql e sqlserver) simultaneamente com NHibernate, operando da mesma forma com que abstrai operações com MongoDB. Tudo para que o desenvolvedor se preocupe apenas com código de negócio e não com a infraestrutura.

public class ExemploServico
{
  [NHContext("MetaContext", false)]
  public Photo GetOrCreatePhoto(string fileName)
  {
    if(string.IsNullOrWhiteSpace(fileName)
        throw new ArgumentNullException("fileName")

    Photo photo = this.PhotoRep.MatchByName(fileName);
    if (photo == null)
    {
        photo = this.CreatePhoto(fileName);
    }
    return photo;
  }

  [NHContext("MetaContext", true)]
  public Photo CreatePhoto(string fileName)
  {
    if(string.IsNullOrWhiteSpace(fileName)
        throw new ArgumentNullException("fileName")

    Photo photo = new Photo()
    {
        Src = fileName
    };
    this.PhotoRep.Save(photo);
    return photo;
  }
}

Você pode se perguntar e afirmar que é chato desenvolver assim, que não há complexidade etc. Bom, quem gosta de ficar repetidamente gerenciando complexidade em tudo é minha esposa!

A primeira vista você pode considerar eventualmente burocrático, no entanto relembro: O desenvolvedor não deixa de cuidar desses assuntos, mas o local onde ele escreve esse tipo de código, de iteração com tecnologias específicas, reside não mais no core do projeto de negócio, mas em um projeto dedicado à arquitetura. Em vez de criar a dependências diretas com o código de negócio, ele é incentivado a criar mecanismos e abstrações genéricas, para prover maior reaproveitamento. Aí entra o refactoring, novamente.

Uma abordagem que uso para contingenciar duplicidade de comportamentos na arquitetura é:

Uma mesma feature não pode ser implementada por 2 mecanismos, não é possível conviver com 2 comportamentos parecidos e 2 mecanismos parecidos com a mesma finalidade. Uma nova demanda na arquitetura, que enderece um assunto que já há tratamento no passado exige adequação de requisitos e refactoring. No que compete a adequação de requisitos, o novo mecanismo deve no mínimo atender às mesmas necessidades do anterior, agregando as novas features e requisitos. Isso é necessário para que não tenhamos código duplicado e padrões duvidosos, dúbios.

Só deve haver 1 implementação para tratamento de transações, 1 implementação de exception handling, 1 implementação para contextos. E quando uma nova demanda surgir, ou a implementação anterior é refatorada, ou uma nova é criada, e a antiga é REMOVIDA/DELETADA/EXCLUÍDA do código. O responsável por tal modificação é, em geral o profissional que está criando a nova feature. Ele precisa ter total consciência sobre os impactos daquilo que está sendo feito, e precisa conhecer a arquitetura e seus mecanismos arquiteturais.

Essa abordagem em algum nível inibe o desenvolvimento de abstrações burras, feitas exclusivamente por gostos pessoais, mas a longo prazo, garante a coesão dos mecanismos arquiteturais, além de favorecer a compreensão de como a arquitetura é conectada à solução. Esse é um aspecto que em geral tenho enfrentado dificuldades com os times. Já que na minha visão, se uma nova ideia suplanta a anterior, é papel de quem implementa essa nova ideia, refatorar todo o resto que dependia da ideia anterior, gerando comprometimento e aprendizado sobre como as partes estava funcionam.Para isso é fundamental e não abro mão de que seja a mesma pessoa a fazer ambos. O refactoring do que já estava pronto, é parte da tarefa de criação do mecanismo. E se sou eu quem faço o mecanismo, eu também faço a mudança no código legado (mesmo que esse legado tenha sido criado a 2 semanas). Quem quer que faça o mecanismo é responsável por rever essas quebras.

Débitos técnicos são revistos sempre, mesmo que fora do horário do expediente. E morrem sim! Mesmo que eu tenha de dar sangue para que esse débito técnico morra, será feito! Promessa é dívida!

Essa é uma decisão autoritária sim, e não abro mão desta em virtude do benéfico resultado. Já vi muita gente querendo criar coisas novas, mas querendo esquivar do refactoring. Por medo. Se a própria arquitetura não for um lugar no qual também aplicamos a política de refactoring, não há esperança de que essa política funcione no resto do projeto.

Essa arquitetura, esse nível de simplicidade só é possível com abstrações, e muitas abstrações. Essa é realmente a mágica que possibilita construir soluções robustas. Abstraia o máximo de complexidade do código de negócio, movendo para pontos centrais de sua arquitetura. Para o desenvolvedor, precisa ser algo simples, algo que não o leve a cometer erros, para que ele possa ser mais produtivo.

Mas quem tiver interesse na parte mais complexa da arquitetura, colaborará com abstrações, já quem não estiver interessado, não precisará aprender, apenas usar. A escolha é de quem desenvolve, mas em um time misto, onde 20% é interessado e 80% está à deriva, você facilmente consegue produtividade e motivação dos 2 grupos, e fazer com que entreguem melhor suas soluções.

Bom, meus contatos estão aqui no blog, estou sempre disposto a ajudar e espero possa contribuir um pouquinho com seus projetos.

[2016] Esse post nasceu em 2013, mas só foi publicado em 2014. Agora no finalzinho de 2016 estou revendo-o para não deixá-lo tão datado. Esse foi um dos posts mais vistos desde 2013 e já me rendeu boas histórias. Espero que tenha curtido, agradeço ter chegado até aqui, eu sei que é uma leitura densa, e muitas vezes chata. Mas alguém tem de escrever o que precisa ser escrito.

[05/05/2018] Revisitei alguns pontos novamente para complementar.

[13/05/2018] Revisão.

Obrigado pela extensa leitura.

Luiz Carlos Faria

 
 
 

O Cloud Native .NET é meu principal projeto.

Onde empenho energia para ajudar, acompanhar, direcionar Desenvolvedores, Líderes Técnicos e jovens Arquitetos na jornada Cloud Native.

Conduzo entregando a maior e mais completa stack de tecnologias do mercado.

Ao trabalhar com desenvolvedores experientes, eu consigo usar seu aprendizado com .NET, banco de dados, e arquitetura para encurtar a jornada.

Ao restringir à desenvolvedores .NET eu consigo usar do contexto de tecnologias e problemas do seu dia-a-dia, coisas que você conhece hoje, como WCF, WebForms, IIS e MVC, por exemplo, para mostrar a comparação entre o que você conhece e o que está sendo apresentado.

É assim que construímos fundamentos sólidos, digerindo a complexidade com didática, tornando o complexo, simples.

É assim que conseguimos tornar uma jornada densa, em um pacote de ~4 meses.

Eu não acredito que um desenvolvedor possa entender uma tecnologia sem compreender seus fundamentos. Ele no máximo consegue ser produtivo, mas isso não faz desse desenvolvedor um bom tomador de decisões técnicas.

É preciso entender os fundamentos para conseguir tomar boas decisões.

4 Comentários

  1. Regivaldo Fraga

    Parabéns, um ótimo artigo… obrigado e abraços

    Responder
  2. Thiago juliano da Silva

    Parabéns Luiz, um ótimo artigo. Completo e escalrecedor como deve ser.
    O que achei mais interessante é que você não ficou com o famoso “mimimi”, foi direto no ponto. Realmente apresentou a ferida, não apenas ficou colocando o dedo nela….

    Responder
  3. Jonas

    Parabéns Luiz Carlos. Vou iniciar na carreira de arquiteto de soluções e certamente, vou levar comigo suas citações e ensinamentos aqui. Obrigado por compartilhar e falar a verdade….

    Responder
  4. Lucas Leati

    Parabéns!!!! Conteúdo com muita qualidade! Estou aprendendo muito com seus artigos!

    Responder

Enviar um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Esse site utiliza o Akismet para reduzir spam. Aprenda como seus dados de comentários são processados.

Luiz Carlos Faria

Mensagem do Autor

Espero que goste desse post. Não deixe de comentar e falar o que achou.

Se acha que esse post pode ajudar alguém que você conheça, compartilhe!

 

Lives

Fique de olho nas lives

Fique de olho nas lives no meu canal do Youtube, no Canal .NET e nos Grupos do Facebook e Instagram.

Aceleradores

Existem diversas formas de viabilizar o suporte ao teu projeto. Seja com os treinamentos, consultoria, mentorias em grupo.