Clean Architecture aplicada a ADVPL/TLPP
Destilação prática dos princípios do livro Clean Architecture para o ecossistema Protheus.
O objetivo não é academicismo: é que a regra de negócio do cliente sobreviva a troca de
tela, de banco, de release e de dev — e seja testável por unidade, sem subir tela nem
depender de massa de dados.
Skills irmãs: /protheus:ddd (modelagem do domínio — o que construir),
esta skill (estrutura — como organizar), /protheus:migrate (procedural → TLPP OO) e
/protheus:refactor-method-complexity-reduce (extração de métodos em função complexa).
A Regra de Dependência (o coração do livro)
Dependências de código apontam sempre para dentro — da infraestrutura para o domínio,
nunca o contrário. A regra de negócio não conhece tela, endpoint, tabela nem framework.
[ REST @Get/@Post · ModelDef/ViewDef · Ponto de Entrada · Schedule ] ← entrada/saída
↓ chama
[ Caso de uso: orquestra, valida entrada, controla transação ] ← aplicação
↓ chama ↓ chama
[ Regra de negócio pura ] [ Repositório / ExecAuto wrapper ] ← domínio · persistência
(sem SQL, sem tela) (todo SQL/RecLock vive AQUI)
Teste rápido de violação: se a função tem BeginSQL/RecLock E um cálculo de negócio
E monta JSON/tela, ela está em três camadas ao mesmo tempo — dividir.
Mapa de camadas → artefatos Protheus
| Camada (livro) |
No Protheus |
Regra |
| Entidades / regras de negócio |
Classes TLPP ou Static Functions puras: cálculo, validação, decisão |
Zero SQL, zero tela, zero RecLock. Recebe dados, devolve resultado. Filial/parâmetros (SuperGetMV) entram por argumento, não são lidos dentro |
| Casos de uso |
Uma classe/função de aplicação por operação (IncluirPedido, AprovarDesconto) |
Orquestra: valida entrada → chama regra → chama repositório/ExecAuto → devolve resultado tipado. É a única camada que controla transação (BeginTran) |
| Adaptadores de interface |
Endpoint REST TLPP, ModelDef/ViewDef, Ponto de Entrada, job/Schedule |
Só traduz: parseia entrada, chama o caso de uso, formata a resposta. PE nunca contém a regra — delega para função externa (regra que o plugin já exige) |
| Frameworks & drivers |
Dicionário SX*, FWFormModel, ExecAuto, DBAccess, BeginSQL |
Acessados somente via repositórios/wrappers. ExecAuto é a "porta oficial" de escrita em tabela padrão — nunca RecLock direto em SA1/SC5/etc. |
SOLID em TLPP — resumo operacional
- SRP — um fonte/classe/função = um motivo para mudar. Função > ~60 linhas ou que mistura
camadas → extrair (a skill
/protheus:refactor-method-complexity-reduce automatiza a
extração). Detalhes e exemplos: references/solid-tlpp.md.
- OCP — variações de comportamento por classe/bloco injetado, não por
Do Case de tipo
espalhado em N funções.
- LSP — subclasse TLPP honra o contrato da base (mesmos pré/pós-requisitos; não "lança erro
se for do tipo X").
- ISP — interfaces TLPP pequenas por papel (
ICalculaFrete), não uma IUtils gorda.
- DIP — caso de uso depende de interface de repositório; a implementação concreta
(BeginSQL/ExecAuto) é injetada no construtor. É isso que permite testar a regra com dublê.
Organização de fontes (screaming architecture)
O diretório grita o domínio, não a tecnologia: agrupe por assunto de negócio
(faturamento/, estoque/), não por tipo (apis/, queries/, telas/). Dentro de cada
assunto, os sufixos de camada dizem o papel: *Service (caso de uso), *Repo (persistência),
regra pura sem sufixo. A nomenclatura de arquivo segue a convenção do projeto; a organização
é por pasta e namespace TLPP (namespace cliente.faturamento).
Quando aplicar (pragmatismo)
| Tamanho da mudança |
Estrutura mínima |
Orçamento de fontes (fora os de teste) |
| PE trivial, ajuste de 1 função |
Só a regra de sempre: PE delega para User Function |
1 |
| Rotina nova média |
Separar ao menos regra pura × acesso a dados (2 funções/classes) — o suficiente para testar a regra por unidade, sem banco |
até 3 |
| Desenvolvimento grande (módulo, integração) |
Camadas completas: adaptador → caso de uso → domínio + repositórios, 1 namespace por contexto |
1 fonte por camada por contexto; acima disso, justifique fonte a fonte no plano |
Sobre-engenharia também é dívida: não crie interface + repositório + service para
encapsular um Posicione(). O critério é: a regra de negócio merece viver isolada e testada.
Camada ≠ arquivo (o SOLID não é para virar enxurrada de fontes)
Um fonte gigante é problema; vinte fontes minúsculos também são — cada .tlpp entra no
patch, precisa compilar, versionar e ser conferido na documentação de customizações. Separe
por motivo de mudança, não por contagem de classes.
- TLPP aceita mais de uma classe no mesmo fonte (verificado em código compilado em
produção: adapter + service convivendo no mesmo arquivo, sob o mesmo
namespace). Classes
coesas — mesmo papel, mesmo contexto, que mudam pelo mesmo motivo — ficam juntas.
- Piso: classe pequena que existe só "para ficar SOLID" volta para o fonte do seu papel.
Interface com uma implementação só: só crie se o teste precisa do dublê.
- Teto: fonte passando de ~800 linhas, ou classe de ~500, é sinal de divisão real.
- A lista de fontes é fechada no
/protheus:plan. Fonte que não está no plano não é
criado na implementação — se apareceu a necessidade, ela é decisão de design e volta ao
/protheus:brainstorm.
Os números são ponto de partida calibrado em desenvolvimento real. Ajuste no seu projeto se
o padrão local for outro — mas mantenha um teto declarado, não "o quanto sair".
Fluxo de uso
- No design (
/protheus:brainstorm / /protheus:plan / /protheus:advpl-tlpp-sdd):
para cada caso de uso da especificação, defina os artefatos por camada (tabela acima)
antes de codar. Liste que regra é pura (testável por unidade) e que acesso a dados vira
repositório/ExecAuto.
- Na implementação (
/protheus:writer / /protheus:implement): siga
references/regra-dependencia-camadas.md (esqueleto de cada camada com código) e valide
contra os sinais de violação.
- No review (
/protheus:reviewer / /protheus:code-review): aplique o checklist
estrutural de references/refatoracao-exemplo.md — o exemplo before/after mostra o
monólito típico (endpoint que faz tudo) e a versão em camadas.
- Testes: a recompensa da separação — regra pura testada por unidade (ex.: PROBAT, o
framework de testes do tlppCore) sem fixture de banco; o E2E de tela fica com
/protheus:tir-test-generator.
Regras inegociáveis
- Regra de negócio nunca contém
BeginSQL, RecLock, MsMessage ou montagem de JSON.
- Endpoint REST / PE / ViewDef nunca acessa tabela diretamente — sempre via caso de uso.
- Escrita em tabela padrão TOTVS = ExecAuto (encapsulado em repositório), nunca
RecLock
direto (pula validações do dicionário).
- Transação (
BeginTran/EndTran) pertence ao caso de uso — nem à regra, nem ao repositório.
- Todo caso de uso novo nasce com teste unitário da(s) regra(s) pura(s) que orquestra.
1---2name: clean-architecture-23description: Aplica os princípios de Clean Architecture (livro de Robert Martin, o Uncle Bob) ao desenvolvimento ADVPL/TLPP — regra de dependência, separação em camadas (domínio, caso de uso, adaptador, framework Protheus), SOLID em TLPP OO e organização de fontes por domínio. Use quando o dev pedir para "organizar em camadas", "desacoplar regra de negócio", "aplicar clean architecture", "aplicar SOLID", "onde colocar essa regra", "separar SQL da regra", "estruturar um desenvolvimento grande" ou quando o brainstorm/plan/reviewer precisar de critério estrutural de design.4---56# Clean Architecture aplicada a ADVPL/TLPP78Destilação prática dos princípios do livro *Clean Architecture* para o ecossistema Protheus.9O objetivo não é academicismo: é que a **regra de negócio do cliente sobreviva** a troca de10tela, de banco, de release e de dev — e seja **testável por unidade, sem subir tela nem11depender de massa de dados**.1213> Skills irmãs: `/protheus:ddd` (modelagem do domínio — *o que* construir),14> esta skill (estrutura — *como organizar*), `/protheus:migrate` (procedural → TLPP OO) e15> `/protheus:refactor-method-complexity-reduce` (extração de métodos em função complexa).1617## A Regra de Dependência (o coração do livro)1819**Dependências de código apontam sempre para dentro — da infraestrutura para o domínio,20nunca o contrário.** A regra de negócio não conhece tela, endpoint, tabela nem framework.2122```23[ REST @Get/@Post · ModelDef/ViewDef · Ponto de Entrada · Schedule ] ← entrada/saída24 ↓ chama25[ Caso de uso: orquestra, valida entrada, controla transação ] ← aplicação26 ↓ chama ↓ chama27[ Regra de negócio pura ] [ Repositório / ExecAuto wrapper ] ← domínio · persistência28 (sem SQL, sem tela) (todo SQL/RecLock vive AQUI)29```3031Teste rápido de violação: **se a função tem `BeginSQL`/`RecLock` E um cálculo de negócio32E monta JSON/tela, ela está em três camadas ao mesmo tempo** — dividir.3334## Mapa de camadas → artefatos Protheus3536| Camada (livro) | No Protheus | Regra |37|---|---|---|38| Entidades / regras de negócio | Classes TLPP ou Static Functions **puras**: cálculo, validação, decisão | Zero SQL, zero tela, zero `RecLock`. Recebe dados, devolve resultado. Filial/parâmetros (`SuperGetMV`) entram **por argumento**, não são lidos dentro |39| Casos de uso | Uma classe/função de aplicação por operação (`IncluirPedido`, `AprovarDesconto`) | Orquestra: valida entrada → chama regra → chama repositório/ExecAuto → devolve resultado tipado. É a **única** camada que controla transação (`BeginTran`) |40| Adaptadores de interface | Endpoint REST TLPP, `ModelDef`/`ViewDef`, Ponto de Entrada, job/Schedule | Só traduz: parseia entrada, chama o caso de uso, formata a resposta. **PE nunca contém a regra** — delega para função externa (regra que o plugin já exige) |41| Frameworks & drivers | Dicionário SX*, FWFormModel, ExecAuto, DBAccess, BeginSQL | Acessados somente via repositórios/wrappers. `ExecAuto` é a "porta oficial" de escrita em tabela padrão — nunca `RecLock` direto em SA1/SC5/etc. |4243## SOLID em TLPP — resumo operacional4445- **S**RP — um fonte/classe/função = um motivo para mudar. Função > ~60 linhas ou que mistura46 camadas → extrair (a skill `/protheus:refactor-method-complexity-reduce` automatiza a47 extração). Detalhes e exemplos: `references/solid-tlpp.md`.48- **O**CP — variações de comportamento por **classe/bloco injetado**, não por `Do Case` de tipo49 espalhado em N funções.50- **L**SP — subclasse TLPP honra o contrato da base (mesmos pré/pós-requisitos; não "lança erro51 se for do tipo X").52- **I**SP — interfaces TLPP pequenas por papel (`ICalculaFrete`), não uma `IUtils` gorda.53- **D**IP — caso de uso depende de **interface** de repositório; a implementação concreta54 (BeginSQL/ExecAuto) é injetada no construtor. É isso que permite testar a regra com dublê.5556## Organização de fontes (screaming architecture)5758O diretório grita **o domínio**, não a tecnologia: agrupe por assunto de negócio59(`faturamento/`, `estoque/`), não por tipo (`apis/`, `queries/`, `telas/`). Dentro de cada60assunto, os sufixos de camada dizem o papel: `*Service` (caso de uso), `*Repo` (persistência),61regra pura sem sufixo. A nomenclatura de arquivo segue a convenção do projeto; a organização62é por pasta e namespace TLPP (`namespace cliente.faturamento`).6364## Quando aplicar (pragmatismo)6566| Tamanho da mudança | Estrutura mínima | Orçamento de fontes (fora os de teste) |67|---|---|---|68| PE trivial, ajuste de 1 função | Só a regra de sempre: PE delega para User Function | 1 |69| Rotina nova média | Separar ao menos **regra pura** × **acesso a dados** (2 funções/classes) — o suficiente para testar a regra por unidade, sem banco | até 3 |70| Desenvolvimento grande (módulo, integração) | Camadas completas: adaptador → caso de uso → domínio + repositórios, 1 namespace por contexto | 1 fonte por camada por contexto; acima disso, justifique fonte a fonte no plano |7172Sobre-engenharia também é dívida: **não** crie interface + repositório + service para73encapsular um `Posicione()`. O critério é: a regra de negócio merece viver isolada e testada.7475### Camada ≠ arquivo (o SOLID não é para virar enxurrada de fontes)7677Um fonte gigante é problema; **vinte fontes minúsculos também são** — cada `.tlpp` entra no78patch, precisa compilar, versionar e ser conferido na documentação de customizações. Separe79por **motivo de mudança**, não por contagem de classes.8081- **TLPP aceita mais de uma classe no mesmo fonte** (verificado em código compilado em82 produção: adapter + service convivendo no mesmo arquivo, sob o mesmo `namespace`). Classes83 coesas — mesmo papel, mesmo contexto, que mudam pelo mesmo motivo — ficam juntas.84- **Piso:** classe pequena que existe só "para ficar SOLID" volta para o fonte do seu papel.85 Interface com uma implementação só: só crie se o teste precisa do dublê.86- **Teto:** fonte passando de ~800 linhas, ou classe de ~500, é sinal de divisão real.87- **A lista de fontes é fechada no `/protheus:plan`.** Fonte que não está no plano não é88 criado na implementação — se apareceu a necessidade, ela é decisão de design e volta ao89 `/protheus:brainstorm`.9091> Os números são ponto de partida calibrado em desenvolvimento real. Ajuste no seu projeto se92> o padrão local for outro — mas mantenha um teto **declarado**, não "o quanto sair".9394## Fluxo de uso95961. **No design** (`/protheus:brainstorm` / `/protheus:plan` / `/protheus:advpl-tlpp-sdd`):97 para cada caso de uso da especificação, defina os artefatos por camada (tabela acima)98 antes de codar. Liste que regra é pura (testável por unidade) e que acesso a dados vira99 repositório/ExecAuto.1002. **Na implementação** (`/protheus:writer` / `/protheus:implement`): siga101 `references/regra-dependencia-camadas.md` (esqueleto de cada camada com código) e valide102 contra os sinais de violação.1033. **No review** (`/protheus:reviewer` / `/protheus:code-review`): aplique o checklist104 estrutural de `references/refatoracao-exemplo.md` — o exemplo before/after mostra o105 monólito típico (endpoint que faz tudo) e a versão em camadas.1064. **Testes**: a recompensa da separação — regra pura testada por unidade (ex.: PROBAT, o107 framework de testes do tlppCore) sem fixture de banco; o E2E de tela fica com108 `/protheus:tir-test-generator`.109110## Regras inegociáveis111112- Regra de negócio **nunca** contém `BeginSQL`, `RecLock`, `MsMessage` ou montagem de JSON.113- Endpoint REST / PE / ViewDef **nunca** acessa tabela diretamente — sempre via caso de uso.114- Escrita em tabela padrão TOTVS = **ExecAuto** (encapsulado em repositório), nunca `RecLock`115 direto (pula validações do dicionário).116- Transação (`BeginTran`/`EndTran`) pertence ao caso de uso — nem à regra, nem ao repositório.117- Todo caso de uso novo nasce com teste unitário da(s) regra(s) pura(s) que orquestra.