Come strutturare un'applicazione web moderna? Come assicurarsi che rimanga manutenibile nel tempo? In questa guida, creeremo un'applicazione Next.js utilizzando l'architettura esagonale e una robusta validazione dei form. Per rendere l'apprendimento più divertente, creeremo un sistema di registrazione per l'Accademia Jedi!
Il codice sorgente è disponibile su GitHub.
Indice
- Introduzione all'Architettura Esagonale
- Installazione e Struttura
- Il Dominio
- L'Infrastruttura
- L'Interfaccia Utente
- Approfondimenti
1. Introduzione all'Architettura Esagonale
1.1 Cos'è l'Architettura Esagonale?
L'Architettura Esagonale, nota anche come "Ports and Adapters", è un pattern architetturale che permette di creare applicazioni dove i componenti di business sono:
- Disaccoppiati dai dettagli tecnici
- Testabili in isolamento
- Indipendenti dal framework utilizzato
L'architettura è divisa in tre strati principali:
- Il Dominio: Contiene la pura logica di business
- Le Porte: Definiscono le interfacce per comunicare con il dominio
- Gli Adattatori: Implementano le porte per interagire con il mondo esterno
1.2 Perché Usare l'Architettura Esagonale?
Vantaggi
- Chiara separazione delle responsabilità
- Logica di business protetta e centralizzata
- Testing facilitato
- Flessibilità nel cambiare tecnologie
- Manutenzione semplificata
Casi d'Uso Ideali
- Applicazioni con regole di business complesse
- Progetti destinati ad evolversi nel tempo
- Sistemi che richiedono alta testabilità
- Applicazioni che necessitano di supportare multiple interfacce
[Il contenuto continua...]
2. Installazione e Struttura
2.1 Installazione Completa
1# Creazione del progetto
2npx create-next-app@latest jedi-academy
Quando crei il progetto, rispondi alle seguenti domande:
Would you like to use TypeScript? Yes
Would you like to use ESLint? Yes
Would you like to use Tailwind CSS? Yes
Would you like to use `src/` directory? Yes
Would you like to use App Router? (recommended) Yes
Would you like to customize the default import alias (@/*)? Yes
Using Next.js version 15.0.0+ with Turbopack
Una volta creato il progetto, installa le dipendenze:
1cd jedi-academy
2
3# Dipendenze principali per la gestione e validazione dei form
4npm install @hookform/resolvers zod react-hook-form
5
6# Database Prisma
7npm install @prisma/client
8npm install -D prisma
9
10# Installazione shadcn/ui
11npx shadcn@latest init
Durante l'inizializzazione di shadcn/ui, rispondi alle seguenti domande:
Would you like to use TypeScript (recommended)? Yes
Which style would you like to use? › Default
Which color would you like to use as base color? › Slate
Where is your global CSS file? › src/app/globals.css
Would you like to use CSS variables for colors? › Yes
Where is your tailwind.config.js located? › tailwind.config.js
Configure the import alias for components: › @/components
Configure the import alias for utils: › @/lib/utils
Are you using React Server Components? › Yes
# Installa i componenti necessari
npx shadcn@latest add form input select textarea button card toast table
1# Utilità per le date
2npm install date-fns
3
4# Database
5npx prisma init --datasource-provider sqlite
2.2 Struttura del Progetto
src/
├── app/
│ ├── actions/
│ │ └── register-padawan.ts
│ ├── components/
│ │ ├── ui/ # componenti shadcn/ui
│ │ ├── jedi-form.tsx
│ │ └── padawan-list.tsx
│ ├── lib/
│ │ └── utils.ts # utilità shadcn/ui
│ └── page.tsx
├── core/
│ ├── domain/
│ │ ├── models/
│ │ │ └── padawan.ts
│ │ ├── ports/
│ │ │ └── padawan-repository.ts
│ │ ├── services/
│ │ │ └── padawan-eligibility.ts
│ │ ├── validation/
│ │ │ └── padawan-schema.ts
│ │ └── events/
│ │ └── padawan-registered.ts
│ └── usecases/
│ └── register-padawan.ts
└── infrastructure/
└── db/
├── prisma/
│ ├── migrations/
│ └── schema.prisma
├── prisma.ts
└── repositories/
└── prisma-padawan-repository.ts
Questa struttura segue una chiara architettura esagonale con:
app/
- Strato interfaccia utente Next.js
actions/
- Server Actions per la gestione dei form
components/
- Componenti React con separazione UI/business
lib/
- Utilità condivise
core/
- Logica di business pura
domain/
- Modelli, porte e servizi di business
usecases/
- Casi d'uso dell'applicazione
infrastructure/
- Implementazioni tecniche
db/
- Configurazione Prisma e implementazioni repository
3. Il Dominio: Cuore dell'Applicazione
3.1 Comprendere il Dominio
Il dominio è il cuore della nostra applicazione. Qui traduciamo le regole di business in codice, indipendentemente da qualsiasi considerazione tecnica. Nel nostro caso, il dominio rappresenta tutte le regole che governano la registrazione all'Accademia Jedi.
Principi Chiave del Dominio
- Indipendenza Tecnologica
- Nessuna dipendenza dal framework
- Nessun codice relativo alla persistenza
- Nessuna logica UI
- Regole di Business Centralizzate
- Tutta la logica di business è nel dominio
- Le regole sono esplicite e documentate
- Le validazioni di business sono separate dalle validazioni tecniche
- Modellazione Ricca
- Uso di tipi e interfacce precise
- Value Objects per concetti di business importanti
- Entità con identità chiara
3.2 Componenti del Dominio
3.2.1 Modelli
I modelli rappresentano concetti fondamentali di business.
1// src/core/domain/models/padawan.ts
2export type PrimarySkill = "combat" | "healing" | "meditation" | "telepathy";
3
4export interface Padawan {
5 id?: string;
6 name: string;
7 age: number;
8 midichlorianCount: number;
9 homeworld: string;
10 primarySkill: PrimarySkill;
11 background: string;
12 createdAt?: Date;
13}
14
15export class Result<T> {
16 private constructor(
17 private readonly _isSuccess: boolean,
18 private readonly value?: T,
19 private readonly error?: string
20 ) {}
21
22 static success<T>(value: T): Result<T> {
23 return new Result<T>(true, value);
24 }
25
26 static failure<T>(error: string): Result<T> {
27 return new Result<T>(false, undefined, error);
28 }
29
30 getError(): string | undefined {
31 return this.error;
32 }
33
34 getValue(): T | undefined {
35 return this.value;
36 }
37
38 isSuccess(): boolean {
39 return this._isSuccess;
40 }
41}
3.2.2 Eventi del Dominio
Gli eventi rappresentano fatti importanti che sono accaduti nel dominio.
1// src/core/domain/events/padawan-registered.ts
2export interface DomainEvent {
3 occurredOn: Date;
4}
5
6export class PadawanRegistered implements DomainEvent {
7 occurredOn: Date = new Date();
8
9 constructor(
10 public readonly padawanId: string,
11 public readonly name: string,
12 public readonly midichlorianCount: number
13 ) {}
14}
3.2.3 Porte (Interfacce)
Le porte definiscono come il dominio interagisce con il mondo esterno.
1// src/core/domain/ports/padawan-repository.ts
2import { Padawan, Result } from "@/core/domain/models/padawan";
3
4export interface PadawanRepository {
5 save(padawan: Omit<Padawan, "id" | "createdAt">): Promise<Result<Padawan>>;
6 findById(id: string): Promise<Result<Padawan | null>>;
7 findAll(): Promise<Result<Padawan[]>>;
8}
3.2.4 Servizi del Dominio
I servizi del dominio incapsulano la logica di business che non si adatta naturalmente in un'entità.
1// src/core/domain/services/padawan-eligibility.ts
2import { Padawan, Result } from "@/core/domain/models/padawan";
3
4export class PadawanEligibilityService {
5 static check(padawan: Omit<Padawan, "id" | "createdAt">): Result<void> {
6 if (padawan.age < 4) {
7 return Result.failure("Troppo giovane per iniziare l'addestramento");
8 }
9 if (padawan.age > 30) {
10 return Result.failure("Troppo vecchio per iniziare l'addestramento");
11 }
12
13 if (padawan.midichlorianCount < 7000) {
14 return Result.failure("Conteggio midi-chlorian troppo basso");
15 }
16 if (padawan.midichlorianCount > 20000) {
17 return Result.failure("Conteggio midi-chlorian sospetto");
18 }
19
20 if (padawan.background.length < 20) {
21 return Result.failure("La storia personale richiede più dettagli");
22 }
23
24 return Result.success(void 0);
25 }
26}
3.2.5 Casi d'Uso
I casi d'uso orchestrano le interazioni tra diversi elementi del dominio per raggiungere una funzionalità di business.
1// src/core/usecases/register-padawan.ts
2import { Padawan, Result } from "@/core/domain/models/padawan";
3import { PadawanRepository } from "@/core/domain/ports/padawan-repository";
4import { PadawanEligibilityService } from "@/core/domain/services/padawan-eligibility";
5import { PadawanRegistered } from "@/core/domain/events/padawan-registered";
6import { EventDispatcher } from "@/core/domain/ports/event-dispatcher";
7
8export class RegisterPadawanUseCase {
9 constructor(
10 private readonly padawanRepository: PadawanRepository,
11 private readonly eventDispatcher: EventDispatcher
12 ) {}
13
14 async execute(
15 padawan: Omit<Padawan, "id" | "createdAt">
16 ): Promise<Result<Padawan>> {
17 const eligibilityResult = PadawanEligibilityService.check(padawan);
18 if (!eligibilityResult.isSuccess()) {
19 return Result.failure(eligibilityResult.getError()!);
20 }
21
22 const saveResult = await this.padawanRepository.save(padawan);
23 if (!saveResult.isSuccess()) {
24 return Result.failure(saveResult.getError()!);
25 }
26
27 const savedPadawan = saveResult.getValue()!;
28
29 await this.eventDispatcher.dispatch(
30 new PadawanRegistered(
31 savedPadawan.id!,
32 savedPadawan.name,
33 savedPadawan.midichlorianCount
34 )
35 );
36
37 return Result.success(savedPadawan);
38 }
39}
4. L'Infrastruttura
L'infrastruttura implementa le porte definite nel dominio per interagire con il mondo esterno.
4.1 Configurazione Prisma
1// prisma/schema.prisma
2generator client {
3 provider = "prisma-client-js"
4}
5
6datasource db {
7 provider = "sqlite"
8 url = env("DATABASE_URL")
9}
10
11model Padawan {
12 id String @id @default(uuid())
13 name String
14 age Int
15 midichlorianCount Int
16 homeworld String
17 primarySkill String
18 background String
19 createdAt DateTime @default(now())
20
21 @@map("padawans")
22}
4.2 Implementazione dell'Adattatore
1// src/infrastructure/db/repositories/prisma-padawan-repository.ts
2import { Padawan, Result } from "@/core/domain/models/padawan";
3import { PadawanRepository } from "@/core/domain/ports/padawan-repository";
4import { prisma } from "@/infrastructure/db/prisma";
5
6export class PrismaPadawanRepository implements PadawanRepository {
7 async save(
8 padawan: Omit<Padawan, "id" | "createdAt">
9 ): Promise<Result<Padawan>> {
10 try {
11 const saved = await prisma.padawan.create({
12 data: padawan,
13 });
14
15 return Result.success(this.mapPrismaPadawanToDomain(saved));
16 } catch (error: unknown) {
17 if (error instanceof Error) {
18 return Result.failure(`Errore durante il salvataggio: ${error.message}`);
19 }
20 return Result.failure('Si è verificato un errore sconosciuto durante il salvataggio');
21 }
22 }
23
24 async findById(id: string): Promise<Result<Padawan | null>> {
25 try {
26 const padawan = await prisma.padawan.findUnique({
27 where: { id },
28 });
29
30 return Result.success(
31 padawan ? this.mapPrismaPadawanToDomain(padawan) : null
32 );
33 } catch (error: unknown) {
34 if (error instanceof Error) {
35 return Result.failure(`Errore durante la ricerca: ${error.message}`);
36 }
37 return Result.failure('Si è verificato un errore sconosciuto durante la ricerca');
38 }
39 }
40
41 async findAll(): Promise<Result<Padawan[]>> {
42 try {
43 const padawans = await prisma.padawan.findMany({
44 orderBy: { createdAt: "desc" },
45 });
46
47 return Result.success(padawans.map(this.mapPrismaPadawanToDomain));
48 } catch (error: unknown) {
49 if (error instanceof Error) {
50 return Result.failure(`Errore durante il recupero: ${error.message}`);
51 }
52 return Result.failure('Si è verificato un errore sconosciuto durante il recupero');
53 }
54 }
55
56 private mapPrismaPadawanToDomain(padawan: any): Padawan {
57 return {
58 id: padawan.id,
59 name: padawan.name,
60 age: padawan.age,
61 midichlorianCount: padawan.midichlorianCount,
62 homeworld: padawan.homeworld,
63 primarySkill: padawan.primarySkill,
64 background: padawan.background,
65 createdAt: padawan.createdAt,
66 };
67 }
68}
5. L'Interfaccia Utente
5.1 Validazione con Zod
La validazione dei dati di input utilizza Zod per garantire la coerenza con il nostro dominio.
1// src/core/domain/validation/padawan-schema.ts
2import { z } from "zod";
3
4export const padawanSchema = z.object({
5 name: z
6 .string()
7 .min(2, "Un Padawan deve avere un nome più lungo di 2 caratteri")
8 .max(50, "Nemmeno Qui-Gon Jinn ha un nome così lungo"),
9 age: z.coerce
10 .number()
11 .min(4, "Persino Grogu ha iniziato il suo addestramento a 4 anni")
12 .max(30, "Il Consiglio considera questo il limite d'età per iniziare l'addestramento"),
13 midichlorianCount: z.coerce
14 .number()
15 .min(7000, "Il conteggio midi-chlorian è troppo basso per l'addestramento Jedi")
16 .max(20000, "Persino Anakin aveva solo 20000 midi-chlorian"),
17 homeworld: z
18 .string()
19 .min(2, "Il nome del tuo pianeta è richiesto"),
20 primarySkill: z.enum(["combat", "healing", "meditation", "telepathy"], {
21 errorMap: () => ({
22 message: "Questa abilità non è riconosciuta dall'Ordine Jedi",
23 }),
24 }),
25 background: z
26 .string()
27 .min(20, "Raccontaci di più sul tuo viaggio"),
28});
29
30export type PadawanFormData = z.infer<typeof padawanSchema>;
Ora possiamo generare il db.
Per ulteriori esplorazioni, Prisma fornisce un client per visualizzare il db.
5.2 Server Action per la Registrazione
1// src/app/actions/register-padawan.ts
2'use server'
3
4import { PrismaPadawanRepository } from '@/infrastructure/db/repositories/prisma-padawan-repository';
5import { ConsoleEventDispatcher } from '@/infrastructure/services/event-dispatcher';
6import { RegisterPadawanUseCase } from '@/core/usecases/register-padawan';
7import { padawanSchema } from '@/core/domain/validation/padawan-schema';
8import { revalidatePath } from 'next/cache';
9
10const padawanRepository = new PrismaPadawanRepository();
11const eventDispatcher = new ConsoleEventDispatcher();
12const registerPadawan = new RegisterPadawanUseCase(
13 padawanRepository,
14 eventDispatcher
15);
16
17export async function handlePadawanRegistration(formData: FormData) {
18 try {
19 const validatedData = padawanSchema.parse(Object.fromEntries(formData));
20 const result = await registerPadawan.execute(validatedData);
21
22 if (!result.isSuccess()) {
23 return {
24 success: false,
25 message: result.getError()
26 };
27 }
28
29 revalidatePath('/');
30 return {
31 success: true,
32 message: "Che la Forza sia con te, giovane Padawan! 🌟"
33 };
34 } catch (error) {
35 return {
36 success: false,
37 message: "È stata rilevata una perturbazione nella Forza... 🌀"
38 };
39 }
40}
5.3 Componente Form
1// src/app/components/jedi-form.tsx
2"use client";
3
4import { useForm } from "react-hook-form";
5import { zodResolver } from "@hookform/resolvers/zod";
6import { useToast } from "@/hooks/use-toast";
7
8import {
9 Form,
10 FormControl,
11 FormField,
12 FormItem,
13 FormLabel,
14 FormMessage,
15} from "@/components/ui/form";
16import {
17 Card,
18 CardHeader,
19 CardTitle,
20 CardDescription,
21 CardContent,
22} from "@/components/ui/card";
23import { Input } from "@/components/ui/input";
24import { Button } from "@/components/ui/button";
25import { Textarea } from "@/components/ui/textarea";
26import {
27 Select,
28 SelectContent,
29 SelectItem,
30 SelectTrigger,
31 SelectValue,
32} from "@/components/ui/select";
33import {
34 padawanSchema,
35 type PadawanFormData,
36} from "@/core/domain/validation/padawan-schema";
37import { handlePadawanRegistration } from "../actions/register-padawan";
38
39export function JediRegistrationForm() {
40 const { toast } = useToast();
41 const form = useForm<PadawanFormData>({
42 resolver: zodResolver(padawanSchema),
43 defaultValues: {
44 name: "",
45 age: 0,
46 midichlorianCount: 7000,
47 homeworld: "",
48 primarySkill: "combat",
49 background: "",
50 },
51 });
52
53 async function onSubmit(data: PadawanFormData) {
54 const formData = new FormData();
55 Object.entries(data).forEach(([key, value]) => {
56 formData.append(key, value.toString());
57 });
58
59 const result = await handlePadawanRegistration(formData);
60
61 toast({
62 title: result.success ? "Registrazione completata" : "Errore",
63 description: result.message,
64 variant: result.success ? "default" : "destructive",
65 });
66
67 if (result.success) {
68 form.reset();
69 }
70 }
71
72 return (
73 <Card className="w-full max-w-md mx-auto">
74 <CardHeader>
75 <CardTitle>Accademia Jedi</CardTitle>
76 <CardDescription>
77 Invia la tua candidatura per unirti all'Ordine
78 </CardDescription>
79 </CardHeader>
80 <CardContent>
81 <Form {...form}>
82 <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
83 <FormField
84 control={form.control}
85 name="name"
86 render={({ field }) => (
87 <FormItem>
88 <FormLabel>Nome del Padawan</FormLabel>
89 <FormControl>
90 <Input placeholder="Luke Skywalker" {...field} />
91 </FormControl>
92 <FormMessage />
93 </FormItem>
94 )}
95 />
96
97 <FormField
98 control={form.control}
99 name="age"
100 render={({ field }) => (
101 <FormItem>
102 <FormLabel>Età</FormLabel>
103 <FormControl>
104 <Input
105 type="number"
106 min="4"
107 max="30"
108 placeholder="19"
109 {...field}
110 onChange={(e) => field.onChange(parseInt(e.target.value))}
111 />
112 </FormControl>
113 <FormMessage />
114 </FormItem>
115 )}
116 />
117
118 <FormField
119 control={form.control}
120 name="midichlorianCount"
121 render={({ field }) => (
122 <FormItem>
123 <FormLabel>Conteggio Midi-chlorian</FormLabel>
124 <FormControl>
125 <Input
126 type="number"
127 min="7000"
128 max="20000"
129 step="100"
130 {...field}
131 onChange={(e) => field.onChange(parseInt(e.target.value))}
132 />
133 </FormControl>
134 <FormMessage />
135 </FormItem>
136 )}
137 />
138
139 <FormField
140 control={form.control}
141 name="homeworld"
142 render={({ field }) => (
143 <FormItem>
144 <FormLabel>Mondo d'origine</FormLabel>
145 <FormControl>
146 <Input placeholder="Tatooine" {...field} />
147 </FormControl>
148 <FormMessage />
149 </FormItem>
150 )}
151 />
152
153 <FormField
154 control={form.control}
155 name="primarySkill"
156 render={({ field }) => (
157 <FormItem>
158 <FormLabel>Abilità Primaria</FormLabel>
159 <Select
160 onValueChange={field.onChange}
161 defaultValue={field.value}
162 >
163 <FormControl>
164 <SelectTrigger>
165 <SelectValue placeholder="Scegli un'abilità" />
166 </SelectTrigger>
167 </FormControl>
168 <SelectContent>
169 <SelectItem value="combat">
170 Combattimento con la Spada Laser ⚔️
171 </SelectItem>
172 <SelectItem value="healing">
173 Guarigione della Forza 💚
174 </SelectItem>
175 <SelectItem value="meditation">
176 Meditazione Profonda 🧘
177 </SelectItem>
178 <SelectItem value="telepathy">Telepatia 🧠</SelectItem>
179 </SelectContent>
180 </Select>
181 <FormMessage />
182 </FormItem>
183 )}
184 />
185
186 <FormField
187 control={form.control}
188 name="background"
189 render={({ field }) => (
190 <FormItem>
191 <FormLabel>La Tua Storia</FormLabel>
192 <FormControl>
193 <Textarea
194 placeholder="Raccontaci del tuo viaggio..."
195 className="resize-none"
196 rows={4}
197 {...field}
198 />
199 </FormControl>
200 <FormMessage />
201 </FormItem>
202 )}
203 />
204
205 <Button type="submit" className="w-full">
206 Invia la mia candidatura 🌟
207 </Button>
208 </form>
209 </Form>
210 </CardContent>
211 </Card>
212 );
213}
5.4 Lista dei Padawan
1// src/app/components/padawan-list.tsx
2"use client";
3
4import {
5 Table,
6 TableBody,
7 TableCaption,
8 TableCell,
9 TableHead,
10 TableHeader,
11 TableRow,
12} from "@/components/ui/table";
13import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
14import { format } from "date-fns";
15import { enUS } from "date-fns/locale";
16import type { Padawan } from "@/core/domain/models/padawan";
17
18type PadawanListProps = {
19 padawans: Padawan[];
20};
21
22const skillEmoji = {
23 combat: "⚔️ Combattimento con la Spada Laser",
24 healing: "💚 Guarigione della Forza",
25 meditation: "🧘 Meditazione Profonda",
26 telepathy: "🧠 Telepatia",
27} as const;
28
29export function PadawanList({ padawans }: PadawanListProps) {
30 if (padawans.length === 0) {
31 return (
32 <Card>
33 <CardContent className="pt-6">
34 <p className="text-center text-muted-foreground">
35 Nessun Padawan registrato ancora...
36 </p>
37 </CardContent>
38 </Card>
39 );
40 }
41
42 return (
43 <Card>
44 <CardHeader>
45 <CardTitle>Lista dei Candidati</CardTitle>
46 </CardHeader>
47 <CardContent>
48 <Table>
49 <TableHeader>
50 <TableRow>
51 <TableHead>Nome</TableHead>
52 <TableHead>Età</TableHead>
53 <TableHead>Mondo d'origine</TableHead>
54 <TableHead>Specialità</TableHead>
55 <TableHead>Midi-chlorian</TableHead>
56 <TableHead>Registrazione</TableHead>
57 </TableRow>
58 </TableHeader>
59 <TableBody>
60 {padawans.map((padawan) => (
61 <TableRow key={padawan.id}>
62 <TableCell className="font-medium">{padawan.name}</TableCell>
63 <TableCell>{padawan.age} anni</TableCell>
64 <TableCell>{padawan.homeworld}</TableCell>
65 <TableCell>{skillEmoji[padawan.primarySkill]}</TableCell>
66 <TableCell>
67 {padawan.midichlorianCount.toLocaleString()}
68 </TableCell>
69 <TableCell>
70 {format(new Date(padawan.createdAt!), "Pp", { locale: enUS })}
71 </TableCell>
72 </TableRow>
73 ))}
74 </TableBody>
75 </Table>
76 </CardContent>
77 </Card>
78 );
79}
Questi componenti utilizzano shadcn/ui per creare un'interfaccia utente moderna e accessibile, con:
- JediRegistrationForm:
- Validazione completa con react-hook-form e Zod
- Gestione degli errori con messaggi contestuali
- Feedback utente con toast
- Reset del form dopo l'invio riuscito
- PadawanList:
- Visualizzazione tabulare dei dati
- Gestione dello stato vuoto
- Formattazione delle date in inglese
- Icone per le abilità
- Stile coerente con il form
Tutto si integra perfettamente nella nostra architettura esagonale rimanendo nello strato di presentazione, senza logica di business.
6. Approfondimenti
6.1 Miglioramenti Tecnici
- Cache e Performance
1// Esempio di implementazione cache Redis
2export class RedisPadawanCache {
3 async cacheResults(key: string, data: any): Promise<void> {
4 await redis.set(key, JSON.stringify(data), "EX", 3600);
5 }
6}
- Monitoraggio e Logging
1// Servizio di monitoraggio
2export class ApplicationMonitoring {
3 trackDomainEvent(event: DomainEvent): void {
4 // Invio al servizio di monitoraggio
5 }
6}
6.2 Evoluzioni Funzionali
- Flusso di Approvazione
- Sistema di Valutazione
- Gestione delle Promozioni
- Comunicazione tra Padawan
Conclusione
L'architettura esagonale ci ha permesso di creare un'applicazione che è:
- Manutenibile
- Chiara separazione delle responsabilità
- Codice di business isolato e protetto
- Scalabile
- Facile aggiunta di nuove funzionalità
- Possibilità di cambiare infrastruttura
- Supporto per nuovi canali di input/output
Per andare oltre, potresti:
- Aggiungere l'autenticazione
- Implementare un sistema di cache
- Configurare il monitoraggio
- Impostare test automatizzati
- Aggiungere webhook
Ricorda: una buona architettura è come la Forza - deve essere in equilibrio! 🌟
[Fine della traduzione]