溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

.NET中用戶端的防腐層作用及設計是怎樣的

發(fā)布時間:2022-01-07 21:34:53 來源:億速云 閱讀:127 作者:柒染 欄目:編程語言

本篇文章為大家展示了.NET中用戶端的防腐層作用及設計是怎樣的,內(nèi)容簡明扼要并且容易理解,絕對能使你眼前一亮,通過這篇文章的詳細介紹希望你能有所收獲。

1.背景介紹

隨著現(xiàn)在的企業(yè)應用架構(gòu)都在向著SOA方向轉(zhuǎn)變,目的就是將一個龐大的業(yè)務系統(tǒng)按照業(yè)務進行劃分,不管從公司的管理上、產(chǎn)品的開發(fā)上,這一系列流程來看,都是正確的。SOA確實帶來了解決現(xiàn)在大型企業(yè)級應用系統(tǒng)快速膨脹的解決辦法。

但是本文要說的是,我們都將目光轉(zhuǎn)向到了后端,也就是服務端,而將精力和時間都重點投在了后端服務的架構(gòu)設計上,漸漸的忽視了顯示端的架構(gòu)設計。然而顯示端的邏輯也越來越復雜,顯示端輕薄的架構(gòu)其實已經(jīng)浮現(xiàn)出難以應付后端服務接口快速膨脹的危險,服務接口都是按照指數(shù)級增加,基本上每一個新的業(yè)務需求都是提供新的接口,這沒有問題。按照服務的設計原則,服務接口就應該有著明確的作用,而不是按照代碼的思維來考慮接口的設計。

但是由此帶來的問題就是組合這些接口的顯示端的結(jié)構(gòu)是否和這種變化是一致的,是否做好了這種變化帶來顯示端邏輯復雜的準備。

根據(jù)我自己的親身體會,我發(fā)現(xiàn)顯示端的架構(gòu)設計不被重視,這里的重視不是老板是否重視,而是我們開發(fā)人員沒有重視,當然這里排除時間問題。我觀察過很多用戶接口項目架構(gòu),結(jié)構(gòu)及其簡單,沒有封裝、沒有重用,看不到任何的設計原則。這樣就會導致這些代碼很難隨著業(yè)務的快速推動由服務接口帶來的沖擊,這里還有一個最大的問題就是,作為程序員的我們是否有快速重構(gòu)的意識,我很喜歡這條程序員職業(yè)素質(zhì)。它可以讓我們敏捷的、快速的跟上由業(yè)務的發(fā)展帶來的項目結(jié)構(gòu)的變化。

迭代重構(gòu)對項目有著微妙的作用,重構(gòu)不能夠過早也不能夠過遲,要剛好在需要的時候重構(gòu)。對于重構(gòu)我的經(jīng)驗就是,當你面對新功能寫起來比較蹩腳的時候時,這是一個重構(gòu)信號,此時應該是最優(yōu)的重構(gòu)時間。重構(gòu)不是專門的去準備時間,而是穿插在你寫代碼的過程中,它是你編碼的一部分。所以我覺得TDD被人接受的理由也在于此。

2.SOA架構(gòu)下的顯示端架構(gòu)腐化

顯示端的架構(gòu)腐化我個人覺得有兩個問題導致,第一個,原本顯示端的結(jié)構(gòu)在傳統(tǒng)系統(tǒng)架構(gòu)中可以工作的很好,但是現(xiàn)在的整體架構(gòu)變了,所以需要及時作出調(diào)整。第二,顯示端的架構(gòu)未能及時的重構(gòu),未能將顯示端結(jié)構(gòu)進行進一步分離,將顯示邏輯獨立可測試。

這樣隨著SOA接口的不斷增加,顯示端直接將調(diào)用服務的方法嵌入到顯示邏輯中,如,ASP.NET Mvc、ASP.NET Webapi的控制器中,包括兩個層面之間的DTO轉(zhuǎn)換。

按照DDD的上下文設計方法,在用戶顯示端也是可以有選擇的創(chuàng)建面向顯示的領(lǐng)域模型,此模型主要處理領(lǐng)域在即將到達服務端之后的前期處理。畢竟一個領(lǐng)域?qū)嶓w有著多個方面的職責,如果能在顯示端建立起輕量級的領(lǐng)域模型,對顯示邏輯的重構(gòu)將大有好處,當然前提是你有著復雜的領(lǐng)域邏輯。(我之前的上一家公司(美國知名的電子商務平臺),他們的顯示端有著復雜的領(lǐng)域邏輯,就光一個顯示端就復雜的讓人吃驚,如果能在此基礎上引入領(lǐng)域模型顯示端上下文,將對復雜的邏輯處理很有好好處,當然這只是我未經(jīng)驗證的猜測而已,僅供參考。)

對顯示端領(lǐng)域模型處理有興趣的可以參考本人寫的有關(guān)這方面的兩篇文章:

.NET應用架構(gòu)設計—面向查詢的領(lǐng)域驅(qū)動設計實踐(調(diào)整傳統(tǒng)三層架構(gòu),外加維護型的業(yè)務開關(guān))

.NET應用架構(gòu)設計—面向查詢服務的參數(shù)化查詢設計(分解業(yè)務點,單獨配置各自的數(shù)據(jù)查詢契約)

原本干凈的顯示邏輯多了很多無關(guān)的服務調(diào)用細節(jié),還有很多轉(zhuǎn)換邏輯,判斷邏輯,而這些東西原本不屬于這個地方,讓他們放在合適的地方對顯示邏輯的重構(gòu)、重用很有幫助。

如果不將其移出顯示邏輯中,那么隨著服務接口的不斷增加和擴展,將直接導致你修改顯示邏輯代碼,如果你的顯示邏輯代碼是MVC、Webapi共用的邏輯,那么情況就更加復雜了,最后顯示邏輯里面將被ViewModel與Service Dto之間的轉(zhuǎn)換占領(lǐng),你很難找到有價值的邏輯了。

3.有效使用防腐層來隔離碎片服務導致顯示端邏輯腐爛

解決這些問題的方法就是引入防腐層,盡管防腐層的初衷是為了解決系統(tǒng)集成時的領(lǐng)域模型之間的轉(zhuǎn)換,但是我覺得現(xiàn)在的系統(tǒng)架構(gòu)和集成有著很多相似之處,我們可以適當?shù)慕梃b這些好的設計方法來解決相似的問題。

引入防腐層之后,將原本不該出現(xiàn)在顯示邏輯中的代碼全部搬到防腐層中來,在防腐層中建立起OO機制,讓這些OO對象能夠和顯示邏輯一起搭配使用。

圖1:

.NET中用戶端的防腐層作用及設計是怎樣的

將用戶層分層三個子層,UiLayer,Show Logic Layer,Anticorrosive Layer,最后一個是服務的接口組,所有的服務接口調(diào)用均需要從防腐層走。

我們需要將Show Logic Layer中的服務調(diào)用,類型轉(zhuǎn)換代碼遷移到Anticorrsoive Layer中,在這里可以對象化轉(zhuǎn)換邏輯也可以不對象化,具體可以看下項目是否需要。如果業(yè)務確實比較復雜的時候,那么我們?yōu)榱朔庋b、重用就需要進行對象化。

4.剝離服務調(diào)用的技術(shù)組件讓其依賴接口

首先要做的就是將邏輯代碼中的服務對象重構(gòu)成面向接口的,然后讓其動態(tài)的依賴注入到邏輯類型中。在ASP.NETWEBAPI中,我們基本上將顯示邏輯都寫在這里面,我也將使用此方式來演示本章例子,但是如果你的MVC項目和WEBAPI項目共用顯示邏輯就需要將其提出來形成獨立的項目(Show Logic Layer)。

using OrderManager.Port.Models;
using System.Collections.Generic;
using System.Web.Http; 

namespace OrderManager.Port.Controllers
{
    public class OrderController : ApiController
    {
        [HttpGet]
        public OrderViewModel GetOrderById(long oId)
        {
            OrderService.Contract.OrderServiceClient client = new OrderService.Contract.OrderServiceClient();
            var order = client.GetOrderByOid(oId); 

            if (order == null) return null; 

            return AutoMapper.Mapper.DynamicMap<OrderViewModel>(order);
        }
    }
}

這是一段很簡單的調(diào)用Order服務的代碼,首先需要實例化一個服務契約中包含的客戶端代理,然后通過代理調(diào)用遠程服務方法GetOrderByOid(long oId)。執(zhí)行一個簡單的判斷,最后輸出OrderViewModel。

如果所有的邏輯都這么簡單我想就不需要什么防腐層了,像這種類型的顯示代碼是極其簡單的,我這里的目的不是為了顯示多么的復雜的代碼如何寫,而是將服務調(diào)用調(diào)用的代碼重構(gòu)層接口,然后注入進OrderController實例中。目的就是為了能夠在后續(xù)的迭代重構(gòu)中對該控制器進行單元測試,這可能有點麻煩,但是為了長久的利益還是需要的。

using OrderManager.Port.Component;
using OrderManager.Port.Models;
using System.Collections.Generic;
using System.Web.Http; 

namespace OrderManager.Port.Controllers
{
    public class OrderController : ApiController
    {
        private readonly IOrderServiceClient orderServiceClient;
        public OrderController(IOrderServiceClient orderServiceClient)
        {
            this.orderServiceClient = orderServiceClient;
        } 

        [HttpGet]
        public OrderViewModel GetOrderById(long oId)
        {
            var order = orderServiceClient.GetOrderByOid(oId); 

            if (order == null) return null; 

            return AutoMapper.Mapper.DynamicMap<OrderViewModel>(order);
        }
    }
}

為了能在運行時動態(tài)的注入到控制器中,你需要做一些基礎工作,擴展MVC控制器的初始化代碼。這樣我們就可以對OrderController進行完整的單元測試。

剛才說了,如果顯示邏輯都是這樣的及其簡單,那么一切都沒有問題了,真實的顯示邏輯非常的復雜而且多變,并不是所有的類型轉(zhuǎn)換都能使用Automapper這一類動態(tài)映射工具解決,有些類型之間的轉(zhuǎn)換還有邏輯在里面。GetOrderById(long oId)方法是為了演示此處的重構(gòu)服務調(diào)用組件用的。

大部分情況下我們是需要組合多個服務調(diào)用的,將其多個結(jié)果組合起來返回給前端的,這里的OrderViewModel對象里面的Items屬性類型OrderItem類型中包含了一個Product類型屬性,在正常情況下我們只需要獲取訂單的條目就行了,但是有些時候確實需要將條目中具體的產(chǎn)品信息也要返回給前臺進行部分信息的展現(xiàn)。

using System.Collections.Generic; 

namespace OrderManager.Port.Models
{
    public class OrderViewModel
    {
        public long OId { get; set; } 

        public string OName { get; set; } 

        public string Address { get; set; } 

        public List<OrderItem> Items { get; set; }
    }
}

在OrderViewModel中的Items屬性是一個List<OrderItem>集合,我們再看OrderItem屬性。

using System.Collections.Generic; 

namespace OrderManager.Port.Models
{
    public class OrderItem
    {
        public long OitemId { get; set; } 

        public long Pid { get; set; } 

        public float Price { get; set; } 

        public int Number { get; set; } 

        public Product Product { get; set; }
    }
}

它里面包含了一個Product實例,有些時候需要將該屬性賦上值。

namespace OrderManager.Port.Models
{
    public class Product
    {
        public long Pid { get; set; } 

        public string PName { get; set; } 

        public long PGroup { get; set; } 

        public string Production { get; set; }
    }
}

產(chǎn)品類型中的一些信息主要是用來作為訂單條目展現(xiàn)時能夠更加的人性化一點,你只給一個產(chǎn)品ID,不能夠讓用戶知道是哪個具體的商品。

我們接著看一個隨著業(yè)務變化帶來的代碼急速膨脹的例子,該例子中我們需要根據(jù)OrderItem中的Pid獲取Product完整信息。

using OrderManager.Port.Component;
using OrderManager.Port.Models;
using System.Collections.Generic;
using System.Web.Http;
using System.Linq; 

namespace OrderManager.Port.Controllers
{
    public class OrderController : ApiController
    {
        private readonly IOrderServiceClient orderServiceClient; 

        private readonly IProductServiceClient productServiceClient;
        public OrderController(IOrderServiceClient orderServiceClient, IProductServiceClient productServiceClient)
        {
            this.orderServiceClient = orderServiceClient;
            this.productServiceClient = productServiceClient;
        } 

        [HttpGet]
        public OrderViewModel GetOrderById(long oId)
        {
            var order = orderServiceClient.GetOrderByOid(oId); 

            if (order == null && order.Items != null && order.Items.Count > 0) return null; 

            var result = new OrderViewModel()
            {
                OId = order.OId,
                Address = order.Address,
                OName = order.OName,
                Items = new System.Collections.Generic.List<OrderItem>()
            }; 

            if (order.Items.Count == 1)
            {
                var product = productServiceClient.GetProductByPid(order.Items[0].Pid);//調(diào)用單個獲取商品接口
                if (product != null)
                {
                    result.Items.Add(ConvertOrderItem(order.Items[0], product));
                }
            }
            else
            {
                List<long> pids = (from item in order.Items select item.Pid).ToList(); 

                var products = productServiceClient.GetProductsByIds(pids);//調(diào)用批量獲取商品接口
                if (products != null)
                {
                    result.Items = ConvertOrderItems(products, order.Items);//批量轉(zhuǎn)換OrderItem類型
                } 

            } 

            return result;
        } 

        private static OrderItem ConvertOrderItem(OrderService.OrderItem orderItem, ProductService.Contract.Product product)
        {
            if (product == null) return null; 

            return new OrderItem()
            {
                Number = orderItem.Number,
                OitemId = orderItem.OitemId,
                Pid = orderItem.Pid,
                Price = orderItem.Price, 

                Product = new Product()
                {
                    Pid = product.Pid,
                    PName = product.PName,
                    PGroup = product.PGroup,
                    Production = product.Production
                }
            };
        } 

        private static List<OrderItem> ConvertOrderItems(List<ProductService.Contract.Product> products, List<OrderService.OrderItem> orderItems)
        {
            var result = new List<OrderItem>(); 

            orderItems.ForEach(item =>
            {
                var orderItem = ConvertOrderItem(item, products.Where(p => p.Pid == item.Pid).FirstOrDefault());
                if (orderItem != null)
                    result.Add(orderItem);
            }); 

            return result;
        }
    }
}

我的第一感覺就是,顯示邏輯已經(jīng)基本上都是類型轉(zhuǎn)換代碼,而且這里我沒有添加任何一個有關(guān)顯示的邏輯,在這樣的情況下都讓代碼急速膨脹了,可想而知,如果再在這些代碼中加入顯示邏輯,我們基本上很難在后期維護這些顯示邏輯,而這些顯示邏輯才是這個類的真正職責。

由此帶來的問題就是重要的邏輯淹沒在這些轉(zhuǎn)換代碼中,所以我們急需一個能夠容納這些轉(zhuǎn)換代碼的位置,也就是防腐層,在防腐層中我們專門來處理這些轉(zhuǎn)換邏輯,當然我這里的例子是比較簡單的,只包含了查詢,真正的防腐層是很復雜的,它里面要處理的東西不亞于其他層面的邏輯處理。我們這里僅僅是在轉(zhuǎn)換一些DTO對象而不是復雜的DomainModel對象。

5.將服務的DTO與顯示端的ViewModel之間的轉(zhuǎn)換放入防腐層

我們需要一個防腐層來處理這些轉(zhuǎn)換代碼,包括對后端服務的調(diào)用邏輯,將這部分代碼移入防腐對象中之后會對我們后面重構(gòu)很有幫助。

namespace OrderManager.Anticorrsive
{
    using OrderManager.Port.Component;
    using OrderManager.Port.Models;
    using System.Collections.Generic;
    using System.Linq; 

    /// <summary>
    /// OrderViewModel 防腐對象
    /// </summary>
    public class OrderAnticorrsive : AnticorrsiveBase<OrderViewModel>, IOrderAnticorrsive
    {
        private readonly IOrderServiceClient orderServiceClient; 

        private readonly IProductServiceClient productServiceClient; 

        public OrderAnticorrsive(IOrderServiceClient orderServiceClient, IProductServiceClient productServiceClient)
        {
            this.orderServiceClient = orderServiceClient;
            this.productServiceClient = productServiceClient;
        } 

        public OrderViewModel GetOrderViewModel(long oId)
        {
            var order = orderServiceClient.GetOrderByOid(oId); 

            if (order == null && order.Items != null && order.Items.Count > 0) return null; 

            var result = new OrderViewModel()
            {
                OId = order.OId,
                Address = order.Address,
                OName = order.OName,
                Items = new System.Collections.Generic.List<OrderItem>()
            }; 

            if (order.Items.Count == 1)
            {
                var product = productServiceClient.GetProductByPid(order.Items[0].Pid);//調(diào)用單個獲取商品接口
                if (product != null)
                {
                    result.Items.Add(ConvertOrderItem(order.Items[0], product));
                }
            }
            else
            {
                List<long> pids = (from item in order.Items select item.Pid).ToList(); 

                var products = productServiceClient.GetProductsByIds(pids);//調(diào)用批量獲取商品接口
                if (products != null)
                {
                    result.Items = ConvertOrderItems(products, order.Items);//批量轉(zhuǎn)換OrderItem類型
                } 

            } 

            return result;
        } 

        private static OrderItem ConvertOrderItem(OrderService.OrderItem orderItem, ProductService.Contract.Product product)
        {
            if (product == null) return null; 

            return new OrderItem()
            {
                Number = orderItem.Number,
                OitemId = orderItem.OitemId,
                Pid = orderItem.Pid,
                Price = orderItem.Price, 

                Product = new Product()
                {
                    Pid = product.Pid,
                    PName = product.PName,
                    PGroup = product.PGroup,
                    Production = product.Production
                }
            };
        } 

        private static List<OrderItem> ConvertOrderItems(List<ProductService.Contract.Product> products, List<OrderService.OrderItem> orderItems)
        {
            var result = new List<OrderItem>(); 

            orderItems.ForEach(item =>
            {
                var orderItem = ConvertOrderItem(item, products.Where(p => p.Pid == item.Pid).FirstOrDefault());
                if (orderItem != null)
                    result.Add(orderItem);
            }); 

            return result;
        }
    }
}

如果你覺得有必要可以將IOrderServiceClient、IProductServiceClient 兩個接口放入AnticorrsiveBase<OrderViewModel>基類中。

5.1.轉(zhuǎn)換邏輯過程化,直接寫在防腐層的方法中

對于防腐層的設計,其實如果你的轉(zhuǎn)換代碼不多,業(yè)務也比較簡單時,我建議直接寫成過程式的代碼比較簡單點。將一些可以重用的代碼直接使用靜態(tài)的擴展方法來使用也是比較簡單方便的,最大問題就是不利于后期的持續(xù)重構(gòu),我們無法預知未來的業(yè)務變化,但是我們可以使用重構(gòu)來解決。

5.2.轉(zhuǎn)換邏輯對象化,建立起封裝、重用結(jié)構(gòu),防止進一步腐化

相對應的,可以將轉(zhuǎn)換代碼進行對象化,形成防腐對象,每一個對象專門用來處理某一個業(yè)務點的數(shù)據(jù)獲取和轉(zhuǎn)換邏輯,如果你有數(shù)據(jù)發(fā)送邏輯那么將在防腐對象中大大獲益,對象化后就可以直接訂閱相關(guān)控制器的依賴注入事件,如果你是過程式的代碼想完成動態(tài)的轉(zhuǎn)換、發(fā)送、獲取會比較不方便。

6.防腐層的兩種依賴倒置設計方法

我們接著看一下如何讓防腐對象無干擾的進行自動化的服務調(diào)用和發(fā)送,我們希望防腐對象完全透明的在執(zhí)行著防腐的職責,并不希望它會給我們實現(xiàn)上帶來多大的開銷。

6.1.事件驅(qū)動(防腐層監(jiān)聽顯示邏輯事件)

我們可以使用事件來實現(xiàn)觀察者模式,讓防腐層對象監(jiān)聽某個事件,當事件觸發(fā)時,自動的處理某個動作,而不是要顯示的手動調(diào)用。

namespace OrderManager.Anticorrsive
{
    public interface IOrderAnticorrsive
    {
        void SetController(OrderController orderController); 

        OrderViewModel GetOrderViewModel(long oId);
    }
}

Order防腐對象接口,里面包含了一個void SetController(OrderController orderController); 重要方法,該方法是用來讓防腐對象自動注冊事件用的。

public class OrderController : ApiController
{
    private IOrderAnticorrsive orderAnticorrsive; 

    public OrderController(IOrderAnticorrsive orderAnticorrsive)
    {
        this.orderAnticorrsive = orderAnticorrsive; 

        this.orderAnticorrsive.SetController(this);//設置控制器到防腐對象中
    } 

    public event EventHandler<OrderViewModel> SubmitOrderEvent; 

    [HttpGet]
    public void SubmitOrder(OrderViewModel order)
    {
        this.SubmitOrderEvent(this, order);
    }
}

在控制器中,每當我們發(fā)生某個業(yè)務動作時只管觸發(fā)事件即可,當然主要是以發(fā)送數(shù)據(jù)為主,查詢可以直接調(diào)用對象的方法。因為防腐對象起到一個與后臺服務集成的橋梁,當提交訂單時可能需要同時調(diào)用很多個后臺服務方法,用事件處理會比較方便。

/// <summary>
    /// OrderViewModel 防腐對象
    /// </summary>
    public class OrderAnticorrsive : AnticorrsiveBase<OrderViewModel>, IOrderAnticorrsive
    {
        public void SetController(OrderController orderController)
        {
            orderController.SubmitOrderEvent += orderController_SubmitOrderEvent;
        } 

        private void orderController_SubmitOrderEvent(object sender, OrderViewModel e)
        {
            //提交訂單的邏輯
        }
    }
}

6.2.依賴注入接口

依賴注入接口是完全為了將控制器與防腐對象之間隔離用的,上述代碼中我是將接口定義在了防腐對象層中,那么也就是說控制器對象所在的項目需要引用防腐層,在處理事件和方法同時使用時會顯得有點不倫不類的,既有接口又有方法,其實這就是一種平衡吧,越純粹的東西越要付出一些代價。

如果我們定義純粹的依賴注入接口讓防腐對象去實現(xiàn),那么在觸發(fā)事件時就需要專門的方法來執(zhí)行事件的觸發(fā),因為不在本類中的事件是沒辦法觸發(fā)的。

上述內(nèi)容就是.NET中用戶端的防腐層作用及設計是怎樣的,你們學到知識或技能了嗎?如果還想學到更多技能或者豐富自己的知識儲備,歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI