针对现在的大部分企业,都是网站,后台服务,APP的模式。要是谁家开科技公司特么没一个APP估计才是怪事了,这时候我们一定会面临的问题就是页面和APP的需求不一致,对后台服务提供的数据要求不一致。当然还有需求不断变更这个永恒的话题, 在我们不断开发新功能的时候,却永远都要维护以前的版本功能可用,就像是腾讯的QQ一样,你至今拿起古董级别的QQ,它依旧是可用的。那么我们如何解决这样的问题呢?
我简单的就我目前工作的经验简单谈下此问题,也简单的记录下,期望今后如果创业的时候还能翻起,用于给予我部分提点。
目前的软件走的速度就是谁先上线谁得天下的时代,所以大部分的公司都会以快作为第一要素,996是常事。但是又不面因为快给后续的开发带来很多的兼容问题,就好比V1版本的结构定义的数据处理模式可能是结构{A,B,C},但是目前需求要求必须加一个字段变为{A,B,C,D},那么如何兼容以前的接口呢,可能这时候最方便的做法就是再加一个接口给新的APP或者网站使用,然后再重写一份代码。那么有什么好的解决办法呢?
理想状态应该是,一个APP或者网站,它是需要什么数据就拿什么数据的状态,而不管服务器会提供什么数据,当然提供的数据它都可以拿,但是拿多少,拿什么字段,应该是客户段决定的。这样就能很好的解决APP的兼容旧借口,旧的APP只需要ABC三个字段,那么在发布出去的时候它就拿ABC三个字段,之后的需求变更增加字段也不会影响到旧版本的APP的使用,因为它还是拿原来的三个字段,数据还是保持原来的格式。而新版本的APP,它也可以根据自己的需求多拿D字段的内容。
那么有什么好的解决方案呢?
其实呢,Facebook有一套很好的解决方案,就是graphql,可能很多人知道reset api,但是不一定知道graphql,因为我们目前的开发技术选型Go, 所以针对此类问题,我们选型在:graphql
项目地址:https://github.com/graphql-go/graphql
它属于一种模式定于语言吧,DSL, 同样支持int,bool,string,list这类基本数据类型,可以通过模式的定义形成数据结构。
它可以通过定义query 作为查询,定义Mutation用作更新数据操作,同时定义数据结构时可以加上对数据字段的描述,服务启动后会自动生成doc,这样在APP端开发时即可通过直接访问服务器便可以查看相关的字段的定义文档,免去了部分的沟通时间。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
graphql.NewSchema( graphql.SchemaConfig{ Query: graphql.NewObject( graphql.ObjectConfig{ Name: "Query", Description: "查询所有User相关的信息", Fields: graphql.Fields{ "user": &graphql.Field{ Type: types.GLUser, Args: graphql.FieldConfigArgument{ "userid": &graphql.ArgumentConfig{ Type: graphql.NewNonNull(graphql.String), Description: "通过user id获得所有user关联信息", }, }, Resolve: queryUser, }, }, }), |
这样我们即可定义相关的查询接口,APP端即可通过userid 用户ID查询用户的相关信息,并且我们还可以通过不断的增加Args来扩充我们的字段,提供更多的新功能给新版本的app使用,同时依旧兼容旧版本的APP。同时通过Description 又很好的描述了字段的定义,提供了相应的开发文档。
针对变更部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
Mutation: graphql.NewObject( graphql.ObjectConfig{ Name: "Mutation", Description: "更新用户信息" Fields: graphql.Fields{ "updateUser": &graphql.Field{ Type: types.GLStructUser, Args: graphql.FieldConfigArgument{ "userid": &graphql.ArgumentConfig{ Type: graphql.String, Description: "用户编号", }, "phone": &graphql.ArgumentConfig{ Type: graphql.String, Description: "用户手机号", }, "nickname": &graphql.ArgumentConfig{ Type: graphql.String, Description: "用户昵称", }, }, Resolve: updateUser, }, }, }), |
这样我们便可以定义相关的修改和更新接口,同样的参数可以不断的变更,我们通过resolve定义的方法处理用户的请求,更新用户的数据。
那么返回什么数据呢?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
var GLUserConfig = graphql.ObjectConfig{ Name: "User", Fields: graphql.Fields{ "id": &graphql.Field{ Type: graphql.String, Description: "用户编号", Resolve: id.IDResolve, }, "nickname": &graphql.Field{ Type: graphql.String, Description: "用户昵称", }, "avatar": &graphql.Field{ Type: graphql.String, Description: "头像信息“, "phone": &graphql.Field{ Type: graphql.String, Description: "电话号码", }, "sex": &graphql.Field{ Type: GLUserSex, Description: "性别", }, }, |
我们可以通过定义相应的返回结构,返回相应的数据即可,当然如果我们有新的需求,我们也可以增加相应的字段作为返回数据。
那么关键点在于graphql定义了这些模式后,它的查询语言了,无论是查询或者查询返回的数据,你都可以根据你的需求获取相应的字段用作自己的APP服务。
定义完了数据结构,我们就可以通过http的请求用语请求所有想要的业务数据
1. 可能我们某次查询只需要昵称和ID,那么我们就可以这样取我们需要的数据
1 2 3 4 5 6 |
{ user(id: 3500401) { id, nickname, } } |
2. 如果某次我们的新版APP需要更多的数据,那么我们就可以增加相应的字段,取更多APP需要的数据,而接口缺不需要做任何变更:
1 2 3 4 5 6 7 8 |
{ user(id: 3500401) { id, nickname, phone, sex, } } |
同理修改用户数据的接口也是一样的用法。
总结:
在增加新需求的时候,旧的接口可重用,但是又不影响旧的业务,同时支持新的业务。
在当前的业务不断变化的软件后台服务开发中,我们就可以通业务的分离,不断的形成微服务的形式,通过微服务的互相协作,应对新的需求,同时也保证旧版本的服务,支持更多的新服务和业务需求。
(有说得不到位的不对的,欢迎拍砖。如有转载,请增加原文链接,署名原作者。