[翻譯]在 Go 應用中使用簡明架構(3)

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

原文在此,續前……

——–翻譯分隔線——–

在 Go 應用中使用簡明架構(3)

用例層

現在來看看用例層代碼,同樣,它剛剛好能放在一個檔案中:

package usecasesimport ("domain""fmt")type UserRepository interface {Store(user User)FindById(id int) User}type User struct {Id       intIsAdmin  boolCustomer domain.Customer}type Item struct {Id    intName  stringValue float64}type Logger interface {Log(message string) error}type OrderInteractor struct {UserRepository  UserRepositoryOrderRepository domain.OrderRepositoryItemRepository  domain.ItemRepositoryLogger          Logger}func (interactor *OrderInteractor) Items(userId, orderId int) ([]Item, error) {var items []Itemuser := interactor.UserRepository.FindById(userId)order := interactor.OrderRepository.FindById(orderId)if user.Customer.Id != order.Customer.Id {message := "User #%i (customer #%i) "message += "is not allowed to see items "message += "in order #%i (of customer #%i)"err := fmt.Errorf(message,user.Id,user.Customer.Id,order.Id,order.Customer.Id)interactor.Logger.Log(err.Error())items = make([]Item, 0)return items, err}items = make([]Item, len(order.Items))for i, item := range order.Items {items[i] = Item{item.Id, item.Name, item.Value}}return items, nil}func (interactor *OrderInteractor) Add(userId, orderId, itemId int) error {var message stringuser := interactor.UserRepository.FindById(userId)order := interactor.OrderRepository.FindById(orderId)if user.Customer.Id != order.Customer.Id {message = "User #%i (customer #%i) "message += "is not allowed to add items "message += "to order #%i (of customer #%i)"err := fmt.Errorf(message,user.Id,user.Customer.Id,order.Id,order.Customer.Id)interactor.Logger.Log(err.Error())return err}item := interactor.ItemRepository.FindById(itemId)if domainErr := order.Add(item); domainErr != nil {message = "Could not add item #%i "message += "to order #%i (of customer #%i) "message += "as user #%i because a business "message += "rule was violated: '%s'"err := fmt.Errorf(message,item.Id,order.Id,order.Customer.Id,user.Id,domainErr.Error())interactor.Logger.Log(err.Error())return err}interactor.OrderRepository.Store(order)interactor.Logger.Log(fmt.Sprintf("User added item '%s' (#%i) to order #%i",item.Name, item.Id, order.Id))return nil}type AdminOrderInteractor struct {OrderInteractor}func (interactor *AdminOrderInteractor) Add(userId, orderId, itemId int) error {var message stringuser := interactor.UserRepository.FindById(userId)order := interactor.OrderRepository.FindById(orderId)if !user.IsAdmin {message = "User #%i (customer #%i) "message += "is not allowed to add items "message += "to order #%i (of customer #%i), "message += "because he is not an administrator"err := fmt.Errorf(message,user.Id,user.Customer.Id,order.Id,order.Customer.Id)interactor.Logger.Log(err.Error())return err}item := interactor.ItemRepository.FindById(itemId)if domainErr := order.Add(item); domainErr != nil {message = "Could not add item #%i "message += "to order #%i (of customer #%i) "message += "as user #%i because a business "message += "rule was violated: '%s'"err := fmt.Errorf(message,item.Id,order.Id,order.Customer.Id,user.Id,domainErr.Error())interactor.Logger.Log(err.Error())return err}interactor.OrderRepository.Store(order)interactor.Logger.Log(fmt.Sprintf("Admin added item '%s' (#%i) to order #%i",item.Name, item.Id, order.Id))return nil}

商城的用例層主要是由一個 User 實體和兩個用例組成。由於使用者需要一套儲存和讀取的持久化機制,所以這個實體跟領域層中的實體一樣有儲存區。

意料之中,用例就是函數,例如 OrderInteractor 結構上的方法。這並不是必須的,完全可以是沒有任何綁定的函數。不過,接下來就會看到,將其附加在結構體上使得在確定的依賴關係中,更容易做到功能注入。

上面的代碼就是軟體禪道“將什麼放到哪裡”這個話題的主要樣本。首先,外部層次的所有東西都需要注入到 OrderInteractor 中去 AdminOrderInteractor,而這個結構僅僅對用例層和內部東西命名。再一次強調,這全部都是因為依賴規則。這個包被設計為不依賴任何外部的領域或用例;例如,這樣就可以使用類比儲存區來測試,或可以平滑的替換日誌的實現部分,也就是說,不用修改任何上層代碼。

Bob Martin 這樣描述用例“…安排來自實體的資料流走向,並且讓這些實體使用全面的商業規則來達到用例的目標。”

例如這裡你看到的 OrderInteractor 的 Add 方法。這個方法控制擷取所需要的對象,並讓用合理的方式讓它們工作,以便完整滿足用例的需要。它管理了這個特別的用例可能出現的錯誤情況,並且確保規則生效;同時記錄是哪個規則。由於 $250 的限制規則是在所有用例上生效的商業規則,所以是在領域層處理的。檢查哪個使用者可以向訂單添加商品,從另一方面來說是用例特有的,它所有的實體 User 不應當在領域層考慮。因此在用例層處理,並且依賴於是普通使用者還是管理使用者添加商品而有著不同的處理方式。

也來談談日誌是如果在這個層次處理的吧。在軟體應用中,各種日誌出現在各個層次中。雖然所有的日誌實體最終可能都是硬碟上的一個文字檔,不過再次強調的是將技術從概念細節中分離非常重要。我們的用例層不知道文字檔和硬碟。概念上來說,這個層次只是說:“關於應用的用例,有些有趣的事情發生了,我希望這個事件被記錄下來”,而“記錄”並不是說“寫到什麼地方”,只是說“記錄”而已——不要產生任何多餘的想法是相當明智的。

因此,僅僅提供一個介面來滿足用例的需求,並注入實際的實現——在未來任何時候,不論應用變得多麼複雜,通過這個辦法都可以隨時將日誌訊息記錄到資料庫來代替普通檔案,只要確保介面的調用者與實現分離,就無需修改內部層次的任何一行代碼。

最好的辦法就是在這裡設定兩個不同的 OrderInteractor。當想要管理員的操作記錄到一個檔案,普通使用者的記錄到另一個檔案,這就會變得非常簡單。只需要建立兩個不同的日誌執行個體,都實現 usecases.Logger 介面,並且將它們分別注入到對應的 OrderInteractor 中去。

在用例代碼中另一個重要的細節就是 Item 結構。我們在領域層不是已經有一個了嗎?為什麼不在 Items() 方法裡直接返回這個?是因為不在外部層次上暴露用例層的實體是非常明智的。實體所具有的不僅僅是資料,還有行為。這些行為只應當由用例觸發。只要不將實體暴露給外部層次,就能確保這些行為只會被用例觸發。外部層次僅僅需要的是結構中的資料來完成其工作,因此,這就是我們應當提供給它們的全部東西。

跟領域層一樣,這些代碼協助理解簡明架構對於一個特定的軟體是如何工作的:瞭解實現了哪些商業領域以及哪些規則生效,只需要查看領域層的代碼,瞭解使用者和商業之間的全部內部資訊,只需要查看用例代碼。就可以瞭解到這個應用允許客戶自己添加商品到訂單,並且列出訂單的所有商品,以及管理員可以為使用者向訂單中添加商品。只要列印出來,你就有了一個關於用例的隨時更新的、可靠並準確的格式化文檔。

——–翻譯分隔線——–

未完待續……

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.