Mabo Blog

Bem-vindo(a)! Descubra novas leituras.

Richards & Ford – Fundamentos da Arquitetura de Software (2024)

Escrito por

em

Richards & Ford – Fundamentos da Arquitetura de Software (2024)

Aproximadamente 84.756 palavras.

Página 97

assim, os arquitetos devem ter alta prioridade ao manter uma boa modularidade. Contudo, há forças contra as boas intenções do arquiteto em muitas plataformas. Por exemplo, ao codificar em qualquer ambiente de desenvolvimento Java ou .NET popular, assim como quando um desenvolvedor referencia uma classe ainda não importada, o IDE apresenta uma caixa de diálogo perguntado se ele gostaria de autoimportar a referência. Isso ocorre com tanta frequência que a maioria dos programadores adquire o hábito de fechar a caixa de diálogo de importação automática como um reflexo. Contudo, importar classes ou componentes arbitrariamente entre si significa desastre para a modularidade. Por exemplo, a Figura 6-3 mostra um antipadrão bem perigoso que os arquitetos devem evitar.

Figura 6-3. Dependências cíclicas entre componentes Na Figura 6-3, cada componente referencia algo nos outros. Ter uma rede de componentes assim prejudica a modularidade porque um desenvolvedor não pode reutilizar um único componente sem também trazer os outros junto. E claro, se outros componentes estão acoplados a outros componentes, a arquitetura tende a ser cada vez mais uma Grande Bola de Lama antipadrão. Como os arquitetos podem controlar esse comportamento sem ficarem sempre atentos aos desenvolvedores eufóricos? As revisões do código ajudam, mas ocorrem tarde demais no ciclo de desenvolvimento para que sejam eficientes. Se um arquiteto permitir que uma equipe de desenvolvimento importe de modo desenfreado na base de código por uma semana até a revisão do código, até então já terão ocorrido sérios danos nessa base.

A solução para o problema é escrever uma função de aptidão para cuidar dos ciclos, como mostrado no Exemplo 6-2.

Exemplo 6-2. Função de aptidão para detectar os ciclos do componente

Imagem 1 da página 97 (sem descrição)

Página 98

No código, um arquiteto usa a ferramenta de métrica JDepend para verificar as dependências entre os pacotes. A ferramenta entende a estrutura dos pacotes Java e falha no teste se existir algum ciclo. Um arquiteto pode ligar esse teste à criação contínua em um projeto e parar de se preocupar com a introdução acidental de ciclos por desenvolvedores eufóricos. É um ótimo exemplo de função de aptidão protegendo práticas importantes, em vez de urgentes, do desenvolvimento de software: é uma preocupação importante para os arquitetos, ainda que tenha pouco impacto na codificação do dia a dia.

Distância da função de aptidão da sequência principal Em “Acoplamento”, apresentamos uma métrica mais complexa da distância da sequência principal, que os arquitetos também podem verificar usando funções de aptidão, como no Exemplo 6-3.

Exemplo 6-3. Distância da função de aptidão da sequência principal

Imagem 1 da página 98 (sem descrição)
Imagem 2 da página 98 (sem descrição)

Página 99

No código, o arquiteto usa JDepend para estabelecer um limite para valores aceitáveis, falhando no teste se uma classe fica fora do intervalo.

É um exemplo de medida objetiva para uma característica da arquitetura e a importância da colaboração entre desenvolvedores e arquitetos ao planejarem e implementarem as funções de aptidão. A intenção não é um grupo de arquitetos subir para uma torre de marfim e desenvolver funções de aptidão complexas que os desenvolvedores não conseguem entender.

Os arquitetos devem assegurar que os desenvolvedores entendam a finalidade da função de aptidão antes de fazer a imposição delas.

A sofisticação das ferramentas da função de aptidão aumentou nos últimos anos, inclusive algumas ferramentas especiais. Uma delas é ArchUnit, uma estrutura de teste Java inspirada pelo uso de várias partes do ecossistema JUnit. A ArchUnit fornece várias regras de governança predefinidas e codificadas como testes unitários, e permite que os arquitetos escrevam testes específicos que lidam com a modularidade. Veja a arquitetura em camadas mostrada na Figura 6-4.

Figura 6-4. Arquitetura em camadas Ao projetar um bloco em camadas, como na Figura 6-4, o arquiteto define as camadas por um bom motivo (motivações, trade-offs e outros aspectos da arquitetura em camadas são descritos no Capítulo 10). Contudo, como o arquiteto pode garantir que os desenvolvedores respeitarão

Imagem 1 da página 99 (sem descrição)

Página 100

essas camadas? Alguns desenvolvedores podem não entender a importância dos padrões, já outros podem adotar uma atitude “melhor pedir perdão do que permissão” por causa de uma preocupação local prioritária, como o desempenho. Mas permitir que os implementadores acabem com os motivos da arquitetura prejudica a integridade de longo prazo da arquitetura.

A ArchUnit permite que os arquitetos lidem com o problema via função de aptidão, como no Exemplo 6-4.

Exemplo 6-4. Função de aptidão ArchUnit para controlar as camadas No Exemplo 6-4, o arquiteto define a relação desejável entre as camadas e escreve uma função de aptidão de verificação para controlá-la.

Uma ferramenta parecida no espaço .NET, NetArchTest, permite testes parecidos para a plataforma; uma verificação da camada em C# aparece no Exemplo 6-5.

Exemplo 6-5. NetArchTest para as dependências de camadas Outro exemplo de funções de aptidão é o Macaco do Caos (Chaos Monkey) da Netflix e o assistente Exército Símio (Simian Army). Em particular, os serviços Conformity, Security e Janitor do Monkey exemplificam essa abordagem. O Conformity Monkey permite que os arquitetos na Netflix definam regras de governança aplicadas pelo monkey em produção. Por exemplo, se os arquitetos decidiram que cada serviço deve responder normalmente a todos os verbos RESTful, eles criam essa verificação no Conformity Monkey. Do mesmo modo, o Security Monkey verifica cada serviço em busca de problemas conhecidos na segurança, como portas que não devem estar ativas e erros de configuração. Por fim, o Janitor Monkey procura instâncias para as quais nenhum serviço roteia mais. A Netflix tem uma arquitetura evolutiva, portanto os desenvolvedores sempre migram para serviços mais novos, deixando os antigos rodando sem colaboradores. Como os serviços que rodam na nuvem consomem dinheiro, o Janitor Monkey procura serviços órfãos e os tira de produção.

Imagem 1 da página 100 (sem descrição)
Imagem 2 da página 100 (sem descrição)

Página 101

A Origem do Exército Símio Quando a Netflix decidiu mover suas operações para a nuvem da Amazon, os arquitetos se preocuparam com o fato de que eles não teriam mais controle sobre as operações; o que acontece se surge um defeito na operação? Para resolver isso, eles criaram a disciplina da Engenharia do Caos, com o Macaco do Caos original e, por fim, o Simian Army. O Chaos Monkey simulava um caos geral no ambiente de produção para ver o quão bem o sistema aguentaria. A latência era um problema em algumas instâncias AWS, assim o Chaos Monkey simulava uma alta latência (que era tão problemática que eles acabaram criando um monkey especializado, o Latency Monkey). Ferramentas como Chaos Kong, que simula uma falha total do datacenter Amazon, ajudaram a Netflix a evitar interrupções quando elas ocorriam de verdade.

A engenharia do caos oferece uma nova perspectiva interessante na arquitetura: não é uma questão de se algo parará de funcionar, mas quando. Antecipar essas interrupções e testes para evitá-las torna os sistemas muito mais robustos.

Há alguns anos, o influente livro Checklist: Como fazer as coisas benfeitas de Atul Gawande descreveu como profissionais, como pilotos e cirurgiões, usam checklists (por vezes exigidas por lei). Não é porque esses profissionais não conhecem seus trabalhos ou são esquecidos. Pelo contrário, quando os profissionais fazem um trabalho muitíssimo detalhado repetidas vezes, é fácil deixar passar os detalhes; uma checklist sucinta é um lembrete eficiente. Essa é a perspectiva correta das funções de aptidão; em vez de um mecanismo de governança pesado, essas funções fornecem um mecanismo para os arquitetos expressarem princípios importantes da arquitetura e verificarem isso automaticamente. Os desenvolvedores sabem que não devem lançar um código sem segurança, mas essa prioridade compete com dezenas ou centenas de outras prioridades para os desenvolvedores ocupados. Ferramentas como Security Monkey especificamente, e as funções de aptidão em geral, permitem que os arquitetos codifiquem verificações de governança importantes no substrato da arquitetura.

Página 102

7. Escopo das Características da Arquitetura

CAPÍTULO 7 Escopo das Características da Arquitetura Uma suposição axiomática existente no mundo da arquitetura de software substituiu tradicionalmente o escopo das características da arquitetura no nível do sistema. Por exemplo, quando arquitetos falam sobre escalabilidade, em geral eles expressam isso em torno da escalabilidade do sistema inteiro. Era uma suposição segura uma década atrás, quando quase todos os sistemas eram monolíticos. Com o advento das técnicas de engenharia modernas e os estilos de arquitetura por elas permitidos, como microsserviços, o escopo das características da arquitetura se limitou muito. É um excelente exemplo de axioma ficando desatualizado aos poucos conforme o ecossistema de desenvolvimento de software continua sua evolução incansável.

Durante a escrita do livro Building Evolutionary Architectures (sem publicação no Brasil), os autores precisaram de uma técnica para medir a evolução estrutural de certos estilos de arquitetura. Nenhuma medida existente oferecia o nível correto de detalhamento. Em “Medidas Estruturais”, no Capítulo 6, examinamos várias métricas no nível do código que permitem aos arquitetos analisarem os aspectos estruturais de uma arquitetura. Contudo, todas essas métricas só relevam detalhes de baixo nível sobre o código e não conseguem avaliar os componentes dependentes (como bancos de dados) fora da base de código que ainda impactam muitas características da arquitetura, sobretudo as operacionais.

Por exemplo, não importa o quanto um arquiteto se esforce para projetar uma base de código eficiente ou elástica, se o sistema usar um banco de dados que não combina com essas características, a aplicação não terá sucesso.

Ao avaliar muitas características da arquitetura operacional, um arquiteto deve considerar os componentes dependentes fora da base de código que impactarão essas características. Assim, eles precisam de outro método para medir essas dependências. Isso levou os autores do Building

Página 103

Evolutionary Architectures a definirem o termo quantum da arquitetura.

Para entender essa definição, devemos ver uma métrica principal aqui: a conascência.

Acoplamento e Conascência

Muitas métricas de acoplamento no nível do código, como os acoplamentos aferente e eferente (descrito em “Medidas Estruturais”, Capítulo 6), revelam detalhes em um nível muito específico para a análise arquitetural. Em 1996, Meilir Page-Jones publicou um livro intitulado What Every Programmer Should Know About Object Oriented Design (sem publicação no Brasil), que incluía várias medidas novas de acoplamento que ele chamou de conascência, definida assim:

Conascência Dois componentes são conascentes se uma mudança em um requer que o outro seja modificado para manter a correção geral do sistema Ele definiu dois tipos de conascência: estática, descoberta via análise do código estático; e dinâmica, referente ao comportamento em tempo de execução. Para definir o quantum da arquitetura, precisávamos de uma medida de como os componentes estão “ligados”, que corresponde ao conceito de conascência. Por exemplo, se dois serviços em uma arquitetura de microsserviços compartilham a mesma definição de classe de alguma classe, como endereço, dizemos que são estaticamente conascentes entre si, pois mudar a classe compartilhada requer mudanças nos dois serviços.

Para a conascência dinâmica, definimos dois tipos: síncrona e assíncrona.

As chamadas síncronas entre dois serviços distribuídos fazem quem chama aguardar a resposta do recebedor. Já as chamadas assíncronas permitem uma semântica “disparar e esquecer” nas arquiteturas baseadas em eventos, permitindo que dois serviços diferentes sejam diferentes na arquitetura operacional.

Quanta Arquitetural e Granularidade

Página 104

Página 105

projetam cada serviço para combinar com um único fluxo de trabalho (um contexto delimitado, como descrito em “Contexto Delimitado do Design Orientado a Domínios”), assim exibindo uma alta coesão funcional.

Conascência síncrona Conascência síncrona implica as chamadas síncronas em um contexto da aplicação ou entre serviços distribuídos que formam esse quantum da arquitetura. Por exemplo, se um serviço em uma arquitetura de microsserviços chama outro de forma síncrona, cada serviço não pode exibir diferenças extremas nas características da arquitetura operacionais. Se quem chama for muito mais escalável que o receptor, ocorrerão limites de tempo e outros problemas de confiabilidade.

Assim, as chamadas síncronas criam uma conascência dinâmica durante a chamada; se uma chamada aguarda a outra, suas características da arquitetura operacionais devem ser iguais na duração da chamada.

Voltando ao Capítulo 6, definimos a relação entre as métricas de acoplamento tradicionais e a conascência, que não incluíam nossa nova medida de conascência da comunicação. Atualizamos o diagrama na Figura 7-1.

Figura 7-1. Adicionando a conascência do quantum ao diagrama unificado

Imagem 1 da página 105 (sem descrição)

Página 106

Estudo de Caso: Going, Going, Gone

Página 107

No Capítulo 5, apresentamos o conceito de um kata da arquitetura.

Considere este, relacionado a uma empresa de leilões virtuais. Veja a descrição do kata da arquitetura:

Descrição Uma empresa de leilões deseja realizar leilões virtuais em escala nacional. Os clientes escolhem o leilão no qual participar, aguardam o início, então dão um lance como se estivessem na sala com o leiloeiro.

Usuários Escala de até centenas de participantes por leilão, possivelmente até milhares de participantes e o máximo possível de leilões simultâneos.

Requisitos Os leilões devem ser o máximo possível em tempo real.

Os proponentes registram um cartão de crédito; o sistema cobra automaticamente no cartão se o proponente vence.

Os participantes devem ser rastreados via índice de reputação.

Os proponentes podem ver um vídeo ao vivo do leilão e de todos os lances conforme eles ocorrem.

Os lances online e ao vivo devem ser recebidos na ordem em que são feitos.

Contexto adicional A empresa de leilões expande agressivamente fazendo fusões com concorrentes menores.

O orçamento não é limitado. É uma orientação estratégica.

A empresa acabou de sair de um processo no qual resolveu uma alegação de fraude.

Como no “Estudo de Caso: Silicon Sandwiches” (Capítulo 5), um arquiteto deve considerar cada requisito para determinar as características da arquitetura:

Página 108

1. “Escala nacional”, “escala de até centenas de participantes por leilão, possivelmente até milhares de participantes e o máximo possível de leilões simultâneos”, “os leilões devem ser o máximo possível em tempo real”.

Cada um desses requisitos implica escalabilidade para dar suporte ao grande número de usuários e elasticidade para dar suporte à natureza repentina dos leilões. Embora os requisitos precisem explicitamente de escalabilidade, a elasticidade representa uma característica implícita com base no domínio do problema. Ao considerar os leilões, os usuários se distribuirão educadamente durante o leilão ou eles ficarão mais agitados perto do fim? O conhecimento do domínio é essencial para os arquitetos escolherem as características implícitas da arquitetura. Dada a natureza em tempo real dos leilões, um arquiteto certamente considerará o desempenho uma característica principal.

2. “Os proponentes registram um cartão de crédito; o sistema cobra automaticamente no cartão se o proponente vence”, “a empresa acabou de sair de um processo no qual resolveu uma alegação de fraude”.

Esses dois requisitos apontam claramente para a segurança como uma característica da arquitetura. Como visto no Capítulo 5, a segurança é uma característica implícita em quase toda aplicação. Assim, os arquitetos contam com a segunda parte da definição das características da arquitetura, que influencia certo aspecto estrutural do design. Um arquiteto deve projetar algo especial para incluir a segurança ou o design geral e a limpeza de código serão suficientes? Os arquitetos desenvolveram técnicas para lidar com segurança com os cartões de crédito via design sem criar necessariamente uma estrutura especial.

Por exemplo, contanto que os desenvolvedores garantam que não armazenarão os números do cartão em texto simples, criptografarão durante o trânsito etc., então o arquiteto não precisa desenvolver considerações especiais para a segurança.

Contudo, a segunda frase deve fazer o arquiteto parar e pedir mais esclarecimento. É claro que um aspecto da segurança (fraude) foi um problema no passado, assim o arquiteto deve pedir mais informações, independentemente do nível de segurança planejado.

Página 109

3. “Os participantes devem ser rastreados via índice de reputação.”

Esse requisito sugere alguns nomes mirabolantes como “antitrolagem”, mas a parte rastrear do requisito pode sugerir algumas características da arquitetura, como auditoria e capacidade de se registrar. O fator decisivo novamente recai na característica de definição: está fora do escopo do domínio do problema? Os arquitetos devem se lembrar que a análise para produzir as características da arquitetura representa apenas uma pequena parte do esforço geral para projetar e implementar uma aplicação; muito trabalho de design acontece depois dessa fase! Durante essa parte de definição da arquitetura, os arquitetos procuram requisitos com impacto estrutural ainda não cobertos pelo domínio.

Veja um indicador útil que os arquitetos de teste usam para determinar as características do domínio versus da arquitetura: requer conhecimento do domínio para implementar ou é uma característica da arquitetura abstrata? No kata Going, Going, Gone, ao encontrar a frase “índice de reputação”, um arquiteto buscaria um analista comercial ou outro especialista para explicar o que ele tinha em mente, ou seja, a frase “índice de reputação” não é uma definição padrão como as características da arquitetura mais comuns. Como um exemplo contrário, quando os arquitetos discutem sobre elasticidade, a capacidade de lidar com picos de usuários, eles podem conversar sobre a característica da arquitetura puramente no sentido abstrato, independentemente do tipo de aplicação considerada: banco, site de catálogos, streaming de vídeo etc. Os arquitetos devem determinar se um requisito ainda não foi incluído pelo domínio e requer uma estrutura em particular, o que aumenta a consideração para a característica da arquitetura.

4. “A empresa de leilões expande agressivamente fazendo fusões com concorrentes menores.”

Embora esse requisito possa não ter um impacto imediato no design da aplicação, pode se tornar o fator determinante em um trade-off entre várias opções. Por exemplo, os arquitetos devem sempre escolher detalhes como protocolos de comunicação para a arquitetura de integração: se a integração com as novas empresas consolidadas não for um problema, o arquiteto fica livre para escolher algo altamente

Página 110

específico para o problema. Por outro lado, um arquiteto pode escolher algo menos ideal para incluir um trade-off adicional, como a interoperabilidade. Características sutis da arquitetura implícita, como essa, perpassam a arquitetura, mostrando por que fazer bem o trabalho tem seus desafios.

5. “O orçamento não é limitado. É uma orientação estratégica.”

Alguns katas da arquitetura impõem restrições de orçamento na solução para representar um trade-off comum e real. Contudo, no kata Going, Going, Gone não é assim. Isso permite que o arquiteto escolha arquiteturas mais elaboradas e/ou especiais, que serão benéficas, dados os próximos requisitos.

6. “Os proponentes podem ver um vídeo ao vivo do leilão e de todos os lances conforme eles ocorrem”, “os lances online e ao vivo devem ser recebidos na ordem em que são feitos”.

Esse requisito tem um desafio arquitetural interessante, com certeza impactando a estrutura do aplicação e expondo a futilidade de tratar as características da arquitetura como uma avaliação de todo o sistema. Considere a disponibilidade: essa necessidade é uniforme em toda a arquitetura? Em outras palavras, a disponibilidade de um proponente é mais importante do que a disponibilidade para um dos centenas de proponentes? É óbvio que o arquiteto deseja boas medidas para ambos, mas um deles é claramente mais importante: se o leiloeiro não consegue acessar o site, os lances virtuais não podem ocorrer para ninguém. É comum que a confiabilidade apareça com a disponibilidade; ela aborda os aspectos operacionais, como tempo de atividade, assim como a integridade dos dados e outras medidas do quanto a aplicação é confiável. Por exemplo, em um site de leilões, o arquiteto deve garantir que a ordem das mensagens esteja correta, com segurança, eliminando as condições race e outros problemas.

Esse último requisito no kata Going, Going, Gone destaca a necessidade de um escopo mais granular na arquitetura do que o nível do sistema. Usando a medida do quantum da arquitetura, os arquitetos colocam o escopo das características da arquitetura no nível do quantum. Por exemplo, em Going, Going, Gone, um arquiteto notaria que as diferentes partes dessa arquitetura precisam de diferentes características: lances de streaming,

Página 111

proponentes online e leiloeiro são três escolhas óbvias. Os arquitetos usam a medida do quantum da arquitetura como um modo de pensar na implementação, no acoplamento, onde os dados devem residir e nos estilos de comunicação nas arquiteturas. Nesse kata, um arquiteto pode analisar as diferentes características da arquitetura por quantum da arquitetura, levando a um design híbrido no início do processo.

Assim, para Going, Going, Gone, identificamos os seguintes quanta e características da arquitetura correspondentes:

Feedback do proponente Inclui o fluxo de lances e o vídeo dos lances Disponibilidade Escalabilidade Desempenho Leiloeiro O leiloeiro ao vivo Disponibilidade Confiabilidade Escalabilidade Elasticidade Desempenho Segurança Proponente Proponentes e lances virtuais Confiabilidade Disponibilidade Escalabilidade Elasticidade

Página 112

8. Pensamento Baseado em Componentes

Escopo do Componente

Página 113

Figura 8-1. Diferentes variedades de componentes Os componentes também aparecem como subsistemas ou camadas na arquitetura, como a unidade de trabalho de implementação para muitos processadores de evento. Outro tipo de componente, um serviço, tende a rodar em seu próprio espaço de endereço e se comunica via protocolos de rede de baixo nível como TCP/IP, formatos de nível mais alto como REST ou filas de mensagens, formando unidades de implementação independentes em arquiteturas como microsserviços.

Nada requer que um arquiteto use componentes; acontece que muitas vezes é útil ter um nível maior de modularidade do que o nível mais baixo oferecido pela linguagem. Por exemplo, nas arquiteturas de microsserviços, a simplicidade é um dos princípios. Assim, um serviço pode consistir em código suficiente para garantir os componentes ou talvez simples o bastante para conter apenas uma pequena parte de código, como mostrado na Figura 8-2.

Os componentes formam o bloco de construção modular fundamental na arquitetura, tornando-os uma consideração crítica para os arquitetos. Na verdade, uma das decisões principais que um arquiteto deve tomar é em relação ao particionamento de alto nível dos componentes na arquitetura.

Imagem 1 da página 113 (sem descrição)

Página 114

Função do Arquiteto

Figure 8-2. Um microsserviço pode ter tão pouco código que os componentes não são necessários Função do Arquiteto Em geral, o arquiteto define, aprimora, gerencia e controla os componentes em uma arquitetura. Os arquitetos de software, em colaboração com analistas comerciais, especialistas, desenvolvedores, engenheiros de QA, profissionais de operações e arquitetos corporativos, criam o design inicial do software, incorporando as características da arquitetura vistas no Capítulo 4 e os requisitos para o sistema de software.

Praticamente todos os detalhes que cobrimos neste livro existem sem dependerem do que as equipes do processo de desenvolvimento de software usam: a arquitetura é independente do processo de desenvolvimento. A exceção principal a essa regra envolve as práticas de engenharia iniciadas em vários tipos de desenvolvimento ágil de software, em particular nas áreas de implementação e automação de governança. Contudo, em geral, a arquitetura de software existe separada do processo. Assim, os arquitetos não se interessam pela origem dos requisitos: um processo JAD (Joint Application Design) formal, a análise e o design no estilo cascata longa, cartões de história Ágeis… ou qualquer variação híbrida deles.

Em geral, o componente é o nível mais baixo do sistema de software com o qual o arquiteto interage diretamente, com exceção das muitas métricas de qualidade do código examinadas no Capítulo 6, que afetam as bases de código de forma global. Os componentes consistem em classes ou funções (dependendo da plataforma de implementação), cujo design fica sob a responsabilidades dos principais técnicos ou dos desenvolvedores da tecnologia. Não significa que os arquitetos não devem se envolver no design das classes (em particular ao descobrirem ou aplicarem padrões de design), mas devem evitar microgerenciar cada decisão de cima para baixo no sistema. Se os arquitetos nunca permitirem que outras funções tomem decisões com

Imagem 1 da página 114 (sem descrição)

Página 115

consequências, a organização terá dificuldades com o empoderamento da próxima geração de arquitetos.

Um arquiteto deve identificar os componentes como uma das primeiras tarefas em um novo projeto. Mas antes de um arquiteto conseguir identificar os componentes, ele deve saber como particionar a arquitetura.

Particionamento da Arquitetura

A Primeira Lei da Arquitetura de Software determina que tudo no software é um trade-off, inclusive como os arquitetos criam os componentes em uma arquitetura. Como os componentes representam um mecanismo de conteinerização geral, um arquiteto pode criar qualquer tipo de particionamento desejado. Existem vários estilos comuns, com diferentes trade-offs.

Examinamos melhor os estilos de arquitetura na Parte II. Aqui, vemos um aspecto importante dos estilos, o particionamento de alto nível em uma arquitetura.

Considere os dois tipos de estilos de arquitetura mostrados na Figura 8-3.

Figura 8-3. Dois tipos de particionamento da arquitetura de alto nível: em camadas e modular Na Figura 8-3, um tipo de arquitetura familiar para muitos é o monolítico em camadas (detalhado no Capítulo 10). O outro é um estilo de arquitetura popularizado por Simon Brown, chamado monolítico modular, uma única unidade de implementação associada a um banco de dados e particionada em torno de domínios, em vez das capacidades técnicas. Esses dois estilos representam os diferentes modos de particionar a arquitetura em alto nível. Note que, em cada variação, cada componente de alto nível (camadas ou componentes) provavelmente tem outros componentes incorporados. O particionamento de alto nível é particularmente interessante para os arquitetos porque ele define o estilo de arquitetura fundamental e o modo de particionar o código.

Imagem 1 da página 115 (sem descrição)

Página 116

Organizar a arquitetura com base nas capacidades técnicas, como o monolítico em camadas, representa um particionamento técnico de alto nível. Uma versão comum disso aparece na Figura 8-4.

Figura 8-4. Dois tipos de particionamento de alto nível na arquitetura Na Figura 8-4, o arquiteto particionou a funcionalidade do sistema em capacidades técnicas:

apresentação, regras comerciais, serviços, persistência etc. Esse modo de organizar uma base de código faz muito sentido. Todo o código de persistência reside em uma camada na arquitetura, facilitando que os desenvolvedores encontrem o código relacionado à persistência. Mesmo que o conceito básico da arquitetura em camadas seja anterior há décadas, o padrão de design MVC (Model-View-Controller) corresponde a esse padrão arquitetural, facilitando o entendimento dos desenvolvedores. Assim, costuma ser a arquitetura padrão em muitas organizações.

Um efeito colateral interessante da predominância da arquitetura em camadas se relaciona a como as empresas estabelecem diferentes funções no projeto. Ao usar uma arquitetura em camadas, faz sentido ter todos os desenvolvedores de back-end juntos em um departamento, os DBAs em outro, a equipe de apresentação em outro etc. Por causa da Lei de Conway, isso faz sentido nessas organizações.

Imagem 1 da página 116 (sem descrição)

Página 117

Lei de Conway Voltando ao final dos anos 1960, Melvin Conway fez uma observação importante que ficou conhecida como Lei de Conway:

As organizações que projetam sistemas… ficam limitadas a produzir designs que são cópias das estruturas de comunicação dessas organizações.

Parafraseada, essa lei sugere que, quando um grupo de pessoas projeta um artefato técnico, as estruturas de comunicação entre as pessoas acabam replicadas no design. As pessoas em todos os níveis das organizações veem essa lei em ação e, por vezes, tomam decisões com base nela. Por exemplo, é comum que as organizações particionem os funcionários com base nas capacidades técnicas, o que faz sentido a partir de uma noção organizacional pura, mas dificulta a colaboração por causa da separação artificial das preocupações em comum.

Uma observação relacionada criada por Jonny Leroy da ThoughtWorks é a Manobra de Conway Inversa, que sugere desenvolver uma equipe e uma estrutura organizacional juntas para promover a arquitetura desejada.

Outra variação arquitetural na Figura 8-4 representa o particionamento do domínio, inspirado no livro Domain-Driven Design, de Eric Evan, que é uma técnica de modelagem para decompor sistemas de software complexos. No DDD, o arquiteto identifica os domínios ou os fluxos de trabalho independentes e os desassocia. O estilo de arquitetura de microsserviços (visto no Capítulo 17) se baseia nessa filosofia. Em um bloco monolítico modular, o arquiteto particiona a arquitetura em torno dos domínios e dos fluxos de trabalho, em vez das capacidades técnicas.

Como os componentes costumam se aninhar uns nos outros, cada componente na Figura 8-4 no particionamento do domínio (por exemplo FecharCatálogo) pode usar uma biblioteca de persistência e ter uma camada separada para as regras comerciais, mas o particionamento de alto nível gira em torno dos domínios.

Uma das distinções fundamentais entre os diferentes padrões da arquitetura é qual tipo de particionamento de alto nível cada um suporta, o qual cobrimos para cada padrão individual.

Também tem um grande impacto em como um arquiteto decide identificar inicialmente os componentes — o arquiteto deseja particionar as coisas tecnicamente ou por domínio?

Os arquitetos que usam o particionamento organizam os componentes do sistema por capacidades técnicas: apresentação, regras comerciais, persistência etc. Assim, um dos princípios de organização dessa arquitetura é a separação das questões técnicas. Por sua vez, isso cria níveis úteis de desacoplamento: se a camada de serviço está apenas conectada à camada de persistência abaixo e à camada das regras comerciais acima, então é provável que as alterações na persistência afetem somente essas camadas. Esse estilo de particionamento fornece uma técnica de desacoplamento, reduzindo os efeitos colaterais nos componentes dependentes.

Detalhamos mais esse estilo de arquitetura no padrão de arquitetura em camadas no Capítulo 10. Certamente, é lógico organizar os sistemas usando um particionamento técnico, mas como todas as coisas na arquitetura de software, há alguns trade-offs.

A separação aplicada pelo particionamento técnico permite que os desenvolvedores encontrem certas categorias da base de código com rapidez, pois ela está organizada por capacidades.

Porém os sistemas de software mais reais requerem fluxos de trabalho que perpassam as capacidades técnicas. Considere o fluxo de trabalho comercial comum de FecharCatálogo. O código para lidar com FecharCatálogo na arquitetura em camadas técnicas aparece em todas as camadas, como na Figura 8-5.

Página 118

Figura 8-5. Onde os domínios/fluxos de trabalho aparecem nas arquiteturas particionadas técnicas e do domínio Na Figura 8-5, na arquitetura particionada tecnicamente, FecharCatálogo aparece em todas as camadas; o domínio se espalha pelas camadas técnicas. Compare isso com o particionamento do domínio, que usa um particionamento de alto nível que organiza os componentes por domínio, em vez das capacidades técnicas. Na Figura 8-5, os arquitetos que projetam a arquitetura particionada por domínio criam componentes de alto nível em torno dos fluxos de trabalho e/ou domínios. Cada componente no particionamento do domínio pode ter subcomponentes, inclusive camadas, mas o particionamento de alto nível foca os domínios, o que reflete melhor as alterações que costumam ocorrer nos projetos.

Um estilo não é mais correto que o outro; consulte a Primeira Lei da Arquitetura de Software.

Dito isso, observamos uma tendência do setor nos últimos anos em relação ao particionamento do domínio para as arquiteturas monolíticas e distribuídas (por exemplo, microsserviços).

Contudo, essa é uma das primeiras decisões que um arquiteto deve tomar.

Estudo de Caso: Silicon Sandwiches: Particionamento

Considere o caso de um dos nossos katas de exemplo, “Estudo de Caso: Silicon Sandwiches”

(Capítulo 5). Ao obter os componentes, uma das decisões fundamentais de um arquiteto é o particionamento de alto nível. Veja a primeira das duas possibilidades diferentes para Silicon Sandwiches, um particionamento do domínio, mostrado na Figura 8-6.

Imagem 1 da página 118 (sem descrição)

Página 119

Imagem 1 da página 119 (sem descrição)
Imagem 2 da página 119 (sem descrição)

Página 120

Função do Desenvolvedor

Página 121

inicial deve ser visto como um primeiro rascunho, em que a implementação revelará mais detalhes e aprimoramentos.

Fluxo de Identificação dos Componentes

A identificação dos componentes funciona melhor como um processo iterativo, produzindo candidatos e aprimoramentos por meio de feedback, como na Figura 8-8.

Figura 8-8. Ciclo de identificação dos componentes Esse ciclo descreve um loop de exposição da arquitetura genérico. Certos domínios especializados podem inserir outras etapas nesse processo ou mudar tudo. Por exemplo, em alguns domínios, um código deve passar pelas etapas de segurança ou auditoria nesse processo.

As descrições de cada etapa na Figura 8-8 aparecem nas próximas seções.

Identificando os Componentes Iniciais

Antes de qualquer código existir para um projeto de software, um arquiteto deve determinar com quais componentes de alto nível iniciar, com base no tipo de particionamento de alto nível escolhido. Fora isso, um arquiteto tem liberdade para criar qualquer componente desejado, então mapear a funcionalidade do domínio para ele e ver onde deve residir o comportamento.

Embora possa parecer arbitrário, é difícil iniciar com qualquer coisa mais concreta se um arquiteto projeta um sistema do zero. A probabilidade de conseguir um bom design com esse conjunto inicial de componentes é muitíssimo pequena, sendo por isso que eles devem iterar sobre o design do componente para melhorá-lo.

Atribuir Requisitos aos Componentes

Assim que um arquiteto identifica os componentes iniciais, o próximo passo é alinhar os requisitos (ou as histórias do usuário) com os componentes e ver como se ajustam. Isso pode envolver criar novos componentes, consolidar os existentes ou dividir os componentes porque eles têm responsabilidade demais. Esse mapeamento não precisa ser exato; o arquiteto tenta encontrar um bom substrato geral para permitir mais planejamento e aprimoramento dos arquitetos, dos responsáveis técnicos e/ou dos desenvolvedores.

Analisar Funções e Responsabilidades

Imagem 1 da página 121 (sem descrição)

Página 122

Ao atribuir histórias aos componentes, o arquiteto também vê as funções e as responsabilidades elucidadas durante os requisitos para assegurar a correspondência da granularidade. Pensar nas funções e nos comportamentos que a aplicação deve suportar permite que o arquiteto alinhe a granularidade do componente e do domínio. Uma das grandes vantagens para os arquitetos inclui descobrir a granularidade exata dos componentes, que encoraja a abordagem iterativa descrita aqui.

Analisar as Características da Arquitetura

Ao atribuir requisitos aos componentes, o arquiteto também deve ver as características da arquitetura descobertas antes para considerar como podem impactar a divisão e a granularidade do componente. Por exemplo, embora duas partes de um sistema possam lidar com a entrada do usuário, a parte que lida com centenas de usuários simultâneos precisará de características da arquitetura diferentes em relação à outra parte que precisa dar suporte apenas a alguns. Assim, embora uma visão puramente funcional do design do componente possa produzir um único componente para lidar com a interação do usurário, analisar as características da arquitetura levará a uma subdivisão.

Reestruturar os Componentes

O feedback é essencial no design de software. Assim, os arquitetos devem iterar continuamente no design do componente com os desenvolvedores. Projetar um software levanta muitas dificuldades inesperadas; ninguém pode antecipar todos os problemas desconhecidos que normalmente ocorrem durante os projetos de software. Assim, o segredo é uma abordagem iterativa para o design do componente. Primeiro, é quase impossível representar todas as diferentes descobertas e casos extremos que surgirão para encorajar o novo design. Em segundo lugar, conforme o arquiteto e os desenvolvedores se aprofundam na criação da aplicação, eles têm uma compreensão mais sutil sobre onde devem ficar o comportamento e as funções.

Granularidade do Componente

Encontrar a devida granularidade para os componentes é uma das tarefas mais difíceis de um arquiteto. Um design muito granular de um componente leva a muita comunicação entre os componentes para conseguir resultados. Componentes pouco granulares encorajam um alto acoplamento interno, levando a dificuldades na implementação e no teste, assim como a efeitos colaterais negativos relacionados à modularidade.

Design do Componente

Não há um jeito “certo” aceito para projetar os componentes. Pelo contrário, há uma grande variedade de técnicas, todas com vários trade-offs. Em todos os processos, um arquiteto pega os requisitos e tenta determinar quais blocos de construção de granularidade maior formarão a aplicação. Existem muitas técnicas diferentes, todas com trade-offs variados e associados ao processo de desenvolvimento de software usado pela equipe e pela organização. Aqui, falamos sobre alguns modos gerais de descobrir os componentes e as armadilhas a evitar.

Página 123

Descobrindo os Componentes

Imagem 1 da página 123 (sem descrição)

Página 124

O antipadrão da armadilha da entidade surge quando um arquiteto identifica incorretamente as relações do banco de dados como fluxos de trabalho na aplicação, uma correspondência que raramente se manifesta no mundo real. Ao contrário, esse antipadrão costuma indicar falta de reflexão sobre os fluxos de trabalho reais da aplicação. Os componentes criados com a armadilha da entidade tendem a ser mais gerais, não oferecendo orientação à equipe de desenvolvimento em termos de empacotamento e estruturação global do código-fonte.

Abordagem Ator/Ações A abordagem ator/ações é um modo popular que os arquitetos usam para mapear os requisitos para os componentes. Nessa abordagem, definida originalmente pelo Processo Unificado Racional, os arquitetos identificam os atores que realizam atividades com a aplicação e as ações que esses atores podem realizar. Fornece uma técnica para descobrir os usuários típicos do sistema e as coisas que eles podem fazer com o sistema.

A abordagem ator/ações ficou popular junto com certos processos de desenvolvimento de software, em especial os processos mais formais que favorecem uma parte importante do design inicial. Ainda é popular e funciona bem quando os requisitos apresentam funções distintas e ações que eles podem realizar. Esse estilo de decomposição de componentes funciona bem para todos os sistemas, monolíticos ou distribuídos.

Tempestade de eventos Tempestade de eventos (event storming) como uma técnica de descoberta de componentes vem do design orientado a domínios (DDD) e compartilha popularidade com os microsserviços, muito influenciados também pelo DDD. Na tempestade de eventos, o arquiteto pressupõe que o projeto usará mensagens e/ou eventos para se comunicar entre os vários componentes. Para tanto, a equipe tenta determinar quais eventos ocorrem no sistema com base nos requisitos e nas funções identificadas e cria componentes em torno dessas sub-rotinas de eventos e mensagens. Isso funciona bem nas arquiteturas distribuídas, como microsserviços, que usam eventos e mensagens, pois ajuda os arquitetos a definir as mensagens usadas no eventual sistema.

Abordagem do fluxo de trabalho Uma alternativa à tempestade de eventos oferece uma abordagem mais genérica para os arquitetos que não usam o DDD nem mensageria. A abordagem do fluxo de trabalho modela os componentes em torno dos fluxos de trabalho, muito parecido com a tempestade de eventos, mas sem os limites explícitos de criar um sistema baseado em mensagens. Essa abordagem identifica as principais funções, determina os fluxos de trabalho com os quais as funções interagem e cria componentes em torno das atividades identificadas.

Nenhuma técnica é superior às outras; todas têm trade-offs diferentes. Se uma equipe usa uma abordagem de cascata e outros processos mais antigos de desenvolvimento de software, pode preferir a abordagem Ator/Ações porque ela é geral. Ao usar arquiteturas DDD e correspondentes, como os microsserviços, a tempestade de eventos corresponde exatamente ao processo de desenvolvimento de software.

Página 125

Estudo de Caso: Going, Going, Gone: Descobrindo os Componentes

Imagem 1 da página 125 (sem descrição)

Página 126

Página 127

Figura 8-11. Incorporando características da arquitetura no design do componente GGG Provavelmente, o design mostrado na Figura 8-11 não é o final. Mais requisitos devem ser revelados (como as pessoas se registram, as funções de administração em torno do pagamento etc.). Porém, esse exemplo é um bom ponto de partida para começar a iterar mais no design.

É um conjunto de componentes possível para resolver o problema GGG, mas não está necessariamente correto nem é o único. Alguns sistemas de software têm apenas um modo para a implementação pelos desenvolvedores; todo design tem trade-offs diferentes. Como arquiteto, não fique obcecado para encontrar um design verdadeiro, porque muitos serão suficientes – e menos propensos a serem excessivamente complexos (overengineering). Pelo contrário, tente avaliar objetivamente os trade-offs entre as diferentes decisões de design e escolher uma que tenha os trade-offs menos piores.

Quantum da Arquitetura Revisitado: Escolhendo Entre Arquiteturas Monolíticas Versus Distribuídas

Lembrando a explicação que define o quantum da arquitetura em “Quanta Arquitetural e Granularidade” (Capítulo 7), o quantum da arquitetura define o escopo das características da arquitetura. Isso, por sua vez, leva um arquiteto a uma importante decisão ao terminar o design inicial: a arquitetura deve ser monolítica ou distribuída?

Uma arquitetura monolítica normalmente apresenta uma única unidade de implementação, inclusive toda a funcionalidade do sistema que é executada no processo, em geral conectada a um único banco de dados. Os tipos de arquiteturas monolíticas incluem os blocos monolíticos em camadas e modular, vistos por completo no Capítulo 10. Uma arquitetura distribuída é o oposto; a aplicação consiste em vários serviços rodando em seu próprio ecossistema, comunicando-se via protocolos de rede. As arquiteturas distribuídas podem apresentar modelos de implementação mais refinados, em que cada serviço pode ter seu próprio ritmo de

Imagem 1 da página 127 (sem descrição)

Página 128

Página 129

Parte II. Estilos de Arquitetura

PARTE II Estilos de Arquitetura A diferença entre um estilo e um padrão de arquitetura pode ser confusa. Definimos um estilo de arquitetura como a estrutura global de como a interface de usuário e o código-fonte do backend são organizados (como dentro das camadas de uma implementação monolítica ou serviços implantados separadamente) e como esse código-fonte interage com um armazenamento de dados. Já os padrões da arquitetura são estruturas de design de baixo nível que ajudam a formar soluções específicas em um estilo de arquitetura (como obter uma alta escalabilidade ou um alto desempenho em um conjunto de operações ou entre os conjuntos de serviços).

Entender os estilos de arquitetura ocupa grande parte do tempo e do esforço dos novos arquitetos porque eles compartilham importância e abundância. Os arquitetos devem entender os vários estilos e os tradeoffs embutidos em cada um para tomar boas decisões; cada estilo de arquitetura incorpora um conjunto conhecido de trade-offs que ajudam um arquiteto a fazer a escolha certa para determinado problema comercial.

Página 130

9. Fundamentos

CAPÍTULO 9 Fundamentos Os estilos de arquitetura, por vezes chamados de padrões de arquitetura, descrevem uma relação nomeada de componentes que cobrem várias características da arquitetura. Um nome de estilo de arquitetura, parecido com os padrões de projeto, cria um único nome que atua como atalho entre os arquitetos experientes. Por exemplo, quando um arquiteto fala sobre um bloco monolítico em camadas, seu objetivo na conversa é entender os aspectos da estrutura, quais características da arquitetura funcionam bem (e quais podem causar problemas), os modelos comuns de implementação, as estratégias de dados e muitas outras informações.

Assim, os arquitetos devem estar familiarizados com os nomes básicos dos estilos de arquitetura fundamentais e genéricos.

Cada nome captura muitos detalhes entendidos, uma das finalidades dos padrões de projeto.

Um estilo de arquitetura descreve a topologia, as características da arquitetura pressupostas e padrão, sendo benéficas e prejudiciais. Explicamos muitos padrões comuns da arquitetura moderna no resto desta seção do livro (Parte II). Porém, os arquitetos devem se familiarizar com vários padrões fundamentais que aparecem incorporados nos padrões maiores.

Padrões Fundamentais

Vários padrões fundamentais aparecem de novo na história da arquitetura de software porque eles dão uma perspectiva útil sobre como organizar o código, as implementações ou outros aspectos da arquitetura. Por exemplo, o conceito de camadas na arquitetura, separando diferentes questões com base na funcionalidade, é tão antigo quanto o próprio software.

Contudo, o padrão em camadas continua a se manifestar em diferentes formas, inclusive as variantes modernas vistas no Capítulo 10.

A Grande Bola de Lama

Os arquitetos se referem à ausência de qualquer estrutura de arquitetura perceptível como uma Grande Bola de Lama, nomeada segundo o antipadrão homônimo definido em um documento de 1997 por Brian Foote e Joseph Yoder:

Uma Grande Bola de Lama é uma confusão de código espaguete mal estruturado, desleixado e unido com fita adesiva e arame. Esses sistemas mostram sinais inequívocos de crescimento desregulado e reparos rápidos e repetidos. As informações são compartilhadas de modo indiscriminado entre os elementos distantes do sistema, normalmente ao ponto em que quase todas as informações importantes se tornam globais e duplicadas.

Talvez a estrutura geral do sistema nunca tenha sido definida.

Se foi, pode ter se perdido além do reconhecimento. Os programadores com um traço de sensibilidade arquitetural evitam esses atoleiros. Apenas quem não se preocupa com a arquitetura e, talvez, se sente bem com a inércia da obrigação diária de corrigir os furos nesses canais fracassados, fica contente em trabalhar em tais sistemas.

— Brian Foote e Joseph Yoder

Página 131

Em termos modernos, uma grande bola de lama pode descrever uma aplicação de script simples com manipuladores de eventos ligados diretamente às chamadas de banco de dados, sem nenhuma estrutura interna real. Muitas aplicações comuns iniciam assim, então ficam desajeitados conforme continuam a crescer.

Em geral, os arquitetos desejam evitar esse tipo de arquitetura a todo custo. A falta de estrutura dificulta muito a alteração. Esse tipo também tem problemas na implementabilidade, na testabilidade, na escalabilidade e no desempenho.

Infelizmente, esse antipadrão é bem comum da arquitetura no mundo real. Poucos arquitetos pretendem criar um, mas muitos projetos, sem querer, conseguem fazer uma bagunça devido à falta de governança em torno da qualidade e da estrutura do código. Por exemplo, Neal trabalhou no projeto de um cliente cuja estrutura aparece na Figura 9-1.

O cliente (cujo nome é omitido por motivos óbvios) criou uma aplicação web baseado em Java o mais rápido possível durante anos. A visualização técnica[1] mostra o acoplamento arquitetural: cada ponto no perímetro do círculo representa uma classe e cada linha representa conexões entre as classes, em que as linhas mais grossas indicam conexões mais fortes. Nessa base de código, qualquer alteração em uma classe dificulta prever os efeitos colaterais nas outras, tornando a alteração um terror.

Figura 9-1. Uma arquitetura Grande Bola de Lama visualizada a partir de uma base de código real

Imagem 1 da página 131 (sem descrição)

Página 132

Arquitetura Unitária

Quando o software iniciou, havia apenas o computador e o software que rodava nele. Durante as várias épocas de evolução do hardware e do software, os dois iniciaram como uma entidade, então se dividiram conforme aumentava a necessidade de capacidades mais sofisticadas. Por exemplo, os computadores mainframe começaram como sistemas únicos e, gradualmente, separaram os dados em seu próprio tipo de sistema. Do mesmo modo, quando os computadores pessoais apareceram pela primeira vez, grande parte do desenvolvimento comercial focava máquinas simples. Quando os PCs em rede se tornaram comuns, apareceram os sistemas distribuídos (como cliente/servidor).

Poucas arquiteturas unitárias existem fora de sistemas embarcados e outros ambientes altamente restritos. Em geral, os sistemas de software tendem a crescer em funcionalidade ao longo do tempo, requerendo separar as preocupações para manter as características da arquitetura operacionais, como desempenho e escala.

Cliente/Servidor

Com o tempo, várias forças requereram o particionamento a partir de um único sistema; como fazer isso é a base de muitos desses estilos. Muitos estilos de arquitetura lidam com como separar de modo eficiente as partes do sistema.

Um estilo fundamental na arquitetura separa a funcionalidade técnica entre front-end e backend, chamada de arquitetura de duas camadas ou cliente/ servidor. Existem muitos tipos diferentes dessa arquitetura, dependendo da época e das capacidades de computação.

Desktop + servidor de banco de dados Uma primeira arquitetura de computador pessoal encorajava que os desenvolvedores escrevessem aplicações desktop avançadas nas interfaces do usuário, como Windows, separando os dados em um servidor do banco de dados à parte. Essa arquitetura coincidiu com o surgimento de servidores do banco de dados independentes que podiam se conectar via protocolos de rede padrão. Permitia que a lógica de apresentação residisse no desktop, enquanto as ações mais intensas computacionalmente (em volume e complexidade) ocorria nos serviços de banco de dados mais robustos.

Navegador + servidor web Assim que o desenvolvimento web moderno chegou, a divisão comum tornou o navegador web conectado ao servidor web (que por sua vez conectava um servidor de banco de dados). A separação das responsabilidades lembrava a variante de desktop, mas com clientes ainda mais leves, como navegadores, permitindo uma distribuição mais ampla tanto dentro quanto fora dos firewalls. Mesmo que o banco de dados fosse separado do servidor web, muitas vezes os arquitetos ainda consideravam isso uma arquitetura com duas camadas porque os serviços web e de banco de dados rodavam em uma classe de máquina dentro do centro de operações e a interface de usuário rodava no navegador do usuário.

Comentários

Deixe um comentário

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