ארגון שכבות באמצעות אדריכלות משושה, DDD, ומעיין

ג'אווה טופ

רק הכרזתי על החדש למד אביב קורס, המתמקד ביסודות האביב 5 ומגף האביב 2:

>> בדוק את הקורס

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

במדריך זה נבצע יישום אביב באמצעות DDD. בנוסף, נארגן שכבות בעזרת אדריכלות משושה.

בעזרת גישה זו נוכל להחליף בקלות את השכבות השונות של היישום.

2. אדריכלות משושה

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

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

3. עקרונות

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

מה שנעשה במקום זה לחלק את היישום שלנו לשלוש שכבות; יישום (מחוץ), תחום (בתוך) ותשתית (מחוץ):

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

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

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

4. שכבת תחום

נתחיל ביישום שכבת הליבה שלנו, שהיא שכבת התחום.

ראשית, עלינו ליצור את להזמין מעמד:

הזמנה בכיתה ציבורית {מזהה UUID פרטי; מצב OrderStatus פרטי; פרטי רשימת פריטים; מחיר פרטי BigDecimal; סדר ציבורי (מזהה UUID, מוצר מוצר) {this.id = id; this.orderItems = ArrayList חדש (Arrays.astList (OrderItem חדש (מוצר))); this.status = OrderStatus.CREATED; this.price = product.getPrice (); } הריק הציבורי הושלם () {validateState (); this.status = OrderStatus.COMPLETED; } addidrider מבטלים פומביים (מוצר מוצר) {validateState (); validateProduct (מוצר); orderItems.add (OrderItem חדש (מוצר)); מחיר = price.add (product.getPrice ()); } בטל פומבי removeOrder (מזהה UUID) {validateState (); סופי OrderItem orderItem = getOrderItem (id); orderItems.remove (orderItem); מחיר = price.subtract (orderItem.getPrice ()); } // גטרס}

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

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

יתר על כן, ה להזמין הכיתה אחראית גם ליצירת שלה הזמן פריט.

בואו ניצור את הזמן פריט אז בכיתה:

מעמד ציבורי OrderItem {productUd פרטי UUID; מחיר פרטי BigDecimal; OrderItem ציבורי (מוצר מוצר) {this.productId = product.getId (); this.price = product.getPrice (); } // גטרס}

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

לאחר מכן ניצור ממשק מאגר (א נמל בארכיטקטורה משושה). יישום הממשק יהיה בשכבת התשתית:

ממשק ציבורי OrderRepository {Optional findById (מזהה UUID); בטל שמירה (הזמנת הזמנה); }

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

מחלקה ציבורית DomainOrderService מיישמת OrderService {פרטי סופי OrderRepository orderRepository; Public DomainOrderService (OrderRepository orderRepository) {this.orderRepository = orderRepository; } @ עקוב UUID ציבורי createOrder (מוצר מוצר) {הזמנת הזמנה = הזמנה חדשה (UUID.randomUUID (), מוצר); orderRepository.save (order); הזמנת order.getId (); } @Override public void addProduct (מזהה UUID, מוצר מוצר) {Order order = getOrder (id); order.addOrder (מוצר); orderRepository.save (order); } @ ביטול ריק ריק ציבורי completeOrder (UUID id) {Order order = getOrder (id); הזמנה הושלמה(); orderRepository.save (order); } @ ביטול בטל ציבורי בטל deleteProduct (מזהה UUID, מוצר מזהה UUID) {סדר הזמנה = getOrder (מזהה); order.removeOrder (productId); orderRepository.save (order); } הזמנה פרטית getOrder (מזהה UUID) {הזמנת orderRepository .findById (id) .orElseThrow (RuntimeException :: חדש); }}

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

מכיוון ששכבת התחום מנותקת לחלוטין משכבות יישום ותשתית, אָנוּפחית גַם לבדוק את זה באופן עצמאי:

מחלקה DomainOrderServiceUnitTest {פרטי OrderRepository orderRepository; DomainOrderService פרטי נבדק; @BeforeEach ריק ריק setUp () {orderRepository = מדומה (OrderRepository.class); נבדק = DomainOrderService חדש (orderRepository); } @Test בטל shouldCreateOrder_thenSaveIt () {מוצר מוצר סופי = מוצר חדש (UUID.randomUUID (), BigDecimal.TEN, "productName"); מזהה UUID סופי = test.createOrder (מוצר); אמת (orderRepository) .save (any (Order.class)); assertNotNull (id); }}

5. שכבת יישום

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

לכן, בואו ניצור את OrderController:

@ RestController @ RequestMapping ("/ orders") ציבורי OrderController {Private OrderService orderService; @ OrderController ציבורי אוטומטי (OrderService orderService) {this.orderService = orderservice; } @PostMapping CreateOrderResponse createOrder (@RequestBody CreateOrderRequest בקשה) {UUID id = orderService.createOrder (request.getProduct ()); להחזיר CreateOrderResponse חדש (מזהה); } @PostMapping (value = "/ {id} / products") void addProduct (@PathVariable UUID id, @RequestBody AddProductRequest בקשה) {orderService.addProduct (id, request.getProduct ()); } @DeleteMapping (value = "/ {id} / products") בטל deleteProduct (@PathVariable מזהה UUID, @RequestParam UUID productId) {orderService.deleteProduct (id, productId); } @PostMapping ("/ {id} / complete") void completeOrder (@PathVariable UUID id) {orderService.completeOrder (id); }}

הבקר הפשוט הזה של Rest Rest אחראי לתזמור ביצוע לוגיקת התחום.

בקר זה מתאים את ממשק ה- RESTful החיצוני לתחום שלנו. היא עושה זאת על ידי קריאת השיטות המתאימות מ OrderService (נמל).

6. שכבת תשתית

שכבת התשתית מכילה את ההיגיון הדרוש להפעלת היישום.

לכן, נתחיל ביצירת שיעורי התצורה. ראשית, בואו נשתמש בכיתה שתירשם את שלנו OrderService כשעועית אביבית:

@Configuration מחלקה ציבורית BeanConfiguration {@Bean OrderService orderService (OrderRepository orderRepository) {להחזיר DomainOrderService חדש (orderRepository); }}

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

@EnableMongoRepositories (basePackageClasses = SpringDataMongoOrderRepository.class) מעמד ציבורי MongoDBConfiguration {}

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

לבסוף, אנו ניישם את OrderRepository משכבת ​​התחום. נשתמש ב SpringDataMongoOrderRepository ביישום שלנו:

@Component מחלקה ציבורית MongoDbOrderRepository מיישם OrderRepository {פרטי SpringDataMongoOrderRepository orderRepository; @ MongoDbOrderRepository ציבורי אוטומטי (SpringDataMongoOrderRepository orderRepository) {this.orderRepository = orderRepository; } @Override public אופציונלי findById (UUID id) {return orderRepository.findById (id); } @ שמור על ריק ריק ציבורי (הזמנת הזמנה) {orderRepository.save (הזמנה); }}

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

7. יתרונות

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

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

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

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

@Component class class CassandraDbOrderRepository מיישם OrderRepository {פרטית סופית SpringDataCassandraOrderRepository orderRepository; @ CassandraDbOrderRepository ציבורי אוטומטי (SpringDataCassandraOrderRepository orderRepository) {this.orderRepository = orderRepository; } @Override ציבור אופציונלי findById (מזהה UUID) {OptionEntity = orderRepository.findById (id); אם (orderEntity.isPresent ()) {return Optional.of (orderEntity.get () .toOrder ()); } אחר {return Optional.empty (); }} @ שמירה על ריק ריק ציבורי (סדר הזמנה) {orderRepository.save (OrderEntity חדש (סדר)); }}

שלא כמו MongoDB, כעת אנו משתמשים ב- OrderEntity כדי להמשיך את הדומיין במסד הנתונים.

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

המאגר מתאים את התחום לצרכי ההתמדה שלנו.

בואו נלך צעד קדימה ונהפוך את היישום RESTful שלנו ליישום שורת פקודה:

מחלקה ציבורית @Component CliOrderController {לוגר סופי סטטי פרטי LOG = LoggerFactory.getLogger (CliOrderController.class); פרטי סופי OrderService orderService; @ CliOrderController ציבורי אוטומטי (OrderService orderService) {this.orderService = orderService; } חלל ציבורי createCompleteOrder () {LOG.info ("<>"); UUID orderId = createOrder (); orderService.completeOrder (orderId); } חלל ציבורי createIncompleteOrder () {LOG.info ("<>"); UUID orderId = createOrder (); } UUID פרטי CreateOrder () {LOG.info ("ביצוע הזמנה חדשה עם שני מוצרים"); מוצר mobilePhone = מוצר חדש (UUID.randomUUID (), BigDecimal.valueOf (200), "נייד"); מכונת גילוח מוצר = מוצר חדש (UUID.randomUUID (), BigDecimal.valueOf (50), "מכונת גילוח"); LOG.info ("יצירת הזמנה בטלפון נייד"); UUID orderId = orderService.createOrder (טלפון נייד); LOG.info ("הוספת סכין גילוח להזמנה"); orderService.addProduct (orderId, תער); הזמנת OrderId; }}

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

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

8. מסקנה

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

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

ואז, הגענו ליישום עבור כל שכבה:

לבסוף החלפנו את שכבות היישום והתשתית מבלי להשפיע על התחום.

כמו תמיד, הקוד עבור דוגמאות אלה זמין ב- GitHub.

תחתית Java

רק הכרזתי על החדש למד אביב קורס, המתמקד ביסודות האביב 5 ומגף האביב 2:

>> בדוק את הקורס

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