DDD分层架构

一个好的架构

  • 一套统一的分层,分包,分模块的约束规范。
  • 业务逻辑和技术实现分离,整洁架构的核心思想就是要避免业务逻辑的复杂度与技术实现的复杂度混淆在一起,确定业务逻辑与技术实现的边界,从而隔离各自的复杂度,业务逻辑并不关心技术是如何实现的。
    • 数据库无关性:业务逻辑不用关心使用 Postgres、Oracle、MongoDB 还是 Mysql。可以方便的在这些数据库中切换,业务逻辑不能依赖于这些数据库。
    • UI无关性:核心业务逻辑不关心你使用 Rest API、Dubbo、Grpc还是CLI(command-line interface),业务逻辑不能依赖于这些UI,当你把系统的UI从Web UI改成控制台UI,你并不需要改变任何业务逻辑的代码。
    • 应用框架无关性:使用 nodeJS、express还spring boot?你的核心业务逻辑也不关心这些,这样做不会让你的业务逻辑在使用前就有一些强制性的约束。
    • 与一切外部无关性:系统的业务逻辑并不需要知道任何外部的结构。
  • 可测试性
    • 业务逻辑必须能独立测试,不需要依赖UI,数据库,Web服务器或者一些其他的外部条件。
    • 集成自研的单元测试框架,解决单元测试的痛点。
  • 依赖清晰性
    • 依赖关系不与数据流,控制流挂钩,只与层次挂钩,高层次依赖低层次。
    • 外层次向内层次依赖,内部不依赖外部,依赖包含代码名称,或类的函数,变量或任何其他命名软件实体。
    • 外面层次中使用的数据格式不应被内层次中使用,我们不希望任何外圆的东西会影响内圈层。

DDD整洁架构实践

https://gitpod.io/workspaces

https://github.com/Sairyss/domain-driven-hexagon

六边形架构与洋葱架构其中心思想类似

image

纵向DGA架构设计

image

基础框架设计

image

DDD

战略设计

/* The DDD Cargo sample application modeled in CML. Note that we split the application into multiple bounded contexts. */
ContextMap AdIndexMap {
    
    contains AdIndexContext,GoodsContext,SellerContext,StockContext,PromotionContext,AddressCenterContext
    contains StationContext,BatchStockContext,ForcastSalesVolumeContext,RecallContext,MergeContext,RecommendContext
    contains GoodsTeam,StockPlanTeam,SupplyDemandTeam,PromotionTeam,ShareBusinessTerm,StationTerm,SearchRecommendTerm
    AdIndexContext [D,ACL]<-[U,OHS,PL] GoodsContext
    AdIndexContext [D,ACL]<-[U,OHS,PL] GoodsContext
    AdIndexContext [D,ACL]<-[U,OHS,PL] SellerContext
    AdIndexContext [D,ACL]<-[U,OHS,PL] StockContext
    AdIndexContext [D,ACL]<-[U,OHS,PL] PromotionContext
    AdIndexContext [D,ACL]<-[U,OHS,PL] AddressCenterContext
    AdIndexContext [D,ACL]<-[U,OHS,PL] StationContext
    AdIndexContext [D,ACL]<-[U,OHS,PL] BatchStockContext
    AdIndexContext [D,ACL]<-[U,OHS,PL] ForcastSalesVolumeContext
    MergeContext [C]<-[S] AdIndexContext
    MergeContext [P] <-> [P] RecallContext
    RecommendContext [D] <- [OHS,PL] MergeContext
    RecommendContext [SK] <-> [SK] MergeContext


    //GoodsTeam[U,S]->[D,C]SupplyDemandTeam
    //StockPlanTeam[U,S]->[D,C]SupplyDemandTeam
    //PromotionTeam[U,S]->[D,C]SupplyDemandTeam
    //ShareBusinessTerm[U,S]->[D,C]SupplyDemandTeam
    //StationTerm[U,S]->[D,C]SupplyDemandTeam
    //SupplyDemandTeam[U,S]->[D,C]SearchRecommendTerm
}

BoundedContext AdIndexContext implements AdIndexDomain
BoundedContext GoodsContext implements GoodsDemain
BoundedContext SellerContext implements SellerDomain
BoundedContext StockContext implements StockDomain
BoundedContext PromotionContext implements PromotionDomain
BoundedContext AddressCenterContext implements AddressCenterDomain
BoundedContext StationContext implements StationDomain
BoundedContext BatchStockContext implements BatchStockDomain
BoundedContext ForcastSalesVolumeContext implements ForcastSalesVolumeDomain
BoundedContext RecallContext implements RecallDomain
BoundedContext MergeContext implements MergeDomain
BoundedContext RecommendContext


/* Team Definitions */
BoundedContext GoodsTeam  realizes GoodsContext, SellerContext{
    type = TEAM
    domainVisionStatement = "供应链 商品研发分组."
}

BoundedContext StockPlanTeam  realizes StockContext, BatchStockContext{
    type = TEAM
    domainVisionStatement = "供应链 库存计划."
}

BoundedContext SupplyDemandTeam  realizes ForcastSalesVolumeContext, AdIndexContext,MergeContext,RecallContext{
    type = TEAM
    domainVisionStatement = "供应链 供需决策."
}

BoundedContext PromotionTeam  realizes PromotionContext{
    type = TEAM
    domainVisionStatement = "营销研发组-用户研发分组."
}

BoundedContext ShareBusinessTerm  realizes AddressCenterContext{
    type = TEAM
    domainVisionStatement = "技术中台组-共享服务分组"
}

BoundedContext StationTerm  realizes StationContext{
    type = TEAM
    domainVisionStatement = "履约研发组-门店研发分组"
}

BoundedContext SearchRecommendTerm  realizes RecommendContext{
    type = TEAM
    domainVisionStatement = "搜索推荐"
}




Domain AdDomain {
    Subdomain MergeDomain {
        type = CORE_DOMAIN
        domainVisionStatement = "广告mergeRerank核心子域"
    }
    Subdomain RecallDomain {
        type = SUPPORTING_DOMAIN
        domainVisionStatement = "召回支撑域."
    }
    Subdomain AdIndexDomain {
        type = SUPPORTING_DOMAIN
        domainVisionStatement = "索引支撑域."
    }
}

Domain AdIndexDomain {
    Subdomain IndexDomain {
        type = CORE_DOMAIN
        domainVisionStatement = "广告索引核心子域"
    }
    Subdomain GoodsDemain {
        type = SUPPORTING_DOMAIN
        domainVisionStatement = "商品支撑域."
    }
    Subdomain SellerDomain {
        type = SUPPORTING_DOMAIN
        domainVisionStatement = "卖家支撑域."
    }
    Subdomain StockDomain {
        type = SUPPORTING_DOMAIN
        domainVisionStatement = "库存支撑域."
    }
    Subdomain AddressCenterDomain {
        type = SUPPORTING_DOMAIN
        domainVisionStatement = "基础地址支撑域."
    }
    Subdomain StationDomain {
        type = SUPPORTING_DOMAIN
        domainVisionStatement = "门店支撑域."
    }
    Subdomain PromotionDomain {
        type = SUPPORTING_DOMAIN
        domainVisionStatement = "促销支撑域."
    }

    Subdomain BatchStockDomain {
        type = SUPPORTING_DOMAIN
        domainVisionStatement = "批次库存支撑域."
    }
    Subdomain ForcastSalesVolumeDomain {
        type = SUPPORTING_DOMAIN
        domainVisionStatement = "预估销量支撑域."
    }
}

image

战术设计

/* The original booking application context */

BoundedContext AdMergeRerankContext {
    Module ad {
        /**
         * 
         * {com.公司名.组织架构.业务.上下文.*},这样的组织结构能够明确的将一个上下文限定在包的内部。
         * import com.company.team.bussiness.lottery.*;//抽奖上下文
         * import com.company.team.bussiness.riskcontrol.*;//风控上下文
         * import com.company.team.bussiness.counter.*;//计数上下文
         * import com.company.team.bussiness.condition.*;//活动准入上下文
         * import com.company.team.bussiness.stock.*;//库存上下文

         * import com.company.team.bussiness.lottery.domain.valobj.*;//领域对象-值对象
         * import com.company.team.bussiness.lottery.domain.entity.*;//领域对象-实体
         * import com.company.team.bussiness.lottery.domain.aggregate.*;//领域对象-聚合根
         * import com.company.team.bussiness.lottery.service.*;//领域服务
         * import com.company.team.bussiness.lottery.repo.*;//领域资源库
         * import com.company.team.bussiness.lottery.facade.*;//领域防腐层
         * 
         */
        basePackage = com.company.team.bussiness.ad.merge.rerank.domain
        
        Entity RecoBean {
                 /**
                * sku id .
                */
                Integer skuId;
                /**
                * sku name.
                */
                Integer skuName;
            }
            ValueObject SkuPosition {
                Integer position
                - RecoBean recoBean
            }

            ValueObject PositionSpecification{
                - List<SkuPosition> fixedPositionMap
                 /**
                * 原始列表的售罄标记
                */
                String originSoldOutMark;
                - List<RecoBean> soldOutRecoBeanList 

                def void isSatisfiedBy(List<@RecoBean> mergeResult);
            }

            ValueObject SkuPositionMarker {
                - PositionSpecification positionSpecification
                - List<RecoBean> inclinableRecoBeanList
                - List<SkuEntity> inclinableAdList
            }

            ValueObject ScoreStrategy {
                def double score(SkuEntity SkuEntity);
            }

        Aggregate SkuAggregation {
            Entity SkuEntity {
                aggregateRoot
                /**
                 * sku id
                 */
                SkuId skuId
                Integer originPosition;
                Boolean isIncline;
                Integer rerankPosition;
                Double score;
                Double originScore;
                Boolean isAd;
                Boolean isRecommend;
                - Price price
                - Category category
                - ShelfLife shelfLife
                - Promotion promotion
                - Stock stock
                - BasicInfo basicInfo
                Repository SkuRepository {
                    List<@SkuEntity> findAll(String recallKey);
                }
            }
            ValueObject Price {
                /**
                 * 会员价 .
                 */
                Integer price;
                /**
                 * VIP价.
                 */
                Integer vipPrice;
                /**
                 * 成本价.
                 */
                Integer costPrice;

                def double score(double originScore);
            }
            ValueObject Category {
                /**
                 * 后台一级品类.
                 */
                Integer firstCid;
                /**
                 * 后台二级品类.
                 */
                Integer secondCid;
                /**
                 * 后台三级品类.
                 */
                Integer thirdCid;
                /**
                 * 后台一级品类名称.
                 */
                String firstCname;
                /**
                 * 后台二级品类名称.
                 */
                String secondCname;
                /**
                 * 后台三级品类名称.
                 */
                String thirdCname;
                /**
                 * 前台一级类目ID.
                 */
                Set<Integer> frontFirstCidSet;
                /**
                 * 前台二级类目ID.
                 */
                Set<Integer> frontSecondCidSet;
                /**
                 * 前台一级类目名称.
                 */
                Set<String> frontFirstCnameSet;
                /**
                 * 前台二级类目名称.
                 */
                Set<String> frontSecondCnameSet;

                def double score(double originScore);

            }
            ValueObject ShelfLife {
                /**
                 * 总货架期(天).
                 */
                Integer totalShelfLife;
                /**
                 * 允收期(小时).
                 */
                Integer allowReceiveShelfLife;
                /**
                 * 微仓最小货架期.
                 */
                Integer microWarehouseMinShelfLife;
                /**
                 * 大仓货架期.
                 */
                Integer bigWarehouseShelfLife;
                /**
                 * 周围规则.
                 */
                Integer turnoverRule;
                /**
                 * 超效期周转倍数
                 */
                private Double overExpiryMulti;
                private Double otherOverExpiryMulti;

                def double score(double originScore);

            }
            ValueObject Promotion {
                /**
                 * 促销开始时间
                 */
                Long startPromotionTime;
                /**
                 * 促销结束时间
                 */
                Long endPromotionTime;
                /**
                 * 促销类型
                 */
                Integer promotionType;
                /**
                 * 促销场景
                 */
                Integer promotionScene;

                def double score(double originScore);

            }
            ValueObject Stock {
                /**
                 * 库存.
                 */
                Integer stock;
                /**
                 * 批次库存
                 */
                List<BatchStockBO> batchStocks;
                /**
                 * 总批次库存
                 */
                Integer batchStockWarning;
                /**
                 * 临期报损库存
                 */
                Integer warningStock;

                def double score(double originScore);

            }
            ValueObject BasicInfo {
                /**
                 * sku名称.
                 */
                String skuName;
                /**
                 * 商品上下架状态 2上架,3预下架,4预上架.
                 */
                Integer skuStatus;
                /**
                 * sku类型 默认为0 1原料商品,2普通商品,3虚拟组套,4虚拟商品.
                 */
                Integer skuType;
                /**
                 * 商品类型 默认为0 1原料商品,2普通商品,3虚拟组套,4虚拟商品.
                 */
                Integer itemType;
                /**
                 * 业态 (卖家类型,一个SKU只有一个卖家类型).
                 */
                Integer sellerType;
                /**
                 * 是否预售 1为预售品,0为否.
                 */
                Integer isPresale;
                Long sellerId;

                def double score(double score);
            }

            Service MergeRankService {
        
                @SkuPositionMarker fixedPositionPrepare(List<@RecoBean> originRecoList,List<@SkuEntity> adSkuEntities,Set<String> batchSkuSet);

                @SkuPositionMarker allInclinablePrepare(List<@RecoBean> originRecoList,List<@SkuEntity> adSkuEntities,Set<String> batchSkuSet);

                void rank(List<@SkuEntity> adEntries,@ScoreStrategy scoreStrategy);

                List<@RecoBean> merge(@SkuPositionMarker skuPositionMarker,List<@SkuEntity> adEntries);

                List<@RecoBean> merge(@SkuPositionMarker skuPositionMarker,List<@SkuEntity> adEntries,List<Integer> positions);

                List<@RecoBean> shuffler(@SkuPositionMarker skuPositionMarker,List<@RecoBean> sortedResult);

                Boolean mergeValid(@SkuPositionMarker skuPositionMarker,List<@RecoBean> sortedResult);

            }
        }
    }
}


image.png
#http://bj.91join.com/color.html 

digraph G1 {
    rankdir=TB;
    graph [compound=true]
    node [color=black,shape=egg fillcolor="#FFFFFF" style="filled" shape=egg fontcolor="#000000"] //All nodes will this shape and colour
    edge [color=black] //All the lines look like this

    subgraph cluster_basic {
        fillcolor="#FFAB00" 
        fontcolor="white"
        style="filled"
        label = "Infrastructure Layer";

        SpringBoot[shape=egg]
        Dubbo[shape=egg]
        #_[color="white" fontcolor="#ffffff"]
        ElasticSearchRepositories[shape=egg]
        RedisRepositories[shape=egg]
        "Data_Mapper DO<->PO"
    };

    subgraph cluster_domain {
        fillcolor="#01939A" 
        style="filled"
        fontcolor="white"
        label = "Domain Layer"
        Repository_API[shape=egg]
          subgraph cluster_ddd{
                Entity[shape=egg]
                Value_Object[shape=egg]
                Domain_Service[shape=egg]
                Aggregation[shape=egg]
                label = "Domain DDD"

          }
    };

    subgraph cluster_protocol{
        fillcolor="#9FEE00" 
        fontcolor="white"
        style="filled"
        label = "Protocol Layer"
        DTO RpcAPI
    }

    subgraph cluster_app{
        fillcolor="#9FEE00" 
        fontcolor="white"
        style="filled"
        label = "Application Layer"
        DTO_Assemble
        Adapters
            subgraph cluster_service{
            {
                rank="same";Valid;Shuffler;Merge;ReRank;Rank;Filter;Recall;}
                Recall->Filter->Rank->ReRank->Merge->Shuffler->Valid
                label = "Operation Flow"
            }
    }

    subgraph cluster_commons{
        fillcolor="#CD0074" 
        fontcolor="white"
        style="filled"
        label = "Commons"
        commons[fillcolor="white" 
                         style=filled shape=egg]
    }

    SpringBoot -> Shuffler[label=" " ltail=cluster_basic lhead=cluster_app]

    Shuffler -> Entity [label=" " lhead=cluster_domain ltail=cluster_app];

    Shuffler -> RpcAPI[label=" " ltail=cluster_app lhead=cluster_protocol];

    Domain_Service->commons[label=" " ltail=cluster_domain];

    ElasticSearchRepositories-> Repository_API 
    [label="DI" fontcolor="#9FEE00" style="dotted"];

    RedisRepositories-> Repository_API 
    [label="DI" style="dotted" fontcolor="#9FEE00"];
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,254评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,875评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,682评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,896评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,015评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,152评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,208评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,962评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,388评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,700评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,867评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,551评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,186评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,901评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,689评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,757评论 2 351

推荐阅读更多精彩内容