שלום לכולם, חברים וקוראים יקרים שלי! לפני שנכתוב את המאמר, קצת רקע... לאחרונה נתקלתי בבעיה בעבודה עם ספריית Mapstruct , אותה תיארתי בקצרה בערוץ הטלגרם שלי כאן . בתגובות, הבעיה בפוסט נפתרה; עמיתי מהפרויקט הקודם עזר בזה. לאחר מכן החלטתי לכתוב מאמר בנושא זה, אך כמובן שלא נסתכל צר וננסה קודם כל להתעדכן, להבין מה זה Mapstruct ולמה זה נחוץ, ובאמצעות דוגמה אמיתית לנתח את המצב שנוצר קודם לכן וכיצד לפתור אותו. לכן אני ממליץ בחום לעשות את כל החישובים במקביל לקריאת המאמר על מנת לחוות הכל בפועל. לפני שנתחיל, הירשמו לערוץ הטלגרם שלי , אני אוסף שם את הפעילויות שלי, כותב מחשבות על פיתוח ב-Java ו-IT בכלל. נרשמת? גדול! ובכן, עכשיו בוא נלך!
Mapstruct, שאלות נפוצות?
מחולל קוד למיפוי שעועית מהיר בטוח לסוג. המשימה הראשונה שלנו היא להבין מהי Mapstruct ומדוע אנחנו צריכים אותה. באופן כללי, אתה יכול לקרוא על זה באתר הרשמי. בעמוד הראשי של האתר שלוש תשובות לשאלות: מה זה? בשביל מה? אֵיך? ננסה לעשות גם את זה:
מה זה?
Mapstruct היא ספרייה שעוזרת למפות (באופן כללי, זה מה שתמיד אומרים: מפה, מפה וכו') אובייקטים של ישויות מסוימות לאובייקטים של ישויות אחרות באמצעות קוד שנוצר על סמך תצורות שמתוארות באמצעות ממשקים.
בשביל מה?
על פי רוב, אנו מפתחים אפליקציות רב שכבתיות (שכבה לעבודה עם מסד הנתונים, שכבת לוגיקה עסקית, שכבה לאינטראקציה של האפליקציה עם העולם החיצון) ולכל שכבה יש אובייקטים משלה לאחסון ועיבוד נתונים . ואת הנתונים האלה צריך להעביר משכבה לשכבה על ידי העברה מישות אחת לאחרת. למי שלא עבד עם גישה זו, זה עשוי להיראות מעט מסובך. לדוגמה, יש לנו ישות עבור מסד הנתונים של התלמידים. כאשר הנתונים של ישות זו עוברים לשכבת הלוגיקה העסקית (שירותים), עלינו להעביר את הנתונים מכיתת Student לכיתה StudentModel. לאחר מכן, לאחר כל המניפולציות עם ההיגיון העסקי, הנתונים צריכים להשתחרר בחוץ. ובשביל זה יש לנו את הכיתה StudentDto. כמובן, אנחנו צריכים להעביר נתונים מהכיתה StudentModel ל StudentDto. כתיבה ידנית בכל פעם שהשיטות שיועברו היא עתירת עבודה. בנוסף זהו קוד נוסף בבסיס הקוד שצריך לתחזק. אתה יכול לעשות טעות. ומאפסטרוקטור מייצרת שיטות כאלה בשלב הקומפילציה ומאחסנת אותן במקורות שנוצרו.
אֵיך?
שימוש בהערות. אנחנו רק צריכים ליצור הערה עם הערת Mapper ראשית שאומרת לספרייה שניתן להשתמש בשיטות בממשק הזה כדי לתרגם מאובייקט אחד למשנהו. כפי שאמרתי קודם על תלמידים, במקרה שלנו זה יהיה ממשק StudentMapper, שיהיו לו מספר שיטות להעברת נתונים משכבה אחת לאחרת:
היופי בגישה זו הוא שאם השמות וסוגי השדות זהים במחלקות שונות (כמו במקרה שלנו), אז ההגדרות של Mapstruct מספיקות כדי ליצור את המימוש הדרוש על בסיס ממשק StudentMapper בשלב ההידור, אשר יתרגם. אז זה כבר נעשה ברור יותר, נכון? בואו נלך רחוק יותר ונשתמש בדוגמה אמיתית כדי לנתח את העבודה באפליקציית Spring Boot.
דוגמה לעבודה של Spring Boot ו- Mapstruct
הדבר הראשון שאנו צריכים הוא ליצור פרויקט Spring Boot ולהוסיף לו Mapstruct. לצורך העניין, יש לי ארגון ב-GitHub עם תבניות למאגרים והתחלה של Spring Boot היא אחת מהן. על סמך זה, אנו יוצרים פרויקט חדש: לאחר מכן, נקבל את הפרויקט . כן חברים, תנו כוכב לפרויקט אם מצאתם אותו מועיל, כדי שאדע שאני לא עושה זאת לשווא. בפרויקט זה נחשוף סיטואציה שקיבלתי בעבודה ותיארתי בפוסט בערוץ הטלגרם שלי . אני אתאר בקצרה את המצב למי שלא יודע: כשאנחנו כותבים מבחנים למפות (כלומר, לאותם יישומי ממשק שדיברנו עליהם קודם), אנחנו רוצים שהבדיקות יעברו כמה שיותר מהר. האפשרות הפשוטה ביותר עם ממפים היא להשתמש בהערת SpringBootTest בעת הפעלת הבדיקה, אשר תקלוט את כל ApplicationContext של אפליקציית Spring Boot ויזריק את הממפ הדרוש לבדיקה בתוך הבדיקה. אבל האופציה הזו גוזלת משאבים ולוקחת הרבה יותר זמן ולכן היא לא מתאימה לנו. אנחנו צריכים לכתוב מבחן יחידה שפשוט יוצר את המאפר הרצוי ובודק שהשיטות שלו עובדות בדיוק כמו שאנחנו מצפים. למה אתה צריך בדיקות כדי לרוץ מהר יותר? אם הבדיקות נמשכות זמן רב, זה מאט את כל תהליך הפיתוח. עד שהבדיקות יעברו את הקוד החדש, קוד זה לא יכול להיחשב נכון ולא יילקח לבדיקה, כלומר לא יילקח לייצור ומשמעות הדבר היא שהמפתח לא סיים את העבודה. נראה, מדוע לכתוב מבחן לספריה שתפעולה אינו מוטל בספק? ובכל זאת אנחנו צריכים לכתוב מבחן, כי אנחנו בודקים עד כמה תיארנו נכון את הממפר והאם הוא עושה את מה שאנחנו מצפים. קודם כל, כדי להקל על העבודה שלנו, בואו נוסיף את Lombok לפרויקט שלנו על ידי הוספת תלות נוספת ל-pom.xml:
בפרויקט שלנו, נצטרך לעבור משיעורי מודל (שמשמשים לעבודה עם לוגיקה עסקית) לשיעורי DTO, שבהם אנו משתמשים כדי לתקשר עם העולם החיצון. בגרסה הפשוטה שלנו, נניח שהשדות לא משתנים והממפים שלנו יהיו פשוטים. אבל, אם יש רצון, אפשר יהיה לכתוב מאמר מפורט יותר על איך לעבוד עם Mapstruct, איך להגדיר אותה ואיך לנצל את היתרונות שלה. אבל אז, מכיוון שמאמר זה יהיה ארוך למדי. נניח שיש לנו סטודנט עם רשימת הרצאות ומרצים שהוא מגיע אליהם. בואו ניצור חבילת מודל . על סמך זה, ניצור מודל פשוט:
כעת בואו ניצור ממפה שיתרגם אוסף של מודלים של הרצאות לאוסף של הרצאות DTO. הדבר הראשון שצריך לעשות הוא להוסיף את Mapstruct לפרויקט. לשם כך, נשתמש באתר הרשמי שלהם , הכל מתואר שם. כלומר, אנחנו צריכים להוסיף תלות אחת ותוסף לזיכרון שלנו (אם יש לך שאלות לגבי מה זה זיכרון, הנה, סעיף 1 ו -2 ).
כאן אנו משתמשים בשני ממפים כדי למפות נכון את רשימת ההרצאות ואת רשימת המרצים. עכשיו אנחנו צריכים להרכיב את הקוד שלנו ולראות מה יש ואיך. ניתן לעשות זאת באמצעות הפקודה mvn clean compile . אבל, כפי שהתברר, בעת יצירת יישומי Mapstruct של הממפים שלנו, יישומי המיפוי לא החליפו את השדות. למה? התברר שלא ניתן היה לאסוף את הערת הנתונים מלומבוק. והיה צריך לעשות משהו... לכן יש לנו סעיף חדש בכתבה.
קישור בין לומבוק ומאפסטרוקטור
לאחר מספר דקות של חיפוש, התברר שאנחנו צריכים לחבר את לומבוק ומאפסטרוקטור בצורה מסוימת. יש מידע על כך בתיעוד Mapstruct . לאחר בחינת הדוגמה שהציעו המפתחים מ- Mapstruct, בואו נעדכן את pom.xml שלנו: בואו נוסיף גרסאות נפרדות:
אחרי זה הכל אמור להסתדר. בואו נרכיב את הפרויקט שלנו שוב. אבל איפה אתה יכול למצוא את השיעורים שמאפסטרוקטור יצרה? הם נמצאים ב-generated-sources: ${projectDir}/target/generated-sources/annotations/ עכשיו כשאנחנו מוכנים להבין את האכזבה שלי מהפוסט של Mapstruct, בואו ננסה ליצור בדיקות לממפים.
אנחנו כותבים מבחנים למפותים שלנו
אני אצור בדיקה מהירה ופשוטה שתבדוק את אחד מהמפות במקרה שבו אנו יוצרים מבחן אינטגרציה ואל תדאג לגבי זמן ההשלמה שלו:
כאן, באמצעות הערת SpringBootTest, אנו משיקים את כל האפליקציהContext ומתוכו, באמצעות הערת Autowired, אנו מחלצים את המחלקה שאנו צריכים לבדיקה. מנקודת מבט של מהירות וקלות כתיבת מבחן, זה טוב מאוד. המבחן עובר בהצלחה, הכל בסדר. אבל נלך לכיוון השני ונכתוב מבחן יחידה עבור ממפה, למשל, LectureListMapper...
מכיוון שהמימושים ש-Mapstruct מייצר הם באותה כיתה כמו הפרויקט שלנו, אנחנו יכולים להשתמש בהם בקלות בבדיקות שלנו. הכל נראה נהדר - בלי הערות, אנחנו יוצרים את הכיתה שאנחנו צריכים בצורה הפשוטה ביותר וזהו. אבל כשנריץ את הבדיקה, נבין שהיא תקרוס ותהיה NullPointerException במסוף... הסיבה לכך היא שהיישום של מפה LectureListMapper נראה כך:
אם נסתכל על ה-NPE (קיצור של NullPointerException), נקבל אותו מהמשתנה lectureMapper , שמתברר שהוא לא אתחול. אבל ביישום שלנו אין לנו בנאי שאיתו נוכל לאתחל את המשתנה. זו בדיוק הסיבה שבגללה Mapstruct הטמיעה את המאפר בצורה זו! באביב, אתה יכול להוסיף שעועית לשיעורים בכמה דרכים, אתה יכול להזריק אותם דרך שדה יחד עם ההערה Autowired, כפי שנעשה למעלה, או שאתה יכול להזריק אותם דרך בנאי. מצאתי את עצמי במצב כל כך בעייתי בעבודה כשהייתי צריך לייעל את זמן ביצוע הבדיקה. חשבתי שאי אפשר לעשות כלום בנידון ושפכתי את הכאב שלי בערוץ הטלגרם שלי. ואז הם עזרו לי בהערות ואמרו שאפשר להתאים אישית את אסטרטגיית ההזרקה. לממשק Mapper יש שדה injectionStrategy , שמקבל רק את השם InjectionStrategy , שיש לו שני ערכים: FIELD ו- CONSTRUCTOR . עכשיו, כשאני יודע זאת, בואו נוסיף את ההגדרה הזו למפות שלנו; אני אראה אותה באמצעות LectureListMapper כדוגמה :
הדגשתי במודגש את החלק שהוספתי. בואו נוסיף את האפשרות הזו עבור כל האחרים ונרכיב מחדש את הפרוייקט כך שייווצרו ממפים עם שורה חדשה. לאחר שהדבר נעשה, בוא נראה כיצד השתנה היישום של המאפר עבור LectureListMapper (הדגשה מודגשת החלק שאנו צריכים):
כעת, אם נריץ את המבחן, הכל יעבוד כמצופה, שכן ב-LectureListMapperImpl אנו עוברים את ה-LectureMapper שהוא צריך... ניצחון! זה לא קשה לכם, אבל אני מרוצה: חברים, הכל כרגיל, הירשמו לחשבון GitHub שלי , לחשבון הטלגרם שלי . שם אני מפרסם את תוצאות הפעילות שלי, יש דברים ממש שימושיים) אני מזמין אותך במיוחד להצטרף לקבוצת הדיון של ערוץ הטלגרם . קורה שאם למישהו יש שאלה טכנית, הוא יכול לקבל שם תשובה. הפורמט הזה מעניין לכולם, אפשר לקרוא מי יודע מה ולצבור ניסיון.
סיכום
כחלק ממאמר זה, התוודענו למוצר כה הכרחי ונמצא בשימוש תדיר כמו Mapstruct. הבנו מה זה, למה ואיך. בעזרת דוגמה אמיתית, הרגשנו מה אפשר לעשות ואיך אפשר לשנות את זה. כמו כן, בדקנו כיצד להגדיר את הזרקת השעועית דרך הקונסטרוקטור, כך שניתן יהיה לבדוק כראוי ממפים. עמיתים מ- Mapstruct אפשרו למשתמשים במוצר שלהם לבחור בדיוק איך להזריק ממפים, ועל כך אין ספק שאנו מודים להם. אבל, למרות העובדה ש-Spring ממליץ להזריק שעועית דרך הקונסטרוקטור, החבר'ה מ- Mapstruct קבעו הזרקה דרך השדה כברירת מחדל. למה? אין תשובה. אני חושד שאולי יש סיבות שאנחנו פשוט לא יודעים עליהן, ובגלל זה הם עשו את זה ככה. וכדי לברר מהם, יצרתי בעיה של GitHub במאגר המוצרים הרשמי שלהם.
GO TO FULL VERSION