Wie strukturiert man eine moderne Webanwendung? Wie stellt man sicher, dass sie ĂŒber die Zeit wartbar bleibt? In diesem Leitfaden erstellen wir eine Next.js-Anwendung mit hexagonaler Architektur und robuster Formularvalidierung. Um das Lernen unterhaltsamer zu gestalten, erstellen wir ein Registrierungssystem fĂŒr die Jedi-Akademie!
Der Quellcode ist auf GitHub verfĂŒgbar.
Inhaltsverzeichnis
- EinfĂŒhrung in die Hexagonale Architektur
- Installation und Struktur
- Die Domain
- Die Infrastruktur
- Die BenutzeroberflÀche
- WeiterfĂŒhrende Aspekte
1. EinfĂŒhrung in die Hexagonale Architektur
1.1 Was ist Hexagonale Architektur?
Hexagonale Architektur, auch bekannt als "Ports and Adapters", ist ein Architekturmuster, das die Erstellung von Anwendungen ermöglicht, bei denen GeschÀftskomponenten:
- Von technischen Details entkoppelt sind
- Isoliert testbar sind
- UnabhÀngig vom verwendeten Framework sind
Die Architektur ist in drei Hauptschichten unterteilt:
- Die Domain: EnthÀlt reine GeschÀftslogik
- Die Ports: Definieren Schnittstellen zur Kommunikation mit der Domain
- Die Adapter: Implementieren Ports zur Interaktion mit der AuĂenwelt
1.2 Warum Hexagonale Architektur verwenden?
Vorteile
- Klare Trennung der ZustÀndigkeiten
- GeschĂŒtzte und zentralisierte GeschĂ€ftslogik
- Erleichterte Tests
- FlexibilitÀt beim Technologiewechsel
- Vereinfachte Wartung
Ideale AnwendungsfÀlle
- Anwendungen mit komplexen GeschÀftsregeln
- Projekte, die sich ĂŒber die Zeit entwickeln sollen
- Systeme, die hohe Testbarkeit erfordern
- Anwendungen, die mehrere Schnittstellen unterstĂŒtzen mĂŒssen
[Inhalt wird fortgesetzt...]
2. Installation und Struktur
2.1 VollstÀndige Installation
1# Projekterstellung
2npx create-next-app@latest jedi-academy
Beantworten Sie bei der Projekterstellung folgende Fragen:
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
Nach der Projekterstellung installieren Sie die AbhÀngigkeiten:
1cd jedi-academy
2
3# HauptabhĂ€ngigkeiten fĂŒr Formular-Management und Validierung
4npm install @hookform/resolvers zod react-hook-form
5
6# Prisma Datenbank
7npm install @prisma/client
8npm install -D prisma
9
10# shadcn/ui Installation
11npx shadcn@latest init
Bei der Initialisierung von shadcn/ui beantworten Sie folgende Fragen:
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
# Installation der notwendigen Komponenten
npx shadcn@latest add form input select textarea button card toast table
1# Datum-Utilities
2npm install date-fns
3
4# Datenbank
5npx prisma init --datasource-provider sqlite
2.2 Projektstruktur
src/
âââ app/
â âââ actions/
â â âââ register-padawan.ts
â âââ components/
â â âââ ui/ # shadcn/ui Komponenten
â â âââ jedi-form.tsx
â â âââ padawan-list.tsx
â âââ lib/
â â âââ utils.ts # shadcn/ui Utilities
â âââ 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
Diese Struktur folgt einer klaren hexagonalen Architektur mit:
app/ - Next.js BenutzeroberflÀchen-Schicht
actions/ - Server Actions fĂŒr Formularverarbeitung
components/ - React-Komponenten mit UI/Business-Trennung
lib/ - Gemeinsame Utilities
core/ - Reine GeschÀftslogik
domain/ - Modelle, Ports und GeschÀftsdienste
usecases/ - AnwendungsfÀlle
infrastructure/ - Technische Implementierungen
db/ - Prisma-Konfiguration und Repository-Implementierungen
3. Die Domain: Kern der Anwendung
3.1 VerstÀndnis der Domain
Die Domain ist das Herz unserer Anwendung. Hier ĂŒbersetzen wir GeschĂ€ftsregeln in Code, unabhĂ€ngig von technischen Ăberlegungen. In unserem Fall reprĂ€sentiert die Domain alle Regeln, die die Registrierung an der Jedi-Akademie bestimmen.
Wichtige Domain-Prinzipien
- Technologische UnabhÀngigkeit
- Keine Framework-AbhÀngigkeiten
- Kein persistenzbezogener Code
- Keine UI-Logik
- Zentralisierte GeschÀftsregeln
- Gesamte GeschÀftslogik in der Domain
- Regeln sind explizit und dokumentiert
- GeschÀftsvalidierungen sind von technischen Validierungen getrennt
- Reichhaltiges Modellieren
- Verwendung prÀziser Typen und Schnittstellen
- Wertobjekte fĂŒr wichtige GeschĂ€ftskonzepte
- EntitÀten mit klarer IdentitÀt
3.2 Domain-Komponenten
3.2.1 Modelle
Modelle reprÀsentieren fundamentale GeschÀftskonzepte.
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 Domain-Events
Events reprÀsentieren wichtige Ereignisse, die in der Domain aufgetreten sind.
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 Ports (Schnittstellen)
Ports definieren, wie die Domain mit der AuĂenwelt interagiert.
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 Domain-Services
Domain-Services kapseln GeschĂ€ftslogik, die nicht natĂŒrlich in eine EntitĂ€t passt.
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("Zu jung, um mit dem Training zu beginnen");
8 }
9 if (padawan.age > 30) {
10 return Result.failure("Zu alt, um mit dem Training zu beginnen");
11 }
12
13 if (padawan.midichlorianCount < 7000) {
14 return Result.failure("Midichlorian-Anzahl ist zu niedrig");
15 }
16 if (padawan.midichlorianCount > 20000) {
17 return Result.failure("VerdÀchtige Midichlorian-Anzahl");
18 }
19
20 if (padawan.background.length < 20) {
21 return Result.failure("Persönliche Geschichte benötigt mehr Details");
22 }
23
24 return Result.success(void 0);
25 }
26}
3.2.5 AnwendungsfÀlle
AnwendungsfÀlle orchestrieren Interaktionen zwischen verschiedenen Domain-Elementen, um eine GeschÀftsfunktionalitÀt zu erreichen.
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. Die Infrastruktur
Die Infrastruktur implementiert die in der Domain definierten Ports fĂŒr die Interaktion mit der AuĂenwelt.
4.1 Prisma-Konfiguration
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 Adapter-Implementierung
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(`Fehler beim Speichern: ${error.message}`);
19 }
20 return Result.failure('Ein unbekannter Fehler ist beim Speichern aufgetreten');
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(`Fehler bei der Suche: ${error.message}`);
36 }
37 return Result.failure('Ein unbekannter Fehler ist bei der Suche aufgetreten');
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(`Fehler beim Abrufen: ${error.message}`);
51 }
52 return Result.failure('Ein unbekannter Fehler ist beim Abrufen aufgetreten');
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. Die BenutzeroberflÀche
5.1 Validierung mit Zod
Die Eingabedatenvalidierung verwendet Zod, um Konsistenz mit unserer Domain sicherzustellen.
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, "Ein Padawan muss einen Namen haben, der lÀnger als 2 Zeichen ist")
8 .max(50, "Selbst Qui-Gon Jinn hat keinen so langen Namen"),
9 age: z.coerce
10 .number()
11 .min(4, "Selbst Grogu begann sein Training erst mit 4 Jahren")
12 .max(30, "Der Rat betrachtet dies als Altersgrenze fĂŒr den Trainingsbeginn"),
13 midichlorianCount: z.coerce
14 .number()
15 .min(7000, "Midichlorian-Anzahl ist zu niedrig fĂŒr das Jedi-Training")
16 .max(20000, "Selbst Anakin hatte nur 20000 Midichlorians"),
17 homeworld: z
18 .string()
19 .min(2, "Der Name deines Planeten wird benötigt"),
20 primarySkill: z.enum(["combat", "healing", "meditation", "telepathy"], {
21 errorMap: () => ({
22 message: "Diese FĂ€higkeit wird vom Jedi-Orden nicht anerkannt",
23 }),
24 }),
25 background: z
26 .string()
27 .min(20, "ErzĂ€hle uns mehr ĂŒber deine Reise"),
28});
29
30export type PadawanFormData = z.infer<typeof padawanSchema>;
Jetzt können wir die Datenbank generieren.
FĂŒr weitere Untersuchungen bietet Prisma einen Client zur Visualisierung der Datenbank.
5.2 Server Action fĂŒr die Registrierung
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: "Möge die Macht mit dir sein, junger Padawan! đ"
33 };
34 } catch (error) {
35 return {
36 success: false,
37 message: "Eine Störung in der Macht wurde festgestellt... đ"
38 };
39 }
40}
5.3 Formular-Komponente
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 ? "Registrierung erfolgreich" : "Fehler",
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>Jedi-Akademie</CardTitle>
76 <CardDescription>
77 Reiche deine Bewerbung ein, um dem Orden beizutreten
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>Padawan Name</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>Alter</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>Midichlorian-Anzahl</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>Heimatwelt</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>PrimÀre FÀhigkeit</FormLabel>
159 <Select
160 onValueChange={field.onChange}
161 defaultValue={field.value}
162 >
163 <FormControl>
164 <SelectTrigger>
165 <SelectValue placeholder="WĂ€hle eine FĂ€higkeit" />
166 </SelectTrigger>
167 </FormControl>
168 <SelectContent>
169 <SelectItem value="combat">
170 Lichtschwert-Kampf âïž
171 </SelectItem>
172 <SelectItem value="healing">
173 Macht-Heilung đ
174 </SelectItem>
175 <SelectItem value="meditation">
176 Tiefe Meditation đ§
177 </SelectItem>
178 <SelectItem value="telepathy">Telepathie đ§ </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>Deine Geschichte</FormLabel>
192 <FormControl>
193 <Textarea
194 placeholder="ErzÀhle uns von deiner Reise..."
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 Bewerbung einreichen đ
207 </Button>
208 </form>
209 </Form>
210 </CardContent>
211 </Card>
212 );
213}
5.4 Padawan-Liste
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 { de } from "date-fns/locale";
16import type { Padawan } from "@/core/domain/models/padawan";
17
18type PadawanListProps = {
19 padawans: Padawan[];
20};
21
22const skillEmoji = {
23 combat: "âïž Lichtschwert-Kampf",
24 healing: "đ Macht-Heilung",
25 meditation: "đ§ Tiefe Meditation",
26 telepathy: "đ§ Telepathie",
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 Noch keine Padawane registriert...
36 </p>
37 </CardContent>
38 </Card>
39 );
40 }
41
42 return (
43 <Card>
44 <CardHeader>
45 <CardTitle>Kandidatenliste</CardTitle>
46 </CardHeader>
47 <CardContent>
48 <Table>
49 <TableHeader>
50 <TableRow>
51 <TableHead>Name</TableHead>
52 <TableHead>Alter</TableHead>
53 <TableHead>Heimatwelt</TableHead>
54 <TableHead>SpezialitÀt</TableHead>
55 <TableHead>Midichlorians</TableHead>
56 <TableHead>Registrierung</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} Jahre</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: de })}
71 </TableCell>
72 </TableRow>
73 ))}
74 </TableBody>
75 </Table>
76 </CardContent>
77 </Card>
78 );
79}
Diese Komponenten verwenden shadcn/ui, um eine moderne und zugÀngliche BenutzeroberflÀche zu erstellen, mit:
- JediRegistrationForm:
- VollstÀndige Validierung mit react-hook-form und Zod
- Fehlerbehandlung mit kontextbezogenen Nachrichten
- Benutzer-Feedback mit Toasts
- Formular-Reset nach erfolgreicher Ăbermittlung
- PadawanList:
- Tabellarische Datenanzeige
- Behandlung leerer ZustÀnde
- Datumsformatierung auf Deutsch
- FĂ€higkeits-Icons
- Konsistentes Styling mit dem Formular
Alles integriert sich perfekt in unsere hexagonale Architektur, wÀhrend es in der PrÀsentationsschicht bleibt, ohne GeschÀftslogik.
6. WeiterfĂŒhrende Aspekte
6.1 Technische Verbesserungen
- Cache und Performance
1// Redis-Cache Implementierungsbeispiel
2export class RedisPadawanCache {
3 async cacheResults(key: string, data: any): Promise<void> {
4 await redis.set(key, JSON.stringify(data), "EX", 3600);
5 }
6}
- Ăberwachung und Protokollierung
1// Ăberwachungsdienst
2export class ApplicationMonitoring {
3 trackDomainEvent(event: DomainEvent): void {
4 // An Ăberwachungsdienst senden
5 }
6}
6.2 Funktionale Weiterentwicklungen
- Genehmigungsworkflow
- Bewertungssystem
- Beförderungsmanagement
- Padawan-Kommunikation
Fazit
Die hexagonale Architektur hat es uns ermöglicht, eine Anwendung zu erstellen, die:
- Wartbar ist
- Klare Trennung der ZustÀndigkeiten
- Isolierter und geschĂŒtzter GeschĂ€ftscode
- Skalierbar ist
- Einfaches HinzufĂŒgen neuer Funktionen
- Möglichkeit zur Ănderung der Infrastruktur
- UnterstĂŒtzung neuer Ein-/AusgabekanĂ€le
Um weiterzugehen, könnten Sie:
- Authentifizierung hinzufĂŒgen
- Ein Caching-System implementieren
- Ăberwachung einrichten
- Automatisierte Tests einrichten
- Webhooks hinzufĂŒgen
Denken Sie daran: Gute Architektur ist wie die Macht - sie muss im Gleichgewicht sein! đ
[Ende der Ăbersetzung]