Add core/domain boundary doc; clarify lots (mixed year/unit per variety)
This commit is contained in:
parent
86b0c4d251
commit
59beadc03c
2 changed files with 102 additions and 1 deletions
101
docs/design/core-domain-boundary.md
Normal file
101
docs/design/core-domain-boundary.md
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
# Tanemaki — Frontera `commons_core` ↔ dominio semillas
|
||||||
|
|
||||||
|
*Nota de diseño (discusión). Desarrolla la Parte B de [usability-and-architecture.md](usability-and-architecture.md) y reparte las entidades de [data-model.md](data-model.md) entre el motor genérico y el dominio de semillas.*
|
||||||
|
|
||||||
|
Aquí decidimos **qué vive en el motor común y qué en la app de semillas**, y con qué mecanismo se enchufan. Es la decisión que más amarra el resto, porque fija la costura que permitirá añadir mañana la "biblioteca de cosas" sin tocar Tanemaki.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. La regla para trazar la línea
|
||||||
|
|
||||||
|
Una sola pregunta decide dónde va cada cosa:
|
||||||
|
|
||||||
|
> **¿La segunda app (biblioteca de cosas/herramientas) lo usaría prácticamente igual?**
|
||||||
|
> - **Sí** → va en `commons_core`.
|
||||||
|
> - **No, es de semillas** (especie, germinación, año de cosecha, cultivar, unidades por familia botánica) → va en el dominio `app_seeds`.
|
||||||
|
> - **En la duda → déjalo en el dominio.** Subir algo al core más tarde es barato; bajarlo del core a un dominio cuando ya hay dos apps encima es caro.
|
||||||
|
|
||||||
|
En una frase: **al core solo sube lo que es idéntico entre "compartir semillas" y "prestar una escalera".**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Qué es idéntico entre semillas y cosas (y por tanto va al core)
|
||||||
|
|
||||||
|
- **Identidad** (par de claves que controlas) y firma.
|
||||||
|
- **Party** (persona/colectivo con quien tratas) y **Group** (banco/colectivo).
|
||||||
|
- **Confianza / reputación** (`TrustEdge`, valoraciones) — el grafo de quién confía en quién.
|
||||||
|
- **Offer** (el escaparate) — anunciar lo que ofreces; ya diseñada agnóstica (lleva un *resumen* del objeto, no una FK a tablas de semillas). Serializa a NIP-99.
|
||||||
|
- **Pledge** (promesa de devolución) — el Plantare generalizado. "¿Me devolviste la escalera?" == "¿devolviste semilla similar?".
|
||||||
|
- **Ledger / Movement** (registro append-only de entradas/salidas) — la *mecánica* del diario es genérica.
|
||||||
|
- **Transporte y sincronización** — `OfferTransport`, relays Nostr, motor CRDT, descubrimiento por geohash. **Esta es la parte difícil, y es 100% común.**
|
||||||
|
|
||||||
|
## 3. Qué es de semillas (y por tanto va al dominio)
|
||||||
|
|
||||||
|
- **Species** (catálogo empaquetado Wikidata/GBIF), nombres vernáculos, cultivar.
|
||||||
|
- **Germinación**, **año de cosecha**, condiciones de almacén específicas.
|
||||||
|
- **Unidades por familia botánica** (mazorca, vaina, espiga, cabezuela…) y su mapeo de sugerencia.
|
||||||
|
- La **UI de semillas**, sus textos i18n, sus iconos.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. El mecanismo: cómo se enchufa el dominio al core
|
||||||
|
|
||||||
|
No conviene que el core "sepa" de semillas ni que el dominio reimplemente lo difícil. Patrón elegido, en tres piezas:
|
||||||
|
|
||||||
|
### 4.1 El core define entidades base con **solo columnas universales**
|
||||||
|
`Item` (identidad de algo que tienes) y `Holding` (una remesa/unidades que posees) viven en el core, pero **solo con lo común**: id, etiqueta, dueño, categoría, notas, estado de oferta, y la metadata CRDT (§4 del data-model). El core también posee `Offer`, `Pledge`, `Party`, `Group`, `TrustEdge`, `LedgerEntry`, `Identity`.
|
||||||
|
|
||||||
|
### 4.2 El dominio **extiende** con tablas laterales 1:1 (no toca las del core)
|
||||||
|
Semillas añade `SeedItem(item_id → Item, species_id, cultivar, …)` y `SeedHolding(holding_id → Holding, harvest_year, germination…, plant-aware units)`. Es el patrón clásico de *tabla de extensión*: el core no importa el dominio; el dominio hace `JOIN` cuando necesita la vista rica. Encaja de perlas con Drift (DAOs separados por paquete, migraciones por paquete).
|
||||||
|
|
||||||
|
*Descartado:* una bolsa JSON de atributos genéricos en `Item` (pierdes tipado, consultas y migraciones). Quizá para extras que casi nunca se consultan, no como mecanismo principal.
|
||||||
|
|
||||||
|
### 4.3 El core habla con el dominio por **contratos**, no por conocimiento
|
||||||
|
- `Offer` referencia lo ofrecido por un **resumen desnormalizado** (`variety_summary`) + un id local opaco, no por FK a `SeedItem`. Así el core publica y sincroniza ofertas **sin entender de semillas**. *(Ya lo dejamos así en el modelo — la costura estaba bien encaminada.)*
|
||||||
|
- `Pledge` referencia partes + un texto de lo debido, no tablas de dominio.
|
||||||
|
- `LedgerEntry` apunta a su sujeto por un puntero opaco `(tabla_dominio, id)`; la mecánica append-only/CRDT es del core.
|
||||||
|
|
||||||
|
Dirección de dependencia, inviolable: **`app_seeds` depende de `commons_core`. Nunca al revés.** Si algún día el core necesita algo del dominio, es señal de que ese algo estaba mal colocado.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Dos generalizaciones que conviene fijar ya (son baratas y evitan migrar)
|
||||||
|
|
||||||
|
- **`Pledge.return_kind`** ∈ `{ similar, same_item }`. Semillas usan `similar` (devuelves *algo parecido*); cosas usan `same_item` (devuelves *ese* objeto). **Un solo enum captura toda la diferencia** entre el Plantare y el préstamo de una herramienta.
|
||||||
|
- **`Offer.offer_type`** incluye ya `lend` (devolver el mismo objeto) junto a `gift/exchange/sale/wanted`. Aditivo y barato; en semillas casi no se usa, pero deja la puerta lista.
|
||||||
|
- **Cantidad genérica**: el core define un tipo `Quantity { kind, amount?, label? }`; el *vocabulario* de `kind` (packet/handful/grams para todos; cob/pod/ear para semillas) y la sugerencia por familia **los pone el dominio**. Así "1 unidad / una caja" (cosas) y "una mazorca / una vaina" (semillas) usan la misma forma.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Estructura de repositorio (refinada)
|
||||||
|
|
||||||
|
```
|
||||||
|
tane/ (repo, hogar: ~/dev/tane)
|
||||||
|
packages/
|
||||||
|
commons_core/ ← identidad, Party, Group, TrustEdge, Offer, Pledge,
|
||||||
|
LedgerEntry, Item/Holding base, transporte+CRDT+sync,
|
||||||
|
discovery (geohash). SIN nada de semillas.
|
||||||
|
commons_ui/ (opcional) ← kit de widgets accesibles (targets grandes, foto-first,
|
||||||
|
icono+palabra) reutilizable por futuras apps.
|
||||||
|
apps/
|
||||||
|
app_seeds/ ← Tanemaki: Species, SeedItem/SeedHolding, germinación,
|
||||||
|
cosecha, unidades por familia, UI e i18n de semillas.
|
||||||
|
```
|
||||||
|
|
||||||
|
Drift: tablas del core en `commons_core`, tablas de extensión en `app_seeds`, migraciones versionadas **por paquete** (§5 del data-model). El día que exista `app_things`, reusa `commons_core` (y `commons_ui`) sin tocar `app_seeds`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Pragmatismo: no sobre-diseñar el motor ahora
|
||||||
|
|
||||||
|
- **Construye Tanemaki concreto primero.** Mete en `commons_core` solo lo *ya conocido-compartido* (identidad, Party, Offer, Pledge, trust, sync, discovery). Para `Item/Holding`, empieza con tabla base del core + tabla de extensión de semillas; si al llegar la app de cosas resulta incómodo, **refactorizar dentro del monorepo es barato** (todo el código está junto y se mueve de un tirón).
|
||||||
|
- No inventes interfaces genéricas "por si acaso" para partes cuya forma real solo conocerás cuando exista la segunda app. La regla del §1 ("en la duda, al dominio") protege de la abstracción prematura.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. A decidir
|
||||||
|
|
||||||
|
- **Nombre del paquete núcleo:** `commons_core` / `comunes_core` / otro. (Tu org es Comunes; `commons_core` en inglés encaja con la convención de código.)
|
||||||
|
- ¿Hacemos ya `commons_ui` como tercer paquete, o el kit accesible empieza dentro de `app_seeds` y se extrae cuando llegue la segunda app? (Coherente con §7: probablemente empezar en `app_seeds` y extraer luego — salvo que quieras forzar la disciplina desde el día 1.)
|
||||||
|
- ¿`Item` y `Holding` como tablas base en el core desde el inicio, o el core arranca sin ellas (solo Offer/Pledge/Party/trust/sync) y `Variety/Lot` viven enteras en el dominio hasta que la app de cosas revele la forma común? (Esto último es aún más conservador y quizá lo más sensato.)
|
||||||
|
- ¿Herramienta de monorepo: `pub workspaces` (nativo, Dart 3.5+) o `melos`?
|
||||||
|
|
@ -62,7 +62,7 @@ The lightweight offline catalog (Wikidata CC0 + GBIF CC-BY). Users may add local
|
||||||
Localized common names for the catalog live in `SpeciesCommonName(species_id, name, language)` and are bundled too.
|
Localized common names for the catalog live in `SpeciesCommonName(species_id, name, language)` and are bundled too.
|
||||||
|
|
||||||
### 2.3 `Lot` — a batch you hold
|
### 2.3 `Lot` — a batch you hold
|
||||||
Zero or more per `Variety` (the 2023 batch and the 2024 batch are different lots).
|
Zero or more per `Variety`, each with its **own year and its own unit** — and units may differ between lots of the same variety. A single maize variety can hold, at once: `[2 cobs · 2024]`, `[a few loose seeds · 2023]`, `[a cup · 2022]`. Even the same variety+year in two forms (2 cobs *and* loose kernels from 2024) is just two lots. A `Lot` = one homogeneous batch. This is exactly why quantity and unit live here, not on `Variety`.
|
||||||
|
|
||||||
| Column | Type | Notes |
|
| Column | Type | Notes |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue