DDD实战课(3):实战篇上

  • 实战篇:几个实战项目
    • 11 | DDD实践:如何用DDD重构中台业务模型?(略)
      • 构建中台业务模型
        • 自顶向下的策略
        • 自底向上的策略
    • 12 | 领域建模:事件风暴构建领域模型完整流程(略)
      • 事件风暴概念
      • 事件风暴准备
      • 事件风暴构建领域模型
    • 13 | 代码模型(上):微服务代码模型(重点)
      • DDD 分层架构与微服务代码模型
        • 微服务一级目录结构
        • 各层目录结构
          • 几个区别:
        • 代码模型总目录结构
    • 14 | 代码模型(下):保证领域模型与代码模型的一致性
      • 领域对象的整理
      • 从领域模型到微服务的设计
      • 领域层的领域对象
        • 设计领域服务
      • 应用层的领域对象
      • 领域对象与微服务代码对象的映射
      • 非典型领域模型
    • 15 | 边界:微服务的各种边界在架构演进中的作用?
      • 演进式架构:微服务设计的重点
      • 微服务边界的作用

实战篇:几个实战项目

11 | DDD实践:如何用DDD重构中台业务模型?(略)

Part 1:传统业务转型互联网实例
实例背景(略)

构建中台业务模型

2种建模策略:自顶向下的策略、自底向上的策略

自顶向下的策略

先做顶层设计,从最高领域逐级分解为中台,分别建立领域模型,根据业务属性分为通用中台或核心中台。领域建模过程主要基于业务现状,暂时不考虑系统现状。自顶向下的策略适用于全新的应用系统建设,或旧系统推倒重建的情况。

自底向上的策略

第二种策略是自底向上。这种策略是基于业务和系统现状完成领域建模。首先完成需重构业务域的领域建模;然后对齐业务域,找出具有同类或相似业务功能的领域模型,对比分析领域模型的差异,重组领域对象,重构领域模型。这个过程会沉淀公共和复用的业务能力,会将分散的业务模型整合。自底向上策略适用于遗留系统业务模型的演进式重构。

实例:保险业务转型。参见原文
第一步:锁定系统所在业务域,构建领域模型。
第二步:对齐业务域,构建中台业务模型。构建多业务域的中台业务模型的过程,就是找出同一业务域内所有同类业务的领域模型,对比分析域内领域模型和聚合的差异和共同点,打破原有的模型,完成新的中台业务模型重组或归并的过程。
第三步:中台归类,根据领域模型设计微服务。

重点是:重构过程中的领域对象,理解业务域、领域模型、聚合以及领域对象之间的关系。

业务域、领域模型表格图示

12 | 领域建模:事件风暴构建领域模型完整流程(略)

一般来说一个中型规模的项目,领域建模的时间大概在两周左右。

事件风暴概念

事件风暴是一项团队活动,领域专家与项目团队通过头脑风暴的形式,罗列出领域中所有的领域事件,整合之后形成最终的领域事件集合,然后对每一个事件,标注出导致该事件的命令,再为每一个事件标注出命令发起方的角色。命令可以是用户发起,也可以是第三方系统调用或者定时器触发等,最后对事件进行分类,整理出实体、聚合、聚合根以及限界上下文。

事件风暴准备

参与者:采用工作坊的方式,将项目团队和领域专家聚集在一起。
关注点:需要重点关注这类业务的语言和行为。比如某些业务动作或行为(事件)是否会触发下一个业务动作,这个动作(事件)的输入和输出是什么?是谁(实体)发出的什么动作(命令),触发了这个动作(事件)…我们可以从这些暗藏的词汇中,分析出领域模型中的事件、命令和实体等领域对象。

事件风暴构建领域模型

领域建模的过程主要包括产品愿景、业务场景分析、领域建模和微服务拆分与设计这几个重要阶段。

一、产品愿景

二、业务场景分析
场景分析是从用户视角出发的,根据业务流程或用户旅程,采用用例和场景分析,探索领域中的典型场景,找出领域事件、实体和命令等领域对象,支撑领域建模。事件风暴参与者要尽可能地遍历所有业务细节,充分发表意见,不要遗漏业务要点。

场景分析时会产生很多的命令和领域事件。我用蓝色来表示命令,用橙色表示领域事件,用黄色表示补充信息,比如用户信息数据来源于 HR 系统的说明。

实际上以目前的权限模块来看,不用DDD造成的问题就是后续一个新的方法的补充完全无规律,整个业务流散乱。

三、领域建模(重点)
根据场景分析过程中产生的领域对象,比如命令、事件等之间关系,找出产生命令的实体,分析实体之间的依赖关系组成聚合,为聚合划定限界上下文,建立领域模型以及模型之间的依赖。领域模型利用限界上下文向上可以指导微服务设计,通过聚合向下可以指导聚合根、实体和值对象的设计。

三步:
第一步:从命令和事件中提取产生这些行为的实体。用绿色贴纸表示实体。

第二步:根据聚合根的管理性质从七个实体中找出聚合根。比如,用户管理用户相关实体以及值对象,系统可以管理与系统相关的菜单等实体等,可以找出用户和系统等聚合根。然后根据业务依赖和业务内聚原则,将聚合根以及它关联的实体和值对象组合为聚合,比如系统和菜单实体可以组合为“系统功能”聚合。按照上述方法,用户中台就有了系统功能、岗位、用户信息、用户日志、账户和认证票据六个聚合。

第三步:划定限界上下文,根据上下文语义将聚合归类。
例子:根据用户域的上下文语境,用户基本信息和用户日志信息这两个聚合共同构成用户信息域,分别管理用户基本信息、用户登录和操作日志。认证票据和账户这两个聚合共同构成认证域,分别实现不同方式的登录和认证。系统功能和岗位这两个聚合共同构成权限域,分别实现系统和菜单管理以及系统的岗位配置。根据业务边界,我们可以将用户中台划分为三个限界上下文:用户信息、认证和权限。

借助表格记录过程

四、微服务拆分与设计(重点)

原则上一个领域模型就可以设计为一个微服务,但由于领域建模时只考虑了业务因素,没有考虑微服务落地时的技术、团队以及运行环境等非业务因素,因此在微服务拆分与设计时,我们不能简单地将领域模型作为拆分微服务的唯一标准,它只能作为微服务拆分的一个重要依据。

微服务的设计还需要考虑服务的粒度、分层、边界划分、依赖关系和集成关系。除了考虑业务职责单一外,我们还需要考虑将敏态与稳态业务的分离、非功能性需求(如弹性伸缩要求、安全性等要求)、团队组织和沟通效率、软件包大小以及技术异构等非业务因素。

用户中台微服务设计如果不考虑非业务因素,我们完全可以按照领域模型与微服务一对一的关系来设计,将用户中台设计为:用户、认证和权限三个微服务。但如果用户日志数据量巨大,大到需要采用大数据技术来实现,这时用户信息聚合与用户日志聚合就会有技术异构。虽然在领域建模时,我们将他们放在一个了领域模型内,但如果考虑技术异构,这两个聚合就不适合放到同一个微服务里了。我们可以以聚合作为拆分单位,将用户基本信息管理和用户日志管理拆分为两个技术异构的微服务,分别用不同的技术来实现它们。

13 | 代码模型(上):微服务代码模型(重点)

DDD 分层架构与微服务代码模型

DDD架构图示:

微服务一级目录结构

微服务一级目录是按照 DDD 分层架构的分层职责来定义的。从下面这张图中,我们可以看到,在代码模型里分别为用户接口层、应用层、领域层和基础层,建立了 interfaces、application、domain 和 infrastructure 四个一级代码目录。

Interfaces(用户接口层):它主要存放用户接口层与前端交互、展现数据相关的代码。前端应用通过这一层的接口,向应用服务获取展现所需的数据。这一层主要用来处理用户发送的 Restful 请求,解析用户输入的配置文件,并将数据传递给 Application 层。数据的组装、数据传输格式以及 Facade 接口等代码都会放在这一层目录里。
也可以将所有包的用户接口层抽离出来,作为一个独立的web包,对外提供路由网关的转发。这样每一个包都没有interface包,全部对外的接口放在web包内,这种做法在微服务内部有很多模块组成时便于统一管理对外接口与DTO。

Application(应用层):它主要存放应用层服务组合和编排以及事件相关的代码。应用服务向下基于微服务内的领域服务或外部微服务的接口完成服务的编排和组合,向上为用户接口层提供各种应用数据展现支持服务。应用服务和事件等代码会放在这一层目录里。

Domain(领域层):它主要存放领域层核心业务逻辑相关的代码。领域层包含多个聚合代码包,它们共同实现领域模型的核心业务逻辑。聚合以及聚合内的实体、方法、领域服务和事件等代码会放在这一层目录里。

Infrastructure(基础层):它主要存放基础资源服务相关的代码,为其它各层提供的通用技术能力、三方软件包、数据库服务、配置和基础资源服务的代码都会放在这一层目录里。

各层目录结构

1.用户接口层
Interfaces 的代码目录结构有:assembler、dto 和 façade 三类。

Assembler/convert:实现 DTO 与领域对象之间的相互转换和数据交换。一般来说 Assembler 与 DTO 总是一同出现。

Dto:它是数据传输的载体,内部不存在任何业务逻辑,我们可以通过 DTO 把内部的领域对象与外界隔离。

Facade/Controller or RPC:提供较粗粒度的调用接口,将用户请求委派给一个或多个应用服务进行处理。

2.应用层
Application 的代码目录结构有:event 和 service。

Event(事件):这层目录主要存放事件相关的代码。它包括两个子目录:publish 和 subscribe。前者主要存放事件发布相关代码,后者主要存放事件订阅相关代码(事件处理相关的核心业务逻辑在领域层实现)。
虽然应用层和领域层都可以进行事件的发布和处理,但为了实现事件的统一管理,建议将微服务内所有事件的发布和订阅的处理都统一放到应用层,事件相关的核心业务逻辑实现放在领域层。通过应用层调用领域层服务,来实现完整的事件发布和订阅处理流程。

Service(应用服务):这层的服务是应用服务。应用服务会对多个领域服务或外部应用服务进行封装、编排和组合,对外提供粗粒度的服务。

3.领域层
Domain 是由一个或多个聚合包构成,共同实现领域模型的核心业务逻辑。聚合内的代码模型是标准和统一的,包括:entity、event、repository 和 service 四个子目录。

Aggregate(聚合):它是聚合软件包的根目录,可以根据实际项目的聚合名称命名,比如权限聚合。在聚合内定义聚合根、实体和值对象以及领域服务之间的关系和边界。聚合内实现高内聚的业务逻辑,它的代码可以独立拆分为微服务。
以聚合为单位的代码放在一个包里的主要目的是为了业务内聚,而更大的目的是为了以后微服务之间聚合的重组。聚合之间清晰的代码边界,可以让你轻松地实现以聚合为单位的微服务重组,在微服务架构演进中有着很重要的作用。

Entity(实体):它存放聚合根(聚合根本质上也是一种实体)、实体、值对象以及工厂模式(Factory)相关代码。实体类采用充血模型,同一实体相关的业务逻辑都在实体类代码中实现。跨实体的业务逻辑代码在领域服务中实现。

Event(事件):它存放事件实体以及与事件活动相关的业务逻辑代码。
领域事件实体和处理类放在领域层的 Event 目录结构下。领域事件的发布和订阅类我建议放在应用层的 Event 目录结构下。

Service(领域服务):它存放领域服务代码。一个领域服务是多个实体组合出来的一段业务逻辑。你可以将聚合内所有领域服务都放在一个领域服务类中,你也可以把每一个领域服务设计为一个类。如果领域服务内的业务逻辑相对复杂,建议将一个领域服务设计为一个领域服务类,避免由于所有领域服务代码都放在一个领域服务类中,而出现代码臃肿的问题。领域服务封装多个实体或方法后向上层提供应用服务调用。

Repository(仓储):它存放所在聚合的查询或持久化领域对象的代码,满足依赖倒置原则时,通常只是定义仓储接口,仓储实现方法放置在基础层中。为了方便聚合的拆分和组合,我们设定了一个原则:一个聚合对应一个仓储。

特别说明:按照 DDD 分层架构,仓储实现本应该属于基础层代码,但为了在微服务架构演进时,保证代码拆分和重组的便利性,我是把聚合仓储实现的代码放到了聚合包内。这样,如果需求或者设计发生变化导致聚合需要拆分或重组时,我们就可以将包括核心业务逻辑和仓储代码的聚合包整体迁移,轻松实现微服务架构演进。

几个区别:

聚合根与聚合内领域服务之间的关系,是否在领域服务内只有聚合根这一个实体对象?
聚合之间数据的传递尽可能少,建议用值对象传递(《实现领域驱动设计》)。外界入参进入聚合后构建聚合根,聚合根是对外负责人,索引到所有相关实体信息。但是还可以在领域服务中去索引领域内的其他实体并且直接操作其他实体。所以聚合根在领域服务中并不是唯一存在的实体对象

如果只有一个聚合,领域服务与应用服务之间的关系:基本没有区别,如果要拆分,应用服务是很薄的一层,基本上是委托应用服务进行代理。

聚合内event与应用event之间的区别可以保持上述定义。

4.基础层
Infrastructure 的代码目录结构有:config 和 util 两个子目录。

Config:主要存放配置相关代码。

Util:主要存放平台、开发框架、消息、数据库、缓存、文件、总线、网关、第三方类库、通用算法等基础代码,你可以为不同的资源类别建立不同的子目录。

代码模型总目录结构


上述架构之间不同聚合之间的协作在application中的service进行编排,因此app独立出来,应用服务与领域服务之间区分明显。应用服务内通过分布式事务或者领域事件完成不同聚合之间的操作。
如果聚合之间联系很薄弱,基本上应用服务与领域服务区别很小。
CQRS架构中,应用服务就是一个个对外声明的cmd与对应的cmdexecutor。

14 | 代码模型(下):保证领域模型与代码模型的一致性

需要将领域模型作为微服务设计的输入,对领域对象进行设计和转换,让领域对象与代码对象建立映射关系。

领域对象的整理

第一个重要的工作就是,整理事件风暴过程中产生的各个领域对象,比如:聚合、实体、命令和领域事件等内容,将这些领域对象和业务行为记录到下面的表格中。
一个领域模型会包含多个聚合,一个聚合包含多个领域对象,每个领域对象都有自己的领域类型。领域类型主要标识领域对象的属性,比如:聚合根、实体、命令和领域事件等类型。

从领域模型到微服务的设计

事件风暴中提取的领域对象,还需要经过用户故事或领域故事分析,以及微服务设计,才能用于微服务系统开发。
这个过程会比事件风暴来的更深入和细致。主要关注内容如下:

  1. 分析微服务内有哪些服务?
  2. 服务所在的分层?
  3. 应用服务由哪些服务组合和编排完成?
  4. 领域服务包括哪些实体的业务逻辑?
  5. 采用充血模型的实体有哪些属性和方法?
  6. 有哪些值对象?
  7. 哪个实体是聚合根等?
  8. 最后梳理出所有的领域对象和它们之间的依赖关系,我们会给每个领域对象设计对应的代码对象,定义它们所在的软件包和代码目录。

领域层的领域对象

在完成故事分析和微服务设计后,微服务的聚合内一般会有:聚合根、实体、值对象、领域事件、领域服务和仓储等领域对象。
该章节对各个部分的设计注意事项进行总结,以下仅收录部分。

找出聚合根
聚合根通过工厂和仓储模式,实现聚合内地址、银行账号等实体和值对象数据的初始化和持久化。
聚合根是一种特殊的实体,它有自己的属性和方法。聚合根可以实现聚合之间的对象引用,还可以引用聚合内的所有实体。聚合根类放在代码模型的 Entity 目录结构下。聚合根有自己的实现方法,比如生成客户编码,新增和修改客户信息等方法。
聚合根可以理解为聚合负责人。

设计领域服务

如果一个业务动作或行为跨多个实体,我们就需要设计领域服务。领域服务通过对多个实体和实体方法进行组合,完成核心业务逻辑。
按照严格分层架构层的依赖关系,如果实体的方法需要暴露给应用层,它需要封装成领域服务后才可以被应用服务调用。所以如果有的实体方法需要被前端应用调用,我们会将它封装成领域服务,然后再封装为应用服务。
个人客户聚合根这个实体创建个人客户信息的方法,被封装为创建个人客户信息领域服务。然后再被封装为创建个人客户信息应用服务,向前端应用暴露。

应用层的领域对象

在严格分层架构模式下,不允许服务的跨层调用,每个服务只能调用它的下一层服务。服务从下到上依次为:实体方法、领域服务和应用服务。

实现服务的跨层调用

1.实体方法的封装
封装时服务前面的名字可以保持一致,你可以用 *DomainService 或 *AppService 后缀来区分领域服务或应用服务。
对应于CQRS架构,Controller层调用的是各个包app下的cmd,cmd的executor调用domainservice。executor需要通过分布式事务或者领域事件的方式完成不同聚合之间的一致性。

2.领域服务的组合和封装
应用服务会对多个领域服务进行组合和编排,暴露给用户接口层,供前端应用调用。

3.应用服务的组合和编排
在应用服务组合和编排时,你需要关注一个现象:多个应用服务可能会对多个同样的领域服务重复进行同样业务逻辑的组合和编排。当出现这种情况时,你就需要分析是不是领域服务可以整合了。你可以将这几个不断重复组合的领域服务,合并到一个领域服务中实现。

领域对象与微服务代码对象的映射

对表格的各栏做一个简要的说明。

  1. 层:定义领域对象位于分层架构中的哪一层,比如:接口层、应用层、领域层以及基础层等。
  2. 领域对象:领域模型中领域对象的具体名称,通用语言名称。
  3. 领域类型:根据 DDD 知识体系定义的领域对象的类型,包括:限界上下文、聚合、聚合根、实体、值对象、领域事件、应用服务、领域服务和仓储服务等领域类型,重点是层级分明。
  4. 依赖的领域对象:根据业务对象依赖或分层调用的依赖关系,建立的领域对象的依赖关系,比如:服务调用依赖、关联对象聚合等。
  5. 包名:代码模型中的包名,对应领域对象所在的软件包。
  6. 类名:代码模型中的类名,对应领域对象的类名。
  7. 方法名:代码模型中的方法名,对应领域对象实现或操作的方法名。

微服务代码结构图:

非典型领域模型

有些业务场景可能并不能如你所愿,可能无法设计出典型的领域模型。这类业务中有多个实体,实体之间相互独立,是松耦合的关系,这些实体主要参与分析或者计算,你找不出聚合根,但就业务本身来说它们是高内聚的。
我们还是可以借鉴聚合的思想,仍然用聚合来定义这部分功能,并采用与典型领域模型同样的分析方法,建立实体的属性和方法,对方法和服务进行封装和分层设计,设计仓储,建立领域对象之间的依赖关系。唯一可惜的就是我们找不到聚合根,不过也没关系,除了聚合根管理功能外,我们还可以用 DDD 的其它设计方法。

15 | 边界:微服务的各种边界在架构演进中的作用?

微服务的设计要涉及到逻辑边界、物理边界和代码边界等等。

演进式架构:微服务设计的重点

演进式架构就是以支持增量的、非破坏的变更作为第一原则,同时支持在应用程序结构层面的多维度变化。
如何判断微服务设计是否合理呢?其实很简单,只需要看它是否满足这样的情形就可以了:随着业务的发展或需求的变更,在不断重新拆分或者组合成新的微服务的过程中,不会大幅增加软件开发和维护的成本,并且这个架构演进的过程是非常轻松、简单的。这也是微服务设计的重点。

单体式微服务只定义了一个维度的边界,也就是微服务之间的物理边界,本质上还是单体架构模式。微服务设计时要考虑的不仅仅只有这一个边界,别忘了还要定义好微服务内的逻辑边界和代码边界,这样才能得到你想要的结果。

微服务边界的作用

边界分为:逻辑边界(微服务内部不同聚合)、物理边界(微服务之间)和代码边界。

逻辑边界(聚合边界):微服务架构演进时,在业务端以聚合为单位进行业务能力的重组,在微服务端以聚合的代码目录为单位进行微服务代码的重组。

物理边界:主要从部署和运行的视角来定义微服务之间的边界。不同微服务部署位置和运行环境是相互物理隔离的,分别运行在不同的进程中。这种边界就是微服务之间的物理边界。

代码边界:主要用于微服务内的不同职能代码之间的隔离。微服务开发过程中会根据代码模型建立相应的代码目录,实现不同功能代码的隔离。

DDD实战课(3):实战篇上相关推荐

  1. DDD实战课(实战篇)--学习笔记

    目录 DDD实践:如何用DDD重构中台业务模型? 领域建模:如何用事件风暴构建领域模型? 代码模型(上):如何使用DDD设计微服务代码模型? 代码模型(下):如何保证领域模型与代码模型的一致性? 边界 ...

  2. 新一代分布式实时流处理引擎Flink入门实战之先导理论篇-上

    文章目录 概述 定义 为什么使用Flink 应用行业和场景 应用行业 应用场景 实时数仓演变 Flink VS Spark 架构 系统架构 术语 无界和有界数据 流式分析基础 分层API 运行模式 作 ...

  3. DDD实战课(2):进阶篇

    DDD实战课(2):进阶篇 进阶篇:常见的微服务架构模型以及中台设计思想 06 | 领域事件:解耦微服务的关键 领域事件:领域模型/微服务之间的事件 领域事件驱动设计 微服务内部的领域事件 微服务之间 ...

  4. 后端存储实战课——设计篇

    <后端存储实战课> 的学习笔记,欢迎阅读斧正. 创建和更新订单 表设计 最少应该有以下几张表: 订单主表:保存订单基本信息 订单商品表:保存订单中的商品信息 订单支付表:保存订单支付和退款 ...

  5. 视频教程-Prometheus+Grafana企业级监控实战(运维篇)2020年视频教程-Linux

    Prometheus+Grafana企业级监控实战(运维篇)2020年视频教程 资深DevOps工程师,曾经在华为,乐逗游戏工作,目前就职于知名物流公司工作 希望结合工作实践 给大家带来很多干货 周龙 ...

  6. 【系】微信小程序云开发实战坚果商城-扩展篇

    第 5-1 课:扩展篇 目录 开篇 [系]微信小程序云开发实战坚果商城-开篇 基础篇 [系]微信小程序云开发实战坚果商城-弹性盒子 [系]微信小程序云开发实战坚果商城-ES6 简单入门 [系]微信小程 ...

  7. 微信小程序教学第二章(含视频):小程序中级实战教程之预备篇 - 提取util公用方法 |基于最新版1.0开发者工具

    iKcamp官网:http://www.ikcamp.com 访问官网更快阅读全部免费分享课程:<iKcamp出品|全网最新|微信小程序|基于最新版1.0开发者工具之初中级培训教程分享>. ...

  8. 程序员搞事!动手实战优化自己公司线上系统JVM,结果。。。

    Java性能调优都是老生常谈的问题,特别当"糙快猛"的开发模式大行其道时,随着系统访问量的增加.代码的臃肿,各种性能问题便会层出不穷. 比如,下面这些典型的性能问题,你肯定或多或少 ...

  9. 线下活动【西安站】用Leangoo做Scrum敏捷开发实战课(免费)

    Leangoo诚邀您参加 2017<用leangoo做Scrum敏捷开发>实战课!在此实战课上,您不仅可以听到一线资深敏捷顾问带来的敏捷落地实践经验,还可以和众多企业同仁共同探讨敏捷实践过 ...

最新文章

  1. CGAN生成cifar10, cifar100, mnist, fashion_mnist,STL10,Anime图片(pytorch)
  2. 组合模式java怎么获取钥匙_java中组合模式详解和使用方法
  3. python找出文本的位置和替换_python查找文本文档中特定间隔位置的字符并替换
  4. 血淋淋的事实告诉你:你为什么不应该在JS文件中保存敏感信息
  5. LEADTOOLS Multimedia SDK更新:改进RTSP和H.265/H.264的硬件加速
  6. 单进程服务器-非堵塞模式(python版)
  7. 【spring】注解开发和spring整合junit
  8. Fiddler V5中文版
  9. 某超级注入程序的驱动逆向
  10. 科研绘图软件GraphPad Prism教程(三)
  11. Android MVP详解
  12. HTML+CSS小白入门与进阶教程
  13. CleanMyMac X2023Mac电脑空间内存清理工具
  14. 输入一个字符,判断输入的是控制字符、数字、大小写字母还是其他字符,并给出相应提示
  15. KNN 在手写识别中的应用(Java 实现)
  16. 使用jasypt加密配置的时候,报错:DecryptionException: Unable to decrypt
  17. 网络安全需要掌握的知识有哪些?
  18. 关于HtmlCxx对,C++ 解析,编辑,重新生成HTML的
  19. 借助机器学习,他们发现了阴谋论是如何传播的
  20. NDK版本、获取以及和ANDROID对应关系

热门文章

  1. 3Dmax2017版本,如何将物体切换到点和线框模式,后面贴图仍然显示?
  2. LG. Hankson 的趣味题,C语言
  3. asp.net 客户端msgbox用法
  4. PC微信扫描浏览器Cookies?腾讯:目前无法重现问题;华为手机分拆?官方否认;pip 21.0停止支持Python 3.5...
  5. rebar3编译及发布erlang程序
  6. 云计算机的发展对社会的影响,云计算对个人的影响有哪些
  7. 预言机新手 Band Protocol闪亮 登场!
  8. 多彩英文工作汇报总结动态PPT-朴尔PPT
  9. XBee3 zigbee AT命令集
  10. 在genymotion官网下载genymotion-2.6.0-vbox.exe安装完成模拟器不可用的问题解决