Dyego Maas - Blog

Consultor em IA Generativa e Arquiteto de Software

Gerando diagramas UML programaticamente com yUML e Python

Gerando diagramas UML programaticamente com yUML e Python

Neste artigo, apresento brevemente a ferramenta yUML e como usá-la em conjunto com scripts Python para gerar um diagrama de caso de uso com base na estrutura de uma aplicação.

7 min de leitura

Neste artigo rápido, pretendo fazer uma apresentação rápida do yUML e mostrar como podemos tirar proveito da ferramenta para gerar diagramas UML da documentação de um projeto. Tudo isso de forma programática, de modo que a documentação seja reflexo da realidade do código.

yUML

O yUML é uma ferramenta de geração de diagramas UML como esboço. Ele não tem a ambição de ser ferramenta última de diagramação, mas de ajudar a criar alguns tipos de diagrama de forma facilitada. É muito útil para geração de diagramas UML-como-rascunho.

A ferramenta está disponível na forma de serviço, ou instalação on-premise. No caso do serviço, no plano gratuito, temos um namespace que permite salvar até 5 diagramas. A título de exemplo, o meu é https://yuml.me/dyegomaas/diagrams.

Mas se você não precisa ter seus diagramas salvos em nuvem, pode gerá-los via API e baixar logo em seguida. É essa abordagem que vamos explorar mais adiante.

Para criar nossos diagramas, usamos DSLs (Domain Specific Languages) especialmente projetadas para descrever esses diagramas. Três classes de diagramas são suportadas:

  • Diagramas de classe
  • Diagramas de casos de uso
  • Diagramas de atividades

Exemplos

Não vou entrar nos detalhes das DSLs, pois é fácil encontrá-los na documentação do yUML, mas vou listar alguns exemplos, tirados do próprio site do yUML.

Diagrama de casos de uso

Um diagrama de casos de uso definido assim:

[Customer]-(Sign In)
[Customer]-(Buy Products)
(Buy Products)>(Browse Products)
(Buy Products)>(Checkout)
(Checkout)<(Add New Credit Card)
(Checkout)
[Office Staff]-(Processs Order)

Gera o seguinte diagrama:

Diagrama de casos de uso

Diagrama de classes

Um diagrama de casos de uso definido assim:

// Cool Class Diagram
// ------------------

// Chain elements like this
[Customer]<>-orders*>[Order]++-0..*>[LineItem]

// Add notes
[Order]-[note: Aggregate Root ala DDD{bg:wheat}]

// Add more detail
[≪IDisposable≫;Customer|+forname: string;+surname: string;-password: string|login(user,pass)]

Gera um diagrama como este:

Diagrama de classes

Diagrama de atividades

Um diagrama de atividades definido assim:

(start)-|a|
|a|->(Grind Coffee)->(Pour Shot)->(Froth Milk)->(Pour Coffee)->|b|
|a|->(Fry Eggs)->(Make Toast)->(Butter Toast)->|b|
|b|-><c>[want another coffee]->(Grind Coffee)
<c>[ready to go]->(end)

Gera este diagrama aqui:

Diagrama de atividades

Gerando um diagrama de casos de uso com yUML

O jeito mais simples para gerar um diagrama de caso de uso, é via requisição REST. Para isso, basta realizar um POST para a URL https://yuml.me/diagram/scruffy/usecase/, cujo corpo siga o seguinte formato:

{
  "dsl_text": "[Usuario]-(ObterFilial),[Usuario]-(PersistirFilial)"
}

Esta requisição vai retornar o nome do arquivo gerado. No meu caso, retornou b55a8893.svg, como pode ser visto na imagem abaixo:

Requisição POST para gerar diagrama
Requisição para gerar um diagrama de casos de uso com visual Scruffy

O código do arquivo gerado é b55a8893, e com ele, podemos fazer download do diagrama gerado com um um GET em https://yuml.me/b55a8893.png. Note que eu mudei a extensão. É possível baixar nos formatos .svg, .png, .jpg, .svg e .pdf, apenas mudando a extensão.

No próximo tópico, apresento um exemplo bem simples onde usei Python para gerar um diagrama de casos de uso.

Gerando diagramas com yUML e Python

Para fins de experimento, vou utilizar aqui um exemplo do meu outro artigo sobre arquitetura gritante, mas as situações em que essa abordagem pode servir são inúmeras.

A seguir, vamos gerar um diagrama de casos de uso, mas seria igualmente simples montar um diagrama de classes lendo os fontes de um projeto.

Neste exemplo específico, tenho um projeto C# chamado Clientes.Application. O primeiro nível de pastas, a exceção do Common, descreve entidades de domínio, e o segundo nível de pastas descreve operações de negócio que afetam ou interagem com essas entidades. Cada operação dessas mapeia um caso de uso.

Uma árvore de diretórios, com as pastas de segundo nível representando casos de uso

O objetivo então é gerar um diagrama que reflita essa estrutura. Para isso, vamos usar um simples script Python.

O processo que o script precisa realizar é o seguinte:

  1. Identificar os diretórios de segundo nível
  2. Monstar a DSL descrevendo cada um deles como Caso de Uso
  3. Requisitar a geração do diagrama
  4. Fazer download da imagem no formato desejado

A primeira etapa, é muito específica desse exemplo, então não vou entrar em detalhes, mas você pode conferir no script completo no final do artigo.

Na segunda etapa, montamos a DSL considerando que cada pasta é um caso de uso:

def build_usecase_dsl_for_directories_in(usecase_directories, user_name) -> str:
  per_folder_uc = [
      f'[{user_name}]-({os.path.basename(directory)})'
      for directory in usecase_directories
  ]
  return ','.join(per_folder_uc)

O resultado dessa operação será algo como [Usuario]-(CasoUso1),[Usuario]-(CasoUso2).... Neste caso, cada par identifica uma relação.

No passo seguinte, podemos fazer uma requisição para o yUML para gerar nosso diagrama:

def request_usecase_diagram(usecase_dsl) -> str:
  r = requests.post(
      f'https://yuml.me/diagram/scruffy/usecase/',
      { "dsl_text": usecase_dsl }
  )
  svg_file_name = r.text
  return svg_file_name

Esta operação vai retornar o nome único do nosso diagrama lá no yUML. O nome do arquivo vem com a extensão .svg por padrão.

Vale notar que no caso dos diagramas de caso de uso e de classes, há três estilos visuais diferentes que podemos optar. O “scruffy” da URL acima é o mais bonitinho, mas também tem o “plain” e o “boring”.

Por fim, podemos fazer download do mesmo diagrama nos formatos .png, .jpg e .pdf.

def download_diagrams_to(target_dir, svg_file_name, desired_extensions):
  if not os.path.exists(target_dir):
      os.mkdir(target_dir)

  for extension in desired_extensions:
      file_name = svg_file_name.replace('.svg', extension)

      diagram_url = f'https://yuml.me/{file_name}' # só isso já identifica o arquivo :)
      print('Downloading file', diagram_url)

      r = requests.get(diagram_url)
      file_path = os.path.join(target_dir, f'usecase{extension}')
      with open(file_path, 'wb') as output:
          output.write(r.content)
          output.flush()
      print('Saved file', file_path)

A seguir, o script na sua versão final. Não é um código bonito, nem foi feito para produção, mas é perfeitamente adequado para ser incorporado num pipeline de CI, que atualizaria o diagrama a cada novo deploy. Se novos recursos forem incluídos na aplicação, o diagrama irá contemplá-los, e pelo menos essa parte da documentação se manterá atualizada.

import os
import requests
import sys
import glob

def get_usecase_directories_in(parent_dir):
  second_level_directories = glob.glob(f'{parent_dir}/*/*')
  second_level_directories = list(filter(lambda f: os.path.isdir(f), second_level_directories))

  usecase_directories = []
  for directory in second_level_directories:
      skip = False
      for directory_to_exclude in ['bin', 'obj', 'Common', 'Properties']:
          if (directory_to_exclude in directory):
              skip = True
              continue
      if skip:
          continue
      
      usecase_directories.append(directory)
  
  return usecase_directories

def build_usecase_dsl_for_directories_in(usecase_directories, user_name) -> str:
  per_folder_uc = [
      f'[{user_name}]-({os.path.basename(directory)})'
      for directory in usecase_directories
  ]
  return ','.join(per_folder_uc)

def request_usecase_diagram(usecase_dsl) -> str:
  print('Requesting diagram generation...')
  r = requests.post(
      f'https://yuml.me/diagram/scruffy/usecase/',
      { "dsl_text": usecase_dsl }
  )
  svg_file_name = r.text
  print(f'Generated diagram: {svg_file_name}')
  return svg_file_name

def download_diagrams_to(target_dir, svg_file_name, desired_extensions):
  if not os.path.exists(target_dir):
      os.mkdir(target_dir)

  for extension in desired_extensions:
      file_name = svg_file_name.replace('.svg', extension)
      diagram_url = f'https://yuml.me/{file_name}'
      
      print(f'Downloading {diagram_url}...')
      r = requests.get(diagram_url)
      
      file_path = os.path.join(target_dir, f'usecase{extension}')
      with open(file_path, 'wb') as output:
          output.write(r.content)
          output.flush()
      print(f'Saved: {file_path}')

def main():
  if len(sys.argv) < 2:
      print('Usage: python generate_usecase_diagram.py <project_path>')
      sys.exit(1)
  
  project_path = sys.argv[1]
  user_name = 'Usuario'
  target_dir = './diagrams'
  extensions = ['.png', '.jpg', '.pdf']
  
  # Busca diretórios de casos de uso
  usecase_dirs = get_usecase_directories_in(project_path)
  
  if not usecase_dirs:
      print('No use case directories found!')
      sys.exit(1)
  
  print(f'Found {len(usecase_dirs)} use case directories')
  
  # Gera DSL
  dsl = build_usecase_dsl_for_directories_in(usecase_dirs, user_name)
  print(f'Generated DSL: {dsl}')
  
  # Gera diagrama
  svg_file = request_usecase_diagram(dsl)
  
  # Baixa em diferentes formatos
  download_diagrams_to(target_dir, svg_file, extensions)
  
  print('Done!')

if __name__ == '__main__':
  main()

Conclusão

O yUML é uma ferramenta interessante para gerar diagramas UML de forma programática. Embora não seja a ferramenta mais robusta do mercado, ela serve muito bem para documentação automatizada e diagramas simples.

A principal vantagem dessa abordagem é que a documentação pode ser mantida sempre atualizada através de automação. Toda vez que a estrutura do projeto mudar, o diagrama pode ser regenerado automaticamente.

Algumas ideias para expandir essa abordagem:

  • Integrar no pipeline de CI/CD
  • Gerar diferentes tipos de diagrama baseados na estrutura do código
  • Combinar com outras ferramentas de análise de código
  • Criar dashboards com múltiplos diagramas

O script apresentado é apenas um exemplo simples, mas demonstra como é possível automatizar a geração de documentação visual de forma eficiente.