C4, UML ou ArchiMate: qual notação escolher em arquitetura de software
Times de arquitetura costumam discutir qual é a "melhor" forma de modelar: C4, UML ou ArchiMate. A pergunta mais útil é outra: que decisão queremos apoiar e quem precisa entendê-la? As três abordagens resolvem problemas de comunicação diferentes e operam em níveis de abstração diferentes. Escolher uma notação primeiro e forçar todo problema nela é o erro mais comum.
Este artigo compara as três com um exemplo em .NET 10: um modelo pequeno de uma plataforma de pagamentos, escrito em C#, que gera as vistas C4, UML e ArchiMate a partir da mesma fonte (diagramas como código). No fim, uma regra de decisão e a ferramenta que usei. Nos artigos anteriores da série os diagramas eram figuras soltas; aqui a ideia é que o diagrama acompanhe o código e possa ser testado e versionado com ele.

Diferenças entre C4, UML e ArchiMate: níveis e perguntas de cada uma
C4 (criado por Simon Brown) organiza a arquitetura de software como um zoom em quatro níveis: Contexto (o sistema e quem o usa), Containers (aplicações e armazenamentos que executam ou guardam dados), Componentes (as partes internas de um container) e Código (opcional). É forte para explicar fronteiras, microsserviços e implantação a arquitetos, desenvolvedores, DevOps e SRE. Atenção: "container" no C4 não é, necessariamente, um contêiner Docker.
UML é uma linguagem de modelagem ampla, com diagramas de estrutura e de comportamento. Os mais úteis no dia a dia: sequência (interações ao longo do tempo), estados (ciclo de vida de um objeto), atividade (fluxos) e classes (estrutura de domínio). Ela entra quando a pergunta deixa de ser "o que existe?" e passa a ser "como se comporta?".
ArchiMate, padrão do The Open Group, fica acima na pilha de arquitetura. Conecta estratégia, negócio, aplicação e tecnologia (e ainda motivação e migração) com relações como realização, atendimento e atribuição. Serve para análise de impacto, mapa de capacidades, racionalização de portfólio e rastreabilidade do negócio até a tecnologia.
Em PlantUML, uma linha de cada um parece com isto:
' C4: um container e quem o usa
Container(api, "API", ".NET 10", "Recebe pedidos")
Rel(cliente, api, "Usa", "HTTPS")
' UML: uma mensagem em uma sequência
Cliente -> API : 1. Iniciar pagamento
' ArchiMate: uma capacidade realizada por um processo
Strategy_Capability(cap, "Pagamentos")
Rel_Realization_Up(proc, cap, "realiza")A regra de bolso:
- Panorama corporativo, mapa de capacidades, portfólio ou transformação? ArchiMate.
- Contexto, containers, componentes ou implantação de um sistema? C4.
- Interações detalhadas, fluxos, estados ou estrutura de domínio? UML.
Da pergunta à ferramenta: diagramas como código com um modelo único em C
A abordagem é uma sequência de decisões, e a ferramenta vem por último: pergunta de arquitetura, parte interessada, abstração necessária, viewpoint, notação e só então a ferramenta.

Usadas juntas, as três formam uma cadeia de rastreabilidade: estratégia, capacidade, aplicação, sistema, container, componente, interação e implementação. O ArchiMate cobre o começo da cadeia, o C4 o meio, a UML o detalhe de uma interação, e o código é o último elo.

Para o exemplo, o modelo é uma estrutura única em C#. Um elemento tem identificador, nome e tipo; uma relação liga dois identificadores:
namespace Diagramas;
public record Elemento(string Id, string Nome, string Tipo,
string Tecnologia = "", string Descricao = "");
public record Relacao(string De, string Para, string Rotulo,
string Tecnologia = "", string Tipo = "");Cada vista é um gerador que recebe o modelo e devolve texto: PlantUML para C4 e UML, PlantUML e XML do Open Group para ArchiMate. O PlantUML vira imagem com o pacote PlantUml.Net, e o XML abre no Archi.

Qual ferramenta? O que encontrei, com as datas de hoje:
- PlantUML: texto vira diagrama. A versão 1.2026.8 saiu em setembro de 2026, e a biblioteca padrão traz C4 (C4-PlantUML) e ArchiMate; UML é nativa. O wrapper
PlantUml.Netestá na versão 1.4.80 (agosto de 2022), mas é .NET Standard 2.0 e rodou no .NET 10. - Structurizr: modelo C4 único com várias vistas (DSL e Structurizr Lite). A biblioteca .NET
Structurizr.Coreestá na 1.1.1 (fevereiro de 2023), sem atualização recente; para um projeto novo, prefira a DSL. - Archi: editor gratuito de ArchiMate, importa e exporta o Open Exchange File Format; o jArchi permite scripts em JavaScript.
- Mermaid: sequência, classes e C4 (experimental) direto no Markdown; não cobre ArchiMate.
A escolha do exemplo: PlantUML para C4 e UML, mais o formato de troca do Open Group para o ArchiMate abrir no Archi, tudo gerado de um modelo em C#.
Gerando C4, UML e ArchiMate a partir de um modelo em C# (.NET 10)
O exemplo é a Plataforma de Pagamentos de um banco digital. A solução tem o gerador (src/Diagramas) e os testes (tests/Diagramas.Tests).
Passo 1: o modelo. Além de Elemento e Relacao, uma Visao agrupa elementos e relações de um diagrama; Passo e Transicao descrevem sequências e estados:
public record Visao(string Titulo, string Include,
IReadOnlyList<Elemento> Elementos,
IReadOnlyList<Relacao> Relacoes,
string Fronteira = "",
string MacroFronteira = "System_Boundary");
public record Passo(string De, string Para, string Texto,
bool Retorno = false, string Ramo = "");
public record Transicao(
string De, string Para, string Rotulo = "");Passo 2: C4. O gerador traduz cada tipo de elemento para a macro do C4-PlantUML e põe os elementos internos dentro da fronteira (o sistema ou o container em foco):
using System.Text;
namespace Diagramas;
public static class C4Writer
{
static readonly Dictionary<string, string> Macros = new()
{
["pessoa"] = "Person",
["sistema"] = "System",
["externo"] = "System_Ext",
["container"] = "Container",
["banco"] = "ContainerDb",
["fila"] = "ContainerQueue",
["componente"] = "Component",
};
static readonly HashSet<string> Internos =
["container", "banco", "fila", "componente"];
static readonly HashSet<string> Simples =
["pessoa", "sistema", "externo"];
public static string Escrever(Visao v)
{
var sb = new StringBuilder();
sb.AppendLine("@startuml");
sb.AppendLine("skinparam dpi 150");
sb.AppendLine($"!include <C4/{v.Include}>");
sb.AppendLine($"title {v.Titulo}");
var dentro = v.Fronteira.Length > 0;
foreach (var e in v.Elementos)
if (!(dentro && Internos.Contains(e.Tipo)))
sb.AppendLine(Linha(e, ""));
if (dentro)
{
sb.AppendLine(
$"{v.MacroFronteira}(f, {Q(v.Fronteira)}) {{");
foreach (var e in v.Elementos)
if (Internos.Contains(e.Tipo))
sb.AppendLine(Linha(e, " "));
sb.AppendLine("}");
}
foreach (var r in v.Relacoes)
sb.AppendLine(Rel(r));
sb.AppendLine("@enduml");
return sb.ToString();
}
static string Linha(Elemento e, string recuo)
{
string[] args = Simples.Contains(e.Tipo)
? [e.Nome, e.Descricao]
: [e.Nome, e.Tecnologia, e.Descricao];
var lista = string.Join(", ", args.Select(Q));
return $"{recuo}{Macros[e.Tipo]}({e.Id}, {lista})";
}
static string Rel(Relacao r)
{
var tec = r.Tecnologia.Length == 0
? "" : $", {Q(r.Tecnologia)}";
return $"Rel({r.De}, {r.Para}, {Q(r.Rotulo)}{tec})";
}
static string Q(string s) => $"\"{s.Replace('"', '\'')}\"";
}Os dados do exemplo ficam separados dos geradores. A mesma lista de elementos (Cliente, Antifraude, Db) alimenta os três níveis de zoom:
namespace Diagramas;
public static partial class Pagamentos
{
static readonly Elemento Cliente = new("cliente", "Cliente",
"pessoa", Descricao: "Paga pelo app do banco");
static readonly Elemento Antifraude = new("antifraude",
"Antifraude", "externo", Descricao: "Pontua o risco");
static readonly Elemento Parceiro = new("parceiro",
"Banco Parceiro", "externo",
Descricao: "Liquida a transação");
static readonly Elemento Db = new("db",
"Banco de Pagamentos", "banco", "PostgreSQL",
"Pagamentos e estados");
public static Visao Contexto => new(
"Contexto: Plataforma de Pagamentos", "C4_Context",
[
Cliente,
new("pag", "Plataforma de Pagamentos", "sistema",
Descricao: "Autoriza e liquida pagamentos"),
Antifraude,
Parceiro,
],
[
new("cliente", "pag", "Inicia pagamento", "HTTPS"),
new("pag", "antifraude", "Consulta risco", "HTTPS"),
new("pag", "parceiro", "Liquida", "HTTPS"),
]);
public static Visao Containers => new(
"Containers: Plataforma de Pagamentos", "C4_Container",
[
Cliente,
Antifraude,
Parceiro,
new("api", "API de Pagamentos", "container",
".NET 10", "Recebe e valida pedidos"),
new("worker", "Worker de Liquidação", "container",
".NET 10", "Liquida em segundo plano"),
Db,
new("fila", "Fila", "fila", "RabbitMQ",
"Pagamentos autorizados"),
],
[
new("cliente", "api", "Inicia pagamento", "HTTPS"),
new("api", "db", "Grava", "SQL"),
new("api", "antifraude", "Consulta risco", "HTTPS"),
new("api", "fila", "Publica", "AMQP"),
new("worker", "fila", "Consome", "AMQP"),
new("worker", "parceiro", "Liquida", "HTTPS"),
],
"Plataforma de Pagamentos");
public static Visao Componentes => new(
"Componentes: API de Pagamentos", "C4_Component",
[
Cliente,
Antifraude,
Db,
new("endpoints", "PagamentoEndpoints", "componente",
"Minimal API", "Rotas HTTP /pagamentos"),
new("servico", "PagamentoService", "componente",
"C#", "Regras do pagamento"),
new("risco", "AntifraudeClient", "componente",
"HttpClient", "Consulta o antifraude"),
new("repo", "PagamentoRepositorio", "componente",
"EF Core", "Persiste pagamentos"),
],
[
new("cliente", "endpoints", "Chama", "HTTPS"),
new("endpoints", "servico", "Usa"),
new("servico", "risco", "Consulta risco"),
new("servico", "repo", "Grava"),
new("risco", "antifraude", "Chama", "HTTPS"),
new("repo", "db", "Lê e grava", "SQL"),
],
"API de Pagamentos", "Container_Boundary");
}O resultado são os três níveis. Primeiro o contexto: o sistema, quem o usa e o que ele consome.

Depois os containers: a API, o worker, o banco e a fila, com a tecnologia de cada um.

E os componentes da API: endpoints, serviço, cliente do antifraude e repositório.

Passo 3: UML. A sequência mostra o tempo; o ramo alt separa o pagamento aprovado do recusado. Os estados mostram o ciclo de vida do pagamento:
using System.Text;
namespace Diagramas;
public static class UmlWriter
{
public static string Sequencia(
string titulo, string ator, IEnumerable<Passo> passos)
{
var sb = new StringBuilder();
sb.AppendLine("@startuml");
sb.AppendLine("skinparam dpi 150");
sb.AppendLine($"title {titulo}");
sb.AppendLine($"actor {Q(ator)}");
var ramo = "";
foreach (var p in passos)
{
if (p.Ramo != ramo)
{
sb.AppendLine(p.Ramo == "" ? "end"
: ramo == "" ? $"alt {p.Ramo}"
: $"else {p.Ramo}");
ramo = p.Ramo;
}
var seta = p.Retorno ? "-->" : "->";
sb.AppendLine(
$"{Q(p.De)} {seta} {Q(p.Para)} : {p.Texto}");
}
if (ramo != "") sb.AppendLine("end");
sb.AppendLine("@enduml");
return sb.ToString();
}
public static string Estados(
string titulo, IEnumerable<Transicao> transicoes)
{
var sb = new StringBuilder();
sb.AppendLine("@startuml");
sb.AppendLine("skinparam dpi 150");
sb.AppendLine($"title {titulo}");
foreach (var t in transicoes)
{
var rotulo = t.Rotulo.Length > 0
? $" : {t.Rotulo}" : "";
sb.AppendLine(
$"{t.De} --> {t.Para}{rotulo}");
}
sb.AppendLine("@enduml");
return sb.ToString();
}
static string Q(string s) => $"\"{s}\"";
}namespace Diagramas;
public static partial class Pagamentos
{
const string Ok = "risco aceitável";
const string Alto = "risco alto";
public static IReadOnlyList<Passo> Passos =>
[
new("Cliente", "API", "1. Iniciar pagamento"),
new("API", "Antifraude", "2. Consultar risco"),
new("Antifraude", "API", "3. Aprovado", true, Ok),
new("API", "Banco Parceiro", "4. Liquidar", false, Ok),
new("Banco Parceiro", "API", "5. Confirmado", true, Ok),
new("API", "Cliente", "6. Pagamento confirmado",
true, Ok),
new("Antifraude", "API", "3. Recusado", true, Alto),
new("API", "Cliente", "4. Pagamento recusado",
true, Alto),
];
public static IReadOnlyList<Transicao> Estados =>
[
new("[*]", "Criado"),
new("Criado", "Validado", "dados válidos"),
new("Criado", "Recusado", "dados inválidos"),
new("Validado", "Autorizado", Ok),
new("Validado", "Recusado", Alto),
new("Autorizado", "Liquidado", "banco confirmou"),
new("Liquidado", "Estornado", "estorno"),
new("Recusado", "[*]"),
new("Liquidado", "[*]"),
new("Estornado", "[*]"),
];
}

Passo 4: ArchiMate. Aqui o mesmo modelo gera duas saídas. A primeira é o PlantUML, que desenha o diagrama com as cores das camadas (estratégia, negócio, aplicação e tecnologia):
using System.Text;
namespace Diagramas;
public static class ArchiMatePlantUml
{
static readonly Dictionary<string, string> Macros = new()
{
["capacidade"] = "Strategy_Capability",
["processo"] = "Business_Process",
["servico-app"] = "Application_Service",
["app"] = "Application_Component",
["no"] = "Technology_Node",
["software"] = "Technology_SystemSoftware",
};
static readonly Dictionary<string, string> Relacoes = new()
{
["realizacao"] = "Rel_Realization_Up",
["atendimento"] = "Rel_Serving_Up",
["atribuicao"] = "Rel_Assignment_Right",
};
public static string Escrever(Visao v)
{
var sb = new StringBuilder();
sb.AppendLine("@startuml");
sb.AppendLine("skinparam dpi 150");
sb.AppendLine("!include <archimate/Archimate>");
sb.AppendLine($"title {v.Titulo}");
foreach (var e in v.Elementos)
sb.AppendLine(
$"{Macros[e.Tipo]}({e.Id}, \"{e.Nome}\")");
foreach (var r in v.Relacoes)
sb.AppendLine($"{Relacoes[r.Tipo]}"
+ $"({r.De}, {r.Para}, \"{r.Rotulo}\")");
sb.AppendLine("@enduml");
return sb.ToString();
}
}A segunda é o arquivo do Open Exchange Format, que o Archi importa em File > Import > Open Exchange XML Model. Ele traz elementos, relações e uma vista com os nós posicionados:
using System.Xml.Linq;
namespace Diagramas;
public static class ArchiMateExchange
{
const string Url = "http://www.opengroup.org/xsd/archimate/";
static readonly Dictionary<string, string> TiposXml = new()
{
["capacidade"] = "Capability",
["processo"] = "BusinessProcess",
["servico-app"] = "ApplicationService",
["app"] = "ApplicationComponent",
["no"] = "Node",
["software"] = "SystemSoftware",
["realizacao"] = "Realization",
["atendimento"] = "Serving",
["atribuicao"] = "Assignment",
};
public static XDocument Escrever(Visao v)
{
XNamespace ns = Url + "3.0/";
XNamespace xsi =
"http://www.w3.org/2001/XMLSchema-instance";
XNamespace xml = XNamespace.Xml;
XElement Nome(string s) => new(ns + "name",
new XAttribute(xml + "lang", "pt-BR"), s);
XAttribute Tipo(string t) =>
new(xsi + "type", TiposXml[t]);
var elementos = v.Elementos.Select(e => new XElement(
ns + "element",
new XAttribute("identifier", $"id-{e.Id}"),
Tipo(e.Tipo), Nome(e.Nome)));
var relacoes = v.Relacoes.Select((r, i) => new XElement(
ns + "relationship",
new XAttribute("identifier", $"id-rel{i + 1}"),
new XAttribute("source", $"id-{r.De}"),
new XAttribute("target", $"id-{r.Para}"),
Tipo(r.Tipo)));
var nos = v.Elementos.Select((e, i) => new XElement(
ns + "node",
new XAttribute("identifier", $"id-no-{e.Id}"),
new XAttribute("elementRef", $"id-{e.Id}"),
new XAttribute(xsi + "type", "Element"),
new XAttribute("x", 60),
new XAttribute("y", 40 + i * 110),
new XAttribute("w", 180),
new XAttribute("h", 60)));
var ligacoes = v.Relacoes.Select((r, i) => new XElement(
ns + "connection",
new XAttribute("identifier", $"id-lig{i + 1}"),
new XAttribute("relationshipRef", $"id-rel{i + 1}"),
new XAttribute(xsi + "type", "Relationship"),
new XAttribute("source", $"id-no-{r.De}"),
new XAttribute("target", $"id-no-{r.Para}")));
var visao = new XElement(ns + "view",
new XAttribute("identifier", "id-visao"),
Nome(v.Titulo), nos, ligacoes);
var modelo = new XElement(ns + "model",
new XAttribute(XNamespace.Xmlns + "xsi", xsi),
new XAttribute(xsi + "schemaLocation",
$"{ns} {Url}3.1/archimate3_Diagram.xsd"),
new XAttribute("identifier", "id-modelo"),
Nome(v.Titulo),
new XElement(ns + "elements", elementos),
new XElement(ns + "relationships", relacoes),
new XElement(ns + "views",
new XElement(ns + "diagrams", visao)));
return new XDocument(
new XDeclaration("1.0", "utf-8", null), modelo);
}
}namespace Diagramas;
public static partial class Pagamentos
{
public static Visao Arquitetura => new(
"ArchiMate: Capacidade de Pagamentos", "",
[
new("cap", "Pagamentos", "capacidade"),
new("proc", "Processar pagamento", "processo"),
new("asv", "Serviço de Pagamento", "servico-app"),
new("app", "Plataforma de Pagamentos", "app"),
new("no", "Cluster Kubernetes", "no"),
new("rt", ".NET 10", "software"),
],
[
new("proc", "cap", "realiza", Tipo: "realizacao"),
new("asv", "proc", "atende", Tipo: "atendimento"),
new("app", "asv", "realiza", Tipo: "realizacao"),
new("no", "app", "hospeda", Tipo: "atendimento"),
new("rt", "no", "executa em", Tipo: "atribuicao"),
]);
}
Passo 5: rodar e renderizar. O Program.cs grava os .puml e o .xml; com --render, o Renderer pede o PNG ao PlantUML por meio do PlantUml.Net:
using Diagramas;
var renderizar = args.Contains("--render");
var saida = args.FirstOrDefault(a => !a.StartsWith("--"))
?? "saida";
Directory.CreateDirectory(saida);
var diagramas = new Dictionary<string, string>
{
["01-contexto"] =
C4Writer.Escrever(Pagamentos.Contexto),
["02-containers"] =
C4Writer.Escrever(Pagamentos.Containers),
["03-componentes"] =
C4Writer.Escrever(Pagamentos.Componentes),
["04-sequencia"] = UmlWriter.Sequencia(
"Sequência: pagamento aprovado e recusado",
"Cliente", Pagamentos.Passos),
["05-estados"] = UmlWriter.Estados(
"Estados do pagamento", Pagamentos.Estados),
["06-archimate"] =
ArchiMatePlantUml.Escrever(Pagamentos.Arquitetura),
};
foreach (var (nome, puml) in diagramas)
{
await File.WriteAllTextAsync($"{saida}/{nome}.puml", puml);
if (renderizar)
await Renderer.PngAsync(puml, $"{saida}/{nome}.png");
var linhas = puml.Split('\n').Length;
Console.WriteLine($"{nome}: {linhas} linhas");
}
ArchiMateExchange.Escrever(Pagamentos.Arquitetura)
.Save($"{saida}/07-archimate.xml");
Console.WriteLine("07-archimate.xml: importe no Archi");using PlantUml.Net;
namespace Diagramas;
public static class Renderer
{
public static async Task PngAsync(string puml, string arquivo)
{
var fabrica = new RendererFactory();
var renderer =
fabrica.CreateRenderer(new PlantUmlSettings());
var bytes =
await renderer.RenderAsync(puml, OutputFormat.Png);
await File.WriteAllBytesAsync(arquivo, bytes);
}
}dotnet test
dotnet run --project src/Diagramas -- saida --renderA execução gera sete arquivos de texto (seis .puml e um .xml) e seis .png. O arquivo .xml foi validado contra o esquema do Open Group.
Passo 6: testes. Os testes não comparam imagens: checam o texto gerado e a integridade do modelo do ArchiMate:
using Diagramas;
namespace Diagramas.Tests;
public class DiagramasTests
{
[Fact]
public void C4_container_leva_a_tecnologia()
{
var puml = C4Writer.Escrever(Pagamentos.Containers);
Assert.Contains("!include <C4/C4_Container>", puml);
Assert.Contains(
"Container(api, \"API de Pagamentos\", \".NET 10\"",
puml);
Assert.Contains("ContainerDb(db,", puml);
Assert.Contains("System_Boundary(f,", puml);
}
[Fact]
public void C4_contexto_nao_tem_fronteira()
{
var puml = C4Writer.Escrever(Pagamentos.Contexto);
Assert.DoesNotContain("_Boundary", puml);
Assert.Contains("System(pag,", puml);
}
[Fact]
public void C4_componentes_ficam_em_container_boundary()
{
var puml = C4Writer.Escrever(Pagamentos.Componentes);
Assert.Contains("Container_Boundary(f,", puml);
Assert.Contains("Component(servico,", puml);
}
[Fact]
public void Sequencia_separa_os_ramos_com_alt_else_end()
{
var puml = UmlWriter.Sequencia(
"x", "Cliente", Pagamentos.Passos);
Assert.Contains("alt risco aceitável", puml);
Assert.Contains("else risco alto", puml);
Assert.EndsWith("end\n@enduml\n", puml.Replace("\r", ""));
}
[Fact]
public void Estados_comecam_e_terminam_em_estado_final()
{
var puml = UmlWriter.Estados("x", Pagamentos.Estados);
Assert.Contains("[*] --> Criado", puml);
Assert.Contains("Liquidado --> [*]", puml);
}
[Fact]
public void ArchiMate_plantuml_usa_as_macros_da_biblioteca()
{
var v = Pagamentos.Arquitetura;
var puml = ArchiMatePlantUml.Escrever(v);
Assert.Contains("!include <archimate/Archimate>", puml);
Assert.Contains("Strategy_Capability(cap,", puml);
Assert.Contains("Rel_Serving_Up(asv, proc,", puml);
}
[Fact]
public void Exchange_tem_ids_unicos_e_relacoes_validas()
{
var v = Pagamentos.Arquitetura;
var doc = ArchiMateExchange.Escrever(v);
var todos = doc.Descendants().ToList();
var ids = todos
.Select(e => (string?)e.Attribute("identifier"))
.Where(i => i is not null).ToList();
var elementos = todos
.Where(e => e.Name.LocalName == "element")
.Select(e => (string)e.Attribute("identifier")!)
.ToHashSet();
var relacoes = todos
.Where(e => e.Name.LocalName == "relationship");
Assert.Equal(ids.Count, ids.Distinct().Count());
foreach (var r in relacoes)
{
var de = (string)r.Attribute("source")!;
var para = (string)r.Attribute("target")!;
Assert.Contains(de, elementos);
Assert.Contains(para, elementos);
}
}
}Resultado: 7 testes aprovados.
Boas práticas para diagramas como código e testes do modelo
- Comece pela pergunta, não pela notação. Antes de abrir a ferramenta, escreva a decisão que o diagrama apoia e quem vai lê-lo.
- Uma fonte de verdade. Se o nome "Plataforma de Pagamentos" está digitado em três diagramas, um deles vai divergir. No exemplo, o elemento é declarado uma vez e cada gerador o traduz.
- Teste o gerador. O teste de integridade do modelo do ArchiMate pega identificadores duplicados e relações para elementos que não existem:
[Fact]
public void Exchange_tem_ids_unicos_e_relacoes_validas()
{
var v = Pagamentos.Arquitetura;
var doc = ArchiMateExchange.Escrever(v);
var todos = doc.Descendants().ToList();
var ids = todos
.Select(e => (string?)e.Attribute("identifier"))
.Where(i => i is not null).ToList();
var elementos = todos
.Where(e => e.Name.LocalName == "element")
.Select(e => (string)e.Attribute("identifier")!)
.ToHashSet();
var relacoes = todos
.Where(e => e.Name.LocalName == "relationship");
Assert.Equal(ids.Count, ids.Distinct().Count());
foreach (var r in relacoes)
{
var de = (string)r.Attribute("source")!;
var para = (string)r.Attribute("target")!;
Assert.Contains(de, elementos);
Assert.Contains(para, elementos);
}
}- Valide o XML contra o esquema. Para arquivos com vistas, use
archimate3_Diagram.xsd. Oarchimate3_Model.xsdsozinho não conhece o elementoviewse rejeita o arquivo, mesmo estando correto. - Cuidado com a renderização remota. O
PlantUml.Netusa por padrão o servidor público do PlantUML: o texto do diagrama sai da sua rede. Para arquiteturas confidenciais, renderize localmente (exige Java e Graphviz) ou não use o modo remoto. - Um diagrama, um nível, um público. Misturar contexto com detalhe de classe no mesmo desenho não agrada ninguém.
- Não versione a imagem gerada. Versione o modelo e os testes; o PNG se refaz (a pasta
saida/fica no.gitignore). - Revise a data das bibliotecas. O
PlantUml.Nete oStructurizr.Coreestão sem lançamentos recentes; fixe a versão e tenha um plano B (o PlantUML em si e a DSL do Structurizr seguem ativos).
Quando usar C4, UML ou ArchiMate e quando evitar
Use C4 quando precisa explicar fronteiras e partes de um sistema a públicos mistos. Use UML quando a decisão depende de uma interação, de um fluxo ou de um ciclo de vida preciso (como o pagamento recusado após a análise de risco). Use ArchiMate quando a pergunta atravessa negócio e tecnologia: impacto de uma mudança, mapa de capacidades, portfólio de aplicações.
Evite o ArchiMate para documentar um único serviço: o custo de manter o modelo corporativo não se paga. Evite UML completa em times que não vão manter os diagramas; um diagrama de sequência bem escolhido vale mais que dez diagramas desatualizados. Evite o C4 como substituto de comportamento: ele mostra o que existe e como se liga, não a ordem das mensagens.
E evite diagramas como código quando ninguém revisa o texto: um modelo em C# sem testes e sem dono vira outro artefato esquecido. Se o time prefere desenhar em uma ferramenta visual (como o próprio Archi), o resultado também serve; o que importa é a pergunta, não o formato.
Como escolher a vista certa: a pergunta vem antes da notação
C4, UML e ArchiMate não competem: o ArchiMate dá o contexto corporativo, o C4 dá a clareza da arquitetura de software, e a UML dá a precisão de estrutura e comportamento. A cadeia estratégia, capacidade, aplicação, sistema, container, componente, interação e implementação é o que torna as três vistas um conjunto, e não três desenhos soltos.
A reflexão prática: padronize os viewpoints, não uma única notação, e comece sempre pela pergunta. Quando o diagrama nasce de um modelo versionado e testado, ele deixa de ser um desenho que envelhece e passa a ser parte do sistema.
Referências
- C4 model: c4model.com
- PlantUML: Diagrama ArchiMate e site oficial
- C4-PlantUML: plantuml-stdlib/C4-PlantUML
- PlantUml.Net: pacote no NuGet
- The Open Group: ArchiMate Model Exchange File Format (XSD)
- Archi: archimatetool.com
- Structurizr: structurizr.com e Structurizr.Core no NuGet
- Mermaid: Diagramas C4
- Série: Saga Pattern em .NET: Transações Distribuídas entre Microsserviços (ceseidl/saga-pattern-dotnet)
