Comprendre avec une mise en œuvre pratique
Nous sommes en 2023, et la vague tendance sur les frameworks JavaScript se dirige actuellement vers Remixer. Si vous n’en avez pas entendu parler, je vous recommande vivement de le consulter. Remix est un concurrent de Next.js, et il existe un excellent article de blog sur leurs différences.
Je pense que Remix est un excellent pari pour l’avenir du monde JavaScript, et pour y contribuer, je vais montrer comment nous pouvons utiliser une architecture plus résiliente et évolutive avec ce framework. Et si vous voulez en savoir plus sur cette architecture, j’ai un autre article dessus — Vérifiez le ici.
Remix est un peu un cadre opiniâtre. Il vous indique où vos itinéraires doivent être – car il a un routage basé sur des fichiers – et il a également quelques fonctions par défaut pour qu’il puisse faire sa magie sous le capot, comme :
export async function loader() {}
export async function action() {}
export function links() {}
Ce sont toutes des fonctions qui doivent être exportées de l’intérieur de vos routes et Remix les utilisera automatiquement pour les faire fonctionner côté serveur.
Remix est entièrement côté serveur – différent de Next.js, où vous pouvez avoir SSG, SSR et ISR. Mais ne vous méprenez pas, même si Remix est côté serveur, il est conçu pour fonctionner et être utilisé comme un SPA, avec l’utilisation de ces fonctions exactes.
Laissez-moi vous montrer un exemple de la façon dont ces fonctions seraient utilisées :
import styles from './index.css'export default function AddTodoPage() {
const todoList = useLoaderData();
return (
<main>
<TodoForm />
<TodoList todoList={todoList} />
</main>
);
}
export async function loader() {
const todoList = await getTodoList();
return todoList;
}
export async function action({ request }) {
const formData = await request.formData();
const todoData = Object.fromEntries(formData);
await addTodo(todoData);
return true;
// You can redirect here
// return redirect('/todo-list');
}
export function links() {
return [{ rel: 'stylesheet', href: styles }]
}
Ici, nous avons quatre fonctions :
export default function AddTodoPage() {}
—export async function loader() {}
— La fonction de chargement est l’endroit où nous récupérons les données de la page. Par exemple, récupérer la liste des tâches ajoutées, et cela se produira côté serveur.export async function action() {}
— L’action, où nous gérons les trucs CRUD. Remix a le sien<Form />
composant, et il est directement connecté à cette fonction, tout ce que vous soumettez dans le formulaire sera géré par leaction
.export function links() {}
— Remix vous permet d’importer et d’exporter des styles de n’importe où, tout ce dont vous avez besoin est de les afficher sur votre fichier de route, via la fonction de liens.
Bien que tout soit assis sur le même fichier, il semble simple et facile à utiliser, mais il ne s’adapte pas très bien (pas de problème si vous voulez juste un projet simple, vous ne devriez pas vous soucier de Clean Arch si c’est le cas) .
En y ajoutant quelques couches, nous pouvons séparer les préoccupations et nous concentrer sur le fait de ne pas toucher à notre noyau/domaine.
Comme indiqué dans l’exemple de code ci-dessus, Remix nous oblige à exporter un composant par défaut pour être notre page, et nous pouvons le faire comme ceci :
// src/routes/ducks/new-duck.tsx
import NewDuckPage from '../../presentation/ducks/new-duck';export default NewDuckPage;
// src/presentation/ducks/new-duck.tsx
import { Form, Link } from '@remix-run/react';export default function NewDuckPage() {
return (
<div>
<h1>Add your duck here</h1>
<Form method="post">
<label htmlFor="name"></label>
<input type="text" name="name" id="name" />
<button>Add duck</button>
</Form>
<Link to="/">Home</Link>
</div>
);
}
De cette façon, nous avons un itinéraire uniquement pour être un routage basé sur des fichiers, tandis que tous nos composants et sous-composants restent dans le dossier de présentation.
Couche d’application
Dans la couche d’application, nous aurons des crochets et les connexions qu’ils font avec le référentiel. Nous aurons 3 fichiers ici. Avec le crochet personnalisé, nous gérerons tout ce qui concerne la page elle-même, et les deux fichiers pour le action
et loader
fonctions dont Remix a besoin.
// src/presentation/ducks/new-duck.tsx
import { Form, Link } from '@remix-run/react';
import { useNewDuck, addDuckAction, getDucksLoader } from '../../application/duck/';
import { DuckList } from './components/duck-list';export default function NewDuckPage() {
const { isSubmitting } = useNewDuck();
return (
<div>
<h1>Add your ducko here</h1>
<Form method="post">
<label htmlFor="name"></label>
<input type="text" name="name" id="name" />
<button disabled={isSubmitting}>Add ducko</button>
</Form>
<DuckList />
<Link to="/">Home</Link>
</div>
);
}
export { getDucksLoader as loader, addDuckAction as action };
Comme il s’agit de notre page, nous devons exporter le loader
et action
d’ici, et importez/exportez-le sur la route, comme ceci :
// src/routes/ducks/new-duck.tsximport Index, { loader, action } from '../../presentation/ducks/new-duck';
export { loader, action }
export default Index;
Maintenant, Remix les gérera sur le serveur et tout se passera aussi bien que s’il était défini sur le fichier de route.
La couche adaptatrice
Notre application utilise un référentiel pour gérer les demandes vers l’extérieur, où il lit et envoie des données. Pour cette démonstration, j’utilise un simple .json
fichier et l’y enregistrer.
// src/adapter/repository/duck/duckFileRepository.ts
import fs from 'fs/promises';
import { DuckE } from 'packages/duck/src/domain/entity';
import { DuckRepositoryI } from 'packages/duck/src/domain/ports/DuckRepository';const filepath = 'db/ducks.json';
export function DuckFileRepository(): DuckRepositoryI {
function addDuck(ducks: DuckE[]) {
return fs.writeFile(filepath, JSON.stringify({ ducks }));
}
async function getDucks() {
const rawFileContent = await fs.readFile(filepath, { encoding: 'utf-8' });
const data = JSON.parse(rawFileContent);
const storedDucks = data.ducks ?? [];
return storedDucks;
}
return { addDuck, getDucks };
}
Et nous l’exportons en rejoignant tous les référentiels, au cas où nous en ajouterions d’autres à l’avenir :
// src/adapter/repository/duck/index.ts
import { DuckFileRepository } from './duck/duckFileRepository';function buildRepository() {
return {
duck: { ...DuckFileRepository() },
};
}
export const Repository = buildRepository();
De cette façon, peu importe ce qui est utilisé en dessous, ce sera toujours ce que nous attendons – grâce aux ports sur notre domaine – et l’obligeant à suivre l’interface.
Le cœur de notre application, la couche Domaine
Le domaine est la couche la plus sûre que nous ayons, c’est le noyau et le cœur de l’application, où nous plaçons les services de domaine, les ports, la logique métier et les entités. Il n’a accès à rien à l’extérieur de lui, et il ne change pas non plus pour les besoins de l’extérieur.
Par souci de simplicité de cet exemple, notre entité n’aura qu’un seul attribut.
Entité:
// src/domain/entity/duck.ts
export class DuckE {
constructor(
readonly name: string
) {}
}
Ports :
// src/domain/ports/DuckRepository.ts
import { DuckE } from "../entity/duck";export interface DuckRepositoryI {
getDucks: () => Promise<DuckE[]>;
addDuck: (ducks: DuckE[]) => void;
}
Comme nous pouvons le voir, les ports du référentiel sont ce qui va dire à notre adaptateur ce dont nous avons besoin, et en respectant le port, nous pouvons être sûrs qu’il fonctionnera toujours comme prévu.
Sur l’entité, nous pouvons avoir des validations pour notre classe, et aussi à l’intérieur du domaine, nous pourrions avoir un domain/services/duck.ts
pour plus de logique métier de domaine, qui pourrait être utilisée dans plus que l’entité Duck (si vous jetez un œil à cet autre projetvous en verrez un exemple).
Comme nous pouvons le voir, Remix fonctionne très bien avec cette architecture, et elle est plus fiable, résiliente et testable puisque les préoccupations sont toutes divisées en leurs propres petites parties.
J’espère vraiment que vous essayez ce cadre. Vous pouvez vérifier le référentiel pour le code source ici. N’hésitez pas à me contacter mon LinkedIn ou déposez un commentaire ici. Merci d’avoir lu et bonne année !