Testes de Arquitetura com Konture em Kotlin: Uma Guia Prática - Parte 3/3
Configure um módulo dedicado para testes de arquitetura, adicione o Konture e desenvolva uma pequena série de regras estruturais que protegem as fronteiras em que seu projeto Kotlin realmente depende.
O melhor primeiro teste de arquitetura geralmente não é inteligente.
É uma regra que a equipe já acredita:
Módulos de implementação de recursos não devem depender de módulos irmãos de implementação de recursos.Essa regra é concreta. É fácil de explicar. É doloroso quando quebrada. Também exerce o hábito certo: codifique uma decisão real de arquitetura, não um diagrama idealizado.
Este guia usa Gradle Kotlin DSL e JUnit 5. O Konture em si é agnóstico ao executador de testes, então as mesmas regras podem ser executadas a partir do JUnit, Kotest, TestBalloon ou outro executador Kotlin/JVM.
Configuração do Alvo
Use um módulo dedicado para testes de arquitetura.
Um módulo separado dá à suite de arquitetura uma visão do nível do projeto sem adicionar dependências de teste de arquitetura aos módulos de produção. Também torna a configuração CI direta: execute uma tarefa quando quiser verificação estrutural.
Em um projeto maior, os módulos inspecionados podem parecer assim:
:app
:core:domain
:core:data
:feature:checkout:api
:feature:checkout:impl
:feature:profile:api
:feature:profile:impl
:shared
:androidApp
:iosAppOs nomes não importam. A política sim. Use seus módulos e pacotes reais em todas as regras.
Passo 1: Adicione o Konture
Declare a versão no seu catálogo de versões:
[versions]
konture = "0.6.8"
[plugins]
konture = { id = "io.github.baole.konture", version.ref = "konture" }
[libraries]
konture = { group = "io.github.baole", name = "konture", version.ref = "konture" }Aplicar o plugin na raiz da build:
plugins {
alias(libs.plugins.konture) apply true
}O plugin gera a metadados de layout que o Konture precisa para regras conscientes de módulos.
Passo 2: Crie o módulo de teste da arquitetura
Registre o módulo em settings.gradle.kts:
include(':konture-test')Crie konture-test/build.gradle.kts:
plugins {
kotlin('jvm')
alias(libs.plugins.konture)
}
dependencies {
testImplementation(libs.konture)
testImplementation('org.junit.jupiter:junit-jupiter-api:5.11.0')
testRuntimeOnly('org.junit.jupiter:junit-jupiter-engine:5.11.0')
testImplementation(project(':app'))
testImplementation(project(':core:domain'))
testImplementation(project(':feature:checkout:api'))
testImplementation(project(':feature:checkout:impl'))
testImplementation(project(':feature:profile:api'))
testImplementation(project(':feature:profile:impl'))
}
tasks.test {
useJUnitPlatform()
}Substitua a lista de dependências de exemplo pelos módulos que suas regras inspecionam. O módulo de teste da arquitetura deve ver o código e os metadados de construção que ele verifica.
Passo 3: Comece com uma regra de gráfico de construção
Crie konture-test/src/test/kotlin/com/acme/ArchitectureGuardrailsTest.kt.
package com.acme
import io.github.baole.konture.Konture
import org.junit.jupiter.api.Test
class ArchitectureGuardrailsTest {
@Test
fun `feature implementations must not depend on sibling feature implementations`() {
Konture.modules {
that().haveNameMatching(':feature:**:impl')
should().onlyDependOnModules(
':feature:**:api',
':core:**',
':shared',
)
}
}
}Esta regra verifica o gráfico do projeto Gradle. Se :feature:checkout:impl adicionar implementation(project(':feature:profile:impl')), a verificação de arquitetura falha.
Para um projeto camada mais simples, use os caminhos reais:
Konture.modules {
that().haveNamePath(':core:domain')
should().notDependOnModule(':core:data')
should().notDependOnModule(':app')
}Não embarque nomes de placeholders. As verificações de arquitetura são contratos; contratos precisam de alvos concretos.
Passo 4: Adicionar uma Verificação de Ciclo
Dependências circulares entre módulos lentificam a construção e enfraquecem as fronteiras de propriedade.
@Test
fun `grafo de módulos não deve conter ciclos`() {
Konture.assertNoCycles()
}Isso é uma boa configuração padrão para projetos multi-módulos porque ciclos tendem a tornar cada decisão de fronteira futura mais difícil.
Passo 5: Proteger o Código-fonte do Domínio
Um gráfico de módulos limpo não garante referências de código fonte limpas. Adicione uma regra de pacote no nível do código-fonte:
@Test
fun `classes do domínio devem depender apenas de tipos do domínio e da biblioteca padrão`() {
Konture.classes {
que().resideInAPackage("..domain..")
deve().onlyDependOnClassesInAnyPackage(
"..domain..",
"kotlin..",
"java..",
)
}
}Se a camada de domínio depende intencionalmente de código compartilhado do projeto, declare isso explicitamente:
Konture.classes {
que().resideInAPackage("..domain..")
deve().onlyDependOnClassesInAnyPackage(
"..domain..",
"..shared..",
"kotlin..",
"java..",
)
}A regra deve corresponder à arquitetura escolhida pela equipe, não a uma arquitetura emprestada de um exemplo.
Passo 6: Proibir Importações de Frameworks Onde Não Pertencem
Frameworks externos são frequentemente mais fáceis de detectar por meio de importações do que por dependências de classes no projeto.
@Test
fun `domínio não deve importar APIs de frameworks ou persistência`() {
Konture.scopeFromPackage("com.acme.domain")
.assertTrue("Domínio não deve importar APIs de frameworks ou persistência") { cls ->
cls.imports.none { fqName ->
fqName.startsWith("org.springframework.") ||
fqName.startsWith("io.ktor.") ||
fqName.startsWith("android.") ||
fqName.startsWith("androidx.compose.") ||
fqName.startsWith("jakarta.persistence.") ||
fqName.startsWith("javax.persistence.")
}
}
}scopeFromPackage("com.acme.domain") seleciona um prefixo de pacote específico para afirmações personalizadas. Em contraste, resideInAPackage("..domain..") usa a correspondência de pacotes curinga do Konture dentro das regras fluentes de classes.
Ajuste os prefixos para o projeto. Um backend pode proibir anotações de persistência do domínio. Um aplicativo Android pode proibir APIs de Android e Compose de pacotes compartilhados ou domínios. Um projeto KMP pode aplicar políticas diferentes a commonMain, androidMain e iosMain.
Evite banimentos amplos que capturem dependências legítimas. Por exemplo, banir todos os de kotlinx.. pode bloquear o uso válido de corrotinas.
Passo 7: Imponha Contratos de Repositórios
Se sua arquitetura trata repositórios na camada de domínio como contratos, codifique essa regra:
@Test
fun `repositórios dentro do domínio devem ser interfaces`() {
Konture.classes {
that().resideInAPackage("..domain..")
that().haveNameEndingWith("Repository")
should().beInterfaces()
}
}Isso captura um atalho comum:
class UserRepository {
// comportamento de persistência concreto no domínio
}Se seu projeto usa classes abstratas, portas ou uma convenção de nomenclatura diferente, codifique isso em vez disso. A regra deve impor o modelo do contrato, não a palavra Repository em si.
Passo 8: Mantenha Pacotes de Implementação Internos
Classes e membros Kotlin são públicos por padrão. Em projetos multi-modular, visibilidade pública acidental se torna API acidental.
@Test
fun `classes de implementação devem permanecer internas`() {
Konture.classes {
that().resideInAPackage("..impl..")
should().beInternal()
}
}Isso é especialmente útil para módulos de recurso ou bibliotecas que dividem API e implementação:
:feature:checkout:api
:feature:checkout:implO módulo API expõe contratos. O módulo de implementação não deve se tornar um amontoado para outros recursos.
Passo 9: Proteja Isolamento de Módulos de Recurso
Se você não começou com isolamento de recurso, adicione-o uma vez que as regras básicas dos módulos estejam estáveis. Implementações irmãs de recursos geralmente devem depender umas das outras diretamente.
@Test
fun `implementações de recurso não devem depender de implementações irmãs de recurso`() {
Konture.modules {
that().haveNameMatching(":feature:**:impl")
should().onlyDependOnModules(
":feature:**:api",
":core:**",
":shared",
)
}
}Isso permite que as implementações de recurso dependam dos módulos API de recurso, módulos core e módulos compartilhados. Isso impede a associação entre implementações.
Se o aplicativo tem uma estratégia de modularização diferente, altere a lista permitida. O valor não é o padrão; o valor é tornar o gráfico de dependências pretendido executável.
Passo 10: Use o DSL Camadas para Regras Direcionais
Para regras de camada baseadas em pacotes, um DSL camadas pode ser mais fácil de ler do que uma longa lista de predicados de pacote.
@Test
fun `camadas devem seguir direção de dependência interna`() {
Konture.layered {
val presentation = layer("presentation") definedBy "..presentation.."
val domain = layer("domain") definedBy "..domain.."
val data = layer("data") definedBy "..data.."
onde(presentation) {
podeAcessarSomenteCamadas(domain)
}
onde(data) {
podeAcessarSomenteCamadas(domain)
}
onde(domain) {
podeAcessarSomenteCamadas()
}
}
}Para portas e adaptadores, a mesma ideia pode parecer assim:
Konture.layered {
val domain = layer("domain") definedBy "..domain.."
val application = layer("application") definedBy "..application.."
val adapter = layer("adapter") definedBy "..adapter.."
onde(domain) {
podeAcessarSomenteCamadas()
}
onde(application) {
podeAcessarSomenteCamadas(domain)
}
onde(adapter) {
podeAcessarSomenteCamadas(application, domain)
}
}Use o modelo que sua equipe realmente usa. Uma regra camada que não corresponde ao código real se tornará atrito rapidamente.
Passo 11: Adicione Higiene de Nível de Arquivo com Cautela
Algumas convenções de origem reduzem o custo de navegação e ruído de revisão:
@Test
fun `arquivos de origem devem permanecer simples e explícitos`() {
Konture.files {
deve().naoTerImportsComAsterisco()
deve().terSomenteUmaClassePorArquivo()
deve().terNomeCorrespondendoAoNomeDaClasse()
}
}Não transforme testes de arquitetura em um segundo linter. Se detekt, ktlint ou um formataador já impõem uma regra bem, use essa ferramenta.
Passo 12: Execute o Conjunto
Execute a tarefa dedicada:
./gradlew :konture-test:testOu inclua-o no caminho de verificação normal:
./gradlew checkO repositório usa o mesmo padrão para a demonstração Gradle:
./gradlew -p showcases/sample-gradle :konture-test:testEssa comando executa um módulo dedicado de teste arquitetônico contra pequenos projetos :app, :domain e :data. A suite abrange dependências de módulos, fronteiras de pacotes de classes, contratos de repositórios, vazamento de tipo nas assinaturas de caso de uso e uma afirmação negativa que prova que uma regra de módulo deliberadamente errada falha.
A afirmação negativa demonstra a forma de falha que você deve esperar para uma violação real:
Detecção de violação(s) da arquitetura:
O módulo :data depende de :domain, o que não é permitido pelos padrões: :appA correção não deve enfraquecer a regra. A correção é restaurar o gráfico pretendido, ou alterar a regra apenas se a decisão de arquitetura tiver mudado genuinamente. No exemplo da funcionalidade acima, isso geralmente significa mover o contrato compartilhado para :feature:profile:api e depender desse módulo API em vez de :feature:profile:impl.
Quando uma regra falha, lide com isso como qualquer outra falha de teste:
- Leia a violação.
- Decida se a regra codificada ainda é correta.
- Corrija o código se o código cruzou a fronteira.
- Corrija a regra se a decisão de arquitetura mudou.
- Adicione uma exceção explícita apenas quando a exceção é intencional.
Não enfraqueça silenciosamente as regras até que a CI passe. Isso transforma testes de arquitetura em decoração.
Agrupe Regras Relacionadas
Os exemplos acima usam afirmações focadas e independentes como Konture.modules { ... } e Konture.classes { ... }. Quando várias regras descrevem a mesma fronteira, agrupe-as com Konture.architecture { ... } para que as verificações de nível de módulo e de nível de origem sejam lidas como um contrato único:
Padrões Avançados
Uma vez que a suite inicial esteja estável, adicione regras para os lugares onde projetos Kotlin geralmente exibem vazamentos de arquitetura.
Fronteiras da Superfície DTO e Entidade
Os vazamentos mais caros geralmente aparecem em assinaturas públicas ou voltadas para a interface do usuário. Um estado de tela, contrato de apresentador ou API de recurso que expõe um DTO de transporte transformou um detalhe de implementação em uma dependência longa.
@Test
fun `o estado da apresentação não deve expor tipos de transporte ou persistência`() {
val classesDeApresentacao = Konture.scopeFromPackage("com.acme.profile.presentation").classes
classesDeApresentacao.assertTrue("A API de apresentação não pode exibir DTOs ou entidades") { cls ->
val tiposFuncoesPublicas =
cls.functions
.filter { it.visibility == io.github.baole.konture.Visibility.PUBLIC }
.flatMap { fn -> listOf(fn.returnType) + fn.parameters.map { it.type } }
val tiposPropriedadesPublicas =
cls.properties
.filter { it.visibility == io.github.baole.konture.Visibility.PUBLIC }
.map { it.type }
(tiposFuncoesPublicas + tiposPropriedadesPublicas).none { tipo ->
tipo.endsWith("Dto") ||
tipo.endsWith("Entity") ||
tipo.contains(".network.") ||
tipo.contains(".database.")
}
}
}Você pode associar isso com uma regra de módulo:
Konture.modules {
that().haveNamePath(":shared")
should().notDependOnModule(":androidApp")
}Use o nome dos conjuntos de fontes que seu build realmente usa: commonMain, androidMain, iosMain, desktopMain, jvmMain ou conjuntos de fontes intermediários específicos do projeto.
Convenções do Gráfico DI
O Konture não deve substituir um teste de integração DI em tempo de execução. Ele ainda pode proteger a política estrutural DI:
@Test
fun `módulos do Hilt devem permanecer em pacotes de DI`() {
Konture.classes {
that().haveAnnotationOf("dagger.Module")
should().resideInAPackage(":di:")
}
}Para o Koin, uma política semelhante pode viver no nível de arquivo ou função:
@Test
fun `módulos koin de produção não devem estar em pacotes de teste`() {
Konture.files {
que().satisfy { arquivo ->
arquivo.imports.any { it == "org.koin.dsl.module" }
}
deve().resideInAPackage { packageName ->
!packageName.contains(".test.") &&
!packageName.contains(".fixtures.")
}
}
}Essas regras não provam que a gráfica DI inicia. Elas impedem que o código de conexão se espalhe para lugares onde a propriedade fica incerta.
Código Gerado
O código gerado frequentemente viola convenções de código escrito por boas razões. Trate-o explicitamente.
konture {
excludePackages(
"..generated..","..buildconfig..", "..databinding.."
)
}As fontes geradas do Room, KSP, recursos Compose, protobuf, serialização ou ferramentas DI não devem criar falsos positivos em regras sobre design de API pública ou propriedade de pacote. Se o código gerado faz parte do contrato público, teste a capa escrita publicamente ao invés da implementação detalhada gerada.
Quarentena Legado
Para código legado, não faça como se a arquitetura-alvo já existisse. Isolá-lo.
@Test
fun `novo código de domínio não deve depender da persistência legada`() {
val novoDominio =
Konture.scopeFromPackage("com.acme.domain").classes.filterNot { cls ->
cls.packageName.startsWith("com.acme.domain.legacy")
}
novoDominio.assertTrue("Novo código de domínio não deve importar persistência legada") { cls ->
cls.imports.none { it.startsWith("com.acme.legacy.persistence.") }
}
}A exceção é visível, nomeada e removível. Isso é melhor do que uma regra ampla que falha constantemente ou uma exclusão silenciosa de ninguém se lembra.
Superfície da API Pública
Os testes de arquitetura são especialmente úteis quando uma API pública acidental cria um acoplamento longo prazo.
@Test
fun `public feature api must not expose implementation or persistence types`() {
val apiClasses = Konture.scopeFromModule(":feature:checkout:api").classes
apiClasses.assertTrue("Public API must not leak implementation detail") { cls ->
val publicFunctionTypes =
cls.functions
.filter { it.visibility == io.github.baole.konture.Visibility.PUBLIC }
.flatMap { fn -> listOf(fn.returnType) + fn.parameters.map { it.type } }
val publicPropertyTypes =
cls.properties
.filter { it.visibility == io.github.baole.konture.Visibility.PUBLIC }
.map { it.type }
(publicFunctionTypes + publicPropertyTypes).none { type ->
type.contains(".impl.") ||
type.contains(".data.") ||
type.endsWith("Entity") ||
type.endsWith("Dto")
}
}
}Para bibliotecas, esta também é uma regra de versão semântica. Se uma assinatura pública expõe um entidade de persistência hoje, removê-la amanhã se torna uma alteração quebra a API.
Princípios de Design da Regra
Adicione esses princípios antes de expandir o conjunto:
- Uma política por teste: o nome de um teste falho deve informar ao desenvolvedor qual decisão foi quebrada.
- Prove que a regra pode falhar: introduza temporariamente uma violação, execute o teste, confirme que ele falha e então remova a violação.
- Use nomes reais: evite módulos e pacotes de substituição em regras comprometidas.
- Faça exceções visíveis: o código gerado, pacotes de migração e zonas legadas podem precisar de exclusões, mas essas exclusões devem ser deliberadas.
- Evite wildcards amplas: uma proibição ampla é útil apenas quando a equipe entende quais casos legítimos ela exclui.
- Separe estrutura de estilo: testes de arquitetura devem proteger fronteiras e propriedade, não formatação.
Os projetos de demonstração são material útil para calibração. A suíte Now in Android demonstra a decupla de recursos, verificações do framework ViewModel e separação entre :api/:impl. A suíte KotlinConf KMP demonstra pureza de shared-core, separação backend/frontend e limites de rota-serviço. Use exemplos como esses para projetar regras em torno de pressões arquiteturais reais, não apenas limpeza abstrata.
Solucionando Falhas
A maioria das falhas do Konture se enquadra em algumas categorias.
Leia a primeira violação como uma questão de design: a regra ainda é verdadeira? Se sim, corrija o código. Se não, altere a regra e deixe um rastro claro no nome do teste, ADR ou documentos.
O Que Isso Custo
Os testes de arquitetura são baratos em comparação com a reparação estrutural tardia, mas não são gratuitos:
- A primeira passagem em uma base de código existente frequentemente revela violações legadas e falsos positivos que precisam ser triados antes da aplicação.
- A equipe precisa aprender o suficiente do DSL para expressar a política com precisão em vez de codificar regras amplas e frustrantes.
- Cada regra duradoura se torna uma superfície de manutenção quando a arquitetura muda; as edições da regra devem ser revisadas como alterações de design.
Para um conjunto focado de regras, como a isolação da implementação de recursos ou pureza do domínio, muitas equipes podem geralmente mover-se de CI informativo para CI obrigatório em uma sprint ou duas. Trate isso como uma calibragem aproximada, não como uma promessa: bases de código mais antigas e grandes zonas de migração precisam de mais tempo.
Métricas e Observabilidade
Trate o conjunto de arquitetura como um sinal de saúde do produto, não apenas como uma porta pass/fail.
Métricas úteis:
- Número de regras de arquitetura,
- Duração do teste de arquitetura no CI,
- Contagem de violações por regra antes da aplicação,
- Violations recorrentes por módulo ou pacote,
- Número de exceções explícitas e pacotes em quarentena,
- Módulo fan-in e fan-out para áreas fortemente alteradas,
- Comentários de revisão que desaparecem após uma regra se tornar executável.
Não ajuste demais os números. Um projeto com cinco regras fortes pode ser mais saudável que um projeto com cinquenta cerimoniais. A melhor métrica é se o conjunto detecta erros estruturais caros cedo e explica a reparação claramente.
Plano de Migração
A implementação é um problema social tanto quanto técnico.
- Listar as decisões arquiteturais que já são impostas em revisão.
- Escolher uma regra com alto consenso e um caminho de reparo claro.
- Provar que a regra falha introduzindo e então removendo uma violação local.
- Executar a regra no CI como informativa se houver violações existentes.
- Quarentenar zonas legadas explicitamente em vez de bloquear todas as atividades.
- Fazer a regra obrigatória uma vez que novas violações sejam raras e a equipe entenda o problema.
- Adicionar a próxima regra apenas após a anterior se tornar tediosa.
Esperar resistência quando um teste bloqueia uma atalho que costumava ser invisível. Isso é uma conversa útil se a regra for específica. É um desperdício de tempo se a regra for vaga. Mantenha as primeiras regras ligadas à dor que a equipe já reconhece: ciclos, acoplamento de implementação de recursos, vazamento de plataforma ou exposição pública de DTO/entity.
Manutenção e Evolução
Os testes de arquitetura devem mudar quando a arquitetura muda.
Versão as regras importantes como qualquer outro contrato público: renomeie testes quando a política mudar, remova exclusões quando o trabalho de migração for concluído e mantenha antigas regras informativas por uma janela de lançamento se as equipes precisarem de tempo para mover. Se uma regra tiver acumulado muitas exceções, agende uma revisão da regra em vez de adicionar mais um filterNot.
Uma boa depreciação da regra parece assim:
- Marque a antiga regra como informativa,
- Adicione a nova regra ao lado dela,
- Migre módulos incrementalmente,
- Exclua a antiga regra e sua lista de quarentena uma vez que o gráfico corresponda à nova política.
O conjunto deve descrever a arquitetura que você está escolhendo agora, não a arquitetura que você desejava ter há dois anos.
Um Conjunto Inicial
Aqui está um ponto de partida compacto para um projeto modular de recursos:
pacote com.acme
import io.github.baole.konture.Konture
import org.junit.jupiter.api.Test
class GuardaRailsDeArquiteturaTest {
@Test
fun `o gráfico de módulos não deve conter ciclos`() {
Konture.assertNoCycles()
}
@Test
fun `módulos da API de recursos não devem depender dos módulos de implementação de recursos`() {
Konture.modules {
que().tenhamNomeCorrespondente("feature:**:api")
devem().nãoDependereDoMódulo("feature:**:impl")
}
}
@Test
fun `implementações de recursos não devem depender de implementações irmãs de outros recursos`() {
Konture.modules {
que().tenhamNomeCorrespondente("feature:**:impl")
devem().apenasDependereDeMódulos(
"feature:**:api",
"core:**",
"shared",
)
}
}
@Test
fun `classes de domínio devem apenas depender de tipos de domínio e da biblioteca padrão`() {
Konture.classes {
que().residamEmUmPacote("..domain..")
devem().apenasDependereDeClassesEmQualquerPacote(
"..domain..",
"kotlin..",
"java..",
)
}
}
@Test
fun `classes de implementação devem permanecer internas`() {
Konture.classes {
que().residamEmUmPacote("..impl..")
devem().serInternos()
}
}
}Manter o conjunto inicial pequeno. Deixe-o crescer a partir de dores reais:
- Uma violação de limite encontrada em revisão.
- Uma dependência de módulo que ampliou o impacto da construção.
- Um vazamento de DTO que tornou a refatoração cara.
- Uma correção assistida por IA que cruzou camadas.
- Uma classe de implementação pública que se tornou difícil de remover.
Os testes de arquitetura funcionam melhor quando protegem decisões que as pessoas já se importam.
Uma Forma Madura de Conjunto
Um conjunto maduro não é necessariamente grande. Ele está estratificado por preocupação:
class ArchitectureSuiteTest {
@Test
fun `projeto gráfico deve permanecer acíclico`() {
Konture.assertNoCycles()
}
@Test
fun `módulos de recursos expõem contratos através dos módulos api`() {
Konture.architecture {
modules {
that().haveNameMatching(":feature:**:api")
should().notDependOnModule(":feature:**:impl")
}
modules {
that().haveNameMatching(":feature:**:impl")
should().onlyDependOnModules(
":feature:**:api",
":core:**",
":shared",
)
}
}
}
@Test
fun `domínio deve permanecer independente de frameworks e persistência`() {
Konture.architecture {
modules {
that().haveNamePath(":core:domain")
should().notDependOnModule(":core:data")
}
classes {
that().resideInAPackage("..domain..")
should().notDependOnClassesInAnyPackage(
"..data..",
"..database..",
"..network..",
"android..",
"androidx.compose..",
"org.springframework..",
)
}
}
}
@Test
fun `código compartilhado kmp deve permanecer independente de plataformas`() {
val commonClasses =
Konture.scopeFromModule(":shared").classes.filter { cls ->
cls.filePath.contains("/commonMain/")
}
commonClasses.assertTrue("commonMain deve não importar APIs de plataforma") { cls ->
cls.imports.none { it.startsWith("android.") || it.startsWith("java.awt.") }
}
}
}A suite tem diferentes funções: saúde do gráfico, propriedade de recursos, pureza do domínio e portabilidade da plataforma. Cada falha informa ao desenvolvedor qual decisão arquitetônica foi violada.
Diretrizes de Lançamento
Para um projeto existente, introduza testes de arquitetura em etapas:
- Comece com regras não controversas, como ciclos de módulos e dependências do domínio para dados.
- Execute a suite localmente e no CI como informativo se a primeira passagem revelar muitas violações.
- Corrija ou isole explicitamente as violações legadas.
- Transforme regras de alta confiança em verificação obrigatória no CI.
- Revise alterações nas regras como mudanças de arquitetura, não como ajustes de formatação.
Para código gerado, fixtures de teste e áreas de migração legada, prefira exclusões explícitas:
konture {
excludePackages("..generated..")
}Este bloco em minúsculas konture {} pertence à configuração do Gradle e configura o plugin Konture. É separado da API de afirmação capitalizada Konture.* usada nos arquivos de teste.
A exceção deve ser visível o suficiente para que mantenedores futuros entendam a real fronteira.
Depois que o primeiro conjunto é estável, adicione regras em torno das áreas onde o projeto realmente prejudica:
- Isolamento do módulo de recursos.
- Portabilidade da fonte-set KMP (Kotlin Multiplatform).
- Fuga de API pública.
- Fronteiras entre DTO (Data Transfer Object) e entidades.
- Direção da dependência de roteiros ou controladores.
- Convenções de injeção de dependências.
- Quarentena de pacotes legados.
Konture não é uma prescrição para um estilo de arquitetura específico. É uma maneira de tornar sua arquitetura executável.
Execute-o localmente. Execute-o no CI (Continuous Integration). Deixe que mudanças humanas e assistidas por IA recebam a mesma retroalimentação estrutural.
Quando a estrutura importa, faça parte do build (compilação).

