Professor: Gabriel Soares Baptista
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?
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.
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?
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:
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:
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.
Sommerville destaca pelo menos três razões centrais para projetar e documentar a arquitetura de software de forma explícita:
Na prática, a arquitetura ajuda a responder perguntas como:
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:
Essas perguntas deixam claro que arquitetura não é apenas um desenho bonito. Ela é uma resposta técnica e organizacional para as necessidades do sistema.
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.
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?
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:
Além disso, o livro comenta a utilidade de uma visão conceitual.
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.
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:
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.
O padrão Model-View-Controller separa um sistema em três partes lógicas:
Segundo Sommerville, ele é especialmente útil quando:
Vantagens principais:
Limite principal:
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) {}
void atualizarServico(const std::string& novoServico) {
model.servico = novoServico;
}
void renderizar() {
view.mostrar(model);
}
};
Nesse exemplo:
Pet é o modeloPetView é a visãoPetController coordena alteração e exibiçãoEssa separação é a intuição central do MVC.
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:
Vantagens principais:
Limitações principais:
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);
}
};
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:
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:
Casos típicos:
Vantagens principais:
Desvantagens principais:
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.
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:
Esse estilo é muito comum em sistemas distribuídos e em sistemas baseados em Internet.
Vantagens principais:
Desvantagens principais:
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.
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:
Vantagens principais:
Desvantagens principais:
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;
}
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:
| 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 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:
Isso mostra que os estilos arquiteturais devem ser vistos como ferramentas conceituais e não como rótulos rígidos.
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:
Quando essa transição não é feita, o risco é o sistema nascer apenas como um conjunto de classes desconectadas, sem uma estrutura global coerente.
Ao escolher um estilo arquitetural, algumas perguntas práticas ajudam bastante:
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.
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.