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:

NeedAPI
read from a pageuseQuery
change state from a pageuseCommand
manage a formuseFormFor
call from the serverinvoke
validate inputEntity.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.

Built with Fougere — this site runs on the framework it documents.