Equipe de desenvolvedores discutindo arquitetura de software com quadro branco e anotações

Você já participou de uma reunião onde o analista de negócios fala "aprovação do crédito", o desenvolvedor anota "validar score", e o DBA cria uma tabela chamada tbl_check_credit_001? Três pessoas, três linguagens diferentes, falando sobre a mesma coisa.

Depois de 12+ anos trabalhando com arquitetura de software, eu diria que 80% dos bugs que vi nascerem vieram de má comunicação, não de código ruim. E a raiz desse problema? A ausência de uma Linguagem Ubíqua.

Na minha experiência, este é o conceito mais importante - e mais ignorado - do Domain-Driven Design. Você pode esquecer Aggregates, pode pular Event Sourcing, mas se não tiver uma linguagem comum entre negócio e tecnologia, você vai passar anos traduzindo requisitos errados em código "perfeito".

Neste artigo, vou mostrar como estabelecer uma Linguagem Ubíqua de verdade, não aquele glossário morto em um Confluence que ninguém lê.

O Problema: O Jogo do telefone sem fio

Veja esta situação (baseada em cenários reais que participei):

O que o especialista de negócio disse:

"Quando um sinistro é aberto, ele precisa passar por triagem antes de ir para análise. Se for grave, pula direto para análise técnica."


O que o analista documentou:

"Processar solicitação de abertura com validação de prioridade e roteamento."


O que o desenvolvedor implementou:

public async Task<bool> ProcessRequest(RequestDTO dto)
{
    // Validar request
    if (!ValidateRequest(dto))
        return false;    // Checar prioridade
    var priority = CalculatePriority(dto);    // Rotear
    if (priority > 7)
        await SendToTechnicalQueue(dto);
    else
        await SendToStandardQueue(dto);    return true;
}

O que o DBA criou:

CREATE TABLE claim_requests (
    id INT PRIMARY KEY,
    priority_level INT,
    queue_type VARCHAR(50),
    status VARCHAR(20)
);

Percebe o problema? Cada camada usou sua própria linguagem:

  • Negócio: "sinistro", "triagem", "análise técnica"
  • Dev: "request", "priority", "queue"
  • Banco: "claim_requests", "queue_type"

Quando o bug aparecer (e vai aparecer), alguém vai dizer: "Mas eu implementei exatamente o que estava na spec!" E está certo - implementou a spec errada, porque a tradução já estava quebrada.

O que é linguagem ubíqua (de verdade)

Linguagem Ubíqua não é só um glossário bonitinho, é um contrato verbal e escrito. Ela precisa ser usada por todo mundo — desenvolvedores, analistas, PO, QA e até o CEO quando fala do sistema — e aparecer diretamente no código, com classes, métodos e variáveis usando os mesmos nomes que o negócio usa. Essa linguagem deve evoluir junto com o domínio: se o negócio muda um termo, o código muda também. O objetivo final é eliminar qualquer “tradução” mental entre o que é dito na reunião e o que está escrito no código.

Regra de ouro
Se você precisa "traduzir" o que o especialista de negócio disse para código, sua Linguagem Ubíqua está quebrada.

Como construir linguagem ubíqua: Passo a Passo

Vou compartilhar minha abordagem para construir isso em projetos reais.

Passo 1: Sessões de Event Storming

Minha sugestão é começar com Event Storming - uma técnica colaborativa onde você mapeia eventos de domínio com post-its.

Fluxo básico de Event Storming - eventos (laranja), comandos (azul), atores (amarelo), políticas (roxo)

Como funciona na prática:

Reúna as pessoas certas: coloque desenvolvedores junto com os especialistas de negócio. Em seguida, identifique os eventos de domínio, ou seja, as coisas que de fato acontecem no negócio, como “Sinistro Aberto”, “Triagem Realizada” ou “Análise Técnica Iniciada”. Depois, descubra quais comandos geram esses eventos, por exemplo: “Abrir Sinistro”, “Realizar Triagem” e “Iniciar Análise”. Por fim, mapeie os atores envolvidos, isto é, quem executa cada ação: segurado, atendente, analista técnico, e assim por diante.

O valor real: Durante essas sessões, as discordâncias aparecem. Alguém diz "aprovação" e outro diz "homologação" - essa é a hora de alinhar.

Passo 2: Glossário vivo

Não faça um PDF de 50 páginas que ninguém vai ler. Eu prefiro um glossário vivo e acessível:

Glossário de domínio - Sistema de sinistrosTermos CoreSinistro
Definição: Evento coberto pela apólice que gera direito a indenização.
Exemplos: Colisão de veículo, roubo, dano a terceiros
Termo EVITADO: "Claim", "Solicitação", "Request"
No código: `Sinistro.cs`, `SinistroAberto` (evento)Triagem
Definição: Primeira análise do sinistro para determinar gravidade e rota de processamento.
Quem executa: Atendente de primeiro nível ou sistema automatizado
Termo EVITADO: "Validação inicial", "Processamento"
No código: `RealizarTriagem` (comando), `TriagemRealizada` (evento)Análise técnica
Definição: Avaliação aprofundada do sinistro por especialista técnico.
Critério: Sinistros com valor estimado > R$ 50.000 ou com características atípicas
Termo EVITADO: "Análise avançada", "Processamento complexo"
No código: `AnaliseTecnica.cs`, `IniciarAnaliseTecnica` (comando)
Dica: Eu aconselho versionar esse glossário no Git junto com o código. Quando alguém criar um PR mudando Sinistro para Claim, a revisão vai pegar.

Passo 3: Código que fala a linguagem do negócio

Agora vem a parte onde muitos desenvolvedores travam: usar os termos do negócio literalmente no código, mesmo que pareçam "não técnicos".

Refatoração do exemplo anterior:

public class Sinistro : AggregateRoot
{
    public Guid Id { get; private set; }
    public NumeroSinistro Numero { get; private set; }
    public StatusSinistro Status { get; private set; }
    public GravidadeSinistro Gravidade { get; private set; }
    public DateTime DataAbertura { get; private set; }    private Sinistro() { }    // Factory method usando linguagem do negócio
    public static Sinistro Abrir(
        Segurado segurado,
        Apolice apolice,
        DescricaoOcorrencia descricao)
    {
        // Validações de negócio
        if (!apolice.EstaVigente())
            throw new DomainException("Apólice não está vigente");        var sinistro = new Sinistro
        {
            Id = Guid.NewGuid(),
            Numero = NumeroSinistro.Gerar(),
            Status = StatusSinistro.Aberto,
            DataAbertura = DateTime.UtcNow
        };        // Evento de domínio usando linguagem ubíqua
        sinistro.AddDomainEvent(new SinistroAberto(
            sinistro.Id,
            segurado.Id,
            apolice.Id
        ));        return sinistro;
    }    // Método que reflete exatamente o processo de negócio
    public ResultadoTriagem RealizarTriagem(CriteriosTriagem criterios)
    {
        if (Status != StatusSinistro.Aberto)
            throw new DomainException("Triagem só pode ser realizada em sinistros abertos");        // Lógica de gravidade
        Gravidade = criterios.AvaliarGravidade(this);        // Decide rota conforme regra de negócio
        var resultado = Gravidade.EhGrave()
            ? ResultadoTriagem.EncaminharParaAnaliseTecnica()
            : ResultadoTriagem.EncaminharParaAnaliseComum();        Status = StatusSinistro.EmTriagem;        AddDomainEvent(new TriagemRealizada(Id, Gravidade, resultado.Rota));        return resultado;
    }    public void IniciarAnaliseTecnica(AnalistaTecnico analista)
    {
        if (!Gravidade.EhGrave())
            throw new DomainException("Apenas sinistros graves vão para análise técnica");        Status = StatusSinistro.EmAnaliseTecnica;        AddDomainEvent(new AnaliseTecnicaIniciada(Id, analista.Id));
    }
}

Em vez de ter métodos genéricos como ProcessRequest()ValidateRequest() ou CalculatePriority(), você pode nomear as operações de acordo com o que realmente acontece no domínio: Abrir() ou RealizarTriagem()EstaVigente() na apólice, AvaliarGravidade()IniciarAnaliseTecnica() e algo como Gravidade.EhGrave() no lugar de uma condição opaca como priority > 7. Quando você faz isso, se mostrar um método chamado RealizarTriagem() para o especialista de negócio, ele consegue entender o que o código faz sem precisar que alguém traduza de “tecniquês” para a linguagem do dia a dia.

Value Objects para conceitos de negócio

Termos do domínio merecem classes próprias, não tipos primitivos.

public sealed class NumeroSinistro : ValueObject
{
    public string Valor { get; }    private NumeroSinistro(string valor)
    {
        if (string.IsNullOrWhiteSpace(valor))
            throw new DomainException("Número do sinistro é obrigatório");        // Formato: SIN-2025-000001
        if (!Regex.IsMatch(valor, @"^SIN-\d{4}-\d{6}$"))
            throw new DomainException("Formato inválido de número de sinistro");        Valor = valor;
    }    // Factory method que encapsula a regra de geração
    public static NumeroSinistro Gerar()
    {
        var ano = DateTime.Now.Year;
        var sequencial = ObterProximoSequencial(); // busca do banco
        return new NumeroSinistro($"SIN-{ano}-{sequencial:D6}");
    }    public static NumeroSinistro Criar(string valor)
        => new NumeroSinistro(valor);    protected override IEnumerable<object> GetEqualityComponents()
    {
        yield return Valor;
    }
}public sealed class GravidadeSinistro : ValueObject
{
    public static readonly GravidadeSinistro Leve = new(1, "Leve");
    public static readonly GravidadeSinistro Moderada = new(2, "Moderada");
    public static readonly GravidadeSinistro Grave = new(3, "Grave");
    public static readonly GravidadeSinistro Gravissima = new(4, "Gravíssima");    public int Nivel { get; }
    public string Descricao { get; }    private GravidadeSinistro(int nivel, string descricao)
    {
        Nivel = nivel;
        Descricao = descricao;
    }    // Método de negócio com nome claro
    public bool EhGrave() => Nivel >= Grave.Nivel;    // Termo do negócio: "requer análise técnica"
    public bool RequerAnaliseTecnica() => EhGrave();    protected override IEnumerable<object> GetEqualityComponents()
    {
        yield return Nivel;
    }
}

Por que isso importa?

Quando você escreve:

Antes
if (priority > 7) { ... }Depois
if (gravidade.RequerAnaliseTecnica()) { ... }

você não está só trocando nomes — está capturando conhecimento de negócio dentro do modelo. Se amanhã a regra mudar para “gravidade moderada também requer análise técnica”, você ajusta o comportamento em um único lugar, sem precisar caçar comparações numéricas espalhadas pelo código.

Eventos de domínio com linguagem ubíqua

Eventos devem ser nomeados no passado, refletindo fatos que aconteceram no negócio:

public sealed record SinistroAberto(
    Guid SinistroId,
    Guid SeguradoId,
    Guid ApoliceId,
    DateTime DataAbertura
) : IDomainEvent;public sealed record TriagemRealizada(
    Guid SinistroId,
    GravidadeSinistro Gravidade,
    RotaProcessamento Rota
) : IDomainEvent;public sealed record AnaliseTecnicaIniciada(
    Guid SinistroId,
    Guid AnalistaTecnicoId
) : IDomainEvent;

Em vez de eventos genéricos como EntityCreatedValidationCompleted ou ProcessStarted, imagine que seu código registra eventos com nomes como SinistroAbertoTriagemRealizada e AnaliseTecnicaIniciada. Percebe a diferença? Quando você lê TriagemRealizadafica óbvio o que aconteceu no negócio; já com algo como ValidationCompleted, você precisa parar, lembrar o contexto, descobrir “validação de quê?” e em que parte do processo isso se encaixa. Quanto mais o código fala a língua do domínio, menos esforço mental é gasto traduzindo termos técnicos genéricos.

Diagrama: Mapeamento linguagem ubíqua no código

Fluxo mostrando como termos do negócio (verde) mapeiam diretamente para código (azul) e eventos (amarelo)

Armadilhas comuns

1. Usar termos técnicos no lugar de termos de negócio

Nomes genéricos como DataTransferObjectProcessEntity() ou ValidateAndSave() dizem muito pouco sobre o que está acontecendo no domínio e obrigam todo mundo a ficar traduzindo mentalmente. Em vez disso, prefira nomes que expressem claramente o negócio, como uma classe Sinistro, um método RealizarTriagem() ou um AnalisarSinistro() que retorna um ResultadoAnalise. Assim, o código passa a contar a história do domínio em vez de esconder a intenção atrás de termos abstratos e técnicos.

Se o especialista de negócio não reconhece o termo, não use.

2. Glossário desatualizado

Eu já vi glossários dizendo uma coisa enquanto o código fazia outra — e isso é pior do que não ter glossário nenhum. Por isso, minha recomendação é tratar o glossário como parte do código: versionar no Git, exigir sua atualização em PRs que alterem termos de domínio e fazer revisões periódicas (por exemplo, trimestrais) junto com o time de negócio.

3. Traduzir nomes para inglês incorretamente

Uma tradução ruim faz você perder o significado do domínio:

public class ClaimRequest { }      // "Sinistro" virou "Claim Request"?
public class ScreeningProcess { }  // "Triagem" virou "Screening Process"?

Se for usar inglês, o ideal é preservar o conceito do negócio:

public class Claim { }               // Sinistro
public class Triage { }              // Triagem (termo médico em inglês)
public class TechnicalAssessment { } // Análise Técnica

Ou melhor ainda: use português se o negócio falar português. DDD não exige inglês.

// Perfeitamente válido em .NET
public class Sinistro { }
public void RealizarTriagem() { }

4. Múltiplos termos para o mesmo conceito

Se você tem SinistroClaimOcorrência e Solicitação no mesmo código, você não tem linguagem ubíqua.

Regra: Um conceito de negócio = Um termo em TODO o código.

Como validar se você tem linguagem ubíqua

Aqui está meu teste prático:

  1. Leia um método de domínio em voz alta para o especialista de negócio
  2. Ele entende sem você explicar?
  3. Ele usa os mesmos termos quando fala do processo?

Se sim, você tem Linguagem Ubíqua. Se não, volte para o Event Storming.

Checklist de linguagem ubíqua

Use este checklist ao revisar código:

  • [ ] Classes de domínio têm nomes que o negócio reconhece?
  • [ ] Métodos refletem ações de negócio (verbos que o especialista usa)?
  • [ ] Value Objects encapsulam conceitos de negócio?
  • [ ] Eventos estão no passado e refletem fatos do domínio?
  • [ ] Glossário está atualizado com termos do código?
  • [ ] Zero tradução mental entre reunião e código?
  • [ ] Especialista de negócio consegue ler o código (com ajuda mínima)?

Próximos passos

Linguagem Ubíqua é a fundação do DDD. Sem ela, os padrões táticos (Aggregates, Repositories) viram apenas abstração vazia.

Nos próximos artigos, vamos construir sobre essa base:

  1. Building Blocks Táticos: Entities, Value Objects, Aggregates
  2. Bounded Contexts: Quando a linguagem muda entre partes do sistema
  3. Context Mapping: Como contextos diferentes se comunicam
  4. Domain Events: Comunicação assíncrona entre Aggregates

Reflexão final: Na minha experiência, equipes que investem tempo em Event Storming e glossário ubíquo entregam mais rápido no médio/longo prazo. Parece contraintuitivo, mas faz sentido: menos bugs de interpretação, menos retrabalho, menos "mas eu achei que era X".

O código que você escreve hoje vai ser lido (e xingado) por alguém daqui 2 anos. Esse alguém pode ser você mesmo. Faça um favor para o seu eu do futuro: fale a língua do negócio.

Sobre o autor: Felipe Marciano é Tech Lead e Arquiteto de Software com 12+ anos de experiência em .NET, especializado em DDD, microsserviços e migração de sistemas legados.

Conecte-se:

Gostou do conteúdo?
Compartilhe suas experiências nos comentários! Como vocês estabelecem linguagem comum entre devs e negócio?

Compartilhe este artigo

Quer modernizar seu sistema?

Saiba mais sobre como modernizar suas aplicações e escalar seu negócio com tecnologia de ponta.

Fale com um especialista
Felipe Marciano

Sobre o Autor

Felipe Marciano

Felipe Marciano é um desenvolvedor apaixonado por tecnologia, especializado em .NET Core, Angular e soluções cloud-native. Com mais de 12 anos de experiência, dedica-se à modernização de sistemas legados e à arquitetura de microsserviços, sempre priorizando código limpo, boas práticas e soluções realmente escaláveis. Felipe busca inovação constante em novas ferramentas e frameworks para garantir alta qualidade e ótima experiência do usuário em cada projeto que lidera.

Posts Recomendados