DSM — os módulos de lib/the_band/¶
O que precisa vir antes do quê, dentro do código. As DSMs de dsm/ costumam ser entre
user stories; esta é entre contextos de código, e responde a outra pergunta: se eu mexer
aqui, o que mais vou ter de entender?
Como ler¶
Linhas e colunas são os mesmos 37 contextos, na mesma ordem. A célula (i, j) marcada
significa i depende de j — a linha i usa a coluna j.
A marca diz a natureza da dependência, porque elas se desfazem de formas diferentes:
| Marca | Natureza | Como se desfaz |
|---|---|---|
F |
chamada de fronteira — i chama uma função pública de j |
extraindo a função, ou invertendo quem chama |
S |
alias de schema — i monta consulta sobre um schema que pertence a j |
expondo a consulta em j, em vez do schema |
FS |
as duas coisas na mesma aresta | — |
B |
leitura da base de conhecimento — i lê uma regra do YAML |
não se desfaz, e nem deve: é o princípio I |
A ordem das linhas foi reordenada para empurrar as marcas para baixo da diagonal. O que sobrou acima é realimentação — e é o achado.
A medida¶
| Quanto | |
|---|---|
| contextos | 37 |
arquivos .ex cobertos |
184 |
| arestas entre contextos | 136 |
| referências que as sustentam | 303 |
marcas F (chamada de fronteira) |
113 |
marcas B (base de conhecimento) |
12 |
marcas S (alias de schema) |
6 |
marcas FS (as duas) |
5 |
| arestas acima da diagonal depois de reordenar | 16 de 136 (12%) |
| ciclos (componentes fortemente conexos com mais de 1 nó) | 2 |
Cobertura declarada: 184 de 184 arquivos. Nenhum ficou de fora. O que ficou de fora foram dois conjuntos, por serem outra coisa:
lib/the_band_web/— a camada de tela. A DSM entre LiveViews e contextos é outro documento, e não existe ainda;lib/mix/— as tarefas Mix, que são ferramenta e não domínio.
O índice dos contextos¶
usa é o número de contextos distintos que o contexto chama; é usado por, o inverso.
| # | contexto | arquivos | usa | é usado por |
|---|---|---|---|---|
| M01 | forecast |
1 | 0 | 1 |
| M02 | ontology/schema_check |
1 | 0 | 1 |
| M03 | periodos |
1 | 0 | 2 |
| M04 | provenance |
1 | 0 | 1 |
| M05 | repo |
1 | 0 | 21 |
| M06 | segredo |
1 | 0 | 2 |
| M07 | the_band.ex (raiz) |
1 | 0 | 5 |
| M08 | vault |
1 | 0 | 3 |
| M09 | encrypted |
1 | 1 | 2 |
| M10 | integrations |
5 | 2 | 5 |
| M11 | ontology/yaml_loader |
1 | 1 | 2 |
| M12 | ontology/yaml_validator |
1 | 2 | 1 |
| M13 | ontology/knowledge_base |
1 | 2 | 12 |
| M14 | tenants |
10 | 4 | 22 |
| M15 | work_items |
14 | 7 | 5 |
| M16 | projects |
8 | 5 | 2 |
| M17 | ontology/sro |
5 | 4 | 2 |
| M18 | ontology/cmpo |
6 | 3 | 3 |
| M19 | sources |
4 | 9 | 3 |
| M20 | raw_data |
1 | 4 | 4 |
| M21 | ontology/eo |
20 | 5 | 10 |
| M22 | ontology/spo |
21 | 6 | 6 |
| M23 | semantic_integration |
2 | 5 | 2 |
| M24 | jobs |
5 | 8 | 2 |
| M25 | mapping |
10 | 9 | 3 |
| M26 | ai |
2 | 4 | 1 |
| M27 | profiles |
15 | 7 | 2 |
| M28 | verification |
5 | 4 | 1 |
| M29 | quality |
4 | 3 | 1 |
| M30 | configuration |
3 | 2 | 1 |
| M31 | communication |
3 | 2 | 1 |
| M32 | changes |
7 | 2 | 1 |
| M33 | ingestion |
15 | 20 | 6 |
| M34 | teams |
2 | 7 | 0 |
| M35 | release |
1 | 1 | 0 |
| M36 | ontology/smpo |
3 | 2 | 0 |
| M37 | application |
1 | 5 | 0 |
A matriz¶
A tabela é larga de propósito: é a DSM inteira, e reduzi-la esconderia justamente o que ela existe para mostrar. Leia por linha: o que esta linha precisa entender.
| M01 | M02 | M03 | M04 | M05 | M06 | M07 | M08 | M09 | M10 | M11 | M12 | M13 | M14 | M15 | M16 | M17 | M18 | M19 | M20 | M21 | M22 | M23 | M24 | M25 | M26 | M27 | M28 | M29 | M30 | M31 | M32 | M33 | M34 | M35 | M36 | M37 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| M01 | · | ||||||||||||||||||||||||||||||||||||
| M02 | · | ||||||||||||||||||||||||||||||||||||
| M03 | · | ||||||||||||||||||||||||||||||||||||
| M04 | · | ||||||||||||||||||||||||||||||||||||
| M05 | · | ||||||||||||||||||||||||||||||||||||
| M06 | · | ||||||||||||||||||||||||||||||||||||
| M07 | · | ||||||||||||||||||||||||||||||||||||
| M08 | · | ||||||||||||||||||||||||||||||||||||
| M09 | F | · | |||||||||||||||||||||||||||||||||||
| M10 | F | · | F | ||||||||||||||||||||||||||||||||||
| M11 | · | B | |||||||||||||||||||||||||||||||||||
| M12 | F | F | · | ||||||||||||||||||||||||||||||||||
| M13 | F | F | · | ||||||||||||||||||||||||||||||||||
| M14 | F | B | · | FS | F | ||||||||||||||||||||||||||||||||
| M15 | F | B | F | · | FS | S | FS | F | |||||||||||||||||||||||||||||
| M16 | F | B | F | · | F | F | |||||||||||||||||||||||||||||||
| M17 | F | F | S | F | · | ||||||||||||||||||||||||||||||||
| M18 | F | F | · | F | |||||||||||||||||||||||||||||||||
| M19 | F | F | F | F | F | F | S | · | F | F | |||||||||||||||||||||||||||
| M20 | F | F | F | · | F | ||||||||||||||||||||||||||||||||
| M21 | F | F | B | F | F | · | |||||||||||||||||||||||||||||||
| M22 | F | F | B | F | S | F | · | ||||||||||||||||||||||||||||||
| M23 | F | B | F | F | F | · | |||||||||||||||||||||||||||||||
| M24 | F | F | F | F | F | F | · | F | F | ||||||||||||||||||||||||||||
| M25 | F | F | B | F | FS | F | F | F | F | · | |||||||||||||||||||||||||||
| M26 | F | F | F | F | · | ||||||||||||||||||||||||||||||||
| M27 | F | F | F | B | F | FS | F | · | |||||||||||||||||||||||||||||
| M28 | F | F | F | F | · | ||||||||||||||||||||||||||||||||
| M29 | F | B | F | · | |||||||||||||||||||||||||||||||||
| M30 | F | F | · | ||||||||||||||||||||||||||||||||||
| M31 | F | F | · | ||||||||||||||||||||||||||||||||||
| M32 | F | F | · | ||||||||||||||||||||||||||||||||||
| M33 | F | F | F | F | F | F | F | F | F | F | F | F | F | F | F | F | F | F | F | F | · | ||||||||||||||||
| M34 | F | F | B | F | S | S | F | · | |||||||||||||||||||||||||||||
| M35 | F | · | |||||||||||||||||||||||||||||||||||
| M36 | F | F | · |
F = chamada de fronteira · S = alias de schema · FS = as duas · B = base de conhecimento
· · = a diagonal.
Os quatro blocos¶
A ordenação separa o código em quatro faixas, e a leitura delas é o mapa mental do projeto:
| Bloco | Contextos | O que é |
|---|---|---|
| A — fundação (M01–M08) | forecast, ontology/schema_check, periodos, provenance, repo, segredo, the_band.ex, vault |
não dependem de nada. São cálculo puro, cofre, tipo opaco e o acesso ao banco. |
| B — vocabulário (M09–M13) | encrypted, integrations, ontology/yaml_loader, ontology/yaml_validator, ontology/knowledge_base |
a cifragem, o cliente HTTP da origem e a leitura da rede de ontologias. |
| C — o núcleo (M14–M33) | tenants, work_items, projects, as quatro ontologias, sources, raw_data, jobs, mapping, ai, profiles, verification, quality, configuration, communication, changes, ingestion |
20 contextos que se usam mutuamente. É onde mora o ciclo grande — que inclui também integrations, do bloco B. |
| D — o topo (M34–M37) | teams, release, ontology/smpo, application |
ninguém os usa. São pontos de entrada: a medida da equipe, a release, a leitura de iteração e a árvore de supervisão. |
Os oito da fundação têm usa = 0 — nenhum deles chama contexto nenhum. Os quatro do topo
têm é usado por = 0. Entre eles, tudo.
Os três contextos mais usados, por número de contextos que os chamam:
| Contexto | É usado por | Por quê |
|---|---|---|
tenants |
22 | toda função de domínio recebe %Tenant{} — é o princípio V com forma no código |
repo |
21 | é o Ecto.Repo; quem escreve, escreve por ele |
ontology/knowledge_base |
12 | as regras do YAML, lidas em vez de escritas em constante |
Que tenants supere repo é o fato mais revelador da matriz: há contexto que fala de tenant
sem tocar no banco, e não o contrário. O escopo por organização não é uma cláusula where
acrescentada no fim; é o argumento que atravessa as assinaturas.
Os ciclos — e há dois¶
A resposta direta: sim, há ciclo. Dois componentes fortemente conexos com mais de um nó, achados por Tarjan sobre o grafo de 37 nós e 136 arestas.
Ciclo 1 — a leitura da ontologia (3 contextos)¶
ontology/knowledge_base → ontology/yaml_loader → ontology/knowledge_base
ontology/knowledge_base → ontology/yaml_validator → ontology/yaml_loader → ...
| Aresta | Natureza | Evidência |
|---|---|---|
knowledge_base → yaml_loader |
F |
lib/the_band/ontology/knowledge_base.ex:18 |
knowledge_base → yaml_validator |
F |
lib/the_band/ontology/knowledge_base.ex:19 |
yaml_validator → yaml_loader |
F |
lib/the_band/ontology/yaml_validator.ex:16 |
yaml_loader → knowledge_base |
B |
lib/the_band/ontology/yaml_loader.ex:52 |
A aresta que fecha o ciclo é a última, e é uma só. É pequeno, contido em três arquivos de
um mesmo assunto, e o corte é óbvio se um dia incomodar: o que yaml_loader precisa de
knowledge_base sai para um terceiro módulo, ou passa como argumento.
Recomendação: não mexer. Três módulos que leem o mesmo YAML e se conhecem não é acoplamento que custe; quebrar isso adicionaria um módulo para resolver um problema que ninguém tem.
Ciclo 2 — o núcleo de 21 contextos¶
Este é o grande, e é o achado do documento.
ai, changes, communication, configuration, ingestion, integrations, jobs, mapping,
ontology/cmpo, ontology/eo, ontology/spo, ontology/sro, profiles, projects, quality,
raw_data, semantic_integration, sources, tenants, verification, work_items
21 contextos num único componente fortemente conexo — de qualquer um deles se chega a qualquer outro seguindo as setas. Na prática: qualquer um deles pode, em princípio, ser afetado por qualquer outro.
São os 20 do bloco C mais integrations (M10), que a ordenação empurrou para o bloco B
por ter pouca entrada, mas que pertence ao ciclo pela aresta
integrations → ingestion (client.ex:11).
Mas 21 contextos não formam um novelo de 21 pontas. O sequenciamento mostra que bastam 16 arestas para fechá-lo, e dessas, poucas carregam quase todo o peso.
As 16 arestas de realimentação¶
São as marcas acima da diagonal. Removê-las (ou invertê-las) deixaria o grafo acíclico.
| # | Aresta | Refs | Natureza | Evidência |
|---|---|---|---|---|
| 1 | jobs → ingestion |
12 | F |
lib/the_band/jobs/sync_github_eo.ex:31-33 |
| 2 | tenants → ontology/eo |
3 | FS |
lib/the_band/tenants/access.ex:51-52, auth.ex:29 |
| 3 | work_items → mapping |
2 | FS |
lib/the_band/work_items/rotulos.ex:39, routing.ex:56 |
| 4 | work_items → ontology/eo |
2 | FS |
lib/the_band/work_items/team_work.ex:28-29 |
| 5 | integrations → ingestion |
1 | F |
lib/the_band/integrations/github/client.ex:11 |
| 6 | jobs → mapping |
1 | F |
lib/the_band/jobs/recompute_promotions.ex:40 |
| 7 | ontology/cmpo → ingestion |
1 | F |
lib/the_band/ontology/seon/cmpo/commands.ex:8 |
| 8 | ontology/yaml_loader → knowledge_base |
1 | B |
lib/the_band/ontology/yaml_loader.ex:52 |
| 9 | projects → ontology/spo |
1 | F |
lib/the_band/projects/commands.ex:14 |
| 10 | projects → ontology/sro |
1 | F |
lib/the_band/projects/commands.ex:13 |
| 11 | raw_data → ingestion |
1 | F |
lib/the_band/raw_data.ex:16 |
| 12 | sources → ingestion |
1 | F |
lib/the_band/sources.ex:13 |
| 13 | sources → ontology/eo |
1 | F |
lib/the_band/sources.ex:16 |
| 14 | tenants → ontology/spo |
1 | F |
lib/the_band/tenants/access.ex:53 |
| 15 | work_items → ontology/spo |
1 | S |
lib/the_band/work_items/person_work.ex:25 |
| 16 | work_items → profiles |
1 | F |
lib/the_band/work_items/team_work.ex:30 |
Os doze pares que se olham¶
Doze arestas formam ciclo de comprimento 2 — dois contextos que se usam mutuamente. São o acoplamento mais fácil de ver e o mais caro de ignorar:
| Par | Refs (ida / volta) | Natureza |
|---|---|---|
ingestion ↔ integrations |
8 / 1 | F / F |
ingestion ↔ jobs |
1 / 12 | F / F |
ingestion ↔ sources |
3 / 1 | F / F |
ingestion ↔ ontology/cmpo |
1 / 1 | F / F |
ingestion ↔ raw_data |
1 / 1 | F / F |
mapping ↔ work_items |
6 / 2 | FS / FS |
ontology/eo ↔ tenants |
10 / 3 | F / FS |
ontology/spo ↔ tenants |
8 / 1 | F / F |
ontology/spo ↔ work_items |
1 / 1 | S / S |
ontology/sro ↔ projects |
2 / 1 | F / F |
jobs ↔ mapping |
1 / 1 | F / F |
knowledge_base ↔ yaml_loader |
1 / 1 | F / B |
O que corta o ciclo grande — proposta, e só¶
A decisão de arquitetura não é minha. O que segue é leitura da matriz, para quem decide.
Corte 1 — jobs ↔ ingestion e integrations ↔ ingestion¶
É a aresta mais carregada da matriz (12 referências) e a que mais gente encontra.
jobs → ingestion12x: o job chama a coleta. Natural, e provavelmente certo.ingestion → jobs1x:lib/the_band/ingestion.ex:17citaTheBand.Jobs.SyncGitHubEO— a coleta enfileira o próprio job.integrations → ingestion1x:client.ex:11usaTheBand.Ingestion.Cota— o cliente HTTP consulta a cota, que mora na coleta.
Proposta: as duas arestas de volta são de uma referência cada, e as duas apontam para
coisas que não são "coleta": enfileirar e contar cota. Mover Ingestion.Cota para um contexto
próprio (ou para integrations, que é quem a consome) e tirar o enfileiramento de
ingestion.ex desfaz duas realimentações com pouca cirurgia.
Corte 2 — tenants ↔ ontology/eo e tenants ↔ ontology/spo¶
ontology/eo → tenants 10x é esperado: toda função de EO recebe %Tenant{}.
A volta, tenants → ontology/eo 3x, é o acoplamento real, e está em dois lugares:
access.ex:51-52 (a regra de visibilidade precisa da estrutura organizacional) e
auth.ex:29 (alias do schema Person, para o elo).
Proposta: auth.ex:29 é S — alias de schema atravessando fronteira. Trocá-lo por uma
consulta exposta em EO desfaz um terço desta aresta sem discutir arquitetura. As outras
duas são F e legítimas: decidir visibilidade exige conhecer a estrutura, e essa é a
dependência que o produto tem.
Corte 3 — os seis aliases de schema atravessando contexto¶
São o acoplamento mais barato de desfazer, porque não pedem decisão de desenho: pedem uma função de consulta no dono do schema.
| Quem monta a consulta | Sobre schema de | Onde |
|---|---|---|
ontology/spo |
work_items |
projects.ex:34 — WorkItems.Schemas.CollectedIssue |
ontology/sro |
work_items |
queries.ex:12 — WorkItems.Schemas.CollectedIssue |
sources |
ontology/cmpo |
sources.ex:15 — CMPO.Schemas.ObservedRepository |
teams |
ontology/eo |
problems_now.ex:54 — EO.Schemas.TeamMembership |
teams |
work_items |
problems_now.ex:58-59, flow_per_person.ex:56 |
work_items |
ontology/spo |
person_work.ex:25 — SPO.Schemas.PerformedProjectActivity |
Proposta: os dois de teams não fecham ciclo (ninguém usa teams), e são os mais
seguros de deixar como estão. Os quatro restantes participam de realimentação, e cada um vira
uma função na fronteira do dono.
Cuidado, e é o mais importante desta seção. Trocar
SporFnão é automaticamente melhor: uma consulta exposta na fronteira que devolve uma lista grande pode ser mais cara que ojoinque existe hoje. A matriz diz onde está o acoplamento, e não que ele seja defeito. Quem decide é quem conhece a consulta.
O que a matriz NÃO diz¶
- Nada sobre qualidade. Uma aresta
Fcom 12 referências pode ser a coisa mais certa do projeto. A DSM mede acoplamento, não acerto. - Nada sobre a tela.
lib/the_band_web/ficou fora, e é de lá que vem boa parte das chamadas de fronteira. Uma DSM tela × contexto é trabalho que não existe ainda. - Nada sobre chamadas dinâmicas. A varredura é textual:
apply/3, nomes de módulo montados em tempo de execução e módulos passados como configuração não aparecem. Se existirem, há arestas a mais que esta matriz não tem. - Nada sobre frequência de execução. Uma referência escrita uma vez e executada a cada coleta pesa o mesmo que uma escrita doze vezes e nunca executada.
As 7 arestas que existiam só na documentação¶
Registro do método, porque o resultado depende dele. A primeira varredura devolveu 143
arestas; excluindo @moduledoc, @doc e comentários, sobraram 136. As sete diferenças
eram nomes de módulo citados em texto explicativo, e não dependências:
| Aresta aparente | Onde estava |
|---|---|
vault → application |
lib/the_band/vault.ex:30 (moduledoc) |
vault → encrypted |
lib/the_band/vault.ex:6 (moduledoc) |
segredo → sources |
lib/the_band/segredo.ex:49 (moduledoc) |
segredo → vault |
lib/the_band/segredo.ex:38 (moduledoc) |
tenants → release |
lib/the_band/tenants/bootstrap.ex:20 (moduledoc) |
ontology/eo → periodos |
lib/the_band/ontology/seon/eo/queries.ex:343 (comentário) |
ontology/sro → ontology/smpo |
lib/the_band/ontology/continuum/sro.ex:23 (moduledoc) |
Sem essa exclusão, vault e segredo apareceriam em ciclos que não existem. É o mesmo
princípio das outras páginas desta pasta: o número tem de sair de uma contagem cujo método
está escrito.
Como regenerar¶
O método, em quatro passos, para quem precisar refazer depois de uma mudança grande:
- mapear todo
defmoduledelib/the_band/para o arquivo que o define; - varrer cada arquivo por referências
TheBand.*, pulando blocos"""e linhas#, e resolver cada uma para o módulo conhecido de nome mais longo; - atribuir origem e destino ao contexto (primeiro diretório sob
lib/the_band/, comontology/aberta por ontologia), descartando as arestas de um contexto para si mesmo; - rodar Tarjan para os ciclos e Eades-Lin-Smyth para a ordem que minimiza as marcas acima da diagonal.
A classificação da marca sai do alvo: TheBand.Ontology.KnowledgeBase → B; módulo em
schemas/ ou com .Schemas. no nome → S; qualquer outro → F.