一、引言
在当今数字化时代,零售业正迅速发展,消费者的购物行为和期望发生了巨大的变化。为了满足不断增长的需求,零售企业必须构建高度灵活、稳健可靠的商品系统。
本文将深入探讨零售商品系统的底层逻辑,聚焦领域驱动设计(DDD)和复杂业务系统架构经验,揭示其在零售业务中的应用和价值。
二、面临的挑战
商品系统几乎贯穿了整个业务流程,比如:收银机的扫码加购、下单、商品详情页、小程序加购、营销活动、进销存、供应链、售后、履约等,各个业务环节都以商品为载体。由于商品系统的这种基础性和核心性,它所面临的挑战也愈发显得复杂而严峻。
零售领域拥有多元化的行业,每个行业对商品管理的需求都存在显著差异。从超市便利店到酒店教培,再到生鲜行业,不同行业的商品属性截然不同。
商品系统作为零售业务的中枢,需要支持从采购到销售再到退货的各个环节。这涉及到供应链、订单、物流、结算、售后等多个业务域的协同运作。
商品系统不仅要为商家提供强大的商品管理工具,还需要为消费者提供良好的商品展示和购物体验。B端需求关注商家的批量操作、库存管理等,而C端需求则关注用户的浏览、购买、支付等。如何在系统中同时满足两者的需求,保证系统的易用性和扩展性。
面对泛行业需求的融合、复杂业务链路的支撑以及B端与C端双重需求,业务架构师需要有深刻的洞察力,结合架构的“道”与“术”,在复杂性中找到平衡,构建出稳定、灵活零售商品系统,为业务持续迭代提供有力支持。
三、架构“道”与“术”
架构不仅涉及技术层面的决策,还关乎对业务需求的深刻理解以及对系统演化的预判,它决定了系统的整体设计和演化方向。
架构的“道”强调系统的整体规划、设计和演化策略。它包括以下核心要素:
深刻的业务理解。在设计架构之前,要深刻理解业务需求,通过领域驱动设计(DDD)来捕捉业务的核心概念和领域模型,保证架构与业务的紧密契合。
可演化与扩展性。架构需要具备演化的能力,以适应不断变化的业务需求和技术进步。同时,它还应该有足够的扩展性,以应对未来可能出现的挑战和机会。
业务驱动的设计。架构应该以业务为核心,将业务逻辑和技术方案相结合。通过领域建模,将业务的核心概念映射到系统架构中,实现领域模型的高度一致性。
架构的“术”关注具体的技术选型、设计和实现。它包括以下关键方面:
分层与模块化。将系统划分为不同的层次和模块,每个层次和模块负责特定的功能。分层和模块化有助于职责的清晰划分,减少代码的重复性。
设计原则与模式。技术设计时,需要考虑一系列设计原则和设计模式,如单一职责原则、开闭原则、依赖倒置等,构建松耦合、高扩展的系统。
技术选型。选择合适的技术栈是方案中的重要决策之一。技术栈涉及编程语言、中间件框架、数据库等,它们直接影响到系统的性能、可扩展性和开发效率。
四、零售商品架构
零售商品关联了非常多的业务模块,比如:商品SPU、规格SKU、前台类目、后台类目、品牌、库存、标品、属性和模板等,不同业务模块在横向存在千丝万缕的关系。同时随着用户规模扩大业务拓展,各个业务模块垂直化建设也会加大,进一步加剧系统复杂度。
为了解决这一复杂性,我们首先采取“分而治之”,引入限界上下文,将大商品域进一步拆分为主商品、库存、类目、属性等一些列自治的领域,缩小业务域范围,并按职责分为核心域、通用域、支撑域,每个领域专注于处理自己本域的特性问题,弱化横向之间耦合,降低了复杂度。
领域内早期只有单一的普通商品,业务逻辑比较简单。然而,随着业务不断迭代,商品涌现很多新玩法,如组合商品、临时商品、虚拟商品、预售商品等,每一种商品类型都表现出独特的规则和特性。
如果不同类型商品独立构建,像一个个孤立的烟囱,势必会导致重复建设问题。一旦修改通用逻辑,不得不在多处进行重复性改造,增加了维护成本。另一方面,将不同类型商品集中于一个主流程中,虽然可以减少重复建设,但这也可能出现大量的 if、else逻辑以处理差异化逻辑,影响代码整洁性。
为了解决这一问题,领域内我们采用 分解+编排的策略,结合主流设计模式,以构建高效、可维护的系统。
分解:将复杂的业务逻辑分解为更小组件,使每个组件只关注一个特定功能。这种分解符合软件设计的单一职责,同时降低单个组件的复杂度。
编排:采用责任链选择需要的业务组件进行串联,通过组装方式实现一个业务流程,保证了软件的开闭原则,同时也实现同一个组件在不同的业务流程复用。
如何在领域内分解和编排?有什么工具方法论可以开箱即用?
这里推荐使用 “矩阵思维”,梳理每一个业务流程,自上而下分解将一个大流程拆解为若干个小业务组件。以商品创建流程为例,横向表示业务动作,纵向表示业务场景,中间表示详细的业务流程。拆解后得到下面表格所示的矩阵。
通过矩阵工具将横、纵两个维度的复杂度清晰展现出来,方便我们直观发现不同流程之间的共性和特性,接下来就要考虑组件编排工作。
将标准化业务逻辑封装成一个个独立的业务组件,在应用层通过组件编排的方式组装业务流程。每个业务动作都有自己独享的编排流程,它轻量化易修改,承载业务功能的落地实现。
举个例子,当我们接到业务方需求,开发一种新的组合商品,创建商品环节只需要增加两个业务组件:检查组合商品前置条件、记录组合明细,然后复用其他组件快速编排出一个新接口。整个过程对其他业务没有影响,符合软件的开闭原则,并且大大降低测试工作量。
新零售服务的行业非常多,不同行业的商品属性差异非常大,下图示例是商超便利店与教育培训的商品。面对泛行业的“差异性”、“不确定性”特点,我们该如何架构系统?
如果每个行业单独创建一套存储结构,看似满足了需求但成本巨大,而且灵活性也不高。
如果将所有行业的数据放在一个地方存储,那差异化的数据结构如何满足,而且很多字段还涉及到校验规则或作为搜索条件。不仅要能写进去,还要读出来。
软件架构中解决复杂业务的另一法宝就是将业务需求抽象成领域模型,那什么是领域模型,先来看下维基百科给出的定义
领域模型(Domain Model)记录了一个系统中的关键概念和词汇表,显示出了系统中的主要实体之间的关系,并确定了它们的重要的方法和属性。
领域模型是将现实世界的客观对象抽象为一种信息结构,并能精确地反映业务领域的本质特征,这种信息结构不依赖于具体的编程语言,具有很强的独立性。
这样我们就可以将业务与技术实现分离,所有人的焦点放在领域模型,通过统一领域语言,让业务专家、研发人员在一个频道沟通,收集业务核心特征。下图是零售商品的领域模型:
主商品模型的属性根据其是否通用,拆分成两部分,对于相对稳定的数据,如“商品名称”、“类目”、“条形码”、“商品类型”、“售卖单位”等放在主商品上。而对于“产地”、“上课时间”等不固定数据抽取到自定义属性中,并与模板绑定,主商品只绑定一个模板标识号即可。
当商户创建商品时,根据业务类型找到对应的模板和关联的自定义属性,自定义属性主要包含以下内容:
component_config:主要是前端渲染页面使用,如输入框、下拉列表、单选、多选框等;
label:输入框 左侧的字段标题说明;
rules:值校验规则,用于校验商户输入是否满足要求。比如:“字段是否必填”、“输入文本长度”、“字段是否只读”、“字段是否可见”、“正则校验”等。
用户填写完商品页面的各种输入信息,并且通过所有校验规则,接下会将发布一条商品,核心字段放在主商品,而对于“重量”、“起售份数”等非核心字段与属性“field_id”关联,然后采用泛化集合存储到模板实体。
细心的小伙伴可能发现,这里的field_id是个重复字段如 保质期(expiration_data),每个商品都有这个字段,如果商品表的数据量非常大,这种冗余字段会占用很大空间。针对这个问题,可以借鉴 Protocol Buffers 的设计思想,为每个属性分配一个独立编号,通过字典记录它们的关联关系。这样模板实体中绑定的不再是英文字段而是一个数字编号,空间会成倍的降低。
一个好的系统一定有个好的架构模式,不管在六边形架构还是Clean Architecture中,似乎更习惯把Domain作为最底层,而Infrastructure是作为上层来实现Domain的Repository,将业务复杂度和技术复杂度分离。
业务复杂度主要体现在领域模型,通过实体、属性值、聚合根来呈现业务的本我,然后通过领域服务对实体数据驱动,完成一个生命周期。不管规则如何变化,最终通过模型来承接数据。
系统落地主要体现在代码排兵布阵,这里就用到 DDD 分层架构。分层是为了解决技术复杂度,让每一层有自己的专属职责,如:数据转换、RPC远程调用、异步消息解耦、缓存性能加速、动态配置中心、定时任务。
整个系统分为四层,每一层都有明确的职责定义:
1)接口层:定义接口服务,包括入参、响应结果,对外通过二方包的形式呈现。
2)应用层:表述应用和用户的行为,对领域服务进行组合和编排,对领域实体中的字段提取并转换成外部想要的形式。如果写服务,作为生产者对外发布消息事件,当然也可以作为消费者,订阅外部消息。这里也是MQ消息和定时任务的触发入口,所以也称为触发层。
3)领域层:用于表达业务概念和业务逻辑,是系统的核心。包含:实体、值对象、聚合根、领域服务、仓储服务等。
实体主要是承载业务的领域模型及关联关系,以及自身的动作行为。有字段变量,有方法,也成为充血模型。
领域服务主要完成领域中跨实体或值对象的操作转换而封装的服务,领域服务对同一个实体的一个或多个方法进行封装组合,对外隐藏领域层的业务逻辑实现,以黑盒形式对外服务。
仓库服务职责单一,主要负责将领域模型传给存储层,持久化存储。考虑到存储中间件的多样化,为了避免多变性对领域层的干扰影响,这里只定义接口,接口实现放在基础设施层。通过依赖倒置保证了领域层的核心地位。
4)基础设施层:为上层提供基础技术能力。如MySQL数据库操作、缓存数据读和写、远程RPC接口防腐层封装,以及其它中间件的初始化config 等,降低外部资源变化对系统的影响。
纵向,我们从技术视角分为四层,每层有自己的专属职责。横向,从业务维度切割为多个子域,如商品、库存、类目等。落实到具体代码,我们通过 package 实现代码隔离。为了兼顾领域的内聚性,顶层包按领域划分,每个分层内部再按模块职责定义子包。
无论系统的复杂程度如何,其本质都归结为数据操作。从本地存储或远程接口获取数据,经过一些列的转换、加工和组合等来表达业务逻辑。这一流程自上而下经历多个阶段,最终完成数据的持久化存储,同时自下而上地通过各种转换和加工,将数据最终展示给用户。
为了有效限制各个分层数据模型的职责范围,从而避免交织耦合,我们采取了数据模型分层的策略,将其分为三个主要类别:DO(数据库持久对象)、VO(领域模型对象)和DTO(数据传输对象)。
数据库持久对象为 DO,通过 Repository 接口完成 DO 到 VO 的领域模型转换,然后通过领域服务向上暴露。
在这个架构中,数据库持久对象(DO)承载了与数据库之间的直接映射关系,通过Repository接口完成 DO 到 VO 的领域模型转换,然后通过领域服务向上层暴露。在应用层,对领域模型 VO 进行一系列业务逻辑处理,最终转换为 数据传输对象(DTO),以便与外部世界进行交互。DTO充当着外部和内部数据传输的媒介,它不仅可以承载所需数据,还能够在传输过程中进行数据格式的转换和优化,以适应不同的客户端需求。
通过明确的分层和数据模型职责,我们能够实现不同层次之间的解耦。这种解耦使得每个层次都可以专注于特定的任务,而不需要过多考虑其他层次的细节,保证系统低耦合特点,扩展性更强。
五、结语
在零售业的数字化浪潮下,构建高度灵活、稳健可靠的商品系统已成迫切需求。
面对业务挑战,领域驱动设计与分解+编排策略成为解决问题的关键。架构的"道"注重业务理解与业务驱动设计,而"术"关注分层设计和模式运用。从抽象领域模型到DDD分层架构,系统持续演进。这些举措以数据操作为核心,聚焦业务与技术,构建稳定、高效的零售商品系统,为业务发展保驾护航。
如果字段的最大可能长度超过255字节,那么长度值可能…
只能说作者太用心了,优秀
感谢详解
一般干个7-8年(即30岁左右),能做到年入40w-50w;有…
230721