Guia Extra: Engenharia de Software e Modelagem UML • Bloco 2: Requisitos e Processos em Modelo


🗺️ Mapa Conceitual do Capítulo

flowchart TD
    DA["Diagrama de Atividades (UML 2.5)\nModelagem Comportamental de Fluxos de Trabalho"] --> NODOS["Nós Estruturais e de Controle"]
    
    NODOS --> NC_INI["Nós Terminais: Inicial (Círculo Sólido) · Final de Atividade (Olho de Boi) · Final de Fluxo (X)"]
    NODOS --> NC_DEC["Nós de Decisão & Merge (Losangos com Guardas Mutuamente Exclusivas)"]
    NODOS --> NC_SYNC["Sincronização Paralela: Fork (Bifurcação) & Join (Barreira de Espera)"]
    NODOS --> NC_OBJ["Nós de Objeto & Pinos de Parâmetros (Fluxo de Dados)"]

    DA --> PART["Partições de Atividade (Swimlanes / Raias de Responsabilidade)"]
    DA --> EXC["Regiões de Atividade Interrompível (Tratamento de Exceções & Cancelamento)"]

    DA --> SEMANTICA["Semântica Formal de Tokens (Redes de Petri)"]

    style DA fill:#ede7f6,stroke:#512da8,stroke-width:2px
    style NODOS fill:#e1f5fe,stroke:#0288d1,stroke-width:2px
    style NC_SYNC fill:#fff3e0,stroke:#ff9800,stroke-width:2px
    style PART fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
    style SEMANTICA fill:#fce4ec,stroke:#e91e63,stroke-width:2px

🔄 1. Propósito e Semântica Formal Baseada em Tokens

O Diagrama de Atividades da UML é a ferramenta padrão para modelagem de aspectos dinâmicos e comportamentais de sistemas de software:

  • Detalhar passo a passo a lógica procedimental de um Caso de Uso complexo (Capítulo 06).
  • Modelar algoritmos concorrentes e fluxos de dados entre componentes de software.
  • Expressar regras de negócio e fluxos de tarefas distribuídas entre múltiplos atores.

Diferente de um simples fluxograma clássico de engenharia, a semântica da UML 2.5 para o Diagrama de Atividades é herdada diretamente da teoria matemática das Redes de Petri: o fluxo é compreendido como a circulação de Tokens (Fichas de Controle e de Dados) através de uma rede de nós.

  1. Um nó de ação só é ativado quando todos os tokens necessários chegam aos seus fluxos de entrada.
  2. Ao concluir a computação da ação, o nó consome as fichas de entrada e emite novas fichas para seus fluxos de saída.

🧱 2. Primitivas Estruturais e Nós de Controle

Elemento Símbolo Visual UML Semântica de Tokens Diferença Crítica e Armadilha Comum
Nó Inicial Círculo preenchido sólido Gera o token primordial no início da atividade. Uma atividade pode ter múltiplos nós iniciais em cenários de disparo concorrente.
Final de Atividade Círculo vazado com círculo sólido dentro (Olho de Boi) Destrói todos os tokens da atividade inteira imediatamente. Se um token atingir este nó, até mesmo ramos paralelos ativos em execução são sumariamente abortados.
Final de Fluxo Círculo com um X central Destrói apenas o token daquele ramo. Usado quando um ramo concorrente termina mas outros ramos devem continuar vivos.
Nó de Ação Retângulo de cantos arredondados Executa uma tarefa elementar atômica. Representa trabalho do sistema ou humano (ex: “Validar Token JWT”).
Decisão Losango com 1 entrada e $N$ saídas Encaminha o token para estritamente uma saída via Guardas [...]. As guardas devem ser mutuamente exclusivas e exaustivas (use [else]).
Merge (Mesclagem) Losango com $N$ entradas e 1 saída Reúne caminhos alternativos sem sincronização. O primeiro token que chega prossegue imediatamente; não há barreira de espera.
Fork (Bifurcação) Barra sólida com 1 entrada e $N$ saídas Duplica o token, disparando $N$ fluxos concorrentes. Inicia paralelismo real (threads ou goroutines).
Join (Junção) Barra sólida com $N$ entradas e 1 saída Barreira de Sincronização: espera tokens de todos os ramos. Se um dos ramos nunca produzir token, o join fica em deadlock permanente.

⚡ 3. Concorrência: A Diferença Vital entre Decisão e Fork

Uma das falhas mais frequentes em provas de certificação e na prática de engenharia é confundir nós de decisão com nós de paralelismo:

graph TD
    subgraph DECISAO["Decisão (Roteamento Exclusivo OR)"]
        D1["Entrada"] --> L1{"Losango"}
        L1 -->|"[saldo >= valor]"| D2["Debitar Conta"]
        L1 -->|"[else]"| D3["Rejeitar Compra"]
    end

    subgraph FORK_JOIN["Fork & Join (Concorrência Real AND)"]
        F1["Entrada"] --> B1["=== BARRA FORK ==="]
        B1 --> F2["Emitir Nota Fiscal"]
        B1 --> F3["Separar Produto no Estoque"]
        F2 --> B2["=== BARRA JOIN ==="]
        F3 --> B2
        B2 --> F4["Despachar Pedido"]
    end
  • No Losango de Decisão: O token de entrada segue apenas um único caminho avaliado pela expressão de guarda booleana.
  • Na Barra de Fork: O token de entrada é clonado em paralelo para todos os caminhos de saída, executando as ações simultaneamente. A barra de Join atua como uma barreira que só libera o fluxo seguinte quando todas as ações concorrentes tiverem finalizado.

🏊 4. Partições de Atividade (Swimlanes) e Fluxo de Objetos

As Partições (Raias) organizam as ações de acordo com o responsável pela sua execução (Departamentos corporativos, Microsserviços ou Camadas arquiteturais):

  • Nó de Objeto (Object Node): Representado por um retângulo tradicional de cantos retos, indica dados físicos ou conceituais que fluem entre ações (ex: Pedido [Criado] $\rightarrow$ Pedido [Faturado]).
  • Pinos de Ação (Pins): Pequenos quadrados nas bordas dos nós de ação que representam os parâmetros de entrada e valores de retorno das funções.

💻 5. Mapeamento Direto para Linguagens de Programação

O Diagrama de Atividades mapeia diretamente para estruturas de controle em código-fonte moderno:

// Exemplo de Mapeamento em Go: Fork + Join com sync.WaitGroup
func ProcessarCompraConcorrente(pedido Pedido) error {
    var wg sync.WaitGroup
    erros := make(chan error, 2)

    wg.Add(2) // Fork: 2 ramos paralelos

    // Ramo 1: Emissão de Nota Fiscal
    go func() {
        defer wg.Done()
        if err := emitirNotaFiscal(pedido); err != nil {
            erros <- err
        }
    }()

    // Ramo 2: Reserva no Almoxarifado
    go func() {
        defer wg.Done()
        if err := reservarEstoque(pedido); err != nil {
            erros <- err
        }
    }()

    wg.Wait() // Join: Barreira de sincronização
    close(erros)

    // Avaliação de Merge e Decisão
    for err := range erros {
        if err != nil {
            return err // Final com Erro
        }
    }

    return despacharPedido(pedido) // Final de Sucesso
}