The shortest path
When a task is urgent, the simplest available path will usually be used. Fougere therefore aims to make the conventional path shorter than bypassing it.
This page covers a design principle and its related tooling. fougere graph is available
today. CI graph checks and the refusal catalog described below are still under study.
One API for each common need
Common operations have a recommended API:
| Need | API |
|---|---|
| read from a page | useQuery |
| change state from a page | useCommand |
| manage a form | useFormFor |
| call from the server | invoke |
| validate input | Entity.validate |
These APIs share the same entity-and-operation designation. They also pass the context expected by collectors and normalize errors. A manually assembled call must handle those responsibilities explicitly.
How a codebase drifts
Architecture drift often comes from reasonable local fixes: a rule added to a form, then to a route, then to an adapter. Each change solves its immediate problem, but the rule no longer has one owner.
Fougere conventions place field rules in entities and business rules in operations. They do not guarantee that the model is correct, but they give elements that must change together a shared location.
Read the graph
The scanner knows about Fronds, their entities, and their references. The following command prints those relationships:
fougere graph
Post -> Author, Category (2 incoming)
Author
This graph can be read along three dimensions:
- coupling: the number of relationships attached to an entity;
- duplication: locations where the same rule is declared;
- ownership: the domain responsible for a rule or piece of data.
The current command exposes relationships and suggests clusters beyond six entities. It does not yet measure duplication or ownership automatically. Showing graph changes in pull requests remains a possible CI feature.
Errors that suggest a solution
A useful safeguard should identify the detected usage, its risk, and the recommended API.
The following shape illustrates the planned catalog; defineRefusal is not a shipped API:
defineRefusal({
id: 'F-042',
detect: (site) => isHandRolledCallToFrond(site),
message: (site) => dedent`
Hand-assembled call to the ${site.frond} Frond.
This call does not automatically pass identity and locale.
-> invoke('${site.suggestion}')
`,
origin: 'incident: context omitted from several routes',
});
These build-time errors would remain separate from business errors returned at runtime.
Limit
Fougere can check the structure of a declaration, not whether it is correct for the business. An incorrect rule remains incorrect when declared only once. Choosing concepts and their boundaries remains a design decision.
Next: Entities.