写点什么

DDD 实战 (12- 终篇):微服务的“分分合合”及一个倡议

作者:深清秋
  • 2022 年 8 月 23 日
    湖北
  • 本文字数:12972 字

    阅读完需:约 43 分钟

DDD实战(12-终篇):微服务的“分分合合”及一个倡议

在前面的《DDD 实战 (6):战略设计之技术决策》中,我曾经提到“微服务随时可拆可分”。而在上篇《DDD 实战(11):冲刺 1 代码 TDD 实现之道》几乎展示了所有 DDD 相关的、基于 TDD 代码“三部曲”的编程方式之后,就只上下这一个问题没有从代码角度进行演示了。本篇就来演示“微服务的随时可拆可分”这一 DDD 编程特性。同时,这将是本系列的最后一篇文章。

6. DDD 指导下的微服务实现

对于 DDD 方法体系下的微服务来说,其要回答的两个核心是:

  1. 如何在“北向网关”层向其它上下文提供合适的微服务输出,以便其客户端端口调用。

  2. 如何在“南向网关”层实现到其它上下文的微服务调用。


下面的篇幅中,我将分别来进行说明。为了回答这两个问题,我们假设一个这要的“微服务拆分”应用场景:


  • 但现在,假设考虑到“商品”上下文需求变更的频度极低(大概一季度不超过 1 次)、而“订单、接龙”上下文的需求则几乎每个月都要修改 10 多次,为此我们要将“商品、接龙、订单”三个上下文的“命令”部分拆分开来。这就会形成“商品中心”、“订单中心”、“接龙中心”3 个微服务。


  • 为了演示这一微服务过程是“随时可拆可合并”的,我们拿一个实际的例子,来看看如何作为不修改一行“商品、订单、接龙”领域核心(即:领域服务+聚合)的业务代码,而能够方便的进行拆分的。


  • 这个实际的例子就是:订单领域服务 OrderManagingService 在方法 submitOrder() 中进行订单提交时,将会通过南向网关 OrderItemsSettlementClient 端口的适配器调用商品上下文的领域服务 ProductSettlementService.calcSettlement()方法来获取商品结算价格。而在我们下面的示例代码中,将会看到如何不修改这两个领域服务(即 OrderManagingService 和 ProductSettlementService)的任何一行代码,实现了从原来的本地调用转换为远程服务调用。


注:本篇中所有代码,均可以在 gitee 或 github 代码库中下载:

6.1 定义远程服务公共接口

远程服务调用时,需要在“客户端”和“服务端”之间定义一个通用的 api 接口,这样才能方便客户端和服务端使用“同一种语言交谈”。为此,我们需要将 ProductSettlementService 这一领域服务的 calcSettlement() 方法进行“接口统一化”——事实上,是将 Product 命令上下文的应用服务方法进行封装,即 ProductAppService.calcSettlement()方法进行“接口统一化”。

6.1.1 定义统一接口

为此,我们在原有的工程目录下,新增一个 module,命名为 api,如下图:

然后,我们再新建一个“统一化接口”,该接口规定了商品价格结算服务的输入、输出。如下:

package com.stardata.starshop2.api;
import org.springframework.cloud.openfeign.FeignClient;import org.springframework.web.bind.annotation.PutMapping;import org.springframework.web.bind.annotation.RequestBody;
/** * @author Samson Shu * @email shush@stardata.top * @date 2022/8/15 22:46 * @version 1.0 */@FeignClient(value = "product-biz-services")public interface ProductBizService { @PutMapping("/v2/products/settlements") ProductSettlementResponse calcSettlement(@RequestBody ProductSettlementRequest request);}
复制代码

需要说明的是:为了支持 spring cloud 框架,我这里使用了 Feign。为此,我们需要在 api module 的 pom.xml 中引入相关依赖,如下:

<dependency>  <groupId>org.springframework.cloud</groupId>  <artifactId>spring-cloud-openfeign-core</artifactId>  <version>3.1.3</version></dependency>
复制代码

6.1.2 改造 pl DTO 类

在统一接口 ProductBizService 的定义中,用到了商品价格结算请求类 ProductSettlementRequest、商品价格结算响应类 ProductSettlementResponse,而这两个类原来是商品上下文 pl 层的 DTO 类。为了能够实现商品上下文服务的“远程服务输出”,我们需要做如下的改造:


  • 将 ProductSettlementRequest 和 ProductSettlementResponse 从 product-command module 移动到 api module。


  • 将 ProductSettlementResponse 类中原有的工厂方法 from 独立到工厂类 ProductSettlementFactory 方法中去。之所以要将该工厂方法独立出来,是因为该工厂方法会引用到 Product 上下文的值对象类 ProductSettlement,也就是对 Product 上下文的“领域核心层”产生了依赖。为此,我们只能将该工厂方法独立出来,并仍然放到 Product 上下文的 pl 层中。


上面两个改造后,api 模块下的代码结构变成如下图:


ProductSettlementRequest DTO 类的代码如下:

@Data@AllArgsConstructorpublic class ProductSettlementRequest {    private List<Long> productIds;    private List<Integer> productCounts;
public Map<LongIdentity, Integer> composeRequestToMap() { List<LongIdentity> ids = this.getProductIds().stream().map(LongIdentity::from).collect(Collectors.toList());
Map<LongIdentity, Integer> productCountsMap = new HashMap<>(); for (int i = 0; i < this.getProductIds().size(); i++) { productCountsMap.put(ids.get(i), this.getProductCounts().get(i)); } return productCountsMap; }}
复制代码

ProductSettlementResponse DTO 类的代码如下:

@Datapublic class ProductSettlementResponse {    @Getter    @AllArgsConstructor    public static class Item {            private Long id;            private String name;            private Long priceFen;            private Integer count;            private BigDecimal quantity;            private boolean available;            private String productSnapshot;    }
private final List<Item> items = new ArrayList<>();
}
复制代码

product-command 模块改造后的独立工厂类所处代码结构如下:


工厂类 ProductSettlementFactory 的代码如下:

public class ProductSettlementFactory {
public static ProductSettlementResponse settlementToResponse(Map<LongIdentity, ProductSettlement> settlements) { ProductSettlementResponse result = new ProductSettlementResponse(); settlements.values().forEach(item -> result.getItems().add(ProductSettlementResponseItemMapper.INSTANCE.convert(item))); return result; }}
复制代码

6.2 商品上下文“北向网关”输出微服务


我们先来看商品上下文如何通过远程服务来输出微服务。为了更好的演示微服务常规实现方式,我这里演示了 Spring Cloud 和 dubbo 两种微服务实现。

6.2.1 Spring Cloud 北向网关远程服务输出

引入依赖

为了使用 spring cloud 框架,我们在 product 命令上下文的 pom.xml,即下图所示的 pom.xml 文件中引入相关依赖。


引入的依赖内容如下:

    <properties>      <eureka.version>3.1.3</eureka.version>      <spring-cloud.version>2021.0.1</spring-cloud.version>    </properties>    <dependencies>      <!-- spring cloud -->      <dependency>        <groupId>org.springframework.cloud</groupId>        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>        <version>${eureka.version}</version>      </dependency>      <!--actuator monitor -->      <dependency>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-actuator</artifactId>      </dependency>    </dependencies>
<dependencyManagement> <!--springBoot的核心依赖,也是继承,可插拔机制,只会加载配置的依赖--> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
复制代码


输出 spring cloud 远程服务

事实上,我们并不需要修改原有的北向网关的本地服务(即应用服务),而是在远程服务中增加 ProductSpringCloudService 即可。


我们在 product-command 模块中增加 ProductSpringCloudService 类如下图所示的代码结构:

该类的代码内容如下(注意该服务实现了前面 api 模块定义的 PrductBizService 接口):

@RestController@AllArgsConstructor@RequestMapping("/v2/products")public class ProductSpringCloudService implements ProductBizService {    private final ProductAppService appService;
@PutMapping("/settlements") public ProductSettlementResponse calcSettlement(@RequestBody ProductSettlementRequest request) { return appService.calcSettlement(request); }
}
复制代码

可以看出:该远程服务只是实现了请求和响应的“透传”,直接调用本地应用服务 appservice 的 calcSettlement 方法,没有实现任何业务逻辑。

实现 spring cloud 微服务应用

微服务应用就是可以运行起来后作为微服务提供者的主程序。这个步骤在 spring boot 框架下特别简单,只需要新建一个 application,并使用相应的 annotation 注解即可。代码内容如下:

@SpringBootApplication@ComponentScan(basePackages = {"com.stardata.starshop2"})@EnableDiscoveryClientpublic class Starshop2ProductCommandSpringCloudApplication {    public static void main(String[] args) {        SpringApplication.run(Starshop2ProductCommandSpringCloudApplication.class, args);    }}
复制代码

这里只是使用了 @EnableDiscoveryClient 这一注解指令而已,没有任何实质性代码。


另外,为了让 spring cloud 的应用作为微服务提供者运行,需要配置 application.properties 或 application.yml。配置内容如下(以 application.yml 为例):

eureka:  #客户端  client:    # 注册中心地址    service-url:      defaultZone: http://localhost:7771/eureka/  # 修改Eureka上的默认描述信息  instance:    instance-id: product-biz-center-8082management:  endpoints:    web:      exposure:        include: "*"
复制代码

其它说明

需要说明的是:spring cloud 还需要实现一个 Eureka Server 主程序,该实现过程特别简单,只需要新建一个模块,在 @SpringApplication 的注解基础上,引入 @EnableEurekaServer 注解即可(当然,包括 pom.xml 中需要引入相关依赖)。这里不再赘述。该主程序的代码如下:

@SpringBootApplication@EnableEurekaServerpublic class EurekaApplication {    public static void main(String[] args) {        SpringApplication.run(EurekaApplication.class, args);    }}
复制代码

6.2.2 Dubbo 北向网关远程服务输出

实现 dubbo 微服务的输出,基本上和 spring cloud 的过程类似,无非还是:引入依赖、输出 dubbo 远程服务、实现 dubbo 微服务应用。具体说明如下:

引入依赖

引入 dubbo 相关依赖如下(由于 dubbo 项目本身包装的依赖内容与 spring cloud 依赖包有冲突,故这里是自行引入相关依赖包内容):

 <!-- dubbo -->        <dependency>            <groupId>org.apache.dubbo</groupId>            <artifactId>dubbo</artifactId>            <version>${dubbo.version}</version>        </dependency>        <dependency>            <groupId>org.apache.dubbo</groupId>            <artifactId>dubbo-dependencies-zookeeper</artifactId>            <version>${dubbo.version}</version>            <type>pom</type>            <exclusions>                <exclusion>                    <artifactId>slf4j-log4j12</artifactId>                    <groupId>org.slf4j</groupId>                </exclusion>                <exclusion>                    <artifactId>zookeeper</artifactId>                    <groupId>org.apache.zookeeper</groupId>                </exclusion>            </exclusions>        </dependency>        <dependency>            <groupId>org.apache.curator</groupId>            <artifactId>curator-framework</artifactId>            <version>${curator-framework.version}</version>        </dependency>        <dependency>            <groupId>org.apache.curator</groupId>            <artifactId>curator-recipes</artifactId>            <version>${curator-recipes.version}</version>            <exclusions>                <exclusion>                    <groupId>org.apache.zookeeper</groupId>                    <artifactId>zookeeper</artifactId>                </exclusion>            </exclusions>        </dependency>        <dependency>            <groupId>org.apache.zookeeper</groupId>            <artifactId>zookeeper</artifactId>            <version>${zookeeper.version}</version>            <exclusions>                <exclusion>                    <groupId>org.slf4j</groupId>                    <artifactId>slf4j-log4j12</artifactId>                </exclusion>            </exclusions>        </dependency>
<!-- dubbo starter --> <dependency> <groupId>org.apache.dubbo</groupId> <artifactId>dubbo-spring-boot-starter</artifactId> <version>${dubbo.version}</version> </dependency>
复制代码

输出 dubbo 远程服务

与前面的 spring cloud 类似,也是新增一个 remote 远程服务类 ProductDubboService。新增该远程服务后的代码结构图如下:

同样,ProductDubboService 类实现了 ProductBizService 接口,其代码如下:

@DubboService@AllArgsConstructorpublic class ProductDubboService implements ProductBizService {    private final ProductAppService appService;
public ProductSettlementResponse calcSettlement(ProductSettlementRequest request) { return appService.calcSettlement(request); }}
复制代码

注:该服务通过 @DubboService 注解实现了 dubbo 服务输出。同样,该远程服务只是协议透传,没有任何业务逻辑。

实现 dubbo 微服务应用

同样,dubbo 微服务需要有个主程序应用。其实现方式仍然是很简单,在 spring boot application 上加一个注解即可。代码如下:

@SpringBootApplication@ComponentScan(basePackages = {"com.stardata.starshop2"})@EnableDiscoveryClient@EnableDubbopublic class Starshop2ProductCommandSpringCloudApplication {    public static void main(String[] args) {        SpringApplication.run(Starshop2ProductCommandSpringCloudApplication.class, args);    }}
复制代码

可以看出:这里只是增加了 @EnableDubbo 注解而已。

6.3 订单上下文“南向网关”调用微服务

将“商品上下文”命令部分的代码部署成 spring cloud 或 dubbo 微服务提供者后,下面要做的工作,就是让订单上下文的领域服务 OrderManagingService.submitOrder() 方法在提交订单时,从原来本地方法调用(通过南向网关的 OrderItemsSettlementClientLocalAdapter 适配器)改成调用对应的微服务。


为此,我们需要做如下 3 方面的工作:

6.3.1 实现微服务应用

这其实和前面商品上下文实现微服务应用的做法类似,无非就是引入 spring cloud 或 dubbo 微服务框架(或任何其它微服务框架)相应的依赖。然后将 spring application 加上相应的注解后,将其转化为支持微服务框架的应用。


考虑到引入的 spring cloud 或 dubbo 依赖的内容与前面商品上下文相同,这里就不再赘述。改造后的订单上下文应用主程序代码如下:

@SpringBootApplication@EnableDiscoveryClient@EnableFeignClients(basePackages = {"com.stardata.starshop2"})@ComponentScan(basePackages = {"com.stardata.starshop2"})@EnableDubbopublic class Starshop2OrderCommandApplication {    public static void main(String[] args) {        SpringApplication.run(Starshop2OrderCommandApplication.class, args);    }}
复制代码

注意看:这里只是引入了 @EnableDiscoveryClient (支持 spring cloud 服务注册)、@EnableFeignClients(支持 spring cloud 客户端服务发现和负载均衡)、@EnableDubbo(支持 dubbo 服务注册)。

6.3.2 将南向网关适配器改为可配置模式

为了演示订单上下文调用商品上下文的端口 OrderItemsSettlementClient 的实现方式,其实是可以通过“配置化”的方式来启用不同的适配器的,我们对订单上下文端口 OrderItemsSettlementClient 实现“配置化改造”。


  • 首先,我们在应用配置文件 application.yml 中增加一个参数: adapter.orderItemsSettlement,该参数的配置示例如下:

adapter:#  orderItemsSettlement: orderItemsSettlementClientLocalAdapter#  orderItemsSettlement: orderItemsSettlementSpringCloudAdapter  orderItemsSettlement: orderItemsSettlementDubboAdapter
复制代码

从这里可以看出:这个适配器是可以随意通过配置文件来进行修改的。


  • 其次,我们在领域服务 OrderManagingService 的实现代码中,将适配器注入改造为“可配置”模式。改造后的 OrderManagingService 代码类似下图:

从红色框子可以看出,这里的 bean 注入,变成了使用配置文件中参数名称作为 bean 名称的方式。事实上,我们甚至可以将这里所有的端口(如:预支付客户端端口 prepayingClient、订单资源库端口 orderRepository、参数资源库端口 parameterRepository 等),都通过配置文件来实现适配器的动态注入。

6.3.3 实现南向网关微服务远程调用

最后一步要做的工作,就是实现 spring cloud 或 dubbo 微服务框架下的、实现南向端口 OrderItemsSettlementClient 的适配器类。这两个适配器类所处的代码结构如下图:

这两个适配器的代码实现如下:

@Adapter(PortType.Client)@Component("orderItemsSettlementSpringCloudAdapter")@AllArgsConstructorpublic class OrderItemsSettlementSpringCloudAdapter implements OrderItemsSettlementClient {    private final ProductBizService productSettlementService;
@Override public void settleProducts(@NotNull Order order) { List<Long> productIds = new ArrayList<>(); List<Integer> productCounts = new ArrayList<>(); order.getItems().forEach(item -> { productIds.add(item.getProductId().value()); productCounts.add(item.getPurchaseCount()); }); ProductSettlementRequest request = new ProductSettlementRequest(productIds, productCounts); ProductSettlementResponse response = productSettlementService.calcSettlement(request); for (ProductSettlementResponse.Item item : response.getItems()) { order.settleItem(LongIdentity.from(item.getId()), item.getName(), item.getCount(), item.getPriceFen(), item.getProductSnapshot()); } }}
复制代码


@Adapter(PortType.Client)@Component("orderItemsSettlementDubboAdapter")@AllArgsConstructor@Primarypublic class OrderItemsSettlementDubboAdapter implements OrderItemsSettlementClient {    @DubboReference    private final ProductBizService productSettlementService;
@Override public void settleProducts(@NotNull Order order) { List<Long> productIds = new ArrayList<>(); List<Integer> productCounts = new ArrayList<>(); order.getItems().forEach(item -> { productIds.add(item.getProductId().value()); productCounts.add(item.getPurchaseCount()); }); ProductSettlementRequest request = new ProductSettlementRequest(productIds, productCounts); ProductSettlementResponse response = productSettlementService.calcSettlement(request); for (ProductSettlementResponse.Item item : response.getItems()) { order.settleItem(LongIdentity.from(item.getId()), item.getName(), item.getCount(), item.getPriceFen(), item.getProductSnapshot()); } }}
复制代码

我们再对比下原来本地调用的适配器类代码:

@Adapter(PortType.Client)@Component("orderItemsSettlementClientLocalAdapter")@AllArgsConstructorpublic class OrderItemsSettlementClientLocalAdapter implements OrderItemsSettlementClient {    private final ProductAppService productAppService;
@Override public void settleProducts(@NotNull Order order) {
List<Long> productIds = new ArrayList<>(); List<Integer> productCounts = new ArrayList<>(); order.getItems().forEach(item -> { productIds.add(item.getProductId().value()); productCounts.add(item.getPurchaseCount()); }); ProductSettlementRequest request = new ProductSettlementRequest(productIds, productCounts); ProductSettlementResponse response = productAppService.calcSettlement(request); for (ProductSettlementResponse.Item item : response.getItems()) { order.settleItem(LongIdentity.from(item.getId()), item.getName(), item.getCount(), item.getPriceFen(), item.getProductSnapshot()); }
}}
复制代码

可以看出,所修改的地方,无非就是:

  • 区别 1:使用的是本地 appService 调用、还是 dubbo 远程服务调用(通过 @DubboReference 注解引入)、或是 spring cloud 远程服务调用(通过 application 的 @EnableFeignClients 注解发现);


  • 区别 2: 给不同的 adapter 进行了不同的命名(使用 @Component 注解给出);


6.4 小结

通过如上的代码示例,我们可以看出“订单”和“商品上下文”的“领域核心”部分的代码逻辑,没有做哪怕一行的代码修改。所以,我们可以做出如下的几点小结:


  1. 当严格遵循“菱形架构”要求的 DDD 战术设计来实现代码后,微服务的拆分与否,其实完全不会影响 DDD 菱形架构下的“领域核心”部分的代码实现(包括:领域服务、实体和值对象);

  2. 为了很好的支持微服务的“分分合合”,建议将所有的上下文应用服务(也叫“本地服务”)的服务接口,都进行 api 公共服务接口的定义;

  3. 同时,为了很好的支持微服务的“分分合合”,也需要将所有上下文应用服务的出入参对应的 DTO 对象,也放到 api 公共服务接口的定义中去;

  4. 当然,当涉及到跨上下文的“数据库事务时”,建议通过“可靠消息总线”驱动下的“补偿交易 TCC”或“长时间事务 SAGA”来实现“最终事务一致性”,而不是“强事务一致性”。

  5. 当满足了以上几条后,微服务就可以做到“随时可拆可合”了。


事实上,我们也可以换句话说:只要 DDD 战略设计(限界上下文拆分与映射)、DDD 战术设计(遵循菱形架构)做得好,就完全可以在“代码开发层面上”完全不用关心“微服务拆分与否”这一问题,而将该问题完全转换成了系统运维层面上需要考虑的事情。

7. 结束语及倡议发起


本系列到这里,就算全部结束了。希望本系列的文章,能够帮助您或您的团队,在“如何将 DDD 实际落地应用”上有所帮助。


说实话,这一系列是我个人学习 DDD 过程中的一种记录,可能其中会有很多不完善之处。如果您发现我的 DDD 设计、或代码实现中,有任何不足之处,欢迎您随时加我微信: beautautumn,大家一起交流学习!


作为结束语,也作为我个人的一点私心,我在这里提出两个倡议:

7.1 倡议 1:在实践中使用


最近几年,DDD 在网络上有很多文章分享,很多程序员或架构师,也都有很多不同的理解和应用,大家多多少少都存在着一定的“实践性”困难。


对此,我个人的建议是:不要停留在“书本知识”或“阅读他人文章”的层面,而是实实在在的拿着一个实际的项目、或者自己找一个练习性的项目(正如本文中做到的那样),来从头到尾把 DDD 方法体系用到的需求分析、战略设计、战术设计的内容进行实践,只有这样您才能真正深入的理解 DDD,而不仅仅是人云亦云、或人云非不云,那样的盲目遵从或盲目杠精其实都没有意义——相信我:作为软件架构师,从来没有一项软件架构的技能实践,不是在实践编程过程中、通过足够长的时间(一般半年以上)积累起来的。


如果您认同我的观点:确实打算扎实掌握一门好的软件架构实践,而又实在找不到好的实践项目,我十分欢迎您参与我下面的一项倡议:共同打造一款 web3.0 模式下的生鲜供应链系统。

7.2 倡议 2:共同打造 web3.0 生鲜供应链系统

这个倡议,是出于我个人的私心:希望建立一个开源社区,打造一款 web3.0 模式下的、分布式应用和数据存储的生鲜供应链系统。


如果您有兴趣和耐心,且听我慢慢道来。。。😀

7.2.1 基本设想


我之所以提出这个设想,是基于两个基本假设:1. web3.0 是互联网发展的必然趋势所在; 2. web3.0 真正的价值,是切入到实体经济,而不是玩虚拟货币。


关于这两点,我个人是非常坚信的。所以,一方面我个人积极拥抱区块链、智能合约等技术,另一方面我绝不参与任何虚拟货币的交易。


web3.0 一个非常重要的应用,是在供应链领域,见这篇文章《万字长文|为什么Web3.0革命必将发生在中国》。而生鲜供应链就是一个特别复杂的、很适合使用 web3.0 技术进行改造的低效率行业。

1. web3.0 对生鲜供应链的价值


众所周知,最近几年生鲜电商的发展很快,但生鲜电商的各种模式,无论是前置仓模式,已经倒下了每日优鲜;而前期发展新零售概念的盒马鲜生,在阿里内部也失去了“宠儿”的地位,阿里也受不了它的持续烧钱了要出去融资了。。。。更别说还在断臂关店求生的叮咚买菜,以及举步维艰的朴朴买菜(已经开始跑通大店朴朴超市模式)。所有的这些,都说明通过所谓的“传统电商”模式是很难解决生鲜行业的效率问题的。本人有过 2 年左右的生鲜创业经验,就本人一些粗浅的看法,生鲜电商难以解决效率问题的根本在于:


  1. 生鲜是个高损耗的行业,而且商品的地区差异很小,导致生鲜生产、物流、消费的效率永远是“区域性”的,无法实现“规模效应”。由于大部分生鲜产品,都是只有很短的保质期(保鲜期),所以从供应链上是没法做到“集中生产、集中物流、集中消费”的模式的。一旦无法做到这些“集中”,做互联网平台性企业就无法实现“规模效应”,也就不存在“早期低价补贴投资占领市场、后期提价获取正常化利润”的过程——因为,你卖 1000 斤白菜需要 90 块成本赚 10 元、你卖到 1000 万斤白菜还是需要 90 万成本赚 10 万,这里根本不存在所谓“规模效应”。


  1. 生鲜产品不存在所谓的“品牌溢价”,只存在“品质溢价”。生鲜产品因为本质上是“农业生产”而来(无论是种植还是养殖),不会因为你是阿里种出来的白菜(或养出来的猪肉)、就比农民小作坊种出来的白菜更贵的道理,所以不存在“品牌溢价”;而只存在“有机蔬菜”(或“有机猪肉”)这样的品质溢价。我们知道,一旦不存在“品牌溢价”,而只存在某种生产过程的溢价(这些农业生产过程是很难专利等商业机密保护机制的),企业就无法凭借自身品牌优势而形成竞争优势。


这是从“目前的互联网技术改造传统行业效率”的角度,来剖析当前的 web2.0 技术无法帮助到生鲜供应链效率的问题。我的看法是:要让互联网技术可以帮助到生鲜供应链,关键点不是改造“生产关系”,不是要打造一个巨无霸的“生鲜电商平台”或“零售品牌”,而是改造生产力。包括如下 2 个方面:


  1. 提升农产品生产技术本身的水平,进而提高单位资源消耗下的农业产出水平。如:立体农业生产蔬菜、滴灌技术、无农药杀虫技术等等。

  2. 提升生鲜供应链要素的流转效率,进而提高资源分配利用水平。如:一窝蜂的种植或养殖某个产品导致滞销、大规模集中化农贸市场导致损耗加剧、农产品需求高峰期物流能力不足等等。


互联网技术不能帮助提升第一方面,即:提升农业本身的生产技术水平。而只能帮助在第二方面,即:提升生鲜供应链要素的流转效率。而且是在不打造“集中化平台”、“集中化品牌”的前提下,互联网技术才能对其有所帮助——web3.0 对生鲜供应链的价值呼之欲出。

2. web3.0 重塑生鲜供应链基本框架


目前中国各类生鲜产品(包含蔬菜、水果、粮食、水产、肉类等等)的供应链环节,典型情况下,大体分为如下 5 个环节:

  1. 农业生产(即种植、养殖、捕捞等)。

  2. 生鲜一批,批发商。这些一批的批发商,一般是到田间地头收购的商户。这些商户需要具备一定的资金能力、大型物流运输能力(一般都是大货车啥的)。

  3. 生鲜二批,大型交易市场。这些二批的批发商,一般都是各城市的大型农贸交易市场(如:北京的新发地、武汉的白沙洲市场等)。一批的批发商将货物运输到城市的大型交易市场后,要么在大型批发市场设摊销售、要么转售给批发市场专门的批发商。

  4. 生鲜三批。会有专门的批发商,从二批大型交易市场拿货,然后发货给各菜市场(一般分布在城市各个角落)、生鲜超市、餐馆、企业等等。

  5. 消费者餐桌。消费者从家门口的菜市场、或生鲜超市购买,或在餐馆就餐、或在企业就餐等等。这是生鲜供应链的最后一环。


经过这 5 个环节,消费者餐桌上的价格,其实是农业生产销售价格的 3 倍以上(如果是餐馆就是 6 倍以上了)。从这其中,可以看出效率的浪费有多严重。


在我设想的 web3.0 生鲜供应链体系中,从农业生产、到一批二批三批,都是可以被互联网优化的地方,包括支撑该供应链底层的基础设施:物流和仓储。


当然,这种优化,鉴于前面提到的两个因素,是不应该基于“集中化平台”或“集中化品牌”的角度来做的——也就是说,只能基于 web3.0 模式。在我设想的 web3.0 优化过的生鲜供应链,大体框架如下:


作为 web3.0 互联网平台,我们要实现的目标是:通过“软件和数据”这一基础设施的支撑,实现“物流、仓储、资金、渠道”四要素的连接,进而“非中心化”的“自然增值式”的优化与发展。


需要特别强调的是:“软件和数据”是基础设施,与其它四个要素一样,是“非中心化的”、“分布式”的,也就是并不是某家“中心化”企业控制的。

7.2.2 计划步骤


在我的心目中,我是打算通过如下的 4 个步骤来打造这个 web3.0 生鲜供应链平台:


  1. 实现完整的“群买菜”生鲜电商零售系统。我将代码开源出来,主要的目的,就是倡议程序员 XDJM 们利用闲暇时间,凭借自己个人的兴趣爱好,逐步一起来丰富这个生鲜电商系统,一直到完成这个生鲜电商零售系统的全部开发——当然,这里面我个人将承担大部分工作。


  1. 将“群买菜”迁移到区块链上、并实现智能合约。一旦“群买菜”成为一个可用的、健康的生鲜零售系统,我们将会把它迁移到区块链上,通过智能合约的方式来实现采购、物流和销售。 之所以实现这一步迁移,其目的是允许任何应用开发者按照自身的软件设计、数据使用需求来进行应用开发(前提是遵循区块链和智能合约,才能实现生态内数据和用户的共享),为将来实现 web3.0 生态供应链打造基础。


  1. 基于生态思维,发展开发出囊括生产、流通、销售等所有环节的成百上千的应用,实现物流、仓储、资金、渠道 4 要素的“非中心化”连接,最终打造出真正的“web3.0 生鲜供应链”生态。


在此,我诚挚的邀请您:一起参与这个 web3.0 供应链系统的开发与建设!


此致

一个热爱自由与奉献的老程序员

2022.8.23 于武汉

发布于: 刚刚阅读数: 3
用户头像

深清秋

关注

从菜鸟向大神攀登。。。 2020.11.15 加入

21年IT从业经验,热爱写代码、软件架构设计、软件产品设计。最近决定把自己的一些代码或设计经验分享出来,希望对大家有用!个人秉承职业理念:自己给自己加班,提升自己的稀缺性!

评论

发布
暂无评论
DDD实战(12-终篇):微服务的“分分合合”及一个倡议_DDD_深清秋_InfoQ写作社区