Engenharia de Software

Estilos Arquiteturais de Software

Professor: Gabriel Soares Baptista

Por que falar de arquitetura agora?

Na aula anterior, usamos UML para enxergar funcionalidades, entidades, colaborações e fluxo.

Isso ajudou a sair da descrição textual dos requisitos e construir representações mais organizadas do problema.

Mas ainda falta uma pergunta decisiva:

como o software será organizado em partes grandes, com responsabilidades bem separadas, para que possa evoluir sem virar um acúmulo caótico de classes, telas e consultas ao banco?

A pergunta central

Essa é a pergunta da arquitetura de software.

Quando falamos em arquitetura, não estamos discutindo detalhes finos de implementação.

Estamos discutindo a estrutura global do sistema, os grandes componentes, a forma como se relacionam e o tipo de organização que melhor atende aos requisitos funcionais e, principalmente, aos requisitos não funcionais.

Segundo Sommerville, o projeto de arquitetura é o elo crítico entre os requisitos e o projeto detalhado.

É nele que começamos a decidir a forma do sistema como um todo. Isso importa muito porque mudar uma função ou uma classe isolada costuma ser relativamente barato. Refatorar a arquitetura de um sistema grande, quando ele já está em produção, costuma ser caro, arriscado e demorado.

Reflita

Se duas equipes diferentes precisassem implementar o mesmo sistema acadêmico, uma poderia acabar com um software fácil de manter e a outra com um sistema frágil, mesmo que ambas usassem a mesma linguagem, o mesmo banco e o mesmo framework?

Ideia central e definição

Se a sua resposta foi “sim”, então a ideia central da aula já apareceu:

a organização global importa tanto quanto a tecnologia escolhida.

Arquitetura de software é a organização de alto nível de um sistema.

Ela descreve:

  1. quais são os principais componentes ou subsistemas
  2. como esses componentes se relacionam
  3. como dados, controle e responsabilidades circulam entre eles
  4. como essa organização ajuda o sistema a satisfazer seus requisitos

Arquitetura não é enfeite

Sommerville chama atenção para um ponto importante.

A arquitetura não existe apenas para “deixar o sistema bonito”. Ela afeta diretamente características como:

  • desempenho
  • segurança
  • disponibilidade
  • robustez
  • manutenção
  • distribuição do sistema em rede

O que a figura mostra

Os requisitos funcionais dizem o que o sistema deve fazer.

A arquitetura influencia fortemente como bem ele fará isso.

Observe o tipo de visão que aparece na figura do Sommerville.

Ela não detalha métodos, nem atributos, nem código. Ela mostra apenas os grandes blocos e como se comunicam. Esse é o nível de abstração mais útil quando queremos conversar com stakeholders, planejar o sistema ou tomar decisões estruturais.

Por que arquitetura importa?

Sommerville destaca pelo menos três razões centrais para projetar e documentar a arquitetura de software de forma explícita:

  1. Comunicação entre stakeholders
  2. Análise antecipada do sistema
  3. Reúso em larga escala

Comunicação, análise e reúso

  • Comunicação entre stakeholders: a arquitetura oferece uma visão de alto nível que ajuda gerentes, clientes, analistas, arquitetos e desenvolvedores a discutirem o sistema sem se perderem nos detalhes da implementação.
  • Análise antecipada do sistema: muitas consequências importantes de projeto aparecem cedo quando pensamos na arquitetura, especialmente as relacionadas a desempenho, disponibilidade, proteção e manutenção.
  • Reúso em larga escala: sistemas parecidos tendem a compartilhar estruturas arquiteturais parecidas. Isso permite reaproveitar ideias, modelos, padrões e até partes inteiras da organização do software.

Perguntas que aparecem cedo

Na prática, a arquitetura ajuda a responder perguntas como:

  1. como separar interface, regra de negócio e dados?
  2. como distribuir o sistema entre cliente, servidor e banco?
  3. como permitir evolução futura sem quebrar tudo?
  4. como organizar o trabalho de várias equipes?

Decisões de projeto arquitetural

Sommerville trata o projeto de arquitetura menos como uma receita fixa e mais como uma sequência de decisões importantes.

Durante esse processo, o arquiteto precisa lidar com questões como:

  1. existe uma arquitetura genérica que pode servir de base para este tipo de sistema?
  2. o sistema será centralizado ou distribuído?
  3. que estilo arquitetural faz mais sentido?
  4. como o sistema será decomposto em componentes?
  5. como esses componentes vão se comunicar e ser controlados?
  6. como os requisitos não funcionais influenciam essa escolha?
  7. como a arquitetura será documentada?

Essas perguntas deixam claro que arquitetura não é apenas um desenho bonito. Ela é uma resposta técnica e organizacional para as necessidades do sistema.

Requisitos não funcionais e arquitetura

Um dos pontos mais importantes do capítulo 6 é a relação entre arquitetura e requisitos não funcionais.

Sommerville argumenta que a escolha arquitetural deve levar em conta, explicitamente, qualidades como desempenho, proteção, segurança, disponibilidade e manutenção.

  • Desempenho: às vezes vale concentrar operações críticas em poucos componentes ou na mesma máquina para reduzir comunicação.
  • Proteção: uma organização em camadas pode ajudar a isolar partes sensíveis do sistema.
  • Segurança: centralizar operações sensíveis em poucos componentes pode facilitar validação e controle.
  • Disponibilidade: sistemas altamente disponíveis costumam exigir redundância e substituição de componentes sem parada total.
  • Manutenção: componentes menores, autocontidos e com baixo acoplamento facilitam evolução.

Cuidado importante

A arquitetura não deve ser escolhida por gosto pessoal ou moda tecnológica.

Em um projeto profissional, a pergunta correta não é “qual arquitetura está popular?”, mas sim:

qual organização ajuda este sistema a cumprir seus objetivos e restrições?

Visões arquiteturais

Uma única figura raramente é suficiente para representar tudo o que importa na arquitetura de um sistema.

Por isso, Sommerville apresenta a noção de múltiplas visões arquiteturais.

Com base no modelo 4+1, ele destaca quatro visões fundamentais:

  1. Visão lógica
  2. Visão de processo
  3. Visão de desenvolvimento
  4. Visão física

Além disso, o livro comenta a utilidade de uma visão conceitual.

O que cada visão responde

  • Visão lógica: mostra as abstrações principais do sistema, como classes ou objetos centrais.
  • Visão de processo: mostra como o sistema se comporta em execução, em termos de processos e interações.
  • Visão de desenvolvimento: mostra como o software é decomposto para ser implementado por equipes.
  • Visão física: mostra em que hardware ou nós de rede os componentes serão distribuídos.
  • Visão conceitual: ajuda a discutir a essência do sistema e orientar a decomposição inicial.

Se a pergunta for “quais grandes módulos existem?”, uma visão conceitual basta. Se a pergunta for “como isso roda em rede?”, você precisa de uma visão física ou cliente-servidor. Se a pergunta for “como a equipe vai dividir o trabalho?”, a visão de desenvolvimento passa a ser mais útil.

Estilos arquiteturais

Sommerville usa a ideia de padrões ou estilos arquiteturais como formas já experimentadas de organizar sistemas.

Um estilo arquitetural é uma organização recorrente, usada em muitos sistemas diferentes, que carrega vantagens, limitações e contextos típicos de uso.

Nesta aula, vamos estudar cinco estilos relevantes:

  1. MVC
  2. arquitetura em camadas
  3. repositório
  4. cliente-servidor
  5. duto e filtro

Esses estilos não são mutuamente exclusivos. Um mesmo sistema pode combinar mais de um deles. Um sistema Web, por exemplo, pode usar MVC na interface, camadas no backend e cliente-servidor na distribuição lógica.

MVC

O padrão Model-View-Controller separa um sistema em três partes lógicas:

  1. Model representa dados e regras associadas
  2. View representa a forma como os dados são exibidos
  3. Controller coordena a interação do usuário e aciona modelo e visão

Segundo Sommerville, ele é especialmente útil quando:

  1. existem várias formas de apresentar os mesmos dados
  2. o modo de interação pode evoluir com o tempo
  3. queremos alterar a interface sem misturar tudo com a lógica de negócio

Vantagens e limite do MVC

Vantagens principais:

  • separação entre dados, apresentação e controle
  • facilidade de alterar visualizações
  • possibilidade de reaproveitar o mesmo modelo com interfaces diferentes

Limite principal:

  • em sistemas muito simples, ele pode introduzir complexidade desnecessária

MVC no Sommerville

MVC em aplicação Web

Exemplo curto em C++: MVC

class Pet {
public:
    std::string nome;
    std::string servico;
};

class PetView {
public:
    void mostrar(const Pet& pet) {
        std::cout << "Pet: " << pet.nome << "\n";
        std::cout << "Servico: " << pet.servico << "\n";
    }
};

class PetController {
private:
    Pet& model;
    PetView& view;

public:
    PetController(Pet& m, PetView& v) : model(m), view(v) {}

Exemplo curto em C++: MVC

    void atualizarServico(const std::string& novoServico) {
        model.servico = novoServico;
    }

    void renderizar() {
        view.mostrar(model);
    }
};

Nesse exemplo:

  1. Pet é o modelo
  2. PetView é a visão
  3. PetController coordena alteração e exibição

Essa separação é a intuição central do MVC.

Arquitetura em camadas

Na arquitetura em camadas, o sistema é organizado em níveis, onde cada camada usa os serviços da camada imediatamente abaixo e fornece serviços para a camada acima.

Sommerville destaca esse estilo como uma forma de promover separação e independência.

Em um sistema corporativo típico, é comum enxergar algo próximo de:

  1. interface do usuário
  2. gerenciamento da interface
  3. lógica de negócio
  4. apoio de sistema, banco de dados e sistema operacional

Arquitetura em camadas no Sommerville

Vantagens e limitações das camadas

Vantagens principais:

  • melhor organização estrutural
  • substituição mais simples de uma camada, desde que a interface seja mantida
  • apoio a manutenção e evolução localizadas
  • boa aderência a cenários com preocupação de proteção multinível

Limitações principais:

  • separar perfeitamente as camadas pode ser difícil
  • a camada superior às vezes quer acessar detalhes da inferior
  • múltiplas camadas podem adicionar overhead e afetar desempenho

Exemplo curto em C++: camadas

class AgendamentoRepository {
public:
    void salvar(const std::string& pet, const std::string& horario) {
        std::cout << "Salvando agendamento de " << pet
                  << " em " << horario << "\n";
    }
};

class AgendamentoService {
private:
    AgendamentoRepository& repository;

public:
    AgendamentoService(AgendamentoRepository& repo) : repository(repo) {}

    void agendar(const std::string& pet, const std::string& horario) {
        repository.salvar(pet, horario);
    }
};

Exemplo curto em C++: camadas

class AgendamentoScreen {
private:
    AgendamentoService& service;

public:
    AgendamentoScreen(AgendamentoService& srv) : service(srv) {}

    void confirmar() {
        service.agendar("Rex", "15:00");
    }
};

Aqui aparece claramente o fluxo em camadas:

  1. a tela recebe a ação do usuário
  2. a camada de serviço aplica a lógica da aplicação
  3. o repositório lida com persistência

Repositório

Na arquitetura de repositório, todos os dados importantes do sistema são mantidos em um repositório central compartilhado.

Os componentes não interagem diretamente entre si. Em vez disso, leem e escrevem nesse repositório comum.

Sommerville observa que esse estilo é apropriado quando:

  1. grandes volumes de dados precisam ser compartilhados
  2. esses dados precisam ser armazenados por longo tempo
  3. diferentes ferramentas ou componentes usam os mesmos dados

Casos típicos:

  • IDEs
  • sistemas CAD
  • sistemas de informação
  • ambientes dirigidos a modelos

Repositório no Sommerville

Vantagens e limites do repositório

Vantagens principais:

  • compartilhamento consistente de dados
  • independência relativa entre componentes
  • centralização facilita backup, controle e consistência

Desvantagens principais:

  • o repositório pode virar ponto único de falha
  • pode haver gargalos de desempenho
  • distribuir o repositório por várias máquinas pode ser difícil
  • novos componentes precisam se adaptar ao modelo de dados já definido

Exemplo curto em C++: repositório

class ProjectRepository {
public:
    void salvarModelo(const std::string& nome) {
        std::cout << "Modelo salvo: " << nome << "\n";
    }

    void gerarRelatorio() {
        std::cout << "Relatorio gerado a partir do repositorio central\n";
    }
};

class UmlEditor {
public:
    void exportar(ProjectRepository& repo) {
        repo.salvarModelo("Diagrama de Classes");
    }
};

class ReportGenerator {
public:
    void executar(ProjectRepository& repo) {
        repo.gerarRelatorio();
    }
};

Nesse caso, UmlEditor e ReportGenerator não precisam conversar diretamente. Ambos operam em torno do mesmo repositório central.

Cliente-servidor

Na arquitetura cliente-servidor, o sistema é organizado como um conjunto de serviços oferecidos por servidores e utilizados por clientes.

Sommerville descreve três elementos centrais:

  1. servidores que oferecem serviços
  2. clientes que os utilizam
  3. uma rede que permite o acesso a esses serviços

Esse estilo é muito comum em sistemas distribuídos e em sistemas baseados em Internet.

Cliente-servidor no Sommerville

Vantagens e limites de cliente-servidor

Vantagens principais:

  • distribuição natural de serviços
  • possibilidade de centralizar funcionalidades comuns
  • facilidade relativa de adicionar ou atualizar servidores

Desvantagens principais:

  • cada servidor pode virar um ponto de falha
  • o desempenho depende da rede
  • ataques de negação de serviço e indisponibilidade de servidor tornam-se relevantes
  • a gestão pode ser difícil quando há vários servidores ou organizações envolvidas

Exemplo curto em C++: cliente-servidor

class CatalogServer {
public:
    std::string buscarFilme(int id) {
        return "Filme #" + std::to_string(id);
    }
};

class CatalogClient {
private:
    CatalogServer& server;

public:
    CatalogClient(CatalogServer& srv) : server(srv) {}

    void mostrarFilme(int id) {
        std::cout << server.buscarFilme(id) << "\n";
    }
};

Aqui não implementamos rede real, mas a separação lógica já aparece: o cliente solicita um serviço e o servidor o fornece.

Duto e filtro

No estilo duto e filtro, o sistema é organizado como uma sequência de transformações.

Cada componente processa dados de entrada, gera uma saída e a repassa para o próximo estágio.

Esse estilo é muito apropriado quando o problema pode ser naturalmente decomposto em etapas de processamento de dados.

Sommerville cita aplicações típicas como:

  1. processamento em lote
  2. faturamento
  3. compiladores
  4. vários tipos de processamento de linguagem

Duto e filtro no Sommerville

Vantagens e limites de duto e filtro

Vantagens principais:

  • reuso de transformações
  • clareza no pipeline de processamento
  • evolução por adição de novos filtros
  • possibilidade de implementação sequencial ou concorrente

Desvantagens principais:

  • necessidade de formatos de dados compatíveis entre etapas
  • overhead de parsing e conversão entre estágios
  • pouca adequação para sistemas fortemente interativos com GUI complexa

Exemplo curto em C++: duto e filtro

std::vector<std::string> lerPedidos() {
    return {"pedido1:pago", "pedido2:pendente", "pedido3:pago"};
}

std::vector<std::string> filtrarPagos(const std::vector<std::string>& pedidos) {
    std::vector<std::string> pagos;
    for (const auto& pedido : pedidos) {
        if (pedido.find("pago") != std::string::npos) {
            pagos.push_back(pedido);
        }
    }
    return pagos;
}

Exemplo curto em C++: duto e filtro

void gerarRecibos(const std::vector<std::string>& pagos) {
    for (const auto& pedido : pagos) {
        std::cout << "Recibo emitido para " << pedido << "\n";
    }
}

Esse pipeline é pequeno, mas mostra exatamente a ideia:

  1. uma etapa lê
  2. outra filtra
  3. outra transforma o resultado em saída útil

Comparando os estilos

Estilo Melhor quando Vantagem forte Risco forte
MVC há múltiplas visualizações e interação rica separa apresentação de dados pode complicar sistemas simples
Camadas queremos organização clara por níveis manutenção e separação de responsabilidades fronteiras entre camadas podem vazar
Repositório dados compartilhados são o centro do sistema consistência e compartilhamento ponto único de falha
Cliente-servidor há serviços distribuídos para vários clientes distribuição natural de funcionalidades dependência de rede e servidor
Duto e filtro o problema é naturalmente um pipeline de transformação composição clara e reuso de etapas ruim para forte interatividade

Um mesmo sistema pode combinar estilos

Um erro comum é imaginar que um sistema precisa “ser” apenas MVC ou apenas cliente-servidor ou apenas em camadas.

Em projetos reais, isso quase nunca é verdade.

Considere um sistema acadêmico Web:

  1. ele pode usar MVC na interface e na lógica de interação com o usuário
  2. pode usar uma arquitetura em camadas no backend
  3. sua implantação lógica pode seguir um modelo cliente-servidor
  4. pode usar um repositório central de dados no banco
  5. pode ainda ter um subsistema de importação de notas com estilo duto e filtro

Isso mostra que os estilos arquiteturais devem ser vistos como ferramentas conceituais e não como rótulos rígidos.

Conectando com a aula de UML

Na aula anterior, usamos UML para enxergar diferentes perspectivas de um sistema.

Agora, a arquitetura nos ajuda a dar um passo acima: em vez de pensar apenas em classes, casos de uso ou sequências, passamos a pensar em subsistemas, responsabilidades globais, distribuição e estratégias de organização.

Essa ligação é importante:

  1. requisitos ajudam a entender o problema
  2. UML ajuda a modelar partes e relações
  3. arquitetura ajuda a organizar a solução em larga escala

Quando essa transição não é feita, o risco é o sistema nascer apenas como um conjunto de classes desconectadas, sem uma estrutura global coerente.

Escolhendo um estilo na prática

Ao escolher um estilo arquitetural, algumas perguntas práticas ajudam bastante:

  1. os dados precisam ser compartilhados por vários componentes?
  2. o sistema é fortemente interativo com interface rica?
  3. o problema parece uma sequência de transformações de dados?
  4. o sistema será distribuído em vários computadores?
  5. há forte preocupação com manutenção, segurança ou disponibilidade?

Framework não é arquitetura completa. Dizer que um sistema usa Spring, Qt, React, Django ou ASP.NET não basta para descrever sua arquitetura. No máximo, isso indica parte da pilha tecnológica e, às vezes, algum estilo favorecido por essa pilha.

Próximos passos

Depois de entender os estilos arquiteturais, o próximo passo natural é aprofundar como essas decisões estruturais se relacionam com qualidades como manutenção, proteção, distribuição e evolução do software ao longo do tempo.