לדלג לתוכן

מה זה CQRS - הסבר פשוט בלי סיסמאות

המונח CQRS הוא אחד השמות שנשמעים הכי מפחידים בעולם עיצוב המערכות, וזה חבל, כי הרעיון שמאחוריו הוא בעצם פשוט מאוד. הבעיה היא שאנשים קופצים ישר ליישם אותו על כל פרויקט בלי להבין למה בכלל הוא קיים. אז בואו נתחיל מהבעיה, לא מהפתרון.

הבעיה - כתיבה וקריאה הן לא אותו דבר

ברוב האפליקציות, אותו מודל נתונים משמש גם לכתיבה וגם לקריאה. יש לכם טבלת orders במסד הנתונים, ואתם גם כותבים אליה הזמנות חדשות וגם קוראים ממנה כדי להציג דוחות, רשימות, וסטטיסטיקות. זה עובד מצוין באפליקציות קטנות ובינוניות.

אבל בשלב מסוים, שני הצרכים האלה מתחילים למשוך לכיוונים שונים. כתיבה טובה דורשת מבנה נתונים שמדגיש שלמות ועקביות - למשל, לוודא שלא ניתן ליצור הזמנה עם כמות שלילית. קריאה טובה דורשת מבנה נתונים שמדגיש מהירות ונוחות תצוגה - למשל, טבלה שכבר מוכנה בדיוק לפי הצורה שממשק המשתמש צריך, בלי לחשב אותה מחדש בכל בקשה. כשמנסים לשרת את שני הצרכים באותו מודל, בדרך כלל מתפשרים על שניהם.

הפתרון - להפריד בין כתיבה לקריאה

הראשי תיבות CQRS מייצגים Command Query Responsibility Segregation - הפרדת אחריות בין פקודות (כתיבות) לשאילתות (קריאות). הרעיון - יש לכם מודל אחד שאחראי אך ורק על כתיבות, ומודל נפרד לגמרי שאחראי אך ורק על קריאות. הם יכולים להיות אפילו במסדי נתונים שונים לגמרי.

בפועל זה נראה כך - כשמשתמש מבצע פעולה (Command), למשל "צור הזמנה", הבקשה עוברת דרך מודל הכתיבה, שאוכף את כל חוקי העסקיים ושומר את הנתון. בנפרד לגמרי, יש מודל קריאה (Query) שמכיל תצוגה כבר מוכנה ומותאמת בדיוק למה שממשק המשתמש צריך להציג - למשל טבלה שכבר מרכזת "כל ההזמנות של משתמש X עם סטטוס וסכום מוכנים מראש". מודל הקריאה מתעדכן ברקע כל פעם שקורה שינוי במודל הכתיבה, לרוב באמצעות אירועים.

למה זה בכלל שווה את הטרחה

  • ביצועים. מודל הקריאה יכול להיות מותאם ומאונדקס בדיוק לצרכי התצוגה, בלי להתפשר על מבנה שגם צריך לשרת כתיבות.
  • מדרגיות עצמאית. ברוב האפליקציות יש הרבה יותר קריאות מכתיבות. אפשר להרחיב את צד הקריאה בנפרד, עם עוד עותקים או קאש, בלי לגעת בצד הכתיבה בכלל.
  • גמישות במודל. אפשר ליצור כמה "תצוגות קריאה" שונות לצרכים שונים - אחת לדשבורד ניהולי, אחת לאפליקציית מובייל - בלי לסבך את מודל הכתיבה עם כל הצרכים האלה.

ומה זה בכלל Event Sourcing

הרעיון של Event Sourcing קשור מאוד ל-CQRS ולעתים מגיע יחד איתו, אבל הוא לא אותו דבר. הרעיון הרגיל הוא לשמור במסד הנתונים את המצב הנוכחי - למשל, "יתרת החשבון היא 500 שקל". גישת Event Sourcing אומרת במקום זה לשמור את כל האירועים שהובילו למצב הזה - "הופקד 300 שקל", "הופקד 400 שקל", "נמשך 200 שקל" - והמצב הנוכחי מחושב על ידי צירוף כל האירועים.

היתרון - יש לכם היסטוריה מלאה ומדויקת של כל מה שקרה אי פעם, מה שמעולה לביקורת, ניפוי שגיאות, ואפילו שחזור המצב בכל נקודת זמן בעבר. החיסרון - זה מודל חשיבה שונה לגמרי מהרגיל, וקריאת "מה המצב הנוכחי" דורשת עיבוד של כל האירועים, מה שלרוב פותרים עם תמונות מצב (snapshots) תקופתיות.

מתי זה משתלם, ומתי זה רק מסבך

וכאן חשוב להיות כנים - CQRS ו-Event Sourcing הם כלים חזקים לבעיה ספציפית, לא ברירת מחדל לכל מערכת. לרוב האפליקציות, מודל אחד פשוט שמשרת גם כתיבה וגם קריאה עובד מצוין ולא צריך יותר מזה. הכלים האלה משתלמים כשיש פער אמיתי וגדול בין דפוסי הכתיבה לדפוסי הקריאה, כשצריך היסטוריה מלאה ומדויקת של שינויים מסיבות עסקיות או רגולטוריות, או כשמדובר במערכת בהיקף עצום שבה הפרדת קריאה מכתיבה היא הדרך היחידה להתמודד עם העומס.

אם אתם מיישמים את זה על מערכת CRUD פשוטה בלי סיבה אמיתית, סביר להניח שיצרתם לעצמכם עבודה ומורכבות מיותרת - בדיוק העיקרון היפ שדיברנו עליו כשמדברים על YAGNI.

בקורס ארכיטקטורת תוכנה שלנו אנחנו עוברים על CQRS ו-Event Sourcing עם דוגמאות קוד מלאות, כולל מתי בדיוק שווה לשלב אותם עם ארכיטקטורה מונעת אירועים.

יש לכם מקרה שאתם לא בטוחים אם CQRS מתאים לו? בואו לדבר על זה בדיסקורד.

לסיכום

בסך הכל, CQRS הוא הפרדה בין מודל הכתיבה למודל הקריאה, כדי שכל אחד מהם יוכל להיות מותאם בדיוק לצורך שלו. גישת Event Sourcing היא שמירת היסטוריית אירועים במקום מצב נוכחי בלבד. שני הכלים חזקים מאוד במקום הנכון, ומיותרים לגמרי במקום הלא נכון. תשאלו את עצמכם קודם אם יש לכם באמת פער בין קריאה לכתיבה, ורק אז תשקלו את המורכבות הנוספת.