Gérer la complexité avec les types opaques
Sébastien BESNIER

Sébastien BESNIER



Code

Consultante
Produit les rapports

Consulte les rapports
Facteur d'émission
Combien de kg de CO2 pour 1kg de produit ?


Quel est le plus émetteur ?
Banane sous plastique VS steak de boeuf local ?
Facteur d'émission


TOTAL : 1.09 kgCO2e/kg
0.29523642* kgCO2e/ kg
Agriculture
0.45139732* kgCO2e/ kg
Transport
0.35** kgCO2e/ kg
Emballage


TOTAL : 1.09 kgCO2e/kg
34.7032563* kgCO2e/ kg
Agriculture
34.7032563* kgCO2e/ kg
Agriculture
* : source Agribalyse 3.2 Tableur produits alimentaires VF 20_11_24
** : Estimation avec un sachet plastique de 10 g pour une banane de 70 g (sans la peau) en comptant 2.5KgCO2e pour 1kg de plastique
34.7032563 kgCO2e/ kg
Agriculture


34.7032563.toPrecision(4) === "34.70"
Number(34.7032563.toPrecision(4)) === 34.7034.7032563.toPrecision(4) === "34.70"
- Comment trouver tous les usages ?
- Que les prochains développeurs ne cassent pas cette règle ?
- Comment trouver tous les usages ?
- Que les prochains développeurs ne cassent pas cette règle ?
Un type
FourDigits
pour les facteurs d'émission
non assignable à number
const Computation = (props: Data) => {
const total = props.quantity * props.emissionFactor;
return <div>
<div>{total}</div>
<EF emissionFactor={props.emissionFactor} />
</div>;
}
const EF = (props: {emissionFactor: number})=>
<div>{emissionFactor}</div>;AVANT
Type number
const Computation = (props: Data) => {
const total = props.quantity * props.emissionFactor;
return <div>
<div>{total}</div>
<EF emissionFactor={props.emissionFactor} />
</div>;
}
const EF = (props: {emissionFactor: number})=>
<div>{emissionFactor}</div>;APRÈS
Type FourDigits
Cannot multiply by FourDigits
FourDigits is not number
const Computation = (props: Data) => {
const total = props.quantity * toNumber(
props.emissionFactor
);
return <div>
<div>{total}</div>
<EF emissionFactor={props.emissionFactor} />
</div>;
}
const EF = (props: {emissionFactor: FourDigits})=>
<div>{emissionFactor}</div>;APRÈS
toNumber: (v: FourDigits) => number
Cannot display FourDigits
const Computation = (props: Data) => {
const total = props.quantity * toNumber(
props.emissionFactor
);
return <div>
<div>{total}</div>
<EF emissionFactor={props.emissionFactor} />
</div>;
}
const EF = (props: {emissionFactor: FourDigits})=>
<div>{display(emissionFactor)}</div>;APRÈS
display: (v: FourDigits) => string
toNumber: (v: FourDigits) => number
- Comment trouver tous les usages ?
- Que les prochains développeurs ne cassent pas cette règle ?
✅
✅
Un type
FourDigits
pour les facteurs d'émission
non assignable à number
declare const brand: unique symbol;
export type FourDigits = { [brand]: "FourDigits"};
export const toFourDigits = (n: number) =>
n as FourDigits;
export const toNumber = (v: FourDigits) =>
Number((v as number).toPrecision(4));
export const display = (v: FourDigits) =>
(v as number).toPrecision(4);Symbole non exporté, impossible à construire en dehors du module
`as` sur FourDigits uniquement dans ce module
fourDigits.ts


Navigateur
Serveur
HTTP


NodeJS
React
tRPC
Les types sont partagés !


NodeJS
React
tRPC
Les types sont partagés !
Validation des données avec Zod
// ...
export const toFourDigits = (n: number) =>
n as FourDigits;
const zFourDigits = z
.number()
.transform(toFourDigits) as ZodType<FourDigits,
FourDigits>;
fourDigits.ts

Navigateur
Serveur


NodeJS
React

tRPC
Les types sont partagés !


Navigateur
Serveur


NodeJS
React

BDD

tRPC
Postgresql
Prisma

NodeJS

Postgresql
Prisma
model Report {
// ...
emissionFactor Float
// ...
}schema.prisma
CREATE TABLE (
-- ...
emissionFactor
DOUBLE PRECISION
NOT NULL,
-- ...
)SQL
const r = report.findFirst({
emissionFactor: true
});
r.emissionFactor: numberserver.ts
Client.ts

NodeJS

Postgresql
Prisma
model Report {
// ...
emissionFactor Float /// type: FourDigits
// ...
}schema.prisma
CREATE TABLE (
-- ...
emissionFactor
DOUBLE PRECISION
NOT NULL,
-- ...
)SQL
const r = report.findFirst({
emissionFactor: true
});
r.emissionFactor: FourDigitsserver.ts
CustomClient.ts


Navigateur
Serveur


NodeJS
React

BDD

tRPC
Postgresql
Prisma
(modifié)
FourDigits
FourDigits
FourDigits
Markdown
**texte** affiché au lieu de texte
Aucune indication que le texte en cours d'édition va être affiché comme du Markdown
Markdown
declare const brand: unique symbol;
export type MarkdownString = { [brand]: "Markdown"};
export const toMarkdown = (n: string) =>
n as MarkdownString;
export const Markdown = (props: {
children: MarkdownString
}): ReactNode => {/*...*/}
export cont MarkdownEditor = (props: {
input: MarkdownString;
setInput: (input: MarkdownString)=> void;
}): ReactNode => {/*...*/ }markdown.ts
Uuid
declare const brand: unique symbol;
export type Uuid = { [brand]: "Uuid"};
uuid.ts
userUuid === userNameCannot compare Uuid and string
userUuid === companyUuid✅

Uuid<Entity>
userUuid === userNameCannot compare Uuid<"User"> and string
userUuid === companyUuiddeclare const brand: unique symbol;
export type Uuid<Entity> = { [brand]: Entity };
uuid.ts
Cannot compare Uuid<"User"> and Uuid<"Company">

NodeJS

Postgresql
Prisma
model Report {
// ...
emissionFactor Float /// type: FourDigits
// ...
}schema.prisma
CREATE TABLE (
-- ...
emissionFactor
DOUBLE PRECISION
NOT NULL,
-- ...
)SQL
const r = report.findMany({
emissionFactor: true
});
r.emissionFactor: FourDigitsserver.ts
CustomClient.ts

NodeJS

Postgresql
Prisma
model Report {
// ...
emissionFactor Float /// type: FourDigits
// ...
}schema.prisma
CREATE TABLE (
-- ...
emissionFactor
DOUBLE PRECISION
NOT NULL,
-- ...
)SQL
const r = report.findMany({
emissionFactor: true
});
r.emissionFactor: FourDigitsserver.ts
CustomClient.ts
- La clef primaire de la table
UserestUuid<"User"> - La clef étrangère
companyUuidestUuid<"Company"> - Champs typés à la création et modification et dans les clauses comme
where: { companyUuid: input }


Navigateur
Serveur


NodeJS
React

BDD

tRPC
Postgresql
Prisma
(modifié)
Uuid<Entity>
Uuid<Entity>
Uuid<Entity>
Résumé
- Boeuf 30 fois plus émissif que végétal
- Type opaque : seul le module le définissant peut le manipuler
- Typage "bout à bout" : de la BDD au Front
Gérer la complexité avec les types opaques
Sébastien BESNIER
deck
By sebbes
deck
- 103