בפוסטים הקודמים דיברנו לעומק על ארכיטקטורה נקייה, סקרנו איך מיישמים Dependency Injection, והסברנו כיצד לעצב נכון את השכבה העסקית (Domain Layer).
היום נדבר על חלק נוסף וחשוב לא פחות: ניהול טעויות ואירועים (Events & Error Handling).
למה ניהול שגיאות חשוב כל כך?
כמפתחים, אנחנו יודעים ששגיאות הן בלתי נמנעות. הבעיה מתחילה כשלא מנהלים אותן נכון. התוצאות:
- קוד מסורבל ומלא בלוקים של try-catch.
- לוגים מבלבלים שקשה להבין מהם מה קרה.
- זליגת לוגיקה טכנית לתוך השכבה העסקית.
כשאנחנו עובדים בארכיטקטורה נקייה, המטרה היא שהקוד יהיה קריא, ברור וקל לתחזוקה. לכן אנחנו צריכים מנגנון אחיד וברור לטיפול בשגיאות.
עקרונות לניהול שגיאות נקי (Clean Error Handling)
1. הפרדה בין שגיאות עסקיות לטכניות
- שגיאות עסקיות הן חלק מהלוגיקה של האפליקציה (למשל "משתמש כבר רשום", "יתרת חשבון נמוכה").
- שגיאות טכניות הן תקלות תשתית (למשל "ה-DB לא זמין", "בעיית רשת").
הנה דוגמה קצרה ב־TypeScript להבחנה ברורה בין סוגי שגיאות:
// Business Error
export class BusinessError extends Error {
constructor(message: string) {
super(message);
this.name = "BusinessError";
}
}
// Technical Error
export class TechnicalError extends Error {
constructor(message: string) {
super(message);
this.name = "TechnicalError";
}
}
2. טיפול בשגיאות בשכבת Use Cases בלבד
בשכבת ה-Use Case אנחנו זורקים שגיאות עסקיות במפורש:
class RegisterUserUseCase {
constructor(private userRepo: IUserRepository) {}
async execute(user: User) {
const existing = await this.userRepo.findByEmail(user.email);
if (existing) {
throw new BusinessError("User already exists");
}
await this.userRepo.save(user);
}
}
שימו לב: הישויות (Entities) עצמן לא זורקות שגיאות טכניות, הן מטפלות רק בווידוא חוקים עסקיים פשוטים (validation).
3. טיפול בשגיאות בשכבת ה־Controllers (Interface Adapters)
השכבה החיצונית של האפליקציה תופסת את השגיאות ומחזירה תגובות ברורות ללקוח:
app.post("/users", async (req, res) => {
try {
await registerUserUseCase.execute(req.body);
res.status(201).send("User created");
} catch (err) {
if (err instanceof BusinessError) {
res.status(400).send(err.message);
} else {
res.status(500).send("Internal Server Error");
}
}
});
4. שימוש אחיד בלוגים (Logging)
הימנעו מלכתוב console.log
בכל מקום. במקום זאת השתמשו בספריית לוגים מסודרת (לדוגמה winston
):
import winston from "winston";
const logger = winston.createLogger({
level: "info",
transports: [new winston.transports.Console()],
});
// בלוגיקה העסקית אין לוגים טכניים, אלא רק נקודות עסקיות משמעותיות:
logger.info("User registered successfully", { userId: user.id });
logger.error("Database connection failed", { error: err.message });
ניהול אירועים ו־Event-Driven Architecture כחלק מהארכיטקטורה הנקייה
אירועים (Events) הם דרך מצוינת להפוך את האפליקציה שלנו לגמישה, מודולרית, וקלה לתחזוקה.
הרעיון הוא פשוט: אנחנו מפרסמים אירועים והמערכת (או חלקים ממנה) מאזינה להם ומגיבה לפי הצורך.
לדוגמה, ברגע שנוצר משתמש חדש, ייתכן שנרצה לשלוח מייל ״ברוכים הבאים״, לעדכן מערכת אחרת, ולכתוב ללוג – וכל זה, מבלי לשנות את הקוד שאחראי על יצירת המשתמש.
דוגמה מעשית ב־Node.js ו־TypeScript:
שלב 1: יצירת תשתית אירועים פשוטה
import { EventEmitter } from "events";
// Event Dispatcher פשוט
export class DomainEventEmitter extends EventEmitter {}
export const domainEvents = new DomainEventEmitter();
שלב 2: אירועים בשכבת הליבה (Domain Layer)
// אירוע עסקי
class UserRegisteredEvent {
constructor(public readonly userId: string,
public readonly email: string
) {}
}
// ה־Use Case מפרסם אירוע
class RegisterUserUseCase {
constructor(private userRepo: IUserRepository) {}
async execute(user: User) {
const existing = await this.userRepo.findByEmail(user.email);
if (existing) {
throw new BusinessError("User already exists");
}
await this.userRepo.save(user);
domainEvents.emit("UserRegistered", new UserRegisteredEvent(user.id, user.email));
}
}
שלב 3: האזנה לאירועים בשכבת התשתית
// שליחת אימייל בעת אירוע
domainEvents.on("UserRegistered", async (event: UserRegisteredEvent) => {
try {
await emailService.sendWelcomeEmail(event.email);
logger.info("Welcome email sent", { userId: event.userId });
} catch (err) {
logger.error("Failed to send welcome email", { error: err.message });
}
});
כך, יצירת המשתמש מנותקת משליחת המייל. הוספת לוגיקה חדשה (למשל עדכון CRM) תהיה פשוטה מאוד – פשוט מאזינים לאותו אירוע בנפרד.
טיפים לניהול אירועים נכון בארכיטקטורה נקייה:
אירועים עסקיים בשכבה העסקית בלבד – שכבת ה-UI או DB לא מפרסמת אירועים.
מאזינים (Listeners) בשכבה החיצונית – כך השכבה העסקית נשארת נקייה.
האירועים צריכים להיות פשוטים – אובייקט אירוע לא אמור לכלול לוגיקה מורכבת, אלא רק נתונים.
טעויות נפוצות שכדאי להימנע מהן:
זליגת אירועים טכניים ללוגיקה העסקית:
אל תייצרו אירועים טכניים ("DB התעדכן", "API נענה") בתוך השכבה העסקית.
ריבוי אירועים מיותרים:
כל אירוע צריך לייצג משהו בעל משמעות עסקית ברורה ("משתמש נרשם", "הזמנה אושרה").
ניהול שגיאות לא עקבי:
הקפידו על טיפול אחיד וברור בשגיאות. אל תערבבו טיפול בשגיאות טכניות ועסקיות באותו מקום.
בשורה התחתונה…
ניהול שגיאות ואירועים הוא לא רק עניין טכני. כשעושים אותו נכון – הקוד הופך להיות ברור, גמיש ועמיד יותר לשינויים. שמירה על גבולות ברורים בין השכבות תבטיח שהמערכת שלנו תישאר נקייה ומתוחזקת היטב.
אשמח לשמוע איך אתם מנהלים את השגיאות והאירועים בפרויקטים שלכם. האם יצא לכם להיתקל בקשיים? האם יש פתרונות מעניינים שהייתם רוצים לשתף?
ספרו לי בתגובות – נתראה שם!