# Arquitetura empresarial

Separar operações da aplicação, regras do domínio e persistência, reconhecendo padrões e os seus custos.

Página: https://resumos.rgo.pt/cadeiras/ldts/arquitetura-empresarial/

Terminar a partida 7 exige validar o estado, calcular a pontuação final e guardar o resultado. Trocar Swing por uma API não deve mudar essas regras; trocar ficheiros por SQL não deve mudar a operação. MVC organiza a apresentação. As operações da aplicação, o domínio e a persistência precisam também de contratos próprios.

## Camadas e dependências

Podemos distinguir apresentação, operações da aplicação, domínio e persistência. Uma camada é um grupo de responsabilidades, não necessariamente uma pasta ou um processo diferente.

A apresentação recebe o pedido. Uma operação da aplicação coordena o caso de uso. O domínio valida a mudança. Um adaptador persiste o resultado. Os detalhes de SQL ou ficheiros não precisam de entrar nas entidades do domínio.

No caso terminar a partida 7, com entidade, serviço e repositório, a entidade protege a regra:

```
class Partida {
    enum Estado { EM_CURSO, TERMINADA }
    private Estado estado = Estado.EM_CURSO;
    private int pontos;
    Partida(int pontos) { this.pontos = pontos; }
    void terminar() {
        if (estado != Estado.EM_CURSO) {
            throw new IllegalStateException("partida ja terminada");
        }
        if (pontos < 0) pontos = 0;
        estado = Estado.TERMINADA;
    }
    boolean terminada() { return estado == Estado.TERMINADA; }
    int getPontos() { return pontos; }
}
```

O serviço coordena procura, operação e persistência, distinguindo ausência de falha:

```
class TerminarPartida {
    private final Partidas partidas;
    TerminarPartida(Partidas partidas) { this.partidas = partidas; }
    String terminar(long id) {
        Partida partida = partidas.procurarPorId(id)
            .orElseThrow(() -> new IllegalArgumentException("partida inexistente"));
        partida.terminar();
        partidas.guardar(partida);
        return "partida " + id + ": " + partida.getPontos() + " pontos";
    }
}
```

Compara percursos da mesma operação:

| Procura | Terminar? | Apresentação |
| --- | --- | --- |
| Partida 7 em curso, com 120 pontos | Sim, termina e pede `guardar`. | `partida 7: 120 pontos`, se a escrita terminar. |
| Consulta válida sem partida 7 | Não, não há entidade. | Exceção de ausência. |
| Falha de acesso ao armazenamento | Não, a procura falhou. | Erro de acesso, nunca ausência confirmada. |
| Partida 7 já terminada | Não, a regra rejeita. | Exceção de estado, sem nova escrita. |

A ausência lança `IllegalArgumentException`; a tentativa de terminar de novo lança `IllegalStateException` sem chamar `guardar`, e são falhas distintas de uma consulta que não conseguiu aceder ao armazenamento.

Em memória, `HashMap<Long, Partida>` pode servir de fake; em SQL, o mapper traduz linhas em entidades válidas. Os excertos mostram contratos, não uma implementação completa da persistência, e os erros de acesso precisam de ser traduzidos ou declarados conforme a implementação escolhida.

```
chamadas em execução: Apresentacao -> TerminarPartida -> Partida, Partidas
dependências de código: TerminarPartida --> Partidas; PartidasMemoria --> Partidas; nada no domínio importa SQL
```

Uma Identity Map no âmbito da operação devolve a mesma instância nas duas leituras da partida 7, evitando dois objetos divergentes; não torna as escritas atómicas nem impede outra operação de alterar a mesma linha, pois isso pertence à transação.

Uma falha depois da alteração em memória e antes de guardar exige uma política; não basta dividir o código em camadas para tornar a operação atómica, por isso é preciso definir o que fica confirmado e o que pode ser repetido.

## Transaction Script e Domain Model

**Transaction Script** organiza uma operação como um procedimento que recebe dados, aplica regras e escreve o resultado. Para operações simples e independentes, pode ser claro e direto. Se as mesmas regras surgem em muitos procedimentos, a duplicação torna mudanças mais difíceis.

**Domain Model** coloca estado e comportamento em objetos que representam conceitos do domínio. Uma `Partida` oferece `terminar` e protege o seu estado, em vez de qualquer cliente alterar um campo público. É útil quando as regras e relações precisam de evoluir em conjunto.

A diferença não é usar classes ou funções: um Transaction Script também pode ser um método de uma classe. Pergunta onde ficam as regras e quem protege os invariantes.

Para uma pontuação lida e mostrada sem regras adicionais, um procedimento pequeno pode chegar. Para uma partida com estados, penalizações e condições de encerramento, um modelo com operações próprias pode tornar o contrato mais claro.

## Service Layer

Uma **Service Layer** define as operações da aplicação, como `terminarPartida(id)`. Coordena o trabalho e pode definir a fronteira transacional. É um ponto de entrada para diferentes interfaces, como uma janela e uma API.

Não precisa de absorver toda a lógica do domínio. Se um serviço calcula manualmente tudo através de getters e setters, as entidades podem ter perdido a responsabilidade pelos seus invariantes.

O serviço pode delegar a validação em `Partida`, usar um repositório e devolver um resultado adequado à apresentação. Assim, trocar Swing por outra interface não exige reescrever o caso de uso.

## Active Record e Data Mapper

**Active Record** combina um objeto que representa uma linha com operações de persistência e, por vezes, regras desse registo. Um método `resultado.guardar()` faz o objeto conhecer a forma de guardar. Pode simplificar operações centradas em dados, mas liga o objeto à persistência.

**Data Mapper** coloca a tradução entre objetos e armazenamento num componente separado. O domínio não precisa de conhecer tabelas nem SQL. O mapper lê valores, constrói objetos válidos e escreve a representação correspondente.

Escolhe pela complexidade e pela independência de que o domínio precisa. Criar uma classe chamada `Mapper` que só passa chamadas à entidade não separa a persistência se a entidade continua a emitir SQL.

## Repository

Um **Repository** oferece acesso a objetos do domínio com uma interface semelhante a uma coleção. Pode ter `procurarPorId` e `guardar`. Esconde a tradução entre consultas e objetos, normalmente usando mappers por baixo.

Este excerto mostra um contrato que não expõe SQL:

```
interface Partidas {
    java.util.Optional<Partida> procurarPorId(long id);
    void guardar(Partida partida);
}
```

O tipo `Partida` pertence ao domínio. Uma implementação em memória serve para testes e uma implementação SQL trata o armazenamento. `Optional.empty()` representa ausência; uma falha de ligação não deve fingir que a partida não existe.

Não acrescentes um método genérico que recebe strings SQL ao contrato para contornar a fronteira. Se uma nova operação precisa de encontrar partidas em curso, acrescenta uma consulta com esse significado e decide a sua semântica.

## Unit of Work e Identity Map

Uma **Unit of Work** acompanha objetos novos, alterados e removidos durante uma operação e coordena a escrita dessas mudanças. Ajuda a aplicar um conjunto coerente de alterações. Não substitui as garantias transacionais nem resolve sozinha acessos concorrentes.

Uma **Identity Map** conserva uma instância por identidade dentro de um âmbito. Se carregares duas vezes a partida 7 na mesma operação, evita obter dois objetos independentes que possam divergir. O âmbito deve ser definido, por exemplo uma operação, não um mapa global que cresce sem limite.

Identidade de uma entidade, como o identificador da partida, difere da igualdade de um valor, como duas posições com as mesmas coordenadas. Esta distinção orienta a forma de atualizar e persistir objetos.

## DTO

Um **Data Transfer Object**, DTO, transporta os dados necessários numa fronteira. Pode reunir identificador e pontuação sem permitir que a interface altere diretamente a entidade. O DTO não tem de conter todas as operações do domínio.

## Lazy Load

**Lazy Load** adia o carregamento até os dados serem necessários. Pode evitar trabalho, mas torna um acesso aparentemente simples numa operação de I/O. Se cada linha de uma lista dispara outra consulta, surge o problema de N+1 consultas. A ausência de uma consulta no construtor não significa ausência de custo.

## Seguir um caso de uso

Para terminar a partida 7, a apresentação pede `terminar(7)` ao serviço, o que mostra onde vive cada regra e que padrão guarda cada parte. O serviço obtém a entidade através de `Partidas`, trata a ausência com erro explícito, pede `partida.terminar()`, persiste com `guardar` e devolve o texto com identificador e pontuação mostrado no excerto. Uma API poderia devolver um DTO com esses campos em vez do texto. Nos dois casos, a apresentação não altera os campos internos da entidade.

Testa separadamente a regra de encerramento e a coordenação. Num teste do serviço, uma implementação em memória pode confirmar qual partida foi alterada. Um teste de integração da persistência verifica que o registo realmente sobrevive a uma nova leitura. Uma verificação de chamadas a um mock não prova essa durabilidade.

Cada padrão responde a uma dificuldade diferente. Explica primeiro a regra e a fronteira que precisas, e só depois escolhe o nome do padrão que descreve a solução.

## Exercícios

Quem protege o encerramento?

Terminar uma partida exige que esteja em curso e calcula uma penalização própria do seu estado. Num Domain Model, onde colocarias esta regra e o que deixarias ao serviço?

Pista

Distingue uma regra da partida da coordenação de uma operação.

Mais uma pista

A entidade pode oferecer `terminar()`, em vez de setters para estado e pontuação.

Solução

`Partida.terminar()` valida o estado e aplica a penalização preservando invariantes. O serviço obtém a partida, chama a operação e persiste-a. A apresentação pede o caso de uso e mostra o resultado.

Confere a tua resposta:

*   A entidade Partida protege a transição e calcula a regra.
*   O serviço coordena procura, operação e persistência.
*   A apresentação recebe o resultado sem alterar campos internos.

Erros frequentes

Não coloques toda a regra num serviço que altera livremente campos da entidade.

[Voltar à explicação](https://resumos.rgo.pt/cadeiras/ldts/arquitetura-empresarial/#transaction-script-e-domain-model)

O que prova que foi guardado?

Um teste verifica uma chamada a `repositorio.guardar(partida)` num mock. Qual afirmação fica demonstrada por esse teste?

Pista

O que realmente correu foi o serviço e um substituto.

Mais uma pista

A durabilidade precisa de uma fronteira de armazenamento real.

Solução

O teste prova o pedido do serviço ao repositório. Para provar a persistência, um teste de integração deve guardar no adaptador real e voltar a ler num âmbito novo adequado.

*   **Os dados sobrevivem ao reinício da aplicação..** O mock não escreveu num armazenamento real.
*   **O serviço pediu ao colaborador que guardasse essa partida..** Essa é a interação observada.
*   **O SQL tem a tabela e os parâmetros corretos..** O adaptador SQL não foi exercitado.

Erros frequentes

Uma chamada verificada não demonstra o efeito de I/O de uma implementação que não correu.

[Voltar à explicação](https://resumos.rgo.pt/cadeiras/ldts/arquitetura-empresarial/#seguir-um-caso-de-uso)

Ausência não é falha de acesso

procurarPorId devolve Optional.empty quando não encontra a partida. Uma implementação captura qualquer erro de ligação e devolve também empty. Explica que distinção perdeu.

Pista

Não conseguir consultar é diferente de consultar e não encontrar.

Mais uma pista

A apresentação pode dizer incorretamente que a partida não existe.

Solução

A implementação perdeu a distinção entre ausência de dados e impossibilidade de verificar os dados. Propaga um erro de acesso adequado ou usa um resultado explícito que preserve esse caso. Optional.empty continua a significar ausência após uma consulta válida.

Confere a tua resposta:

*   Distingue um registo inexistente de uma falha de armazenamento.
*   Não apresenta indisponibilidade como ausência confirmada.
*   Propõe erro ou resultado explícito segundo o contrato.

Erros frequentes

Capturar todos os erros e devolver um valor normal pode esconder falhas e provocar decisões incorretas.

[Voltar à explicação](https://resumos.rgo.pt/cadeiras/ldts/arquitetura-empresarial/#repository)

Duas cópias da mesma entidade

Durante uma operação, duas consultas carregam a partida 7 em objetos A e B independentes. A primeira parte da operação altera A; a segunda consulta B e vê o estado antigo. Explica o que uma Identity Map resolve, qual deve ser o seu âmbito e por que não substitui uma transação ou a coordenação com outras operações.

Pista

O identificador é o mesmo, mas as referências podem apontar para objetos diferentes.

Mais uma pista

Conservar uma instância não impede outra operação de alterar a mesma linha na base de dados.

Solução

A Identity Map mantém uma instância por identificador durante a operação. A segunda consulta da partida 7 devolve A, por isso as duas partes observam as alterações desse objeto. Limita o mapa ao âmbito decidido e liberta-o no fim. Isto não torna várias escritas atómicas nem impede outra operação de atualizar a mesma partida. Essas garantias dependem da transação e da política de concorrência. Uma Unit of Work pode coordenar mudanças acompanhadas, mas também não fornece sozinha todas essas garantias.

Confere a tua resposta:

*   Devolve a mesma instância para a mesma identidade dentro da operação.
*   Define o âmbito e evita um mapa global ilimitado.
*   Distingue consistência em memória de atomicidade e concorrência no armazenamento.

Erros frequentes

Não confundas identidade persistente da entidade com igualdade de valores nem uses a cache como prova de dados atuais noutras operações.

[Voltar à explicação](https://resumos.rgo.pt/cadeiras/ldts/arquitetura-empresarial/#unit-of-work-e-identity-map)
