Fundamentos para arquiteturas baseadas em serviços

Julgar algo demanda a combinação de bom senso e conhecimento sólido de tudo o que está sendo julgado. No mundo dos projetos SOA, isso significa entender a computação orientada a serviços com clareza absoluta e, também, entender ambientes, restrições e objetivos estratégicos do negócio.
Thomas Erl

— Este capítulo ainda carece de edição e conexão apropriada entre os tópicos. Aprecie com moderação.

Um dos grandes desafios da arquitetura de software é decompor sistemas em unidades modulares pequenas, com conjuntos bem delimitados de responsabilidades. Service-oriented computing representa uma estratégia para superação desse desafio.

De forma ampla, abordagens service-oriented implicam em “perceber” sistemas complexos como conjunto de serviços, cada um deles atendendo um conjunto bem claro de atividades de negócio ou demandas técnicas, sempre orientadas a um determinado outcome.

Um serviço implementa uma coleção de capabilities.

Thomas Erl

Todo serviço deve ser, dentro do possível, self-contained – ou seja, com condições de cumprir suas atribuições de maneira independente – sem depender de outros. Além disso, devem operar em black-box, tornando detalhes de implementação e funcionamento irrelevantes para eventuais consumidores.

Eventualmente, serviços podem “combinar” funcionalidades de outros serviços de forma a atender plenamente objetivos de negócio, respeitando restrições e atributos de qualidade.

Muitas das ideias indicadas para SOA são plenamente aplicáveis com microsserviços. As principais distinções estão na forma como a comunicação é implementada (em SOA, tradicionalmente, recorre-se a um barramento para orquestração) e na granularidade (cada microsserviço, idealmente, realiza uma capability).

Organizações com seus sistemas organizados a partir de arquiteturas service-oriented, mantém rígidos processos de governança, partindo de um inventário.

Elementos fundamentais em SOA

Ao projetar sistemas com SOA, cabe ao arquiteto considerar, em primeiro nível, segregar componentes de software em quatro categorias.

  1. Frontend – contendo tanto as interfaces com usuários quanto APIs externas;
  2. Services Repository ou Inventário – contendo uma relação organizada e estrutura dos serviços, bem como seus contratos e interfaces;
  3. Service Bus – como principal elemento de comunicação e orquestração
  4. Services, podendo ser implementações finais ou composições de serviços.
Em nível mais alto, arquiteturas service-oriented são tecnicamente particionadas.

Interessante observar que SOA abre espaço para formação de, pelo menos, quatro frentes de trabalho dentro das organizações. Não é raro, por exemplo, a formação de squads inteiros voltados ao relacionamento com usuários internos e externos, através dos frontends e serviços de “experiência”. Também não é raro o estabelecimento de times inteiros arquitetando e implementando “integrações” (integration architects operando barramento e o inventário).
Em empresas maiores, é comum implementar governança a partir do inventário de serviços.

Para cada serviço, por sua vez, é importante ponderar sobre:

  1. contract – estabelecendo como um serviço pode ser consumido, com detalhes técnicos e indicação de restrições e requisitos, geralmente descritos em conjuntos de documentos como (WSDL, XSD e WS-Policy)
  2. implementation – contendo o código, propriamente dito, do serviço.
  3. interface – realizando elementos especificados em contrato.

É na implementação dos serviços que reside a lógica de negócio e a persistência.

Idealmente, o projeto de serviço deve ser particionado por critérios do domínio.

Alguns princípios arquiteturais para SOA

Definição: Princípio arquitetural

Um princípio arquitetural é uma prática generalizada e aceita. Trata-se não somente de recomendações adotadas por outros, mas amplamente defendidas.

Princípios são guidelines contendo recomendações que direcionam para a elaboração de soluções com determinados objetivos explicitamente definidos.

Thomas Erl, uma das grandes referências em projetos de arquiteturas baseadas em serviços (SOA), ensina que há oito princípios fundamentais a observar sempre que estivermos projetando sistemas nesse estilo.

  1. O design deve ser orientado a contratos coerentemente padronizados, ou seja, todos os serviços dentro de um mesmo inventário devem estar em conformidade com um mesmo conjunto de padrões de projeto. Os contratos dos serviços devem ser sempre fornecidos com seu respectivo serviço, compreendendo especificação técnica e/ou documentos descritivos formais. Essa padronização pode ser desafiadora para ambientes com muitos serviços, ao longo do tempo.
  2. Serviços devem ter baixo acoplamento entre eles, através de contratos, que não revelem nem dependam de aspectos internos de implementação, como tecnologias adotadas e padrões de armazenamento.
  3. Qualquer informação não essencial de um serviço deve ser abstraída, através de uma classificação criteriosa de propriedades expostas;
  4. Serviços devem ser projetados para reuso, ou seja, de forma a atender diferentes contextos de utilização, não relacionados intimamente a processos ou atividades específicas;
  5. Serviços devem ser autônomos, ou seja, devem ter influência principal dos ambientes de execução onde operam;
  6. Serviços devem minimizar gestão de estado, reduzindo a demenda de gestão por recursos de persistência, transferindo a gestão do estado para aplicações clientes sempre que aplicável.
  7. Serviços devem ser “descobríveis”, expostos através de um registro central (inventário)
  8. Serviços devem ser componíveis, por exemplo, pela adoção da estratégia funcionalidade -> processo -> Experiência

Classificações para segregação de responsabilidades em serviços

Há duas abordagens amplas, interessantes, a adotar quando se está segregando responsabilidades de um sistema em serviços.

A primeira, proposta por Thomas Erl, consiste na separação de serviços em:

  • Task Servicesimplementando atividades diretamente relacionadas a processos de negócio – como processamento de uma venda.
  • Entity Services – que “digitalizam” membros do modelo de negócios, tais como informações relacionadas a clientes, contratos, pedidos, etc
  • Utility Services – importantes para automações, não necessariamente implementando aspectos relacionados ao negócio, mas concerns técnicos, como transformação de dados e integração entre sistemas.

No modelo proposto por Erl, task services operam em uma primeira camada, combinando entity services de uma segunda camada que, por sua vez, combinam, utility services.

The Entity Services Antipattern

Michael Nygard considera, modernamente, entity services como um antipattern. Nesse artigo, apresenta suas justificativas e aponta algumas implicações da adoção dessa estratégia de modularização.

Acessar artigo

A segunda abordagem (API-Led), proposta pela MuleSoft, consiste na separação de serviços em:

  • Experience Services – composições de diversos process services, visando tangibilizar uma “experiência”, como, por exemplo “efetivar uma compra”
  • Process Services – composições de diversos system services , visando consolidar sequências de atividades de negócio, como, “cadastrar um cliente”, “separar um produto”, etc.
  • System Services – expondo funções de base de diversos sistemas (como APIs de sistemas de ERP e CRMs) ou expondo (como transformações de dados).
Abordagens API-led parecem mais aderentes a cenários modernos.

Composição interna de um serviço

A estrutura interna dos serviços em arquiteturas particionadas pelo domínio costuma ter uma façade, lógica de negócios e o modelo de persistência.

Eventualmente, cada serviço poderá ser estruturado seguindo o modelo hexagonal, proposto por Alistair Cockburn (a imagem abaixo foi extraída do site de Cockburn).

Para serviços maiores e mais complexos, pode ser interessante, estruturar, decompor, internamente, o serviço em componentes isolados abrindo margem para implementações futuras baseadas em microsserviços.

O “lado Conway” de SOA

A arquitetura de um software, muito além dos aspectos técnicos, impacta na dinâmica de trabalho das equipes que o desenvolvem. Boas arquiteturas minimizam os esforços de coordenação e sincronização entre pessoas e times, colaborando para lead times menores, sem comprometer a qualidade interna.

Revisitando 'Arquitetura' e o 'Papel do Arquiteto'

A arquitetura de um software tem relação com as decisões de design que contribuem para os objetivos do negócio, respeitando restrições e atingindo atributos de qualidade.

O projeto da arquitetura vai determinar quais serão os componentes de uma aplicação, as responsabilidades de cada um desses componentes e a forma como se relacionam, além da estratégia de evolução, sempre perseguindo evolvability. 

Por implicações da lei de Conway, a arquitetura do software é diretamente impactada pelas organizações dos times, e vice-versa. Logo, a arquitetura tem implicações diretas sobre as demandas de coordenação e sincronização e, indiretamente, pela eficiência operacional.

O arquiteto de software, evidentemente, precisa ser sensível a todas essas implicações e desenvolver habilidades para ponderar em dimensões que, evidentemente, extrapolam tecnologia.

Importante ter clareza que mesmo arquiteturas modulares, se inadequadamente planejadas, não autorizam trabalho paralelo. Arquiteturas tecnicamente particionadas, por exemplo, são mais restritivas do que aquelas particionadas por características do domínio. SOA destaca-se por fazer bom equilíbrio entre particionamento técnico e por características do domínio.

Em arquiteturas tecnicamente particionadas, quando há um time dedicado para cada componente técnico, todos os limites de componentes se convertem em “pontos de coordenação”. O impacto mais perceptível ocorre na ampliação dos lead-times. Além disso, quando os times são organizados por features, há “difusão de responsabilidade” e queda da qualidade interna que ocasiona, também, no longo prazo, prejuízo de lead-time.

Sistemas com arquiteturas particionadas tecnicamente, como em implementações mais simples do estilo em camadas, são adequadas para times pequenos, com no máximo 20 pessoas. Em times maiores, arquiteturas precisam ser particionadas pelo domínio para serem sustentáveis. Essa é uma das justificativas para desenvolver arquiteturas baseadas em serviços.

Two-pizzas rule


Jeff Bezos, fundador da Amazon, argumentou que o número ideal de membros em um time é aquele onde todos possam ser alimentados com duas pizzas – geralmente, sete pessoas. Esse posicionamento ficou conhecido como “The two pizzas rule”.


Times pequenos apresentam menos desafios para comunicação, reduzindo também problemas de coordenação e sincronização. Em uma arquitetura em quatro camadas, tradicional, com um time responsável por cada camada, teríamos então um limite razoável de 30 pessoas – mas geralmente esse número seria menor, não mais do que 20, afinal, as “alocações” na gestão do banco, por exemplo, pelo seu alto acoplamento aferente são menores. No mesmo modelo em camadas, uma tentativa de separar times por contexto delimitado, sem “colapsar” o código, restringe o número de times a, no máximo, três (~20 pessoas).

SOA como “antídoto” para a difusão de responsabilidades

Desenvolver software é um “esporte coletivo” e qualquer atividade em grupo tem demandas de coordenação. Quanto mais desenvolvedores tocam am um trecho de código, maiores são as chances de que este código tenha defeitos e, além disso, maiores são os custos operacionais de gestão. Quanto maior o número de envolvidos, mais importantes, frequentes e custosas são atividades de comunicação e de coordenação. Analogamente, mais difusa é a responsabilidade pela qualidade.

De forma objetiva, um bom proxy para a difusão de responsabilidade pode ser obtido pela concentração de contribuições (commits) de diversos autores em um determinado artefato, como indicado na fórmula abaixo:

Na fórmula ai se refere a cada autor individualmente, nc(ai)  é o número de contribuições para um determinado autor e, finalmente, NC é o número total de contribuições. Artefatos mantidos por um único autor tem difusão de responsabilidade zerada.

Git como 'rede social'

Ferramentas modernas de controle de versão são frequentemente usadas apenas como mecanismos sofisticados de backup de código-fonte. Entretanto, podem ser bem mais do que isso.


O Git, por exemplo, consegue apontar arquivos que são modificados com mais frequência, colaboradores mais ativos, além, é claro, parâmetros para cálculo da difusão de responsabilidade. A instrução 'git shortlog -s', por exemplo, relaciona a quantidade de commits , por desenvolvedor, em uma determinada pasta.

Artefatos com indicadores altos de difusão de responsabilidade geralmente apresentam qualidade interna mais baixa. A regra geral predominante parece ser “o que é responsabilidade de todos, não é de ninguém”. No fim, adaptações acabam sendo implantadas de maneira descuidada causando problemas em produção.

Curiosamente, artefatos com maior difusão de responsabilidade costumam ser, também, aqueles que tem maior acoplamento eferente e que, por isso, quebram com mais facilidade, ou com maior acoplamento aferente, com potencial para gerar prejuízos maiores no sistema, caso apresentem defeito.
Arquiteturas particionadas por domínio tendem a ter artefatos com difusão de responsabilidade mais baixa. Artefatos com difusão de responsabilidade mais baixa tendem a ser mais fáceis de manter e evoluir, o que converte essa métrica em uma boa fitness function.

Times de engenharia que mantém sistemas SOA costumam operar de forma concisa com contratos bem definidos no inventário.

Propriedade sobre artefatos

A atribuição de “propriedade” para artefatos de um software – como documentação, código, banco de dados, etc – é um tema controverso. Muitas pessoas associam “propriedade” como justificativa para redução da colaboração ou, pior ainda, formação de silos. Entretanto, esse não é o ponto! A atribuição de “propriedade” é uma medida para tentar minimizar os efeitos da difusão de responsabilidades. 

Atribuir “propriedade” de artefatos a times ou indivíduos é similar a iniciativa de atribuir mantenedores para projetos open source. Ou seja, determinar responsabilidade pela qualidade atual e futura de um artefato para alguém que irá o “zelar e protejer”.

A “propriedade” de artefatos não é uma maneira de coibir contribuições, muito pelo contrário, é um incentivo para participação ativa. Cabe aos “proprietários” arbitrar, apenas, que contribuições estão prontas para serem aceitas.

Artefatos sem um “dono” são órfãos. Aliás, é um cenário comum nas organizações que “donos” de artefatos deixem a empresa e, consequentemente, deixem seus “artefatos filhos” desprovidos de cuidado – daí, logo, “difusão de responsabilidade”.

Iniciando a “jornada SOA”

O “começo natural” para adoção de SOA consiste na segregação da “camada de negócios” em serviços.

A evolução dos modelos de operação e distribuição de software, associados com o desenvolvimento de técnicas mais apropriadas de análise de domínios de negócios, autorizaram a decomposição das tradicionais “camadas de negócios” em contextos delimitados, distribuídos idealmente em serviços, nos moldes prescritos por SOA.

Na visão do modelo indicada acima, os serviços são desenvolvidos e distribuídos de forma “quase independente”, exceto por serem todos acoplados a um único banco de dados monolítico.


Domain-driven Design

Eric Evans escreveu, em 2003, o livro ‘Domain-driven Design‘. Trata-se de uma formalização de práticas e padrões para, segundo o autor, atacara complexidade no coração do software.

Um dos fundamentos de Domain-driven Design é identificar, dentro de um determinado domínio de negócio, subdomínios que, mais tarde, são modelados em contextos delimitados. Na modelagem de sistemas da forma como apresentamos aqui, cada contexto delimitado pode ser “implementado” como um serviço

Os serviços, individualmente, tendem a ter difusão de responsabilidade mais baixa, enquanto o banco de dados e a interface com usuário, além de acoplamento aferente e eferente maiores, tendem a ter difusão de responsabilidade mais alta. Nesses cenários, não é incomum que a base de dados (e a interface com o usuário) passem a ter controle direto de times especialista, com “senso de propriedade” suficiente para manter, via burocracia, qualidade interna mais alta.
A segregação do negócio em serviços leva a formação natural de camadas de serviços como experiência -> processo -> sistema.

O problema com 'Kernel Compartilhado'

Domain-driven Design autoriza a identificação e implementação de kernels compartilhados: parte do modelo de domínio de uso comum para dois ou mais contextos delimitados. Entretanto, antes de solução, este tipo de artefato representa problema.

Kernels compartilhados são naturalmente acoplados (de maneira aferente) e nascem com difusão de responsabilidade alta. Eventualmente, representam redução dos custos de desenvolvimento, mas, seguramente, representam acréscimo no custo de manutenção.

Considerações sobre as interfaces com usuários e sistemas externos

Particionando a “interface com o usuário” conforme serviços

Uma forma de reduzir os impactos do aumento crescente do acoplamento eferente na interface com o usuário é particioná-la também, reduzindo indiretamente os riscos de difusão de responsabilidade.

Não é raro que diferentes perfis de usuários demandem e valorizem características diferentes de usabilidade. Por exemplo, enquanto vendedores tendem a valorizar simplicidade e objetividade, analistas financeiros geralmente gostam dor maior volume de informações concentrado (com menos espaços em branco). Por isso, antes de ser um problema, o particionamento da interface pode ser uma “solução” para a inclusão de adaptações, com coesão de padrões de projeto de UX.

O particionamento da interface com o usuário geralmente segue os mesmos critérios de domínio aplicados nos serviços na “camada de negócios”.

A “emergência” das APIs externas

Eventualmente, serviços internos irão interagir diretamente com sistemas de terceiros que irão oferecer uma “interface alternativa” para alguns “serviços de negócios” de um software. Nesses casos, pode ser uma boa ideia oferecer um serviço específico para atender às demandas desse serviço externo, reduzindo acoplamento eferente externo (e as chances de quebra).

Prover uma API externa também é um bom caminho para reduzir a difusão de responsabilidade de integrações críticas e proteger o negócio.

APIs externas “dedicadas” para cada sistema terceiro podem reduzir as “barreiras de adoção”, entretanto, obviamente, não se pode ignorar os custos, sobretudo de manutenção. Para parcerias menos impactantes para o negócio é desejável deixar emergir uma API comum, entretanto, essa não deve ser a primeira linha de ação.

A “emergência” de camadas de orquestração (gateways e barramentos)

Caso exista necessidade crescente de fazer acesso externo aos serviços de domínio, além da API externa, é uma boa prática adicionar uma camada com um proxy reverso ou gateway.

O gateway reduz o acoplamento aferente dos serviços e também é um bom mecanismo para consolidar demandas por métricas, segurança, bilhetagem, auditoria e descoberta.

Outra atividade comum é orquestrar a relação entre os serviços através de uma ESB.

O banco de dados em um “mundo SOA”

Particionando logicamente o “banco de dados”

A decomposição da “camada de negócios” em serviços e da “camada de interface” em experiências independentes, implicam no aumento do acoplamento aferente do banco de dados, o que é um problema em potencial, mesmo com o número reduzido de serviços (geralmente, 7 em arquiteturas assim). Se não forem feitas de maneira apropriada, alterações de esquema podem causar problemas nos diversos serviços, o que aumenta bastante a necessidade de coordenação.

Uma saída rápida, costuma ser prover um componente com modelo de persistência para ser utilizado nos diversos serviços, suficiente para causar falhas no build quando alterações de esquema forem realizadas. O versionamento do modelo de persistência é fiel a evolução do esquema.

Em seguida, uma boa ideia é recorrer a mecanismos para particionar logicamente o banco de dados criando modelos de persistência decompostos pelos diferentes contextos delimitados. Modernamente, recursos como particionamento vertical, permitem a administração de bases de maneira inteligente, melhorando a performance.

O particionamento vertical, eventualmente, demandará um componentes com modelos de persistência também particionados para serem utilizados nos diversos serviços, suficientes para causar falhas no build quando alterações de esquema forem realizadas. O versionamento do modelo de persistência é fiel a evolução do esquema.

Particionando fisicamente o “banco de dados”

Eventualmente, há possibilidades para particionar um banco de dados monolítico em instâncias independentes, alinhadas ao domínio, de forma semelhante ao que ocorre com microsserviços.

O ponto importante, aqui, é garantir que cada banco de dados não seja necessário para outros serviços e, também, duplicação de dados, exceto quando isso for natural. Excepcionalmente, o particionamento das bases de dados pode ser justificada por problemas de performance.

Cuidado com a complexidade

A complexidade tem quatro origens genéricas distintas que devem ser combatidas. São elas: dimensionalidade, interdependência, influência do ambiente e irreversibilidade.

Quanto maior o número de variáveis envolvidas, maior será a dimensionalidade. Por isso, por exemplo, qualquer software com mais features se torna mais complexo. A cada novo processo na organização, menor a simplicidade. Toda exceção suportada aumenta o custo. Times maiores tornam a coordenação e sincronização mais complexa. Por outro lado, o fracionamento em serviços, com particionamento lógico ou físico de bancos de dados, aumenta a dimensionalidade da arquitetura. Por isso, benefícios precisam ser ponderados!
Operações interdependentes demandam sincronização. Não raro, a indisponibilidade de um recurso impossibilita o uso de outro, aparentemente não relacionado. É bastante comum que uma performance mais pobre de uma parte de um sistema deixe ele todo “lento”. Este é o mesmo limitante em times particionados por critérios técnicos, eventualmente, a performance ruim de um time acarreta em atrasos para todos os demais.

Sistemas mais “sensíveis” ao ambiente também são mais complexos. Afinal, demandam planejamento de contingências e workarounds. Da mesma forma, times mais “sensíveis” a influências externas também precisam se preparar para contingências e workarounds.

Finalmente, a irreversibilidade também demanda cuidados. Ações ou eventos cujo ocorrências resultem em consequências que não podem ser desfeitas implicam em custo maior de planejamento, nem sempre eficiente. Daí, a ênfase para servidores imutáveis, principalmente para sistemas com índice maior de particionamento.

A gestão da complexidade na arquitetura de um software implica, invariavelmente, na gestão da complexidade dos times desenvolvedores de software. O particionamento em serviços, por exemplo, aumenta a dimensionalidade dos componentes, mas reduz a dimensionalidade de features por componente. Da mesma forma, aumenta a dimensionalidade na quantidade de times, mas reduz a quantidade de membros por time. Ganhos e perdas!
0
Você concordax

// TODO

Antes de avançar para o próximo capítulo, recomendo as seguintes reflexões:

  • Você conseguiria decompor seu sistema em contextos delimitados claros e independentes?
  • Quais seriam as dificuldades e facilidades para particionamento da base de dados (lógico ou físico)?

 

Referências bibliográficas

BIRD, Christian; NAGAPPAN, Nachi; MURPHY, Brendan; GALL, Harald; DEVANBU, Premkumar. Don’t Touch My Code! Examining the Effects of Ownership on Software Quality. ACM SIGSOFT Symposium on The Foundations of Software Engineering, Szeged, Hungary, p. 4-14, set. 2011.

CATALDO, Marcelo; HERBSLEB, James D.. Coordination Breakdowns and Their Impact on Development Productivity and Software Failures. IEEE Transactions On Software Engineering, [s. l], v. 3, p. 343-360, nov. 2013.

COCKBURN, Allistair. Hexagonal architecture. Disponível em: https://alistair.cockburn.us/hexagonal-architecture/. Acesso em: 27 maio 2021.

Descomplicando “Arquitetura Hexagonal”. Intérpretes: Elemar Júnior. Campo Bom, Rs: Eximiaco, 2020. Youtube, color. Série Padrões Arquiteturais. Disponível em: https://youtu.be/V7JnDDQY1m0. Acesso em: 27 maio 2021.

EVANS, Eric. Domain-driven Design: tackling complexity in the heart of software. Boston, Ma: Addison-Wesley, 2002.

ERL, Thomas. SOA: principles of service design. Boston, Ma: Person Education, 2008.

FLETCHER, Matt. Service-Based Architecture as an Alternative to Microservice Architecture. 2016. Disponível em: https://www.infoq.com/news/2016/10/service-based-architecture/. Acesso em: 25 maio 2021

NAGAPPAN, Nachiappan; MURPHY, Brendan; BASILI, Victor; NAGAPPAN, Nachi. The Influence of Organizational Structure on Software Quality: an empirical case study. International Conference Of Software Engineering, Proceedings, Leipzig, Germany., p. 521-530, jan. 2008.

RICHARDS, Mark; FORD, Neal. Fundamentals of Software Architecture: an engineering approach. Sebastopol, Ca: O’Reilly Media, Inc, 2020.

Compartilhe este capítulo:

Compartilhe:

Comentários

Participe da construção deste capítulo deixando seu comentário:

Inscrever-se
Notify of
guest
0 Comentários
Feedbacks interativos
Ver todos os comentários

Fundador e CEO da EximiaCo, atua como tech trusted advisor ajudando diversas empresas a gerar mais resultados através da tecnologia.

Mentoria

para arquitetos de software

Imersão, em grupo, supervisionada por Elemar Júnior, onde serão discutidos tópicos avançados de arquitetura de software, extraídos de cenários reais, com ênfase em systems design.

Consultoria e Assessoria em

Arquitetura de Software

EximiaCo oferece a alocação de um Arquiteto de Software em sua empresa para orientar seu time no uso das melhores práticas de arquitetura para projetar a evolução consistente de suas aplicações.

ElemarJúnior

Fundador e CEO da EximiaCo, atua como tech trusted advisor ajudando diversas empresas a gerar mais resultados através da tecnologia.

+55 51 99942-0609 |  contato@eximia.co

+55 51 99942-0609  contato@eximia.co

0
Quero saber a sua opinião, deixe seu comentáriox
()
x