發(fā)布于:2021-01-06 17:33:09
0
114
0
即使是最簡(jiǎn)單的軟件,有時(shí)也可能陷入意大利面條式的代碼中,成為瀏覽的噩夢(mèng),尤其是在舊系統(tǒng)中。在本文中,從應(yīng)用程序的業(yè)務(wù)層查看一些錯(cuò)誤的代碼,以及如何使用更好的設(shè)計(jì)實(shí)踐對(duì)其進(jìn)行修復(fù)。在這些小小的改進(jìn)無法處理之前,請(qǐng)注意噩夢(mèng)代碼。
當(dāng)我們開始編寫軟件時(shí),我們總是希望有一個(gè)好的設(shè)計(jì)。我們閱讀書籍,運(yùn)用最佳實(shí)踐,最后,最后常常是一團(tuán)糟。根據(jù)我在一家定制軟件開發(fā)公司的經(jīng)驗(yàn),我必須每天處理此類代碼,尤其是在某些舊系統(tǒng)上工作時(shí)。
造成這種情況的原因多種多樣,我將在一系列文章中嘗試涵蓋其中一些,以實(shí)際的方式對(duì)其進(jìn)行研究。在我的第一個(gè)示例中,我將說明為什么簡(jiǎn)單的軟件會(huì)演變成一場(chǎng)噩夢(mèng),并建議進(jìn)行一些改進(jìn)。我將僅專注于處理業(yè)務(wù)邏輯的服務(wù)層。
首先,我們實(shí)際上經(jīng)??吹剑ê途帉懀┮恍╁e(cuò)誤的代碼
讓我們從一個(gè)簡(jiǎn)單的存儲(chǔ)應(yīng)用程序開始。我們擁有帶有服務(wù),存儲(chǔ)庫(kù)的產(chǎn)品資源,并且可以執(zhí)行CRUD操作,這正是我們認(rèn)為需要的。我們的產(chǎn)品服務(wù)如下所示:
public class ProductService { public String create(Product product) { return productRepository.create(product); } public String update(Product product) { return productRepository.update(product); } public Product get(String productId) { return productRepository.get(productId); } public void delete(Product product) { productRepository.delete(product); } }
還會(huì)有其他一些東西,例如DTO到實(shí)體的映射,控制器等。但是正如我所說的,我們將考慮將它們編寫為簡(jiǎn)單起見。我們的產(chǎn)品實(shí)體是簡(jiǎn)單的Java Bean,我們的存儲(chǔ)庫(kù)保存在正確的數(shù)據(jù)庫(kù)表中。然后,我們得到另一個(gè)要求,即我們還將創(chuàng)建一個(gè)在線商店,并且需要一種下訂單的方法。因此,我們添加了快速訂購(gòu)服務(wù)來滿足我們?nèi)匀缓芎?jiǎn)單的要求:
public class OrderService { public String saveOrder(Order order) { return orderRepository.save(order); } }
它簡(jiǎn)單,易讀且有效!然后,下訂單時(shí)就需要更新庫(kù)存中的產(chǎn)品。我們這樣做:
public class OrderService { public String saveOrder(Order order) { Product product=productService.get(order.getProductId()); product.setAvailableQuantity(product.getAvailableQuantity()-order.getQuantity()); productService.update(product); return orderRepository.save(order); } }
您可能會(huì)看到我要去的地方,但這仍然可讀并且可以正常工作。之后,我們又獲得了三個(gè)要求。1 /我們需要致電運(yùn)輸服務(wù)將該產(chǎn)品運(yùn)送到一個(gè)地址2 /如果沒有足夠的庫(kù)存來履行訂單,則拋出一個(gè)錯(cuò)誤3 /如果產(chǎn)品的可用數(shù)量低于最低數(shù)量以進(jìn)行重新庫(kù)存。結(jié)果如下:
public class OrderService { public String saveOrder(Order order) { Product product=productService.get(order.getProductId()); //The order service works more like a product service in the following liness if(product.getAvailableQuantity()<order.getQuantity()){ throw new ProductNotAvailableException(); } product.setAvailableQuantity(product.getAvailableQuantity()-order.getQuantity()); productService.update(product); if(product.getAvailableQuantity()<Product.MINIMUM_STOCK_QUANTITY){ productService.restock(product); } //It also needs to know how shipments are created Shipment shipment=new Shipment(product, order.getQuantity(), order.getAddressTo()); shipmentService.save(shipment); return orderRepository.save(order); } }
我知道這可能是一個(gè)極端的例子,但是我確信我們?cè)陧?xiàng)目中已經(jīng)看到了類似的代碼。這樣做有多個(gè)問題–責(zé)任共擔(dān),與其他領(lǐng)域的邏輯和基礎(chǔ)架構(gòu)打亂等等。如果這是一個(gè)真實(shí)的商店,那么接單的人就像總經(jīng)理–照顧一切,從實(shí)際訂購(gòu)庫(kù)存維護(hù)和交付。
現(xiàn)在到一個(gè)更好的版本
讓我們嘗試以不同的方式處理相同的情況。我將從訂購(gòu)服務(wù)開始。為什么我們調(diào)用方法saveOrder?因?yàn)槲覀儗⑵湟暈殚_發(fā)人員,而不是從業(yè)務(wù)角度來看。我們開發(fā)人員的想法通常是數(shù)據(jù)庫(kù)驅(qū)動(dòng)的(或REST驅(qū)動(dòng)的),我們將我們的軟件視為一系列CRUD操作。通常,當(dāng)我們閱讀有關(guān)域驅(qū)動(dòng)設(shè)計(jì)的書籍時(shí),會(huì)提到通用語言(Ubiquitous Language)這一術(shù)語,即開發(fā)人員和用戶之間的通用語言。如果我們嘗試在我們的代碼中為業(yè)務(wù)建模,那么為什么不使用正確的術(shù)語。我們可以將初始代碼更改為:
public class OrderService { public String placeOrder(Order order) { return orderRepository.save(order); } }
進(jìn)行很小的更改,但即使那樣也會(huì)使其更具可讀性。這是業(yè)務(wù)層,而不是數(shù)據(jù)庫(kù)層-我們?nèi)ド痰陼r(shí)下訂單,但不保存訂單。然后,當(dāng)其他需求出現(xiàn)時(shí),而不是開始使用帶有CRUD操作的現(xiàn)有服務(wù)對(duì)它們進(jìn)行編碼,我們可以嘗試重新創(chuàng)建業(yè)務(wù)模型。我們?cè)儐枠I(yè)務(wù)人員,他們告訴我們,下訂單時(shí),接單的人會(huì)致電庫(kù)存部門,詢問他們產(chǎn)品是否可用,然后進(jìn)行儲(chǔ)備并致電帶有預(yù)定號(hào)和地址的交貨人,以便他們裝運(yùn)它。是什么阻止我們?cè)诖a中執(zhí)行相同的操作?
public class OrderService { public String placeOrder(Order order) { String productReservationId=productService.requestProductReservation(order.getProductId, order.getQuantity()); String shippingId=shipmentService.requestDelivery(productReservationId, order.getAddressTo()); order.addShippingId(shippingId); return orderRepository.save(order); } }
在我看來,它看起來更干凈,代表了實(shí)際商店中發(fā)生的事件的順序。訂單服務(wù)不需要知道產(chǎn)品的工作方式或運(yùn)輸方式。它只是使用完成工作所需的方法。我們也需要修改其他服務(wù):
public class ProductService { //Method used in Orders Service public String requestProductReservation(String productId, int quantity){ Product product=productRepository.get(productId); product.reserve(quantity); productRepository.update(product); return createProductReservation(product, quantity); } private String createProductReservation(Product product, int quantity){ ProductReservation reservation=new ProductReservation(product,quantity); reservation.setStatus(ReservationStatus.CREATED); return reservationRepository.save(reservation); } //Method used in Shipment Service public ProductReservation getProductsForDelivery(String reservationId){ ProductReservation reservation=reservationRepository.getProductReservation(reservationId); reservation.getProduct.releaseReserved(reservation.getQuantity()); if(reservation.getProduct().needRestock()){ this.restock(product); } reservation.setStatus(ReservationStatus.PROCESSED); reservationRepository.update(reservation); } }
產(chǎn)品服務(wù)提供了其他服務(wù)要使用的兩種方法,但對(duì)它們的結(jié)構(gòu)一無所知。它不關(guān)心訂單,發(fā)貨等。當(dāng)產(chǎn)品需要補(bǔ)貨以及產(chǎn)品數(shù)量是否足夠時(shí),邏輯就在實(shí)際產(chǎn)品內(nèi)部。
public class Product() { //Fields, getters, setters etc... public void reserve(int quantity){ if(this.availableQuantity - this.reservedQuantity > quantity){ this.reservedQuantity+=quantity; } else throw new ProductReservationException(); } public releaseReserved(int requested){ if(this.reservedQuantity>=requested){ this.reservedQuantity-=requested; this.availableQuantity-=requested; } else throw new ProductReservationException(); } public boolean needsRestock(){ return this.availableQuantity<MINIMUM_STOCK_QUANTITY; } }
貨運(yùn)服務(wù)可以是這樣的:
public class ShipmentService { public String requestDelivery(String reservationId, Address address){ ProductReservation reservation=productService.getProductForDelivery(reservationId); Shipment shipment=new Shipment(reservation, address); return shipmentRepository.save(shipment); } }
我并不是說這是最好的設(shè)計(jì),但我認(rèn)為它要干凈得多。每個(gè)服務(wù)都照顧自己的領(lǐng)域,并且對(duì)其他服務(wù)了解得最少。實(shí)際的實(shí)體不僅是數(shù)據(jù)持有者,而且還攜帶與之相關(guān)的邏輯,因此服務(wù)不需要直接修改其內(nèi)部狀態(tài)。在我看來,最有價(jià)值的是代碼真正代表了業(yè)務(wù)運(yùn)作方式。
結(jié)論
如果我們從本文的第一部分開始不討論這種情況,則應(yīng)該嘗試花一些時(shí)間并正確地理解我們的模型。即使出現(xiàn)了新的要求并且我們受到時(shí)間的壓力,或者重構(gòu)將花費(fèi)更多的時(shí)間,我們也不應(yīng)該懶惰。服務(wù)和實(shí)體中來自不同領(lǐng)域的混合邏輯乍一看似乎是可維護(hù)的,但隨著項(xiàng)目規(guī)模的擴(kuò)大,它變成了意大利面。就像我們?cè)诂F(xiàn)實(shí)生活中的商店示例一樣,小型的在線商店所有者可以處理從接單,進(jìn)貨,交付和財(cái)務(wù)等所有事務(wù)。但是,當(dāng)商店發(fā)展壯大時(shí),他將無能為力,這將變得一團(tuán)糟。
作者介紹
熱門博客推薦