モダンなWebアプリケーションをどのように構築すべきでしょうか?時間とともにメンテナンス性を確保するにはどうすればよいでしょうか?このガイドでは、ヘキサゴナルアーキテクチャと堅牢なフォームバリデーションを使用してNext.jsアプリケーションを作成します。学習をより楽しくするために、ジェダイ・アカデミーの登録システムを作成しましょう!
ソースコードはGitHubで入手できます。
目次
- ヘキサゴナルアーキテクチャの紹介
- インストールと構造
- ドメイン
- インフラストラクチャ
- ユーザーインターフェース
- さらなる展開
1. ヘキサゴナルアーキテクチャの紹介
1.1 ヘキサゴナルアーキテクチャとは
ヘキサゴナルアーキテクチャ(「ポートとアダプター」としても知られる)は、以下のようなビジネスコンポーネントを作成できるアーキテクチャパターンです:
- 技術的な詳細から切り離されている
- 単独でテスト可能
- 使用されるフレームワークから独立している
アーキテクチャは3つの主要なレイヤーに分かれています:
- ドメイン:純粋なビジネスロジックを含む
- ポート:ドメインとの通信のためのインターフェースを定義
- アダプター:外部世界と相互作用するためのポートを実装
1.2 なぜヘキサゴナルアーキテクチャを使用するのか
利点
- 関心事の明確な分離
- 保護され集中化されたビジネスロジック
- テストの容易さ
- 技術変更の柔軟性
- メンテナンスの簡素化
理想的なユースケース
- 複雑なビジネスルールを持つアプリケーション
- 時間とともに進化することを意図したプロジェクト
- 高いテスト可能性を必要とするシステム
- 複数のインターフェースをサポートする必要があるアプリケーション
[コンテンツは続く...]
2. インストールと構造
2.1 完全なインストール
1# プロジェクトの作成
2npx create-next-app@latest jedi-academy
プロジェクト作成時には、以下の質問に答えてください:
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
プロジェクトが作成されたら、依存関係をインストールします:
1cd jedi-academy
2
3# フォーム管理とバリデーションのための主要な依存関係
4npm install @hookform/resolvers zod react-hook-form
5
6# Prismaデータベース
7npm install @prisma/client
8npm install -D prisma
9
10# shadcn/uiのインストール
11npx shadcn@latest init
shadcn/uiの初期化時には、以下の質問に答えてください:
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
# 必要なコンポーネントのインストール
npx shadcn@latest add form input select textarea button card toast table
1# 日付ユーティリティ
2npm install date-fns
3
4# データベース
5npx prisma init --datasource-provider sqlite
2.2 プロジェクト構造
src/
├── app/
│ ├── actions/
│ │ └── register-padawan.ts
│ ├── components/
│ │ ├── ui/ # shadcn/uiコンポーネント
│ │ ├── jedi-form.tsx
│ │ └── padawan-list.tsx
│ ├── lib/
│ │ └── utils.ts # 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
この構造は、以下の明確なヘキサゴナルアーキテクチャに従っています:
app/ - Next.jsユーザーインターフェースレイヤー
actions/ - フォーム処理用のサーバーアクション
components/ - UI/ビジネスの分離を持つReactコンポーネント
lib/ - 共有ユーティリティ
core/ - 純粋なビジネスロジック
domain/ - モデル、ポート、ビジネスサービス
usecases/ - アプリケーションのユースケース
infrastructure/ - 技術的な実装
3. ドメイン:アプリケーションのコア
3.1 ドメインの理解
ドメインはアプリケーションの心臓部です。ここで、技術的な考慮事項とは独立してビジネスルールをコードに変換します。私たちの場合、ドメインはジェダイ・アカデミーへの登録を管理するすべてのルールを表します。
ドメインの主要な原則
- 技術的な独立性
- フレームワークへの依存なし
- 永続化関連のコードなし
- UIロジックなし
- 集中化されたビジネスルール
- すべてのビジネスロジックはドメインに存在
- ルールは明示的で文書化されている
- ビジネスバリデーションは技術的バリデーションから分離
- 豊かなモデリング
- 正確な型とインターフェースの使用
- 重要なビジネス概念のための値オブジェクト
- 明確なアイデンティティを持つエンティティ
3.2 ドメインコンポーネント
3.2.1 モデル
モデルは基本的なビジネス概念を表します。
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 ドメインイベント
イベントはドメインで発生した重要な事実を表します。
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 ポート(インターフェース)
ポートはドメインが外部世界とどのように相互作用するかを定義します。
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 ドメインサービス
ドメインサービスは、エンティティに自然に適合しないビジネスロジックをカプセル化します。
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("訓練を始めるには若すぎます");
8 }
9 if (padawan.age > 30) {
10 return Result.failure("評議会は訓練開始の年齢制限をこれに設定しています");
11 }
12
13 if (padawan.midichlorianCount < 7000) {
14 return Result.failure("ミディクロリアン値がジェダイ訓練には低すぎます");
15 }
16 if (padawan.midichlorianCount > 20000) {
17 return Result.failure("不自然なミディクロリアン値です");
18 }
19
20 if (padawan.background.length < 20) {
21 return Result.failure("個人の物語についてもっと詳しく教えてください");
22 }
23
24 return Result.success(void 0);
25 }
26}
3.2.5 ユースケース
ユースケースは、ビジネス機能を達成するために異なるドメイン要素間の相互作用を調整します。
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. インフラストラクチャ
インフラストラクチャは、外部世界と相互作用するためにドメインで定義されたポートを実装します。
4.1 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 アダプターの実装
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(`保存中にエラーが発生しました: ${error.message}`);
19 }
20 return Result.failure('保存中に不明なエラーが発生しました');
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(`検索中にエラーが発生しました: ${error.message}`);
36 }
37 return Result.failure('検索中に不明なエラーが発生しました');
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(`取得中にエラーが発生しました: ${error.message}`);
51 }
52 return Result.failure('取得中に不明なエラーが発生しました');
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. ユーザーインターフェース
5.1 Zodによるバリデーション
入力データのバリデーションは、ドメインとの一貫性を確保するためにZodを使用します。
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, "パダワンの名前は2文字以上である必要があります")
8 .max(50, "クワイ=ガン・ジンでさえそんなに長い名前ではありません"),
9 age: z.coerce
10 .number()
11 .min(4, "グローグュでさえ4歳から訓練を始めました")
12 .max(30, "評議会はこれを訓練開始の年齢制限としています"),
13 midichlorianCount: z.coerce
14 .number()
15 .min(7000, "ミディクロリアン値がジェダイ訓練には低すぎます")
16 .max(20000, "アナキンでさえミディクロリアン値は20000でした"),
17 homeworld: z
18 .string()
19 .min(2, "惑星名は必須です"),
20 primarySkill: z.enum(["combat", "healing", "meditation", "telepathy"], {
21 errorMap: () => ({
22 message: "このスキルはジェダイ・オーダーに認められていません",
23 }),
24 }),
25 background: z
26 .string()
27 .min(20, "あなたの旅についてもっと教えてください"),
28});
29
30export type PadawanFormData = z.infer<typeof padawanSchema>;
データベースを生成しましょう。
さらなる探索のために、Prismaはデータベースを可視化するクライアントを提供しています。
5.2 登録用のサーバーアクション
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: "フォースと共にあらんことを、若きパダワン! 🌟"
33 };
34 } catch (error) {
35 return {
36 success: false,
37 message: "フォースの乱れを感じる... 🌀"
38 };
39 }
40}
5.3 フォームコンポーネント
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 ? "登録成功" : "エラー",
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>ジェダイ・アカデミー</CardTitle>
76 <CardDescription>
77 オーダーへの入学申請を提出してください
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>パダワン名</FormLabel>
89 <FormControl>
90 <Input placeholder="ルーク・スカイウォーカー" {...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>年齢</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>ミディクロリアン値</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>出身惑星</FormLabel>
145 <FormControl>
146 <Input placeholder="タトゥイーン" {...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>主要スキル</FormLabel>
159 <Select
160 onValueChange={field.onChange}
161 defaultValue={field.value}
162 >
163 <FormControl>
164 <SelectTrigger>
165 <SelectValue placeholder="スキルを選択" />
166 </SelectTrigger>
167 </FormControl>
168 <SelectContent>
169 <SelectItem value="combat">
170 ライトセーバー戦闘 ⚔️
171 </SelectItem>
172 <SelectItem value="healing">
173 フォースヒーリング 💚
174 </SelectItem>
175 <SelectItem value="meditation">
176 深い瞑想 🧘
177 </SelectItem>
178 <SelectItem value="telepathy">テレパシー 🧠</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>あなたの物語</FormLabel>
192 <FormControl>
193 <Textarea
194 placeholder="あなたの旅について教えてください..."
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 申請を提出する 🌟
207 </Button>
208 </form>
209 </Form>
210 </CardContent>
211 </Card>
212 );
213}
5.4 パダワンリスト
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: "⚔️ ライトセーバー戦闘",
24 healing: "💚 フォースヒーリング",
25 meditation: "🧘 深い瞑想",
26 telepathy: "🧠 テレパシー",
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 まだパダワンの登録がありません...
36 </p>
37 </CardContent>
38 </Card>
39 );
40 }
41
42 return (
43 <Card>
44 <CardHeader>
45 <CardTitle>候補者リスト</CardTitle>
46 </CardHeader>
47 <CardContent>
48 <Table>
49 <TableHeader>
50 <TableRow>
51 <TableHead>名前</TableHead>
52 <TableHead>年齢</TableHead>
53 <TableHead>出身惑星</TableHead>
54 <TableHead>専門</TableHead>
55 <TableHead>ミディクロリアン値</TableHead>
56 <TableHead>登録日時</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}歳</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}
これらのコンポーネントはshadcn/uiを使用して、以下の特徴を持つモダンでアクセシブルなユーザーインターフェースを作成します:
- JediRegistrationForm:
- react-hook-formとZodによる完全なバリデーション
- コンテキストに応じたエラーメッセージ
- トーストによるユーザーフィードバック
- 送信成功後のフォームリセット
- PadawanList:
- 表形式のデータ表示
- 空の状態の処理
- 英語での日付フォーマット
- スキルアイコン
- フォームと一貫性のあるスタイリング
すべてがプレゼンテーション層に留まりながら、ビジネスロジックを含まずにヘキサゴナルアーキテクチャに完璧に統合されています。
6. さらなる展開
6.1 技術的な改善点
- キャッシュとパフォーマンス
1// 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}
- モニタリングとロギング
1// モニタリングサービス
2export class ApplicationMonitoring {
3 trackDomainEvent(event: DomainEvent): void {
4 // モニタリングサービスに送信
5 }
6}
6.2 機能的な発展
- 承認ワークフロー
- 評価システム
- 昇進管理
- パダワン間のコミュニケーション
結論
ヘキサゴナルアーキテクチャにより、以下のような特徴を持つアプリケーションを作成することができました:
- 保守性
- 関心事の明確な分離
- 隔離され保護されたビジネスコード
- スケーラビリティ
- 新機能の追加が容易
- インフラストラクチャの変更が可能
- 新しい入出力チャネルのサポート
さらなる展開として以下が考えられます:
- 認証の追加
- キャッシュシステムの実装
- モニタリングの設定
- 自動テストの設定
- Webhookの追加
覚えておいてください:良いアーキテクチャはフォースのようなもの - バランスが取れていなければなりません! 🌟
[翻訳終了]