2022-10-20 00:24:12    91    0    0

DDD系列-如何判断要不要使用DDD

如何判断要不要使用DDD

当我们介绍业务背景的时候,一定要足够复杂,才可能继续讨论DDD如何解决这种复杂度的。如果是一个掷骰子游戏,那肯定不会用DDD去实现,这种例子只帮你理解一些概念。

这里参考尤达老师的零售项目,整个背景可以用下面一张图表示

1)运营人员通过电脑操作投放售卖机

2)用户通过手机支付的方式购买商品

3)安装人员按照安装单到指定地点安装售卖机

4)商务会去各商场洽谈合作售卖机事宜

5)配送人员必须及时按照配送单补充售卖机商品

 

一个智能零售主要包含以上功能,注意,这里不包含细节,但是所有的角色都在里面,需要讨论每个细节的时候,会再次深入到内部。比如你需要考虑购买问题的时候,用户是使用微信,支付宝,或者硬币支付,或者小程序会员的方式进行购买,再或者,这里搞打折活动......

 

 以及同理,下面是一个简化的在线医院系统,目的是让用户足不出户即可达到看病的目的

这个系统也很复杂,模拟了一套线下看病的流程

1)用户在手机上输入足迹的病情,类似去医院对着窗口说自己感冒嗓子疼

2)助手,类似那个窗口给你挂号的,会告你去什么科室看病,这里直接是指派给一名医生了,简化了一下

3)医生,医生进行接诊操作,类似现实中你坐到了医生的对面,然后就行一些谈话

4)医生给你开局药品清淡,然后你进行线上支付,来购买药品

5)药品配送人员会将这些药品送到你的家门口

 

整个系统是极其复杂的,任何一个环节拿出来都可以构成一个小系统,比如开药的环节,就会引入处方药和非处方药等领域的概念,另外他们也需要处理监管的问题

 

总结,以上两种情况都非常适合使用DDD,理由是他们足够复杂,并且相应的现实世界已经有一套成熟的模型,软件开发人员必须亲自搞懂现实中的这些系统是如何运作的,它们的关系是怎样的,然后将这一套模型映射到我们的软件系统中。

我们通常说代码尽量不要复用,软件模型也是同样的道理,现实生活中有的模型尽量参考,改进,避免盲目创建出一个毫不相干的新模型。



 

 

2022-10-18 23:54:17    307    0    0

DDD系列-聊聊敏捷开发模式的缺点

敏捷开发模式

在我以前的软件开发经验中,很多软件都是基于分层思想+贫血模式设计的(如下图),也许从入行开始就一直是这样,也并没有发现什么问题。

一句话总结以前的开发思路:"表建好了,代码也就写好了"

敏捷研发流程

一般从需求开始(其实是局部的需求,RD往往没有全局信息),产品同学将需求分析完和开发同学进行需求评审,评审完毕后开发同学开始基于需求进行技术方案设计。这时候一般会落到数据库设计,将库表设计完毕。设计完表之后,我们常说的"Dao层"就有了,接下来就进行逐层向上开发,最后封装出一个api接口。

 

这时候你会发现真的是"表建好了,代码也就写好了",数据驱动开发或者说是一种基于数据模型的开发。这种开发模型所有的业务逻辑都堆砌在Model层,其实可能开发者本身都没理解这一层的含义,因为Controller层不能直接穿透到Dao层,所以不得不在Model层继续傻瓜式封装一下dao层

敏捷研发最有价值的产出

由于敏捷开发基本是基于数据驱动开发的,所以它的设计基本就是敏捷需求开发的最有价值的输出了,例如客户下单的需求为例子

敏捷开发的舒适期

这种开发方式在创新项目的前期,非常适合用这种方式,因为首要任务是项目能够活下来,不要被淘汰掉。这种模式就非常适合低成本

快速迭代起来,并且新人这个时候是友好的,从api入口开代码能够一口气看下来,逻辑基本上就明了了。

敏捷开发的阵痛期

如果跨过了舒适期,来到阵痛期,恭喜你,至少项目活下来了。这个时间开发和迭代变得非常困难了,更有甚者,一张表的扩展字段可能达到恐怖的100多个了(笔者曾经所在的公司)。这个时候没有一个新人来了不骂娘的,因为根本不可能是正常人能理解的东西了。整个大项目来到一个大单体时代

1)大单体-拆表:治标不治本,只是解决容量问题,对业务的复杂度是没有贡献的

2)大单体-拆微服务(伪服务):为什么说是伪微服务,把dao层暴露api接口作为一个一个服务,然后让大单体接入,后面介绍DDD会知道,这只是改变了调用方式,根本对减少业务复杂度没有任何贡献,反而因为网络请求增加了复杂度

3)大单体-拆微服务(局部拆分):比如电商中的收件人作为微服务,然后包含了各种冲突,融合,关系等完善的服务。然后用这个新微服务潜入到大单体中。这也是死路一条,后面介绍DDD会知道,它改变了模型(虽然作者可能都不知道隐含的模型),模型的改变必然导致

2022-10-15 23:16:49    98    0    0

DDD系列-背景

其实笔者认为,在软件的发展过程中,业务价值始终是排在第一位的,笔者之前所在的公司一直是敏捷开发模式,问题也基本上朝着单体应用的毛病一步一步演进,但是在期间也创造了大量的业务价值。在演进的过程中,会出现各种问题,更高级的开发模式往往能解决更多的问题,但是这并不大表越高级越好,适合当前业务的模式才是最好的开发模式。

瀑布流开发模式

模式介绍

软件设计有不同的方法,其中之一是瀑布设计法。这种方法包含了一些阶段,业务专家提出一堆需求同业务分析人员进行交流,分析人员基于那些需求来创建模型并将结果传递给开发人员,开发人员根据他们收到的内容开始编码。瀑布设计法中知识只有单一的流向。这种传统方法虽然应用很多年,但是还是有一定的缺陷和局限。主要是业务专家得不到分析人员的反馈信息,分析人员也得不到开发人员的反馈信息。

 

假设要做一个在线售卖机系统,那这个售卖机所有的用例集合起来称为需求,然后严格按照需求-设计-开发-测试一次性搞定。

有的人可能会有疑问,我们现在不就是这样开发的吗,注意,这里的需求不同于我们平时PM说的需求,PM层面的需求是一个一个用例,这里的需求是所有的用例集合。

整个过程可以用下图描述

主要缺点

这种方式的主要缺点是周期长,没有反馈闭环,项目失败的风险极高。而且,这种方式一开始就确定了所有需求,考虑面面俱到,但是这所有的需求中大部分的需求

都没有机会使用。这种方式的项目大部分以失败告终。

主要优点

但是,有一些场景必须用瀑布流开发模式,比如航天飞机,摩天大楼这些中途一旦变更需求,将会造成巨大的成本,所以肯定是需求阶段就已经面面俱到了。

敏捷开发模式

模式介绍

由于瀑布流开发模式失败率太高,另一个方法是敏捷方法学,这些方法不同于瀑布方法的一堆动作,产生背景是预先很难确定所有的需求,特别是需求经常变化的情况。敏捷开发试图解决另一个问题被称为"分析瘫痪",团队成员会因为害怕做出任何设计决定而无济于事。敏捷开发者反对预先设计,相反,他们应用大量的灵活设计。在敏捷设计者的眼中,他们崇尚,天下武功,唯快不破。

还是用售卖机为例子,那这个售卖机中的一个支付支持作为一个需求,就仅仅这一个需求在敏捷者的眼里,也不会一次性开发完成,而是周期迭代方式进行。

敏捷开发的优点非常多,比如团队协作,项目风险控制,市场验证,非常适合创新项目,也是现在很多公司主流的开发模式

主要缺点

敏捷开发也有自己的不足,敏捷开发

2022-05-29 16:22:35    93    0    0

GO语言设计模式 - 构建者模式

背景

假设我们需要实例化一个MySQL对象,在GO语言中,可以使用下面两种方式

逐个参数传递

func NewMySQL(a TypeA,b TypeB,c TypeC) MySQL

在GO语言中,函数是没有默认值的,这就意味着,假设NewMySQL函数有10个参数,所有的使用方必须传递满这是个参数,如果这勉强还能接受

那么,假设不值10个参数,后果可想而知。可以说这种办法几乎不可取,函数签名随着参数的个数变化不断变化

结构体传递

Type Params{
    a TypeA
    b TypeB
    c TypeC
}
func NewMySQL(p Params) MySQL

这种方式比第一种好一点了,至少看起来是不用传递多个参数,函数签名也不用发生改变。这里我们把MySQL的实例化条件复杂一点,结构体层级化,看下使用方视角

opt := Params{
    Endpoint: {
        IP: "xxxx",
        Port: "xxx",
        ....
    },
    Timeout:{
        ReadTimeout: "xxx",
        WriteTimeout: "xxx",
        ConnectTimeout: "xxx",
        ....
    },
    ....
    //更多陌生的选项
}

obj := new NewMySQL(opt);

这种方式对使用者不友好,创建对象时需要知道的细节太多,其次,代码的可读性也差。当然还有一个更致命的缺点,默认值问题

在GO里面变量声明的时候就等于初始值(int是0,string是"",bool为false),举例:当接受到的参数为0时候,不确定是用户有意传递的0还是用户没有传递结构体变量导致的0,程序很难做出判断。

构建者模式

建造者(Builder)模式由产品、抽象建造者、具体建造者、指挥者等 4 个要素构成,现在我们来分析其基本结构和实现方法。

模式的结构

建造者(Builder)模式的主要角色如下。

  1. 产品角色(Product):它是包含多个组成部件的复杂对象,由具体建造者来创建其各个零部件。
  2. 抽象建造者(Builder):它是一个包含创建产品各个子部件的抽象方法的接口,通常还包含一个返回复杂产品的方法
2022-05-26 01:44:26    62    0    0

GO语言设计模式-工厂模式

工厂模式背景

假设一个场景,在一个基本的MVC框架中,视图层承担着渲染数据的职责,假如要实现一个框架的视图层,必须满足

  1. 支持html输出
  2. 支持json格式输出
  3. 支持xml格式输出
    于是,我们可以这样写出一个视图调用的展示程序View
func render(typ string, data interface{}) {
	switch typ {
	case "json":
		bytedata, _ := json.Marshal(data)
		fmt.Println(string(bytedata))
	case "xml":
		bytedata, _ := xml.Marshal(data)
		fmt.Println(string(bytedata))
	//...继续延续其他类型
	default:
		panic("type err")
	}
}

这样做代码比较简单,但是,这样的实现存在很多问题。

  1. 无法灵活地扩展维护,如果再添加一个excel类型的输出,switch分支将变得老长老长
  2. 修改任何一个功能,都需要改动主体代码块
    实际上,这是一种典型的面向过程的编码方式,这种编码方式,不易分工,职责不明确,如果采用面向对象的方式来救场,针对接口编程而不是针对实现编程。因为这样的视图层在我们的前辈也一样会碰到,只不过他们把这种问题解决掉了,留给了我们后人一些很经典的解决问题的设计模式。接下面,使用设计模式中的工厂模式解决这个问题

工厂模式分类

工厂模式主要是为创建对象提供了接口。工厂模式按照《Java与模式》中的提法分为三类:

  1. 简单工厂模式(Simple Factory)
  2. 工厂方法模式(Factory Method)
  3. 抽象工厂模式(Abstract Factory)
    这三种模式从上到下逐步抽象,并且更具一般性。还有一种分类法,就是将简单工厂模式看为工厂方法模式的一种特例,两个归为一类。下面是使用工厂模式的两种情况:
    1.在编码时不能预见需要创建哪种类的实例。
    2.系统不应依赖于产品类实例如何被创建、组合和表达的细节

简单工厂模式

其实严格意义上来说,这并不算一种设计模式,或者可以说这是一种很好的封装方式。比如,可以解决我们上面的问题,我们可以做出以下优化

  1. 为每个View的实现约定一个接口,这个接口定义每个子类必须实现特定的方法
  2. 变化的动画抽到一个地方,编写一个专门管理视
2022-05-22 18:01:13    65    0    0

GO语言设计模式-单例模式

什么是单例模式

单例模式是一种常用的软件设计模式,其定义是单例对象的类只能允许一个实例存在。

许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。比如在数据库连接中,我们只希望进程内所有的访问DB都是通过一个数据库连接进行的。

普通实现-并发不work

单例模式第一种实现方式

// 单例模式
package design

import "fmt"

type singleton struct {
	Name string
}

var instance *singleton

func GetInstance() *singleton {
	if instance == nil {
		fmt.Println("init") //这里每实例化一次,输出init
		instance = &singleton{}
	}
	return instance
}

这种实现方式在没有并发的情况下是正常工作的

package main

import (
	"learn/design"
)

func main() {
	//串行执行正常
	design.GetInstance() //输出init
	design.GetInstance() //不输出init
}

但是一旦出现并发的情况,这种实现方式是就不能正常工作了

package main

import (
	"learn/design"
	"sync"
)

func main() {
	//并发不正常
	var wg sync.WaitGroup
	for i := 0; i < 10; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			design.GetInstance() //每一次,均输出init
		}()
	}
	wg.Wait()
}

once 方式实现-并发work

上面实现的第一种方式在并发情况下并不能正常work,这里使用once方式支持并发执行一次,一般比较常用的就是这种方式了

// 单例模式
package design

import (
	"fmt"
	"sync"
)

type singleton struct {
	Name string
}

var instance *singleton
va
2018-08-26 20:58:30    517    0    0

关于URL编码常见的三个错误

常见错误1

对URL保留字符不进行编码直接作为参数传递
这一类错误在于没有对RFC中规定的保留字符进行编码,而错误的认为只有中文才需要编码。RFC中保留的字符也不能作为参数在URL中直接传递,需要进行编码,否者将会出现意想不到的结果。
部分转换规则如下:

空格 ! # $ % + @ : = ?
%20 %21 %23 %24 %25 %2B %40 %3A %3D %3F

常见错误2

两次urldecode导致+号变成空格
1. 生成一个有+号的字符串'abc+d',然后使用urlencode编码

  1. <?php
  2. $str = 'abc+d';//希望传递的字符串
  3. $param = urlencode($str);//采用urlencode编码
  4. $url = "http://127.0.0.1:80001/test.php?param="$param;//生成访问链接

生成的url打印出来是
http://127.0.0.1:8001/test.php?param=abc%2Bd
2. 使用浏览器访问url,写一个程序获取这个参数

  1. <?php
  2. $param = urldecode($_GET['param']);
  3. var_dump($param);

结果收到的参数是'abc d',可以发现,+号被替换成了空格

错误原因:在使用浏览器访问url链接的时候,浏览器会自动帮我们进行urldecode,这个时候php接收到的已经是'abc +d',如果再使用PHP进行urldecode一次,那么+号会被解析成空格

常见错误3

对url编码和解码使用不用版本的RFC
这一类错误经常出现在不同系统间的交互中,同样也是+号的问题。在RFC3896协议中,空格编码为%20,而在,在PHP中有两个函数实现。

RFC 编码函数 解码函数 空格编码 +号解码
RFC 3986 rawurlencode rawurldecode '+' '
2018-06-20 12:47:18    474    0    0

最大子数组问题求解

问题描述

问题:输入一个整型数组,数据元素有正数也有负数,求元素组合成连续子数组之和最大的子数组。

描述:输入的数组为1, -2, 3, 10, -4, 7, 2, -5,最大和的连续子数组为3, 10, -4, 7, 2,其最大和为18。

暴力求解法

第一种解法最简单,最暴力,最容易理解的一种方法。这种解法的思想就是,从第一个元素开始,然后和后面的所有元素组合开有没有可能形成最大值。代码很简单

  1. #include<stdio.h>
  2. #define MAX_LEN 8
  3. int main(){
  4. int arr[MAX_LEN] = {1, -2, 3, 10, -4, 7, 2, -5};
  5. int i,j;
  6. int sum = 0;
  7. for(i=0; i < MAX_LEN; i++){
  8. int _tmpSum = arr[i];
  9. for(j=i+1; j < MAX_LEN; j++){
  10. _tmpSum += arr[j];
  11. if(_tmpSum > sum){
  12. sum = _tmpSum;
  13. }
  14. }
  15. }
  16. printf("%d", sum);
  17. }

这种算法的时间复杂度为O(n^2)。所以这种方法虽然简单,单一般不采用。

分治求解法

计算机领域经常使用的一种求解思想,分而治之。想想二分查找,一半一半得淘汰,这样效率会提高很多。我们同样也可以用这种思路求解最大子数组问题。假定我们要寻找数组A[low .. high]的最大子数组,使用分治技术我们将A划分为两个等规模子数组。首先找到数组A的中央位置,比如mid,然后分解成两个子数组A[low .. mid]和A[mid+1 .. high]。这样如果存在一个最大的子元素系列A[i .. j]那么它一定满足下面三种情况之一

  1. 1. 完全位于子数组A[low .. mid]中,此时low<=i<=j<=mid
  2. 2. 完全位于子数组A[mid+1
2018-01-02 23:50:43    2510    0    0

PHP7中Protobuf的安装使用

写这篇文章的缘由是最近在关注RPC框架序列化的一些原理。但是在安装Protobuf的时候,发现网上的教程都太老了,加上目前Protobuf官方已经支持PHP了,不再需要使用第三方插件了。

关于序列化和反序列化

在PRC框架中,数据的传输发生在客户端和服务端,而我们知道基于TCP协议最终传输的是二进制的0/1序列。所以,基于TCP传输协议的RPC服务自然也需要将数据结构转换成二进制,和二进制转换成数据结构的功能。所以,原则上,基于网络的数据传输只能传输二进制表示的字符串

  1. 序列化:将数据结构或对象转换成二进制串的过程
  2. 反序列化:将在序列化过程中所生成的二进制串转换成数据结构或者对象的过程

但是,传输的二进制序列是完全没有意义的,除非有一套解析二进制串的协议。没错,这个协议可以就是目前我们大家熟知的xml,json协议。当然。除了这两者,还有其他的的序列化和反序列化协议。

几种常见的序列化和反序列协议

XML

XML是一种常用的序列化和反序列化协议,具有跨机器,跨语言等优点。XML历史悠久,其1.0版本早在1998年就形成标准,并被广泛使用至今。XML的格式如下

  1. <note>
  2. <to>George</to>
  3. <from>John</from>
  4. <msg>Don't forget the meeting!</msg>
  5. </note>

可以看出这种序列化协议的优点是可读性和易调试行。但是这种协议的缺点也很明显:额外空间开销大,序列化之后的数据量剧增。

JSON

JSON是一种轻量级的数据交换格式。采用完全独立于编程语言的文本格式来存储和表示数据。如果你跟浏览器Web应用打交道的话,那么JSON一定是应用最广泛的,它的数据格式如下

  1. {
  2. "to":"George",
  3. "from":"John",
  4. "msg":"Don't forget the meeting!"
  5. }

这种序列化协议有很大的优势:
1. 这样表示非常符合工程师对对象的理解,尤其是js工程师
2. 和xml一样,可读性强
3. 和xml相比,更加节省空间,解析速度更快

2017-12-20 23:58:12    707    0    0

DNS原理浅析

DNS是什么

DNS(Domain Name System,域名系统)是整个互联网的核心组件,负责域名到IP地址的转换工作。比如说,你要访问www.baidu.com就得先通过DNS服务将www.baidu.com翻译成IP地址115.239.211.112。有的人可能会想,这个不是很简单吗,写一个TXT文本文件保持映射关系不就可以了吗。没错,早期的DNS就是这样的。

DNS的历史

在20世纪70年代,当时的计算机网络只有几百台主机。他们确实是采用一个名为HOSTS.TXT的文件容纳所有的主机信息,这个文件包含了主机名字——地址的映射关系(你可以想象Linux的/etc/hosts)。这样维持了一段时间后,随着网络规模的爆发,暴露了以下问题

  1. 1. 流量和负载的爆发增长,一台主机根本扛不住
  2. 2. 名字冲突问题,当时注册名字是比较随意的
  3. 3. 一致性问题,当时变更是通过邮件形式通知的,然后变更配置中心

毫无疑问,HOSTS.TXT方案最终以失败告终。但是当时的人继续探索,他们希望创造出一个系统,以解决单一主机表系统本身所固有的问题。所以今天我们看到了及其复杂的DNS系统

DNS设计

被上述HOSTS.TXT文件坑惨了之后,DNS被设计成了一个分布式数据库,它允许整个数据库的各个部分进行本地控制。同时,整个网络也能通过客户-服务器的方式访问每个部分的数据。首先为了解决名字冲突,名字被设计成了树状结构,所以我们现在的域名看起来是非常规范的。

域名空间

DNS分布式数据库是以域名为索引的,每个域名实际上就是一棵很大的逆向树中的路径。这棵逆向树就称为域名空间。这棵树的结构如下:

image

域名空间:这整个树就构成了域名空间,当然,现实生活中可能还有子树

域名:树中的一个节点到根节点对的顺序连接表示域名。并且用“.”分割路径上出现的名字。比如map节点对应的域名为map.baidu.com
(我们忽略了路上的根的名字)

:一个域就是这个域名空间(整棵树)中的一个子树。域的名字就是子树根节点到树根的域名。图中的灰色圆框就是baidu.com域。百度掌管着这个域

这样设计一个很大的好处就是域名分散管理,像ba

2/4