מדריך למסגרת אקסון

1. סקירה כללית

במאמר זה נבחן אקסון ואיך זה עוזר לנו ליישם יישומים עם CQRS (הפרדת אחריות שאילתות פיקוד) ו- מקורות אירועים בראש.

במהלך מדריך זה ישתמשו גם ב- Axon Framework וגם ב- Axon Server. הראשון יכיל את היישום שלנו והאחרון יהיה הפיתרון הייעודי שלנו לחנות אירועים וניתוב הודעות.

היישום לדוגמא שנבנה מתמקד ב להזמין תְחוּם. לזה, ננצל את אבני הבניין CQRS ו- Sourcing האירועים שאקסון מספקת לנו.

שימו לב שהרבה מושגים משותפים יוצאים ישירות DDD, שהוא מעבר לתחום המאמר הנוכחי.

2. תלות Maven

ניצור יישום Axon / Spring Boot. לפיכך, עלינו להוסיף את האחרונה אקסון-קפיץ-אתחול המתנע תלות שלנו pom.xml, טוב כמו ה מבחן אקסון תלות לבדיקה:

 org.axonframework axon-spring-boot-starter 4.1.2 org.axonframework axon-test 4.1.2 test 

3. שרת אקסון

אנו נשתמש ב- Axon Server כדי להיות חנות האירועים שלנו ופתרון הייעודי לפקודה, לאירועים ולשאילתות.

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

כפתרון ניתוב הודעות זה נותן לנו אפשרות לחבר מספר מקרים ביחד מבלי להתמקד בתצורה של דברים כמו RabbitMQ או נושא קפקא כדי לשתף ולהעביר הודעות.

שרת אקסון ניתן להוריד כאן. מכיוון שמדובר בקובץ JAR פשוט, הפעולה הבאה מספיקה כדי להפעיל אותו:

java -jar axonserver.jar

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

תצורת ברירת המחדל של Axon Server יחד עם אקסון-קפיץ-אתחול המתנע תלות תבטיח ששירות ההזמנות שלנו יתחבר אליו באופן אוטומטי.

4. API להזמנת שירות - פקודות

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

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

  1. ביצוע הזמנה חדשה
  2. אישור הזמנה
  3. משלוח הזמנה

באופן טבעי, יהיו שלוש הודעות פקודה שהתחום שלנו יכול להתמודד איתן - מקום ההזמנה, ConfirmOrderCommand, ו ShipOrderCommand:

מחלקה ציבורית PlaceOrderCommand {@ TargetAggregateIdentifier פרטי סופי מחרוזת פרטי; מוצר מחרוזת סופי פרטי; // constructor, getters, equals / hashCode and toString} class public ConfirmOrderCommand {@ TargetAggregateIdentifier private final String orderId; // קונסטרוקטור, גטרס, שווה / hashCode ו- toString} ShipOrderCommand בכיתה ציבורית {@ TargetAggregateIdentifier פרטי סופי מחרוזת סופי // constructor, getters, equals / hashCode and toString}

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

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

5. API להזמנת שירות הזמנות - אירועים

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

היא תודיע על שאר הבקשה על החלטתה על ידי פרסום אירוע. נקיים שלושה סוגים של אירועים - OrderPlacedEvent, OrderConfirmedEvent, ו OrderShippedEvent:

class class OrderPlacedEvent {private final String orderId; מוצר מחרוזת סופי פרטי; // בונה ברירת מחדל, getters, שווה / hashCode ו- toString} מחלקה ציבורית OrderConfirmedEvent {private final String orderId; // קונסטרוקטור ברירת מחדל, getters, שווה / hashCode ו- toString} class public OrderShippedEvent {private final String orderId; // בונה ברירת מחדל, getters, שווה / hashCode ו- toString}

6. מודל הפיקוד - צבירת סדר

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

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

6.1. כיתה מצטברת

לפיכך, בואו ניצור את מחלקת המצרפים הבסיסית שלנו:

@Aggregate public class OrderAggregate {@AggregateIdentifier פרטי סדר מחרוזת; צו בוליאני פרטי מאושר; @ CommandHandler ציבורי OrderAggregate (פקודת PlaceOrderCommand) {AggregateLifecycle.apply (חדש OrderPlacedEvent (command.getOrderId (), command.getProduct ())); } @EventSourcingHandler בטל ציבורי ב- (אירוע OrderPlacedEvent) {this.orderId = event.getOrderId (); orderConfirmed = false; } OrderAggregate מוגן () {}}

ה לְקַבֵּץ ביאור הוא ביאור ספציפי של Axon Spring המסמן מחלקה זו כמצטבר. היא תודיע למסגרת כי יש לאשר את אבני הבניין הספציפיות ל- CQRS ולמקורות אירועים לצורך כך OrderAggregate.

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

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

בעת הטיפול ב- PlaceOrderCommand, היא תודיע לשאר היישומים כי הוזמנה על ידי פרסום ה- OrderPlacedEvent. כדי לפרסם אירוע מתוך מכלול, נשתמש AggregateLifecycle # להחיל (אובייקט ...).

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

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

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

6.2. מטפלי פיקוד צבורים

כעת, כשיש לנו את המצרף הבסיסי שלנו, נוכל להתחיל ליישם את המטפלים שנותרו:

@ CommandHandler ידית חלל ציבורית (ConfirmOrderCommand) {להחיל (OrderConfirmedEvent חדש (orderId)); } @ CommandHandler ידית חלל ציבורית (ShipOrderCommand command) {if (! OrderConfirmed) {זרוק UnconfirmedOrderException חדש); } להחיל (OrderShippedEvent חדש (orderId)); } @EventSourcingHandler בטל ציבורי ב (אירוע OrderConfirmedEvent) {orderConfirmed = true; }

החתימה של מטפלי המקור לפיקוד ואירועים שלנו פשוט קובעת לטפל ({הפקודה}) ו ב ({האירוע}) לשמור על פורמט תמציתי.

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

זה מדגים את הצורך ב OrderConfirmedEvent מטפל המקור לעדכון ההזמנה אושרה מדינה ל נָכוֹן עבור צבירת ההזמנה.

7. בדיקת מודל הפיקוד

ראשית, עלינו להגדיר את הבדיקה על ידי יצירת a FixtureConfiguration בשביל ה OrderAggregate:

מתקן FixtureConfiguration פרטי; @ לפני setUp הריק הציבורי () {fixture = AggregateTestFixture חדש (OrderAggregate.class); }

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

סדר מחרוזת = UUID.randomUUID (). ToString (); מוצר מחרוזת = "כסא דלוקס"; fixture.givenNoPriorActivity () .when (מקום PlaceOrderCommand חדש (orderId, מוצר)) .expectEvents (חדש OrderPlacedEvent (orderId, מוצר));

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

בואו נסתכל על התרחיש הראשון, בו אנו מצפים לחריג:

סדר מחרוזת = UUID.randomUUID (). ToString (); מוצר מחרוזת = "כיסא דלוקס"; fixture.given (OrderPlacedEvent חדש (orderId, product)). When (ShipOrderCommand חדש (orderId)) .expectException (IllegalStateException.class); 

ועכשיו התרחיש השני, שבו אנו מצפים ל OrderShippedEvent:

סדר מחרוזת = UUID.randomUUID (). ToString (); מוצר מחרוזת = "כסא דלוקס"; fixture.given (OrderPlacedEvent חדש (orderId, מוצר), OrderConfirmedEvent חדש (orderId)). כאשר (ShipOrderCommand חדש (orderId)) .expectEvents (חדש OrderShippedEvent (orderId));

8. מודל השאילתות - מטפלים באירועים

עד כה הקמנו את ממשק ה- API העיקרי שלנו עם הפקודות והאירועים, ויש לנו את מודל ה- Command של שירות ההזמנות CQRS שלנו, ה- Order המצרפי.

הַבָּא, אנו יכולים להתחיל לחשוב על אחד מדגמי השאילתות שאפליקצייתנו אמורה לשמש.

אחד הדגמים הללו הוא OrderedProducts:

מחלקה ציבורית OrderedProduct {private final String orderId; מוצר מחרוזת סופי פרטי; פרטי OrderStatus orderStatus; Public OrderedProduct (String orderId, String product) {this.orderId = orderId; this.product = מוצר; orderStatus = OrderStatus.PLACED; } public void setOrderConfirmed () {this.orderStatus = OrderStatus.CONFIRMED; } ריק ריק setOrderShipped () {this.orderStatus = OrderStatus.SHIPPED; } // getters, שווים / hashCode ופונקציות toString} ציבור ציבורי OrderStatus {PLACED, CONFIRMED, SHIPPED}

אנו נעדכן מודל זה על סמך האירועים המופצים דרך המערכת שלנו. אביב שֵׁרוּת שעועית לעדכון המודל שלנו תעשה את הטריק:

מחלקה ציבורית @Service OrderedProductsEventHandler {מפה פרטית סופית orderProducts = חדש HashMap (); @EventHandler בטל בציבור ב- (אירוע OrderPlacedEvent) {String orderId = event.getOrderId (); orderProducts.put (orderId, OrderedProduct חדש (orderId, event.getProduct ())); } // מטפלי אירועים עבור OrderConfirmedEvent ו- OrderShippedEvent ...}

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

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

9. מודל השאילתות - מטפלים בשאילתות

לאחר מכן, כדי לשאול על מודל זה, למשל, כדי לאחזר את כל המוצרים שהוזמנו, ראשית עלינו להציג הודעת שאילתה ל- API הליבה שלנו:

מחלקה ציבורית FindAllOrderedProductsQuery {}

שנית, נצטרך לעדכן את OrderedProductsEventHandler כדי להיות מסוגל להתמודד עם FindAllOrderedProductsQuery:

ידית הרשימה הציבורית @QueryHandler (שאילתת FindAllOrderedProductsQuery) {להחזיר ArrayList חדש (orderProducts.values ​​()); }

ה QueryHandler הפונקציה המאושרת תטפל ב FindAllOrderedProductsQuery והוא אמור להחזיר א רשימה ללא קשר, באופן דומה לכל שאילתת 'מצא הכל'.

10. לשים הכל ביחד

השכלנו את ממשק ה- API העיקרי שלנו עם פקודות, אירועים ושאילתות, והקמנו את מודל הפקודה והשאילתה שלנו באמצעות OrderAggregate ו OrderedProducts דֶגֶם.

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

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

שנית, אנו זקוקים למנגנון לאחסון שלנו OrderedProduct מודל שאילתה. לדוגמא זו, אנו יכולים להוסיף h2 כמאגר נתונים בזיכרון ו spring-boot-starter-data-jpa לנוחות השימוש:

 org.springframework.boot spring-boot-starter-data-jpa com.h2database h2 runtime 

10.1. הגדרת נקודת קצה של REST

לאחר מכן, עלינו להיות מסוגלים לגשת ליישום שלנו, שעבורו ננצל נקודת קצה של REST על ידי הוספת ה- קפיץ-אתחול-רשת תלות:

 org.springframework.boot spring-boot-starter-web 

מנקודת הקצה של REST, אנו יכולים להתחיל לשלוח פקודות ושאילתות:

@RestController מחלקה ציבורית OrderRestEndpoint {סופי פרטי CommandGateway commandGateway; QueryGateway פרטית סופית queryGateway; // בנאי חיווט אוטומטי ונקודות סיום של POST / GET}

ה CommandGateway משמש כמנגנון לשליחת הודעות הפקודה שלנו, ו- QueryGatewayבתורו, למשלוח הודעות שאילתה. השערים מספקים ממשק API פשוט ופשוט יותר בהשוואה ל- CommandBus ו QueryBus שהם מתחברים אליהם.

מכאן והלאה, שֶׁלָנוּ OrderRestEndpoint צריכה להיות נקודת סיום של POST כדי לבצע, לאשר ולשלוח הזמנה:

@ PostMapping ("/ Order-ship") shipOrder ריק () {String orderId = UUID.randomUUID (). ToString (); commandGateway.send (PlaceOrderCommand חדש (orderId, "כיסא דלוקס")); commandGateway.send (ConfirmOrderCommand חדש (orderId)); commandGateway.send (ShipOrderCommand חדש (orderId)); }

זה מעגל את צד הפיקוד של היישום CQRS שלנו.

כעת כל מה שנשאר הוא נקודת סיום של GET לשאילתה לכל OrderedProducts:

@GetMapping ("/ all-orders") רשימה ציבורית findAllOrderedProducts () {return queryGateway.query (חדש FindAllOrderedProductsQuery (), ResponseTypes.multipleInstancesOf (OrderedProduct.class)). להצטרף (); }

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

כפי שאנו מצפים למספר OrderedProduct במקרים שיוחזרו, אנו ממנפים את הסטטי ResponseTypes # multipleInstancesOf (Class) פוּנקצִיָה. עם זאת, סיפקנו כניסה בסיסית לצד השאילתות בשירות ההזמנות שלנו.

השלמנו את ההתקנה, כך שעכשיו נוכל לשלוח כמה פקודות ושאילתות דרך בקר ה- REST שלנו לאחר שהפעלנו את ה- OrderApplication.

פוסט לנקודת קצה / הזמנת משלוח יהיה מיידי OrderAggregate שיפרסם אירועים אשר, בתורם, ישמרו / יעדכנו את האירועים שלנו OrderedProducts. מקבל מה- /כל ההזמנות נקודת הקצה תפרסם הודעת שאילתה שתטופל על ידי OrderedProductsEventHandler, שיחזיר את כל הקיים OrderedProducts.

11. מסקנה

במאמר זה הצגנו את Axon Framework כבסיס רב עוצמה לבניית יישום המנצל את היתרונות של CQRS ו- Sourcing אירועים.

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

לבסוף, שרת אקסון התחזה לחנות האירועים שלנו ולמנגנון ניתוב ההודעות.

ניתן למצוא את היישום של כל הדוגמאות וקטעי הקוד ב- GitHub.

לכל שאלה נוספת שיש לך, עיין גם בקבוצת המשתמשים של Axon Framework.


$config[zx-auto] not found$config[zx-overlay] not found