Modelo conceptual em UML
Classes, associações, multiplicidades e restrições a partir dos requisitos, com o diagrama da Loja de Bilhetes.
O bilhete B1 pertence à Ana e refere a sessão S12 da Noite de Fado. Para guardar esta compra precisamos de distinguir quatro tipos de objetos: Utilizador, Evento, Sessão e Bilhete. O modelo conceptual descreve os seus atributos, associações e restrições em UML, sem escolher ainda tabelas ou tipos PostgreSQL.
Classes e atributos
Uma classe é um tipo de coisa que o sistema regista, com atributos que guardam os seus dados. Na Loja de Bilhetes precisamos de quatro:
- Utilizador: nome, email (único) e credencial de autenticação. O hash é a representação usada na implementação.
- Evento: título, descrição e categoria.
- Sessão: data e hora, preço não negativo e lotação positiva. Cada sessão pertence a um evento.
- Bilhete: código e estado (reservado, pago ou cancelado). Cada bilhete liga um utilizador a uma sessão.
Escolhemos atributos que suportam as interações e as regras do domínio. O email é único porque o registo com email duplicado é uma exceção tratada nos requisitos. O estado do bilhete existe porque a compra tem confirmação e pode ser cancelada. Se não consegues justificar um atributo, revê o seu propósito antes de o incluir.
Associações e multiplicidades
Uma associação liga duas classes, e a multiplicidade diz quantos objetos de um lado se ligam a cada objeto do outro. Lê-se nos dois sentidos.
- Um Evento tem uma ou mais Sessões (
1a1..*). Uma sessão pertence a exatamente um evento. Sem esta associação, a pesquisa de eventos por data não teria onde ir buscar as datas. - Um Utilizador compra zero ou mais Bilhetes (
0..*), e cada bilhete pertence a exatamente um utilizador. - Uma Sessão tem zero ou mais Bilhetes, e cada bilhete refere exatamente uma sessão.
As linhas são associações, e a multiplicidade junto a uma classe conta objetos dessa classe para um objeto do lado oposto; as setas de navegabilidade UML são outra informação, não o sentido de uma chave estrangeira. Por isso, dizer “uma sessão tem muitos bilhetes” não chega, pois também é preciso dizer que “cada bilhete é de uma só sessão”: as duas leituras juntas é que fecham a regra, e é dessa regra que nascem as chaves estrangeiras no esquema relacional.
Resolver o muitos para muitos
Um utilizador compra bilhetes para várias sessões e cada sessão recebe bilhetes de vários utilizadores: entre Utilizador e Sessão há uma relação de muitos para muitos. Uma associação UML pode ter uma classe de associação para guardar dados próprios. Neste domínio precisamos de algo diferente, porque o mesmo par utilizador-sessão pode ter vários bilhetes. O Bilhete é uma entidade associativa que a representa: guarda o código e o estado da compra e liga cada compra a exatamente um utilizador e uma sessão.
Uma ligação com atributos pode ser modelada como classe de associação ou como entidade associativa; decide também a identidade e se podem existir várias ocorrências para o mesmo par. Aqui, os bilhetes B1 e B2 pertencem à mesma pessoa U7 e à mesma sessão S12, mas são compras distintas, como mostra a figura de ocorrências abaixo.
Restrições que o desenho não mostra
O diagrama mostra estrutura, por isso algumas regras precisam de texto. Escreve-as como restrições junto ao modelo:
- O email do utilizador é único em todo o sistema.
- Não se vendem mais bilhetes do que a lotação da sessão.
- Ao criar uma sessão, a sua data tem de estar no futuro; o histórico pode conter sessões passadas.
- Um bilhete cancelado liberta o lugar para nova venda.
Estas frases são o contrato com as fases seguintes: a unicidade do email vira restrição na tabela, a lotação exige um protocolo de concorrência e a data mínima vira validação na aplicação. Um modelo sem restrições escritas empurra estas decisões para o código, onde cada programador do grupo inventa a sua versão.
Generalização e restrições dos subtipos
Se utilizador tem os subtipos comprador e administrador, distingue duas perguntas independentes:
- Disjoint ou overlapping: uma pessoa pode pertencer a ambos?
- Total ou partial: todos os utilizadores pertencem a pelo menos um subtipo?
Não deduzas uma resposta da outra: uma especialização total pode ser overlapping, e uma especialização disjoint pode ser partial. A tradução para tabelas tem de preservar a combinação escolhida ou declarar como a vai verificar; a Ana pertence aos dois subtipos quando a especialização é overlapping, o Bruno pertence só ao subtipo comprador em ambas as políticas, e um utilizador sem qualquer subtipo só é permitido em partial.
Uma composição representa uma relação todo-parte com pertença forte e restrições de ciclo de vida. Não marques composição apenas porque existe uma chave estrangeira. Um bilhete comprado pode ter de permanecer como registo histórico mesmo após desativar a conta. Nesse domínio, apagar automaticamente o registo pode contrariar o requisito.
Uma associação reflexiva, como utilizador segue utilizador, precisa de nomes de papéis: seguidor e seguido. Sem esses papéis, duas chaves para a mesma tabela ficam ambíguas. Se não se pode seguir a si próprio, essa restrição tem de ser escrita.
O que o modelo não garante sozinho
A multiplicidade 1..* de sessões por evento inclui um mínimo de uma sessão, mas uma chave estrangeira NOT NULL da sessão para o evento só garante que cada sessão tem evento; o mínimo na direção inversa precisa de operações coordenadas ou outras verificações. Um diagrama é uma especificação que não executa as regras, por isso confirma cada sentido de cada associação com exemplos válidos e inválidos antes de escolheres as restrições do esquema.
Escrever restrições em frases testáveis e em OCL
Escreve restrições em frases testáveis e, quando a regra é precisa, em OCL. Por exemplo, “um bilhete cancelado não ocupa lugar” traduz-se em context Bilhete inv: self.estado = 'cancelado' implies self.ocupaLugar = false, que se lê assim: para cada bilhete, se o estado é cancelado, então não ocupa lugar. Testa com dois casos: B1 pago ocupa um lugar; B2 cancelado liberta o lugar para nova venda.
Exercícios
Ler os dois sentidos
Cada sessão pertence a um evento e cada evento tem pelo menos uma sessão. Quais são as multiplicidades nas extremidades?
Pista
A etiqueta perto de Evento responde quantos eventos há para uma sessão.
Mais uma pista
O mínimo de sessões também faz parte da regra.
Solução
Evento 1; Sessão 1..*. A localização da etiqueta indica os objetos dessa classe ligados a um objeto da classe oposta.
Confere a tua resposta:
- Indico 1 na extremidade Evento.
- Indico 1..* na extremidade Sessão.
- Leio cada multiplicidade a partir de um objeto do outro lado.
Erros frequentes
Não troques os lados nem apagues o mínimo 1.
Modelar papéis que se sobrepõem
Cada utilizador registado é comprador ou administrador, podendo ter os dois papéis. Classifica a especialização quanto a totalidade e disjunção. Dá uma ocorrência que distingue “pelo menos um” de “exatamente um”. Como muda o modelo se também admitirmos utilizadores sem nenhum destes papéis?
Pista
Testa separadamente existência de subtipo e pertença a vários.
Solução
É total e overlapping. U7 comprador, U8 administrador e U9 em ambos são ocorrências válidas. U9 mostra que totalidade não significa exatamente um subtipo.
Ao admitir U10 sem nenhum dos papéis, a especialização passa a partial, conservando overlapping. Mudou a obrigação de pertencer a algum subtipo; a possibilidade de pertencer aos dois mantém-se.
Confere a tua resposta:
- Escolho total e overlapping.
- Apresento uma pessoa em ambos os subtipos.
- Distingo a mudança de totalidade da de disjunção.
Identidade da compra
Uma pessoa pode comprar três bilhetes para a mesma sessão. O par utilizador-sessão pode ser chave de Bilhete?
Pista
Compara duas compras da mesma pessoa para a mesma sessão.
Mais uma pista
Um bilhete tem uma identidade própria.
Solução
O par não é único. Pode haver um id de bilhete e código único; o par mantém-se como duas referências.
- Não, sob essa regra.. As três compras teriam o mesmo par.
- Sim, toda a ligação N:N usa esse par como chave.. Este domínio admite ocorrências múltiplas por par.
- Só se a sessão tiver lotação 3.. A lotação não altera a unicidade do par.
Erros frequentes
Não transfiras automaticamente a chave de uma associação simples para uma entidade com identidade própria.