小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

寫代碼原來如此簡單:兩種常用代碼范式

 芥子c1yw3tb42g 2024-12-21

圖片

阿里妹導讀

一次項目包含非常多的流程,有需求拆解,業(yè)務(wù)建模,項目管理,風險識別,代碼模塊設(shè)計等等,如果我們在每次項目中,都將精力大量放在這些過程的思考上面,那我們剩余的,放在業(yè)務(wù)上思考的精力和時間就會大大減少;這也是為什么我們要 總結(jié)經(jīng)驗/方法論/范式 的原因;這篇文章旨在建立代碼模塊設(shè)計上的思路,給出了兩種非常常用的設(shè)計范式,減少未來在這一塊的精力開銷。

一、領(lǐng)域模型驅(qū)動的代碼范式

領(lǐng)域模型驅(qū)動的代碼范式,是圍繞著領(lǐng)域知識設(shè)計的,需要先理解業(yè)務(wù)模型,再將業(yè)務(wù)模型映射到軟件的對象模型中來;本章節(jié)重點在我們有了業(yè)務(wù)模型之后的代碼模式,具體業(yè)務(wù)模型如何構(gòu)建在《架構(gòu)之道:人人都是架構(gòu)師》中有詳細討論;

?圖片?

上圖中間就是該模式最重要的領(lǐng)域,領(lǐng)域?qū)哟a作為系統(tǒng)的最核心資產(chǎn)模塊,可以被打包遷移到任何應(yīng)用上,而不關(guān)心具體的三方服務(wù)提供方和具體的持久化方案,即外部服務(wù)的變化對領(lǐng)域?qū)哟a是沒有任何侵入的;

從上圖來看,領(lǐng)域?qū)ο?/strong>(ENTITY、AGGREGATION、VALUEOBJECT、MODEL)用于描述業(yè)務(wù)模型,是業(yè)務(wù)關(guān)系最重要的體現(xiàn);為了屏蔽持久化方案的細節(jié),我們使用倉庫(REPOSITORY)來查詢和持久化領(lǐng)域?qū)ο螅挥械臅r候我們期望直接獲取到一個非空的領(lǐng)域?qū)ο?,而不關(guān)心這個對象從哪里來,如何構(gòu)造,那我們就需要工廠(FACTORY)來幫我們生產(chǎn)這個對象;當領(lǐng)域內(nèi)依賴外部服務(wù)能力時,需要門面(FACADE)幫助我們屏蔽具體的服務(wù)提供方;

有了以上這些模型對象和基礎(chǔ)能力模塊,我們需要領(lǐng)域服務(wù)(DomainServie)層作為“上帝之手”幫我們編排具體的業(yè)務(wù)邏輯;本章對領(lǐng)域服務(wù)有更細的三層劃分,第一層是實體操作服務(wù)(BaseUpdateDomainService),用于收斂操作實體/聚合的真實變更行為;第二層是業(yè)務(wù)流程服務(wù)(BaseBizDomainService),用于收斂基礎(chǔ)的有業(yè)務(wù)語義的行為;第三層是用例領(lǐng)域服務(wù)(UserCaseDomainService),用于映射具體的業(yè)務(wù)需求用例場景。

1.1 領(lǐng)域模型對象

?圖片?

  • 實體

由標識定義,而不依賴它的所有屬性和職責,并在整個生命周期中有連續(xù)性。這句話在初看的時候非常晦澀,簡單來說,就是一個標識沒變的對象,在其他自身屬性發(fā)生變化后,它依然是它,那么它就是實體;以上圖為例,一個商品的商品id沒變,即使它的標題改了,圖片改了,優(yōu)惠信息變了,它發(fā)生了翻天覆地的變化,但它依然是它,它有唯一的標識來表明它還是它,只是一些屬性發(fā)生了變化;通過這種方式來識別實體的目的,是因為領(lǐng)域中的關(guān)鍵對象,通常并不由它們的屬性定義,而是由可見的/不可見的標識來定義,且有完整的生命周期,在這個周期內(nèi)它如何變化,它都依然是它;通過這種方式識別出實體這種領(lǐng)域關(guān)鍵對象,也是領(lǐng)域驅(qū)動設(shè)計和數(shù)據(jù)驅(qū)動設(shè)計最大的差別,數(shù)據(jù)驅(qū)動設(shè)計是先識別出我們需要哪些數(shù)據(jù)表,然后將這些數(shù)據(jù)表映射為對象模型;而領(lǐng)域驅(qū)動設(shè)計是先通過業(yè)務(wù)模型識別出實體,再將實體映射為所需要的數(shù)據(jù)表。

  • 值對象

用于描述領(lǐng)域的某個方面而本身沒有唯一標識的對象。被實例化后用來表示一些設(shè)計元素,對于這些設(shè)計元素,我們只關(guān)心它們是什么,而不關(guān)心它們是誰。如上圖舉個例子,一個商品實體的發(fā)貨地址Address對象有區(qū)域信息、門牌信息、時區(qū)信息這幾個屬性,其中的門牌號從111修改成222后,它就已經(jīng)不再是修改前的那個它了,因為門牌號222并不等于門牌號111的地址。即它是沒有生命周期的,它的equals方法由它的屬性值決定(實體的equals方法由唯一標識決定);

  • 聚合

聚合是一組實體和值對象的組合;內(nèi)部包含一個聚合根,和由聚合根關(guān)聯(lián)起來的實體和值對象;以上圖為例,有商品、優(yōu)惠、庫存這三個實體和地址這一個值對象,對于一個商品而言,完整的商品信息需要包含優(yōu)惠、庫存、地址這些信息,那么在商品模型中,商品就是聚合根,其內(nèi)部通過優(yōu)惠id關(guān)聯(lián)它的優(yōu)惠信息,通過庫存id關(guān)聯(lián)商品的庫存信息;聚合將這組關(guān)聯(lián)關(guān)系建立,對外提供統(tǒng)一的操作,比如需要刪除某個商品,那么這個聚合的內(nèi)部可以在一個事務(wù)(或分布式事務(wù))中,對庫存進行清空,對優(yōu)惠進行清理,最終對商品進行刪除。?

1.2 查詢/構(gòu)造能力

?圖片?

  • 倉庫

倉庫是可持久化的領(lǐng)域?qū)ο蠛驼鎸嵨锢泶鎯Σ僮髦g的媒介,隨意的數(shù)據(jù)庫查詢會破壞領(lǐng)域?qū)ο蟮姆庋b,所以需要抽象出倉庫這種類型,它定義領(lǐng)域?qū)ο蟮墨@取和持久化方法,具體實現(xiàn)不由領(lǐng)域?qū)痈兄?;至于具體用了什么存儲,如何寫入和查詢,是否使用緩存,這些邏輯統(tǒng)一封裝在倉庫的實現(xiàn)層,對于后續(xù)遷移存儲、增刪緩存,都可以做到不侵蝕業(yè)務(wù)領(lǐng)域。

比如整個領(lǐng)域模塊需要打包給海外業(yè)務(wù)使用,在海外我們需要用當?shù)氐拇鎯?,那么這個遷移對于領(lǐng)域?qū)邮菦]有侵入的,只需要在基礎(chǔ)設(shè)施層修改倉庫的實現(xiàn)即可;

再比如我們的數(shù)據(jù)庫存在性能瓶頸,需要在數(shù)據(jù)庫上增加一層緩存,這個操作對領(lǐng)域也是沒有侵入的,只需要在倉庫的實現(xiàn)處,增加緩存的讀寫即可,對業(yè)務(wù)邏輯無感。

  • ?三方能力門面

門面用于封裝第三方的能力,設(shè)計初衷本質(zhì)和倉庫是一樣的,目的都是屏蔽具體的三方能力實現(xiàn),讓穩(wěn)定的領(lǐng)域?qū)硬蝗ヒ蕾嚐o法把控變化方向的第三方;上圖中的三個case是比較經(jīng)典的三個例子:

  • 我們的模型中依賴外部查詢獲取的商品模型,這個模型中有商品標題、商品圖片、店鋪名稱這幾個信息,那么我們需要在Domain層定義一個商品類ItemInfo,包含這幾個屬性,然后在Domain層定義一個獲取ItemInfo對象的服務(wù)接口,比如叫ItemFacade,方法是getItemInfo;接下來我們需要在Infrastructure層實現(xiàn)Domain層定義的這個接口,比如具體的實現(xiàn)是依賴Ic的接口,將ItemDO轉(zhuǎn)換為Domain層的ItemInfo;可以發(fā)現(xiàn),這樣的設(shè)計讓Domain對商品信息的獲取源是無感的,當我們的能力需要部署到海外,或者IC某一天進行了重大改革,需要對模型進行大改,那么我們只需要重新實現(xiàn)Infrastructrue中ItemFacadeImpl即可。這個思想其實就是依賴倒置的思想,穩(wěn)定不應(yīng)該依賴變化,變化應(yīng)該依賴穩(wěn)定;因為第三方的變化方向是無法把控的,它的變化不應(yīng)該侵入到我們的領(lǐng)域知識內(nèi)部。

  • 我們的領(lǐng)域還依賴一些消息發(fā)送、限流等基礎(chǔ)功能,也需要在Domain層定義相應(yīng)能力的Facade,在Infrastructure層實現(xiàn),目的同上,將metaq替換成notify/swift時,領(lǐng)域?qū)邮菬o感的。

  • ?工廠

當創(chuàng)建一個實體對象或聚合的操作很復雜,甚至有很多領(lǐng)域內(nèi)部的結(jié)構(gòu)需要暴露的時候,就可以用工廠進行封裝。一種相對簡單粗暴的判斷方法是看這個類的構(gòu)造方法實現(xiàn)是否復雜,并且看著這些邏輯不應(yīng)該由這個類實現(xiàn),那么不妨用工廠來構(gòu)造這個對象吧!

1.3 領(lǐng)域服務(wù)

?圖片?

有一些對實體/聚合/值對象進行編排操作的概念并不適合被建模為對象,那么它應(yīng)該被抽象為領(lǐng)域服務(wù),化作一只上帝之手,做領(lǐng)域?qū)ο箝g流程操作的編排。服務(wù)很重要的特征,它的操作應(yīng)該是無狀態(tài)的。本文基于開發(fā)實踐,對領(lǐng)域服務(wù)做了三層更細節(jié)的劃分:

  • 實體操作服務(wù)

即圖中的BaseUpdateDomainService,是最基礎(chǔ)的一類領(lǐng)域服務(wù),用于收口某個實體的真實物理操作,它的流程中一般包含一個核心的update/insert操作,作用在寫數(shù)據(jù)庫上,依情況可以增加:

BeforeUpdateHandlers和AfterUpdateHandlers,用于更新前后的一些額外業(yè)務(wù)操作;

如1.1中的圖,我們在內(nèi)存中操作完某個庫存實體對象,需要更新db的時候,可以調(diào)用它對應(yīng)的服務(wù),在這個服務(wù)中,我們除了將變更的值更新db,還需要對外發(fā)送消息,更新前也需要執(zhí)行一些校驗的回調(diào),那么校驗回調(diào)可以放在BeforeUpdateHandler中,對外的消息可以放在AfterUpdateHandler中;抽象出這樣的一個服務(wù),好處是可以收斂最基礎(chǔ)的變更操作,不至于不同的入口對某個對象的更新,還會出現(xiàn)不一樣的操作(比如需要發(fā)送更新消息,不同的入口操作更新,有的發(fā)送消息,有的不發(fā)送)。

一般每個實體都需要有一個對應(yīng)的操作服務(wù)(或者模型極其簡單可以省略這一層),操作服務(wù)可以依賴其他的操作服務(wù),比如1.1中商品模型的更新,是需要依賴庫存更新服務(wù)和優(yōu)惠更新服務(wù)的。

  • ?業(yè)務(wù)流程服務(wù)

這一層的領(lǐng)域服務(wù)對應(yīng)圖中的BaseBizDomainService,用于收斂一些通用的業(yè)務(wù)流程,可以直接對接業(yè)務(wù)接口或者用于上層的用例編排;比如我們在商家請求、接到第三方消息、具體的某幾個用例中,需要先查詢ItemFacade,然后進行一些業(yè)務(wù)邏輯判斷,然后根據(jù)情況對1.1商品模型中的優(yōu)惠進行清理,那么就可以將這段邏輯收斂到“XX優(yōu)惠清理領(lǐng)域服務(wù)”中,在多個上層場景需要進行該操作時,直接調(diào)用這個領(lǐng)域服務(wù)即可。

  • ?用例領(lǐng)域服務(wù)

這一層對應(yīng)圖中的UserCaseDomainService作為領(lǐng)域服務(wù)的最上層,也是最具體的一層,用于實現(xiàn) 定制的/不可復用的 用例場景業(yè)務(wù)邏輯,只直接對接對外的api。

1.4 包結(jié)構(gòu)實踐

?圖片?

該包結(jié)構(gòu)即描述了上述的所有模塊,其中infrastructure模塊依賴domain模塊;domain模塊的pom文件理論上不應(yīng)該有任何三方依賴(除了一些工具類)。?

二、過程驅(qū)動的代碼范式

領(lǐng)域驅(qū)動的代碼,重點是抽象領(lǐng)域模型,沉淀領(lǐng)域?qū)ο髮嶓w,它用模型間的關(guān)系以及模型直接的操作來沉淀知識;過程驅(qū)動的代碼,重點是抽象能力,沉淀函數(shù),并用編排引擎串聯(lián)執(zhí)行過程,實現(xiàn)對知識的描述。

在面向?qū)ο蟠髲埰旃牡慕裉?,大多?shù)人對面向過程編程嗤之以鼻,但有些場景使用過程驅(qū)動的編程思路,反而能更好地描述業(yè)務(wù)規(guī)則以及業(yè)務(wù)流程,比如前臺表達的渲染鏈路,或是章節(jié)一中 比較重的領(lǐng)域服務(wù),使用過程驅(qū)動能更好地描述數(shù)據(jù)處理的過程以及產(chǎn)品用例流程。

?圖片?

上圖能力庫中的能力點是我們過程驅(qū)動最核心的部分,是我們對能力的抽象,一般一個能力只沉淀一個具體的原子方法,并決策流程是否能執(zhí)行下去;往上是階段劃分,不同的能力在具體的業(yè)務(wù)流程中,是處于不同階段的,這里的階段劃分是指從流程階段的維度對能力點進行分類并放在不同的包中,讓我們的流程更加清晰;再往上是不同場景下我們對能力點的執(zhí)行鏈路編排,并對外做統(tǒng)一輸出。?

2.1 能力點

能力點通常由一個接口定義,入?yún)⑹菆?zhí)行上下文,出參是流程是否需要繼續(xù)。

public interface AbilityNode<C extends Context> {
   /**     * 執(zhí)行一個渲染節(jié)點     * @param context 執(zhí)行上下文     * @return 是否還需要繼續(xù)往下執(zhí)行     */    boolean execute(C context);

基于這個接口,我們可以實現(xiàn)非常多原子能力節(jié)點。java中,這些能力節(jié)點作為bean由spring容器統(tǒng)一管理,運行時取出即用。

在一個業(yè)務(wù)場景下,我們的能力點往往會非常多,那么我們就需要對他們進行基于業(yè)務(wù)場景的階段劃分,并分門管理;比如我們在前臺投放場的實踐中,按照召回、補全、過濾、排序、渲染,劃分了五個階段,每一個能力點被歸類到其中一個階段中進行管理。?

2.2 能力編排

有了能力點,我們需要基于編排引擎將這些能力串聯(lián)起來,用以描述業(yè)務(wù)規(guī)則或是業(yè)務(wù)流程;這些能力執(zhí)行的過程有些僅僅是可串行執(zhí)行的,有些是也可以并行執(zhí)行的,下面給出一套通用的流程協(xié)議以及實現(xiàn)過程:

public abstract class AbilityChain<C extends Context<?>> {
   @Resource    private ThreadPool threadPool;
   public abstract C initContext(Request request);
   public Response execute(Request request) {        //獲取渲染上下文        C ctx;        try {            ctx = this.initContext(request);            if (ctx == null || ctx.getOrder() == null || CollectionUtils.isEmpty(ctx.getOrder().getNodeNames())) {                return null;            }        } catch (Throwable t) {            log.error('{} catch an exception when getRenderContext, request={}, e='                    , this.getClass().getName(), JSON.toJSONString(requestItem), t);            return null;        }        try {            //執(zhí)行所有節(jié)點            for (List<String> nodes : ctx.getOrder().getNodeNames()) {                List<AbilityNode<C>> renderNodes = renderNodeContainer.getAbilityNodes(nodes);                boolean isContinue = true;                if (renderNodes.size() > 1) {                    //并發(fā)執(zhí)行多個節(jié)點                    isContinue = this.concurrentExecute(renderNodes, ctx);                } else if (renderNodes.size() == 1){                    isContinue = this.serialExecute(renderNodes, ctx);                }                if (!isContinue) {                    break;                }            }                        return ctx.getResponse();        } catch (Throwable t) {            log.error('RenderChain.execute catch an exception, e=', t);            return null;        }    }
   /**     * 并發(fā)執(zhí)行多個node,如果不需要繼續(xù)進行了則返回false,否則返回true     */    private boolean concurrentExecute(List<AbilityNode<C>> nodes, C ctx) {        if (CollectionUtils.isEmpty(nodes)) {            return true;        }        long start = System.currentTimeMillis();        Set<Boolean> isContinue = Sets.newConcurrentHashSet();        List<Runnable> nodeFuncList = nodes.stream()            .filter(Objects::nonNull)            .map(node -> (Runnable)() -> isContinue.add(this.executePerNode(node, ctx)))            .collect(Collectors.toList());        linkThreadPool.concurrentExecuteWaitFinish(nodeFuncList);        //沒有node認為不繼續(xù),就是要繼續(xù)        return !isContinue.contains(false);    }        /**     * 串行執(zhí)行多個node,如果不需要繼續(xù)進行了則返回false,否則返回true     */    private boolean serialExecute(List<AbilityNode<C>> nodes, C ctx) {        if (CollectionUtils.isEmpty(nodes)) {            return true;        }        for (AbilityNode<C> node : nodes) {            if (node == null) {                continue;            }            boolean isContinue = this.executePerNode(node, ctx);            if (!isContinue) {                //不再繼續(xù)執(zhí)行了                return false;            }        }        return true;    }        /**     * 執(zhí)行單個渲染節(jié)點     */    private boolean executePerNode(AbilityNode<C> node, C context) {        if (node == null || context == null) {            return false;        }        try {            boolean isContinue = node.execute(context);            if (!isContinue) {                context.track('return false, stop render!');            }            return isContinue;        } catch (Throwable t) {            log.error('{} catch an exception, e=', nodeClazz, t);            throw t;        }    }
}

其中流程協(xié)議為:

[    [        'Ability1'    ],    [        'Ability3',        'Ability4',        'Ability6'    ],    [        'Ability5'    ]]

其中Ablitiy3 4 6表示需要并發(fā)執(zhí)行,Ability1、3/4/6、5表示需要串行執(zhí)行。

2.3 切面

有了能力點和流程編排引擎,基于過程編碼的代碼骨架就已經(jīng)有了;但是往往我們還需要一些無法沉淀為能力的邏輯,需要在每個節(jié)點(或指定節(jié)點)執(zhí)行前/后進行,這就需要切面的能力;

?圖片?

如圖,比如流程埋點、預校驗就非常適合放到切面這一層實現(xiàn)。下圖是我們在實踐中,基于切面實現(xiàn)的能力點追蹤,可以清晰地看到每個能力點執(zhí)行的過程以及數(shù)據(jù)變更情況。

?圖片?

2.4 包結(jié)構(gòu)實踐

?圖片?

該包結(jié)構(gòu)即描述了上述的所有模塊,chain目錄下為多個業(yè)務(wù)場景流程,圍繞著外層的AbilityNode和AbilityChain進行實現(xiàn),編排出符合業(yè)務(wù)場景邏輯的流程。?

寫在后面

文章中的代碼范式可以應(yīng)用在大部分場景,在項目初起的時候直接套用,可以省下大部分關(guān)于包模塊劃分的思考精力,并且在后續(xù)迭代中,團隊統(tǒng)一規(guī)范,持續(xù)按照這個框架演進,可以讓代碼更加井井有條,減少一些詭異的類職責劃分問題。

高可用及共享存儲Web服務(wù)

隨著業(yè)務(wù)規(guī)模的增長,數(shù)據(jù)請求和并發(fā)訪問量增大、靜態(tài)文件高頻變更,企業(yè)需要搭建一個高可用和共享存儲的網(wǎng)站架構(gòu)。   

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多