Introdução
Recentemente, as desvantagens das arquiteturas orientadas a serviços e, em particular, das arquiteturas de microsserviço (MA), foram ativamente discutidas. Há poucos anos, muitos estavam dispostos a migrar para o MA por causa de seus muitos benefícios: flexibilidade na forma de implantações independentes, propriedade transparente, maior estabilidade do sistema e melhor separação de interesses. No entanto, a situação mudou recentemente: a abordagem de microsserviço começou a ser criticada por sua tendência de aumentar seriamente a complexidade, o que às vezes torna difícil implementar até mesmo funções triviais . (Falamos sobre isso na palestra " Microsserviços: o tamanho é importante, mesmo se você tiver Kubernetes " - tradução aproximada.)
O Uber tem atualmente cerca de 2.200 microsserviços críticos e nós mesmos experimentamos todos os prós e contras dessa abordagem. Nos últimos dois anos, o Uber tentou reduzir a complexidade do cenário de microsserviços, mantendo as vantagens da arquitetura ao longo do caminho. Com esta postagem, planejamos apresentar nossa abordagem genérica para arquiteturas de microsserviço, chamada Domain-Oriented Microservice Architecture (DOMA).
Embora seja comum criticar as arquiteturas de microsserviço por suas deficiências nos últimos anos, poucos ousaram proclamar que deveriam ser abandonadas inteiramente. Seus benefícios operacionais são muito importantes; além disso, parece não haver alternativas (ou extremamente limitadas) para essa abordagem. O objetivo de nossa abordagem genérica é ajudar as organizações que desejam reduzir a complexidade geral do sistema, mantendo a flexibilidade do MA.
Este artigo explorará o DOMA, os desafios que levaram a essa abordagem no Uber, seus benefícios para as equipes de plataforma e produto e, por fim, algumas dicas para quem deseja migrar para essa arquitetura.
O que é um microsserviço?
Os microsserviços são uma extensão das arquiteturas orientadas a serviços. Ao contrário dos "serviços" bastante grandes da década de 2000, os microsserviços executam uma determinada tarefa limitada. Esses aplicativos são hospedados e acessíveis pela rede e fornecem uma interface bem definida. Outros aplicativos acessam essa interface usando chamada de procedimento remoto (RPC).
Uma característica fundamental do MA é a maneira como o código é postado, invocado e implantado. Aplicativos grandes e monolíticos geralmente são divididos em componentes encapsulados com interfaces bem definidas. Essas interfaces são então chamadas diretamente no processo, em vez de pela rede. Nesse sentido, um microsserviço pode ser considerado uma espécie de biblioteca de menor desempenho (devido ao efeito dos atrasos da rede e do tempo na serialização / desserialização) ao chamar qualquer uma de suas funções.
Pensando em microsserviços dessa maneira, podemos nos perguntar por que precisamos de uma arquitetura de microsserviço? A resposta clássica para essa pergunta é devido à capacidade de implantar componentes individuais de forma independente e escaloná- los facilmente.... Para um aplicativo grande e monolítico, a organização é forçada a implantar ou liberar todo o código ao mesmo tempo. Como resultado, cada nova versão traz muitas mudanças. As implantações tornam-se arriscadas e demoradas. Qualquer erro pode derrubar todo o sistema.
Portanto, as empresas estão mudando para microsserviços para facilitar o uso , sacrificando o desempenho . Eles também devem arcar com os custos adicionais de manutenção da infraestrutura necessária para microsserviços. A experiência mostra que em muitas situações esse compromisso faz sentido. Ao mesmo tempo, é um argumento poderoso contra uma transição prematura para MA.
Motivação
Na época da transição para microsserviços (por volta de 2012-2013), nós do Uber tínhamos dois serviços monolíticos principais e enfrentamos muitos problemas operacionais que os microsserviços resolvem com sucesso:
- Riscos de disponibilidade. Qualquer erro na base de código do monolith pode derrubar todo o sistema (neste caso, todo o Uber).
- Implantações arriscadas e caras. Eles eram muito difíceis de realizar e muitas vezes tinham que voltar para a versão anterior.
- Fraca separação de áreas de responsabilidade. Era muito difícil saber quem era o responsável por quê na colossal base de código. Com o crescimento exponencial, a pressa às vezes confunde os limites entre a lógica e os componentes.
- Trabalho ineficiente. Os problemas acima em conjunto dificultavam o trabalho das equipes de forma independente ou independente umas das outras.
Em outras palavras, no contexto do aumento no número de engenheiros do Uber de dezenas para centenas de pessoas e o surgimento de um grande número de equipes que possuem suas próprias partes da pilha de tecnologia, a arquitetura monolítica cada vez mais amarrou o destino dessas equipes e não permitiu que trabalhassem independentemente.
Portanto, decidimos mudar para MA. Como resultado, nossos sistemas se tornaram mais flexíveis e permitiram que as equipes se tornassem mais autônomas .
- Confiabilidade do sistema. A confiabilidade geral do sistema aumenta com a transição para MA. Um serviço individual pode travar (e pode ser revertido para uma versão anterior) sem o risco de travar todo o sistema.
- . - : « ?», — .
- . , . , , , , .
- . .
- . .
Não é exagero dizer que o Uber não poderia ter atingido sua escala e nível de qualidade atuais sem o MA.
No entanto, conforme a empresa continuou a crescer e o número de engenheiros aumentou de centenas para milhares, começamos a notar uma série de problemas associados ao aumento significativo da complexidade do sistema. No caso do MA, sacrificamos uma única base de código monolítica em troca de uma série de caixas pretas, a funcionalidade das quais pode mudar a qualquer momento e levar a um comportamento inesperado.
Por exemplo, os engenheiros tiveram que analisar cerca de 50 serviços em 12 equipes diferentes para chegar à raiz de um problema.
Compreender as dependências entre os serviços pode se tornar bastante difícil, pois eles podem interagir entre si em vários níveis. Um pico nos atrasos na n-ésima dependência pode causar uma avalanche de problemas nos serviços upstream. Além disso, sem as ferramentas adequadas, será impossível entender o que aconteceu. Tudo isso torna a depuração muito difícil.
Arquitetura de microsserviço do Uber em meados de 2018 por Jaeger
Para implementar a função mais simples, um engenheiro geralmente precisa trabalhar com muitos serviços, enquanto equipes e pessoas completamente diferentes são responsáveis por eles. Como resultado, muito tempo é gasto na organização do trabalho em equipe, reuniões, consultas de design e revisão de código (revisão principal). O benefício inicial da transparência da propriedade está gradualmente se esvaindo à medida que as equipes invadem continuamente os serviços umas das outras, alteram os modelos de dados e até mesmo implantam em nome dos proprietários dos serviços. Isso pode criar monólitos de rede em que os serviços apenas parecem ser independentes, mas na verdade eles precisam ser implantados juntos para fazer qualquer alteração com segurança.
Um exemplo de um sistema tão complexo no Uber (~ 2018) com dez pontos de contato para fácil integração (mesmo antes do DOMA).
Como resultado, temos uma lentidão no processo de desenvolvimento, instabilidade atingindo proprietários de serviço, migrações mais demoradas, etc. Infelizmente, não há como voltar atrás para as organizações que já mudaram para o MA. A situação é perfeitamente ilustrada pela conhecida frase: " É impossível viver com eles e não se pode atirar neles ."
Arquitetura de microsserviço específica de domínio
Pense nos microsserviços como bibliotecas vinculadas a E / S e na arquitetura de microsserviços como um aplicativo enorme e distribuído. Nesse caso, podemos usar soluções arquitetônicas bem conhecidas para pensar sobre a melhor forma de organizar nosso código.
Portanto, uma Arquitetura de Microsserviço Orientada a Domínio (DOMA) pode contar com formas bem estabelecidas de organização de código, como Design Orientado a Domínio , Arquitetura Limpa , Arquitetura Orientada a Serviços e Padrões de Desenvolvimento Orientados a Objetos e Interface.Vemos o DOMA como inovador no sentido de que é uma maneira relativamente nova de alavancar os princípios de design existentes nos sistemas globalmente distribuídos de grandes organizações .
Aqui estão alguns conceitos básicos do DOMA e terminologia relacionada:
- Em vez de olhar para microsserviços individuais, estamos olhando para grupos deles. E nós os chamamos de domínios (domínios) .
- Em seguida, combinamos os domínios das chamadas camadas (as camadas) . A camada à qual um domínio pertence determina quais dependências estão disponíveis para microsserviços nesse domínio. Chamamos a arquitetura resultante de multicamadas (de design de camadas) .
- , . (gateways).
- , , , 'hardcode' , . (, - ), (extension architecture) .
Em outras palavras, arquitetura estruturada, gateways de domínio e pontos de extensibilidade DOMA pré-construídos transformam arquiteturas de microsserviço de algo complexo em algo compreensível e tangível: um conjunto estruturado de componentes flexíveis, reutilizáveis e em camadas.
O restante deste artigo se concentrará na implementação do DOMA pelo Uber e em seus benefícios. Aconselhamento prático também será dado às empresas que desejam adotar esta abordagem.
Implementação em Uber
Domínios
Os domínios Uber são coleções de um ou mais microsserviços vinculados com base em uma combinação lógica de funcionalidade. A questão surge naturalmente: quão grande deve ser o domínio. Nesse caso, não estamos dando nenhuma instrução. Alguns domínios podem incluir dezenas de serviços, outros apenas um. É importante aqui pensar cuidadosamente sobre o papel lógico de cada associação. Por exemplo, agrupamos os serviços de pesquisa no mapa, os serviços de tarifas, os serviços de seleção (comparando motoristas e passageiros) em domínios separados. Além disso, nem sempre repetem a estrutura organizacional da empresa. O Uber Maps é dividido em três domínios com 80 microsserviços ocultos por trás de três gateways diferentes.
Arquitetura baseada em camadas
A arquitetura multicamadas responde à questão de qual serviço e qual pode se comunicar dentro dos limites do MA Uber. Ou seja, pode ser visto como uma distribuição global de áreas de responsabilidade ou como um mecanismo de gerenciamento de dependência global.
A arquitetura em camadas ajuda a entender o raio de dano após as falhas e refletir a especificidade do produto em termos do número de serviços dependentes Uber. Conforme você passa de baixo para cima, o número de serviços afetados no caso de uma falha é reduzido e o escopo de aplicação do produto se estreita . E vice-versa, um maior número de serviços depende da funcionalidade nos níveis inferiores, portanto, o raio de dano em decorrência de uma falha é, via de regra, maior e a gama de tarefas de negócios a serem resolvidas é mais ampla. A figura abaixo ilustra esse conceito.

Você pode imaginar que os níveis superiores são focados em funções responsáveis por uma experiência de usuário específica (limitada) (por exemplo, funções móveis), enquanto os inferiores são ocupados por funções de negócios mais globais (por exemplo, gerenciamento de contas ou viagens pelo mercado de compartilhamento de caronas) ... Cada camada depende apenas das camadas subjacentes, o que traz clareza a conceitos como raio de explosão e integração de domínio.
É importante notar que a funcionalidade geralmente se move para baixo neste gráfico, de estreito para mais amplo. Você pode imaginar alguma função simples que se torna mais importante ("plataforma") com o tempo, conforme os requisitos evoluem. Na verdade, esse tipo de migração para baixo é esperado, e muitas das principais plataformas de negócios do Uber começaram como um recurso para motoristas ou passageiros e, com o tempo, cresceu e se tornou mais generalizado como novas linhas de negócios (como Uber Eats ou Uber Freight ) e conectar mais dependências a eles.
No Uber, distinguimos os cinco níveis a seguir.
- . , . — Uber , .
- -. , Uber , , Rides (), Eats ( ) Freight ( ).
- . , , . , «request a ride» ( ) , Rides: Rider, Rider «Lite», m.uber.com, ..
- . , (/), .
- . Uber . .
Como você pode ver, cada nível subsequente representa um agrupamento cada vez mais estreito de funções e tem um raio de acerto menor (em outras palavras, menos componentes dependem da funcionalidade dentro dessa camada).
Entradas
O termo gateway de API já está bem estabelecido em arquiteturas de microsserviço. Nossa definição não é muito diferente da bem estabelecida - exceto que tendemos a pensar em gateways como um único ponto de entrada para o grupo de serviços correspondente (que chamamos de domínio ). O sucesso de um gateway depende de uma arquitetura de API bem projetada:
Este diagrama ilustra o design de alto nível de um gateway. Ele abstrai os detalhes da estrutura interna de domínios: um conjunto de serviços, tabelas com dados, pipelines ETL, etc. Outros domínios têm acesso apenas a interfaces: API para chamadas de procedimento remoto, eventos e solicitações no sistema de mensagens.
Uma vez que os consumidores upstream só funcionam em um serviço, os gateways oferecem vários benefícios em termos de migrações futuras , capacidade de descoberta e uma redução geral da complexidade do sistema quando os serviços upstream têm apenas uma dependência (em vez de depender de vários serviços downstream que pode existir no domínio). Quando vistos de uma perspectiva de design OO, os gateways são definições de interface e nos permitem fazer o que quisermos com uma “implementação” interna (ou seja, um grupo de microsserviços).
Extensões
Extensões (extensões) , como o nome indica, é um mecanismo para expandir domínios. A definição básica de tal add-on é que ele fornece um mecanismo para estender a funcionalidade de um serviço sem alterar as partes internas desse serviço ou afetar sua confiabilidade geral. Em nosso Uber tem dois modelos de expansão: a lógica (extensões lógicas) e com base em dados (dados as extensões) . O conceito de extensão nos permitiu dimensionar a arquitetura para que várias equipes pudessem trabalhar independentemente umas das outras.
Extensões lógicas
As extensões lógicas fornecem um mecanismo para estender a lógica subjacente de um serviço. Para eles, usamos uma espécie de provedor ou padrão de plugin com uma interface que é definida separadamente para cada serviço. Isso permite que as equipes implementem sua lógica usando apenas a interface e sem interferir no código principal da plataforma.
Suponha, por exemplo, que o driver esteja online. Normalmente, fazemos várias verificações para garantir que é permitido ter um status online (para segurança, conformidade, etc.). Cada um deles tem sua própria equipe. Uma maneira possível de fazer isso é forçar cada comando a escrever lógica no mesmo ponto de extremidade, mas isso pode adicionar complexidade. Cada verificação exigirá uma lógica diferente - e completamente não relacionada.
No caso de extensões lógicas de endpoint, chamadas de entrar onlineirá definir a interface com a qual cada extensão deve estar em conformidade com uma solicitação predefinida e tipo de resposta. Cada equipe cadastrará uma extensão que será responsável por implementar esta lógica. Nesse caso, eles podem simplesmente pegar algumas informações sobre o driver e retornar um valor lógico (bool) que determina se o driver é "digno" do status online ou não. E o próprio ponto de extremidade (ficar online) irá simplesmente iterar essas respostas e estabelecer se alguma delas é falsa .
Essa abordagem separa o código principal das extensões e fornece isolamento entre eles. Neste caso, as extensões não sabem que outra lógica está sendo executada. Isso facilita a criação de funcionalidades adicionais, por exemplo, para observabilidade ou sinalização de recursos .
Extensões baseadas em dados
Esse tipo de extensão fornece um mecanismo para anexar dados arbitrários à interface para evitar o inchaço desnecessário dos modelos de dados da plataforma subjacente. Em extensões de dados, usamos ativamente recursos como Any from Protobuf, que permite adicionar dados arbitrários às solicitações. Os serviços geralmente armazenam esses dados ou os passam para uma extensão lógica, de modo que a plataforma principal nunca desserialize (e, portanto, não "saiba" nada) sobre esse contexto arbitrário. Qualquer implementação incorre em alguma sobrecarga de infraestrutura em troca de uma tipificação mais forte. Uma alternativa mais simples é o formato JSON para representar quaisquer dados:

Complementos arbitrários
Além de extensões booleanas e de dados, muitas equipes do Uber desenvolveram modelos de extensão personalizados para corresponder a seus domínios. Por exemplo, a maioria das integrações relacionadas à arquitetura de apresentação usa lógica de execução de tarefa baseada em DAG.
Benefícios
DOMA influenciou quase todos os grandes negócios do Uber em um grau ou outro. Durante o ano passado, focamos principalmente na camada de negócios. Ele fornece uma lógica generalizada para várias linhas de negócios em uma empresa.
DOMA é relativamente novo no Uber e, no futuro, definitivamente compartilharemos mais informações e exemplos de nossa arquitetura. Os primeiros resultados foram encorajadores: eles simplificaram muito o trabalho dos desenvolvedores e reduziram a complexidade geral do sistema.
Produtos e plataformas
DOMA é o resultado de um esforço colaborativo entre as várias equipes de produtos e plataformas da Uber. Em muitos casos, os custos de suporte da plataforma caíram em uma ordem de magnitude. As equipes de produto se beneficiaram da especificidade e do desenvolvimento acelerado.
Por exemplo, um dos primeiros consumidores de plataforma de nossa arquitetura de extensão foi capaz de reduzir o tempo necessário para priorizar e integrar um novo recurso de três dias para três horas, reduzindo os tempos de revisão de código, agendamento e acelerando a educação do consumidor.
Complexidade reduzida
Anteriormente, as equipes de produto precisavam trabalhar com muitos serviços downstream em um domínio, mas agora só precisam chamar um. Ao reduzir o número de pontos de contato ao introduzir um novo recurso, o tempo de implementação foi reduzido em 25-30%. Além disso, distribuímos 2.200 serviços em 70 domínios. Cerca de metade deles foram implementados, e para a maioria existe um plano de implementação de uma forma ou de outra.
Migrações futuras
No Uber, calculamos que o microsserviço tem meia-vida de 1,5 anos. Ou seja, a cada ano e meio, 50% dos nossos serviços perdem relevância. Sem gateways, uma arquitetura de microsserviço pode se tornar um inferno de migração. Microsserviços em constante mudança exigem migrações upstream constantes. Os gateways permitem que as equipes evitem dependências nos serviços de domínio downstream, o que significa que esses serviços podem mudar sem ter que migrar para o upstream.
Duas das maiores atualizações de plataforma do Uber no ano passado aconteceram atrás de gateways. Essas plataformas têm centenas de serviços dependentes e, sem gateways, todos os consumidores existentes teriam que ser migrados. Seria incrivelmente caro, tornando uma reformulação completa da plataforma irreal.
Novas linhas de negócios e produtos
As estruturas baseadas em DOMA provaram ser muito mais extensíveis e fáceis de manter. A maioria das equipes do Uber que mudaram para DOMA o fizeram porque se tornou muito caro manter novas linhas de negócios.
Conselho prático
Nesta seção, compilei algumas dicas práticas para empresas que podem estar interessadas em DOMA. O princípio orientador aqui é que, em nossa experiência, uma arquitetura de microsserviço madura e bem pensada é baseada em mudanças incrementais na direção certa no momento certo. Na realidade, é quase impossível “reescrever” o MA completamente.
Portanto, vemos a evolução do AM como uma espécie de processo de “corte de sebe”, graças ao qual ele cresce na direção certa, e não como um esforço volitivo único. É um processo dinâmico e gradual.
Iniciantes
As questões-chave aqui são: "Quando devemos mudar para MA?" e "Isso faz sentido para nossa organização?" Como vimos acima, embora os microsserviços forneçam uma vantagem operacional em organizações com um grande número de engenheiros, eles também aumentam a complexidade geral que pode dificultar a implementação de novos recursos.
Em pequenas organizações, é improvável que a vantagem operacional compense o aumento da complexidade da arquitetura. Além disso, os MAs geralmente requerem recursos de engenharia dedicados para apoiá-los, o que pode ser muito caro para uma empresa em estágio inicial ou simplesmente abaixo do ideal em termos de priorização.
Com isso dito, pode ser sensato adiar a transição para microsserviços por um tempo. Se a organização decidir mudar para microsserviços, recomendamos que ela use a analogia de um grande aplicativo distribuído e pense com antecedência sobre como dividir as áreas problemáticas entre os serviços. Lembre-se também de que os primeiros microsserviços provavelmente são os mais importantes e de vida mais longa, pois eles descrevem uma parte importante do negócio.
Média empresa
A utilidade do MA aumenta em empresas de médio porte com muitas equipes, onde as linhas de responsabilidade estão gradualmente se confundindo entre as diferentes funções e plataformas.
É aqui que você pode começar a pensar sobre a hierarquia de microsserviços. O gerenciamento de dependências pode vir à tona, pois alguns serviços podem se tornar muito mais críticos para o funcionamento de uma empresa e mais equipes confiarão neles.
Os primeiros investimentos em plataforma podem render dividendos mais tarde. A criação de plataformas de negócios independentes de outros produtos permite evitar o acúmulo de dívidas técnicas e a penetração da lógica arbitrária do produto nos principais serviços da plataforma. Talvez um mecanismo de extensão deva ser introduzido neste estágio para atingir esse objetivo.
Visto que o número de microsserviços ainda é pequeno, pode não fazer sentido agrupá-los ainda. No entanto, é importante notar aqui que um domínio no contexto da implementação do DOMA no Uber pode muito bem incluir um único serviço, portanto, uma linha de pensamento "orientada para o domínio" ainda não faz mal.
Grande negócio
Grandes organizações de engenharia podem ter centenas de especialistas, microsserviços e muitas dependências. É nestas condições que DOMA atinge todo o seu potencial. Certamente, essas empresas terão clusters óbvios de microsserviços que podem ser facilmente combinados em domínios com gateways na frente deles. Os serviços legados geralmente precisam de refatoração / reescrita e subsequente migração. Isso significa que os gateways logo começarão a trazer benefícios reais em termos de facilidade de migração (se, é claro, eles já estiverem implantados).
A importância de uma hierarquia transparente e compreensível também aumentará: alguns serviços serão “produtos” para certas funções ou grupos de funções, enquanto outros irão suportar vários produtos e atuar como “plataformas”. Nesse estágio, é fundamental manter a lógica arbitrária do produto separada das plataformas para evitar estresse operacional massivo nas equipes da plataforma e para minimizar o risco de instabilidade global do sistema.
Pensamentos finais
No Uber, continuamos a desenvolver ativamente o DOMA à medida que mais equipes migram para ele. A ideia principal por trás do DOMA é que uma arquitetura de microsserviço é apenas um grande programa distribuído. E os mesmos princípios podem ser aplicados à sua evolução como a qualquer outro software. DOMA é apenas uma abordagem para o pensamento prático sobre esses princípios. Esperamos que seja útil e aguardamos seus comentários!
O próprio DOMA é o resultado de um esforço multifuncional de quase 60 engenheiros de todo o Uber. Gostaria de expressar uma gratidão especial às seguintes pessoas por suas contribuições para este trabalho nos últimos 2 anos:
Alex Zylman, Alexandre Wilhelm, Allen Lu, Ankit Srivastava, Anthony Tran, Anupam Dikshit, Anurag Biyani, Daniel Wolf, Deepti Chedda, Dmitriy Bryndin, Gaurav Tungatkar, Jacob Greenleaf, Jaikumar Ganesh, Jennie Ngyabuae, Joeoshinier , Kusha Kapoor, Linda Fu, Madan Thangavelu, Nimish Sheth, Parth Shah, Shawn Burke, Simon Newton, Steve Sherwood, Uday Kiran Medisetty e Waleed Kadous.
Agradecimentos: Este trabalho combinou muitos padrões de projeto existentes na indústria para resolver problemas no Uber e também sugeriu alguns novos padrões (como extensões). Somos gratos à indústria por trabalhar neles. Também somos gratos aos engenheiros do Linkedin que trabalharam no Superblocks por compartilharem suas experiências conosco.
PS do tradutor
Leia também em nosso blog:
- «: , Kubernetes»;
- « 2018 ».