Pular para o conteúdo principal

UML e comunicação entre Componentes

(conteúdo gerado com I.A.)

Antes de abordarmos o conceito de Componentes, vale a pena analisar como um programador iniciante costuma estruturar uma aplicação. Geralmente, a escolha é construir um monólito tradicional, agrupando todas as classes de domínio (como Categoria, Produto, Compra, Venda, Pagamento, etc.) em uma única pasta de Entidades. Da mesma forma, criam-se pacotes globais para Repositórios, Serviços e Controllers.

O grande problema dessa abordagem é que ela gera um forte acoplamento entre as classes e as camadas do sistema. À medida que a aplicação cresce, isso desencadeia uma série de dificuldades:

  • Manutenção e modelagem complexas: As responsabilidades se misturam. Rapidamente, o código passa a ter diversos trechos onde as classes assumem funções que não deveriam ter, tornando a evolução do sistema confusa.

  • Escalabilidade ineficiente: Para escalar o backend, é necessário replicar a aplicação inteira e todo o banco de dados. Além de consumir mais recursos, isso traz o risco de duplicidade e inconsistência de dados.

  • Alto risco para o projeto: Com um processo de desenvolvimento lento e inseguro, os custos de manutenção disparam. No pior dos cenários, a inviabilidade técnica pode causar o cancelamento do projeto, forçando a equipe a reescrever um novo produto do zero (pivotar).

Para evitar esses gargalos, a melhor estratégia é organizar a aplicação dividindo os pacotes em componentes independentes. Mesmo que o sistema continue rodando como um monólito (o chamado "monólito modular"), essa prática traz benefícios imediatos e de longo prazo:

  • Fronteiras definidas: A manutenção se torna muito mais fácil, pois os limites de cada componente ficam claros e a comunicação entre eles pode ser bem documentada.

  • Preparação para o crescimento: Se houver a necessidade de quebrar o monólito em projetos menores para melhorar a escalabilidade e reduzir custos, a separação já estará encaminhada.

  • Migração facilitada: Caso a empresa evolua a ponto de precisar de uma arquitetura baseada em microsserviços, essa transição poderá ser feita de forma natural e a um baixo custo.

Para aprofundar esse conceito, veja a seguir alguns artigos que falam sobre Componentes.

Na UML, esses conceitos são usados principalmente no Diagrama de Componentes e no Diagrama de Estrutura Composta para modelar o encadeamento e as dependências entre módulos de software. [1, 2, 3, 4]
Os elementos utilizados são representados da seguinte forma:
  • Interface Fornecida: Mostra os serviços que um módulo oferece. É desenhada como um círculo (ou "pirulito") preso a uma linha saindo do componente. [1, 5, 6]
  • Interface Requerida: Mostra os serviços ou recursos que um módulo precisa para funcionar. É desenhada como um semicírculo (ou "soquete"). [1, 5]
  • Porta: É um ponto de interação distinto entre o componente e o ambiente externo, permitindo isolar a estrutura interna. É representada por um pequeno quadrado na borda do retângulo do componente. [7, 8, 9]
  • Porta com Interface Fornecida/Requerida: A interface (círculo ou semicírculo) é anexada diretamente à porta (o quadrado), indicando que aquela interação específica só ocorre através da porta. Quando as interfaces se conectam, elas formam uma junta do tipo "bola-e-soquete". [7, 8, 10, 11]

(conteúdo gerado com I.A.)

Diferença entre Componentes e Pacotes

Os elementos do UML que utilizam esses elementos de interface e porta para detalhar seu encapsulamento são os componentes (e as classes estruturadas).
No caso de um Pacote (Package), a lógica é diferente. Os pacotes servem para organizar e agrupar elementos do modelo, funcionando como "pastas". Os principais elementos que um pacote pode utilizar ou conter são: [1, 2, 3]

Elementos Contidos (Dentro do Pacote)

  • Qualquer elemento UML: Classes, interfaces, componentes, casos de uso ou até outros subpacotes.
  • Membros visíveis: Elementos marcados com símbolos de visibilidade pública (+), privada (-) ou protegida (#). [4, 5, 6, 7]

Relacionamentos entre Pacotes (Setas de Conexão)

  • Importação de Pacote («import»): Permite que um pacote use os elementos públicos de outro pacote sem precisar digitar o caminho completo deles.
  • Acesso de Pacote («access»): Semelhante à importação, mas os elementos importados permanecem privados dentro do pacote que os recebeu, impedindo que outros pacotes os vejam.
  • Mesclagem de Pacotes («merge»): Combina o conteúdo de dois pacotes onde os elementos com o mesmo nome são expandidos e unificados.
  • Dependência Comum: Uma linha tracejada com seta simples que indica que mudanças em um pacote podem afetar o outro. [8, 9]
Você está organizando a arquitetura geral do seu sistema ou focando na modelagem das classes? Se me disser o objetivo, posso sugerir como dividir seus pacotes de forma clara.

Um Exemplo

Para o relacionamento entre os componentes Catalogo, Compras, Vendas e Pagamentos, as interfaces e portas servem para desacoplar as responsabilidades.
(conteúdo gerado com I.A.)
Abaixo está a modelagem detalhada desse ecossistema, organizada com a estrutura de pacotes (pastas internas de código) de cada componente e suas respectivas conexões.

1. Componente: Catalogo

Este componente gerencia o inventário e as informações dos produtos. Ele é consultado por Vendas e Compras.
  • Estrutura de Pacotes (Pastas Internas):
    • com.sistema.catalogo.model (Entidades de Produto, Categoria)
    • com.sistema.catalogo.service (Lógica de busca e atualização de estoque)
    • com.sistema.catalogo.api (Contratos expostos para o exterior)
  • Portas:
    • portaConsulta: Centraliza as buscas do sistema.
    • portaEstoque: Exclusiva para movimentação de mercadorias.
  • Interfaces Fornecidas:
    • IQueryCatalogo (na portaConsulta): Métodos como buscarProdutoPorId() e listarDisponiveis(). Utilizada por Vendas e Compras.
    • IGestaoEstoque (na portaEstoque): Métodos como baixarEstoque() e reporEstoque(). Utilizada por Vendas (baixa) e Compras (reposição).
  • Interfaces Requeridas:
    • Nenhuma (o Catálogo costuma ser uma folha independente nesse cenário).

2. Componente: Vendas

Responsável pela jornada do cliente, fechamento de pedidos e carrinho de compras.
  • Estrutura de Pacotes (Pastas Internas):
    • com.sistema.vendas.checkout (Fluxo de fechamento do pedido)
    • com.sistema.vendas.carrinho (Gestão de itens selecionados)
    • com.sistema.vendas.integration (Classes clientes para chamar outros componentes)
  • Portas:
    • portaIntegracaoCatalogo: Canal de saída para consultar e atualizar estoque.
    • portaIntegracaoPagamento: Canal de saída para processar a cobrança do pedido.
  • Interfaces Requeridas:
    • IQueryCatalogo (na portaIntegracaoCatalogo): Necessária para verificar preços e se o item existe.
    • IGestaoEstoque (na portaIntegracaoCatalogo): Necessária para reservar ou abater o item após a venda.
    • IProcessarPagamento (na portaIntegracaoPagamento): Necessária para enviar o valor do pedido e cobrar o cliente.
  • Interfaces Fornecidas:
    • IStatusPedido (em uma porta própria de visualização): Fornece dados para o cliente acompanhar a venda.

3. Componente: Compras

Responsável pelo reabastecimento do estoque junto a fornecedores (B2B).
  • Estrutura de Pacotes (Pastas Internas):
    • com.sistema.compras.cotacao (Gestão de preços com fornecedores)
    • com.sistema.compras.pedido (Pedidos de compra emitidos)
    • com.sistema.compras.gateway (Comunicação com sistemas externos/bancos)
  • Portas:
    • portaAbastecimento: Conecta com o estoque do catálogo.
    • portaFinanceiroCompras: Conecta com o fluxo de saída de dinheiro.
  • Interfaces Requeridas:
    • IQueryCatalogo (na portaAbastecimento): Para saber quais produtos estão abaixo do estoque mínimo.
    • IGestaoEstoque (na portaAbastecimento): Para adicionar os produtos comprados de volta ao inventário físico.
    • IEmitirPagamento (na portaFinanceiroCompras): Necessária para ordenar que o componente de Pagamentos pague um fornecedor.

4. Componente: Pagamentos

O motor financeiro do sistema. Ele processa entradas (Vendas) e saídas (Compras).
  • Estrutura de Pacotes (Pastas Internas):
    • com.sistema.pagamentos.core (Lógica de conciliação, estornos e taxas)
    • com.sistema.pagamentos.gateways (Integrações com Stripe, PayPal, Bancos)
  • Portas:
    • portaRecebimentos: Dedicada a transações de entrada (clientes).
    • portaDesembolsos: Dedicada a transações de saída (fornecedores).
  • Interfaces Fornecidas:
    • IProcessarPagamento (na portaRecebimentos): Métodos como autorizarCartao() ou gerarPix(). Conecta com a requisição de Vendas.
    • IEmitirPagamento (na portaDesembolsos): Métodos como agendarTed() ou pagarBoletoFornecedor(). Conecta com a requisição de Compras.
  • Interfaces Requeridas:
    • Nenhuma interna do sistema (apenas serviços externos como APIs de bancos/adquirentes, que ficam fora do escopo desses quatro componentes).

Resumo das Conexões (Fio de Ligação)

No diagrama UML, as conexões "Bola-e-Soquete" (Fornecida + Requerida) ocorreriam assim:
  1. Vendas (portaIntegracaoCatalogo) => conecta em => Catalogo (portaConsulta) via IQueryCatalogo
  2. Vendas (portaIntegracaoCatalogo) => conecta em => Catalogo (portaEstoque) via IGestaoEstoque
  3. Compras (portaAbastecimento) => conecta em => Catalogo (portaEstoque) via IGestaoEstoque
  4. Vendas (portaIntegracaoPagamento) => conecta em => Pagamentos (portaRecebimentos) via IProcessarPagamento
  5. Compras (portaFinanceiroCompras) => conecta em => Pagamentos (portaDesembolsos) via IEmitirPagamento

Implementação

Dando continuidade, segue a implementação do exemplo em C# que traduz a modelagem UML descrita.
Obs: O
s namespaces dos pacotes não estão de acordo com o padrão do C# e para um caso real deve ser seguido o padrão da linguagem. 
Para representar os conceitos de Portas na orientação a objetos pura (onde não existe uma palavra-chave port), utilizamos classes aninhadas ou propriedades que expõem e encapsulam as interfaces correspondentes, isolando o comportamento interno do componente.
using System;
using System.Collections.Generic;

// ==========================================
// INTERFACES (CONTRATOS)
// ==========================================

public interface IQueryCatalogo
{
    string BuscarProdutoPorId(int id);
    List<string> ListarDisponiveis();
}

public interface IGestaoEstoque
{
    bool BaixarEstoque(int id, int quantidade);
    void ReporEstoque(int id, int quantidade);
}

public interface IProcessarPagamento
{
    bool AutorizarCartao(string tokenCartao, decimal valor);
    string GerarPix(decimal valor);
}

public interface IEmitirPagamento
{
    void AgendarTed(string cnpjFornecedor, decimal valor);
    void PagarBoletoFornecedor(string codigoBarras, decimal valor);
}


// ==========================================
// 1. COMPONENTE: CATALOGO
// ==========================================
namespace com.sistema.catalogo.model
{
    public class Produto { public int Id { get; set; } public string Nome { get; set; } public int QtdEstoque { get; set; } }
}

namespace com.sistema.catalogo.service
{
    using com.sistema.catalogo.model;
    
    // Implementação interna das regras de negócio
    public class CatalogoService : IQueryCatalogo, IGestaoEstoque
    {
        public string BuscarProdutoPorId(int id) => $"Produto {id} Encontrado";
        public List<string> ListarDisponiveis() => new List<string> { "Produto A", "Produto B" };
        public bool BaixarEstoque(int id, int quantidade) { Console.WriteLine($"Estoque baixado em {quantidade} unidades."); return true; }
        public void ReporEstoque(int id, int quantidade) => Console.WriteLine($"Estoque reabastecido em {quantidade} unidades.");
    }
}

namespace com.sistema.catalogo.api
{
    using com.sistema.catalogo.service;

    public class ComponenteCatalogo
    {
        private readonly CatalogoService _service = new CatalogoService();

        // PORTAS COM INTERFACES FORNECIDAS
        public IQueryCatalogo PortaConsulta => _service;
        public IGestaoEstoque PortaEstoque => _service;
    }
}


// ==========================================
// 2. COMPONENTE: VENDAS
// ==========================================
namespace com.sistema.vendas.carrinho
{
    public class CarrinhoItem { public int ProdutoId { get; set; } public int Quantidade { get; set; } }
}

namespace com.sistema.vendas.integration
{
    // Porta de saída agrupada para o Catálogo (Interface Requerida)
    public class PortaIntegracaoCatalogo
    {
        public IQueryCatalogo Query { get; set; }
        public IGestaoEstoque Estoque { get; set; }
    }
}

namespace com.sistema.vendas.checkout
{
    using com.sistema.vendas.integration;

    public class ComponenteVendas
    {
        // PORTAS COM INTERFACES REQUERIDAS (Injetadas via Conexão)
        public PortaIntegracaoCatalogo PortaIntegracaoCatalogo { get; set; } = new PortaIntegracaoCatalogo();
        public IProcessarPagamento PortaIntegracaoPagamento { get; set; }

        public void FecharPedido(int produtoId, int quantidade, decimal valor, string cartao)
        {
            Console.WriteLine("\n--- Iniciando Checkout de Venda ---");
            
            // Usando as Portas/Interfaces Requeridas
            string info = PortaIntegracaoCatalogo.Query.BuscarProdutoPorId(produtoId);
            Console.WriteLine($"Consultando catálogo: {info}");

            if (PortaIntegracaoPagamento.AutorizarCartao(cartao, valor))
            {
                PortaIntegracaoCatalogo.Estoque.BaixarEstoque(produtoId, quantidade);
                Console.WriteLine("Pedido Finalizado com Sucesso!");
            }
        }
    }
}


// ==========================================
// 3. COMPONENTE: COMPRAS
// ==========================================
namespace com.sistema.compras.pedido
{
    public class ComponenteCompras
    {
        // PORTAS COM INTERFACES REQUERIDAS (Injetadas via Conexão)
        public IGestaoEstoque PortaAbastecimento { get; set; }
        public IEmitirPagamento PortaFinanceiroCompras { get; set; }

        public void ReceberMercadoriaFornecedor(int produtoId, int quantidade, string cnpj, decimal valor)
        {
            Console.WriteLine("\n--- Recebendo Compra do Fornecedor ---");
            
            // Usando Interfaces Requeridas
            PortaAbastecimento.ReporEstoque(produtoId, quantity: quantidade);
            PortaFinanceiroCompras.AgendarTed(cnpj, valor);
            
            Console.WriteLine("Processo de reabastecimento e pagamento concluído.");
        }
    }
}


// ==========================================
// 4. COMPONENTE: PAGAMENTOS
// ==========================================
namespace com.sistema.pagamentos.gateways
{
    // Implementação das regras financeiras
    public class MotorFinanceiro : IProcessarPagamento, IEmitirPagamento
    {
        public bool AutorizarCartao(string token, decimal valor) { Console.WriteLine($"Cartão autorizado: R$ {valor}"); return true; }
        public string GerarPix(decimal valor) => "Linha_Digitavel_Pix_Aqui";
        public void AgendarTed(string cnpj, decimal valor) => Console.WriteLine($"TED de R$ {valor} agendado para CNPJ {cnpj}");
        public void PagarBoletoFornecedor(string codigo, decimal valor) => Console.WriteLine($"Boleto de R$ {valor} pago.");
    }
}

namespace com.sistema.pagamentos.core
{
    using com.sistema.pagamentos.gateways;

    public class ComponentePagamentos
    {
        private readonly MotorFinanceiro _motor = new MotorFinanceiro();

        // PORTAS COM INTERFACES FORNECIDAS
        public IProcessarPagamento PortaRecebimentos => _motor;
        public IEmitirPagamento PortaDesembolsos => _motor;
    }
}


// ==========================================
// EXECUÇÃO E FIAÇÃO (BOLA-E-SOQUETE)
// ==========================================
class Program
{
    static void Main()
    {
        // 1. Instanciação dos Componentes isolados
        var catalogo = new com.sistema.catalogo.api.ComponenteCatalogo();
        var pagamentos = new com.sistema.pagamentos.core.ComponentePagamentos();
        var vendas = new com.sistema.vendas.checkout.ComponenteVendas();
        var compras = new com.sistema.compras.pedido.ComponenteCompras();

        // 2. FIACÃO (Conectando as Portas Requeridas nas Fornecidas - O "Lego" do UML)
        
        // Fiação de Vendas
        vendas.PortaIntegracaoCatalogo.Query = catalogo.PortaConsulta;
        vendas.PortaIntegracaoCatalogo.Estoque = catalogo.PortaEstoque;
        vendas.PortaIntegracaoPagamento = pagamentos.PortaRecebimentos;

        // Fiação de Compras
        compras.PortaAbastecimento = catalogo.PortaEstoque;
        compras.PortaFinanceiroCompras = pagamentos.PortaDesembolsos;

        // 3. Executando os fluxos para testar a comunicação limpa
        vendas.FecharPedido(produtoId: 101, quantidade: 2, valor: 150.00m, cartao: "tkn_4567");
        compras.ReceberMercadoriaFornecedor(produtoId: 101, quantidade: 50, cnpj: "12.345.678/0001-99", valor: 2500.00m);
    }
}

O que notar no código:

  1. Namespaces como Pacotes: As divisões de namespace (ex: com.sistema.vendas.checkout) separam fisicamente as pastas internas de cada componente de arquitetura.
  2. Desacoplamento Completo: Note que o ComponenteVendas não conhece a classe CatalogoService ou MotorFinanceiro. Ele conversa estritamente através das propriedades de porta que expõem apenas interfaces (IQueryCatalogo, IProcessarPagamento), respeitando o encapsulamento rígido exigido pelo diagrama de componentes.

Comentários

Postagens mais visitadas deste blog

Quando escolher Flask ou Quart no Python

Atualmente , considerando o suporte assíncrono já presente no Flask, a escolha entre Flask e Quart para uma API Server depende de vários fatores técnicos. Vejamos alguns critérios: Fonte:  https://pt.stackoverflow.com/questions/51268/qual-a-diferen%C3%A7a-entre-comunica%C3%A7%C3%A3o-ass%C3%ADncrona-e-s%C3%ADncrona Suporte Assíncrono : Flask : O Flask oferece suporte assíncrono por meio de extensões de terceiros, como  flask[async] . No entanto, seu desempenho assíncrono é inferior aos frameworks nativamente assíncronos. Quart : O Quart é uma reimplantação do Flask baseada no padrão ASGI (Asynchronous Server Gateway Interface). Ele utiliza E/S assíncrona em todo o código e é mais adequado para bases de código predominantemente assíncronas. Desempenho : Flask : Como aplicação WSGI, o Flask usa um único worker para cada ciclo de solicitação/resposta. Quando uma solicitação chega a uma visualização assíncrona, o Flask inicia um loop de eventos em uma thread, executa a função da vi...

Metodo para criar relacionamento entre tabelas sem dar erro cíclico EFCore

Quando estamos modelando o banco de dados no Entity Framework Core (ou em qualquer outra ferramenta) é muito comum criarmos o erro cíclico. Este erro ocorre devido ao banco de dados não conseguir determinar onde o relacionamento termina, causando um erro de loop. Vejamos uma situação real onde foi gerado esse problema: Um Professor leciona em várias Turmas, Um Professor leciona uma ou mais Disciplinas, Um Professor leciona determinadas Disciplinas em determinadas Turmas, Uma Turma tem muitos Professores, Em todas as Turmas tem 9 Disciplinas. Em todas as turmas podem ter até 9 Professores. Só por essa descrição já é possível perceber que os relacionamentos podem ser complexos de entender. E tem-se também o problema da navegação em via dupla: Professor-Turmas, Turma-Professores. Então, como modelar essas tabelas sem que o banco entre em loop? Para resolver esse problema no Entity Framework Core e evitar o erro de "Multiple Cascade Paths" (caminhos múltiplos de cascata) comum n...