Arquitetura e desenho de software
Responsabilidades, estilos, vistas UML, reutilização e decisões de desenho detalhado.
A aplicação tem ecrãs, regras de reserva, armazenamento e uma API externa de horários. A arquitetura define os elementos principais, as suas responsabilidades e relações, e os princípios que orientam a evolução. O desenho detalhado decide como cada elemento cumpre essas responsabilidades.
A questão decisiva é onde confirmar a reserva de Ana: o telemóvel apresenta o pedido, mas o servidor conhece as tentativas de todos os dispositivos.
Requisitos que orientam a arquitetura
Um requisito arquiteturalmente significativo influencia a estrutura ou uma decisão difícil de alterar: suportar vários dispositivos, manter dados coerentes ou funcionar sem rede pode exigir decisões distintas.
Para a aplicação, precisamos de impedir reservas simultâneas sobrepostas. Uma verificação no ecrã não chega, porque outro dispositivo pode reservar depois dessa verificação. Por isso colocamos a decisão final no servidor, que controla os dados e confirma ou recusa numa operação atómica.
A arquitetura não fica automaticamente correta por usar um padrão conhecido. Precisamos de a avaliar nos cenários críticos. Um protótipo vertical que cria uma reserva desde o ecrã até aos dados pode revelar riscos de integração antes de construir todos os ecrãs.
Coesão, acoplamento e ocultação
A coesão descreve a relação entre as responsabilidades de um módulo. Um serviço de reservas que valida intervalos e coordena reservas tem responsabilidades relacionadas. Misturar aí desenho de botões e exportação de imagens dificulta a compreensão.
O acoplamento descreve dependências entre módulos. Não tentamos eliminar todas: a apresentação precisa de obter dados. Tentamos tornar as dependências explícitas e evitar que uma mudança interna obrigue a alterar muitos consumidores.
A ocultação de informação esconde decisões que podem mudar atrás de uma interface. O serviço pede criar uma reserva através de um contrato; não precisa de saber os nomes das tabelas. Trocar o armazenamento pode assim preservar os consumidores.
Um contrato define o que o chamador fornece, o que a operação garante e que falhas podem ocorrer. A interface deve especificar resultados e falhas. “Guardar” sem explicar conflito ou indisponibilidade deixa o chamador sem saber como agir.
Estilos e padrões
| Organização | Ideia | Quando ajuda | Custo ou limite |
|---|---|---|---|
| Camadas | Agrupar responsabilidades por nível de abstração | Separar apresentação, regras e acesso a dados | Dependências mal escolhidas podem criar atalhos e rigidez |
| Cliente-servidor | Clientes pedem serviços a uma autoridade remota | Partilhar dados e controlo entre dispositivos | Rede, disponibilidade e segurança passam a importar |
| MVC | Separar modelo, vista e controlo da interação | Alterar apresentação sem espalhar regras | É preciso definir responsabilidades concretas |
| Pipes and filters | Etapas transformam dados ao longo de um fluxo | Processamento sequencial de dados | Conversão e coordenação entre etapas podem custar |
| Repositório central | Subsistemas partilham um repositório de dados | Integrar ferramentas sobre informação comum | Esquema e disponibilidade criam dependências comuns |
Uma arquitetura pode usar vários estilos. A aplicação pode ser cliente-servidor fisicamente e organizada em camadas em cada lado. Não são alternativas exclusivas.
Numa arquitetura de camadas estrita, uma camada usa a seguinte. Numa versão relaxada, pode usar camadas inferiores não adjacentes. O atalho pode reduzir passos, mas aumenta as dependências que precisamos de justificar.
Um repositório arquitetural partilhado por subsistemas não é exatamente o mesmo conceito que uma classe Repository que encapsula acesso a dados numa aplicação.
Um fluxo de processamento e uma interface interativa
Para importar horários de vários ficheiros, podemos decompor o processamento em ler registos, normalizar datas, rejeitar linhas inválidas e agrupar horários por sala. Cada etapa recebe um formato e produz o formato acordado para a seguinte. Pipes and filters torna visíveis estas transformações e permite testar ou substituir uma etapa sem redesenhar todas.
Não significa que o fluxo seja automaticamente adequado a qualquer parte da aplicação. O ecrã de pesquisa reage a escolhas, apresenta um estado e permite voltar atrás; separar modelo, vista e controlo da interação pode ser mais útil aí. O mesmo sistema pode ter uma interface MVC e um importador pipes and filters, implantados numa organização cliente-servidor.
Antes de ligar os filtros, definimos o contrato entre etapas: formato das datas, fuso horário e tratamento das linhas rejeitadas. Se um filtro produz datas locais e o seguinte as interpreta como UTC, o fluxo pode estar bem desenhado e produzir horários errados. O padrão organiza responsabilidades; os contratos e os cenários dão evidência da correção.
Vistas e UML
Uma vista destaca um conjunto de preocupações. O modelo 4+1 usa cenários para relacionar quatro vistas:
| Vista | Pergunta | Diagramas possíveis |
|---|---|---|
| Lógica | Como repartimos responsabilidades e abstrações? | Classes e pacotes |
| Implementação | Como organizamos unidades de desenvolvimento? | Componentes |
| Processo | Que atividades e processos interagem em execução? | Atividades e interações |
| Implantação | Onde correm os elementos e como comunicam? | Implantação |
| Casos de uso, +1 | Que cenários atravessam e validam estas decisões? | Casos de uso e descrições |
Um pacote agrupa elementos do modelo. Um componente encapsula uma parte modular e expõe interfaces fornecidas e requeridas. Um artefacto é uma peça concreta, como um executável ou um ficheiro de configuração. Um nó é um recurso de execução onde podemos implantar artefactos.
Na aplicação, o pacote Reservas agrupa conceitos e serviços. Um componente API oferece operações de reserva. O artefacto do servidor corre num nó servidor; o artefacto móvel corre no telemóvel. Um pacote não é um computador, e uma classe não precisa de corresponder a um componente implantável.
Leitura da figura. Na vista lógica, a Apresentação depende do Serviço de reservas, que depende do contrato de Acesso a dados. Na vista de implantação, o artefacto móvel corre no nó telemóvel e o artefacto do servidor corre no nó servidor. A interface ReservaAPI expõe criarSeLivre e declara o conflito como resultado possível. O artefacto é o ficheiro instalado. O nó é a máquina que o executa.
Um desenho para a aplicação
Podemos separar:
- A apresentação mostra salas, recolhe escolhas e apresenta estados de espera, sucesso e falha.
- O serviço de reservas aplica regras e coordena a criação.
- O acesso a dados concretiza consultas e alterações através de um contrato.
- Um adaptador isola a API externa de horários.
A interface de dados deve permitir criarSeLivre como operação atómica. Se oferecermos apenas consultar e guardar, o serviço pode não conseguir preservar a regra de concorrência. Uma fronteira mal desenhada precisa de ser revista.
Numa aplicação Flutter com MVVM, Model-View-ViewModel, a View apresenta o estado e passa ações à ViewModel. A ViewModel prepara o estado da interface e coordena operações. Repositories gerem os dados da aplicação, e Services isolam fontes externas. Uma camada de domínio pode ajudar com regras complexas ou partilhadas, mas não é obrigatória em todas as funcionalidades.
Reutilização
Reutilizar pode envolver funções, bibliotecas, componentes, frameworks, serviços ou sistemas completos. Avalia adequação funcional, interfaces, qualidade, licença, suporte, atualizações e custo de integração.
Uma biblioteca oferece operações que a aplicação chama. Um framework organiza parte da execução e chama o código da aplicação nos pontos previstos. Esta mudança de quem controla as chamadas é uma forma de inversão de controlo.
Para autenticação, uma solução mantida pode reduzir erros de implementação. Continuamos responsáveis pela configuração, autorização e tratamento de falhas. As regras específicas das salas também continuam a exigir conhecimento do domínio, mesmo quando usamos componentes existentes.
Desenho detalhado e decisão justificada
O desenho detalhado escolhe classes, operações, algoritmos e estruturas de dados. Deve manter regras coerentes, invariantes e responsabilidades compreensíveis.
A duração válida de uma reserva exige fim posterior ao início e duração dentro do limite. Centralizar essa regra evita que o ecrã e o servidor discordem. A validação da interface melhora o feedback; a validação na autoridade dos dados preserva a regra.
Regista uma decisão com contexto, alternativas, escolha e consequências.
| Campo | Exemplo da reserva |
|---|---|
| Contexto | Dois dispositivos podem pedir o mesmo intervalo |
| Alternativas | Verificar no ecrã; verificar e criar no servidor |
| Escolha | criarSeLivre atómica no servidor |
| Consequências | Regra preservada; ecrã precisa de tratar o conflito |
Podemos limitar uma troca de armazenamento ao Acesso a dados se a nova implementação conservar o contrato: atomicidade, resultados e falhas acordados. Esconder os nomes das tabelas, por si só, não chega.
Se a interface só oferecesse consultar e guardar, seria preciso rever o contrato para permitir a confirmação atómica. O alcance da mudança depende da decisão que falhou; um erro arquitetural não exige sempre reescrever o sistema inteiro.
Exercícios
Distribuir responsabilidades e execução
A apresentação corre no telemóvel. O serviço de reservas e o acesso a dados correm no servidor. Esboça a vista lógica e a vista de implantação, identificando dependências, nós, artefactos e comunicação. Três responsabilidades exigem três máquinas? Justifica.
Pista
Solução
Na vista lógica, Apresentação depende de Serviço de reservas, que usa o contrato de Acesso a dados. Na implantação, o nó telemóvel recebe o artefacto móvel e o nó servidor recebe o artefacto que implementa serviço e acesso a dados. Uma ligação de rede liga os nós. Não são necessárias três máquinas: responsabilidades lógicas distintas podem partilhar o mesmo recurso de execução. A separação lógica permite discutir mudanças e contratos mesmo quando não há uma fronteira de rede.
Confere a tua resposta:
- Representei apresentação, serviço e acesso a dados numa vista lógica.
- Coloquei os artefactos móvel e servidor nos respetivos nós de implantação.
- Representei a comunicação de rede sem confundir módulo com máquina.
- Expliquei como três responsabilidades lógicas podem correr em dois nós.
Erros frequentes
Não confundas pacotes lógicos com máquinas. Não precisas de pôr cada classe como nó.
Um contrato que não protege a regra
O armazenamento só oferece consultarDisponibilidade e guardarReserva. O serviço chama os dois em sequência. Explica o risco e uma alteração ao contrato.
Pista
Dois chamadores podem ler o mesmo estado antes de qualquer gravação.
Mais uma pista
O contrato precisa de expressar a operação que preserva a regra.
Solução
Ambas as tentativas podem ler livre antes de qualquer gravação e depois guardar duas reservas. O contrato deve oferecer criarSeLivre com semântica atómica: devolve reserva criada ou conflito, sem estado intermédio observável. A implementação garante essa semântica nos dados partilhados. Só mudar o nome sem atomicidade mantém a corrida.
Confere a tua resposta:
- Descrevi outra gravação entre consulta e gravação.
- Propus criarSeLivre com verificação e criação atómicas.
- Mantive confirmação na autoridade dos dados.
Erros frequentes
Um nome novo sem garantia atómica não resolve a corrida. Desativar um botão num dispositivo não coordena outros dispositivos.
Importar dados e reagir ao utilizador
Um importador lê ficheiros, normaliza datas, rejeita linhas inválidas e agrupa sessões por sala. A interface permite filtrar sessões e alterar a seleção. Propõe uma organização para cada parte e justifica. A normalização entrega «14:00» no fuso Europe/Lisbon, mas o agrupamento interpreta-o como UTC. Admitindo nesse dia uma diferença de uma hora, que contrato e que caso de integração expõem o erro? Usar o padrão escolhido impede esse defeito?
Solução
Pipes and filters ajusta-se ao importador: cada filtro concretiza uma transformação e entrega um formato acordado ao seguinte. MVC pode separar estado, apresentação e resposta às escolhas na interface. Não são escolhas exclusivas para o sistema inteiro.
O contrato deve indicar se a hora é local ou UTC, incluindo o fuso ou uma representação inequívoca. Neste dia, 14:00 em Lisboa corresponde a 13:00 UTC. Um teste passa uma sessão de referência pela normalização e pelo agrupamento reais e confere o instante final: interpretar 14:00 como UTC desloca-o uma hora. O padrão não escolhe nem verifica este contrato por nós.
Confere a tua resposta:
- Propus filtros para as transformações sucessivas e MVC para a interação.
- Expliquei que os estilos podem coexistir no mesmo sistema.
- Fixei formato e fuso horário no contrato entre etapas.
- Escolhi um caso de integração que revela a interpretação errada de datas.