关于设计思想:分销商城系统模块分销商城系统模块需求分析OctSHop

在互联网分享经济下,分销商城的经营模式深受宽广企业和商家的青眼,当初比拟大点的平台都能够反对多商户入驻的多店铺分销。分销者无需囤货,通过分享商品就能够取得分佣返利,无需投资就能够轻松获取挪动社交的守业红利。 分销商城零碎模块,分销商城零碎模块需要剖析,分销商城零碎模块次要有 性能列表:https://pc.opencodetiger.com/... 一、分销商城零碎模块次要有1、销售分销模块 分销商城零碎的销售分销模块包含:产品的规格价格治理、订单零碎、用户等级与信用、商品发货快递跟踪,退款业务,售后服务等。以市场为主导,以订单,在线领取为外围,来治理分销商城零碎整个平台的销售业务。 2、洽购进货模块 分销商城零碎能够帮企业与商家治理洽购,管控人员并从洽购数量、洽购订单下达、供应商确认、订单到货测验入库全过程。进步企业经营效率,节俭企业老本。 3、库存零碎模块分销商城库存零碎模块能够很好的组织仓库管理人员对库存物品的入库、出库、挪动和盘点等操作进行全面标准,对于库存数量,商品品类,规格信息都能够实时共享到商城中。对于无库存的商品商城则会主动下架商品,并提醒商家须要洽购商品。 4、财务结算模块分销商城零碎财务结算模块能够很好的治理商城零碎用户的余额与信用,对用户的等级、信用、余额、收支进行时实监控。用户也能够依据在平台的沉闷查看各种用户状态信息。财务结算模块还有一个另外重要的性能就是商家订单结算。主动统计商家实现的订单,并按工夫主动生成结算单。 5、推广分销模块分销商城零碎推广分销模块能够主动为用户生成推广分销二维码,推广分享链接等,用户通过朋友圈、微信群分享推广二维码,推广链接等取得分销返佣。零碎主动计算分销者的分销收益,并且能够自在提现到本人账号。二、分销商城零碎模块需要剖析1、用户需要剖析次要是包含:注册,绑定手机、用户信息管理,余额,收支,订单治理等性能的实现,平台可通过剖析用户相干数据有针对性的进行促销推送与治理保护用户,这对于平台的前期经营很重要。 2、消费者的购物需要分销商城零碎能够十分不便的治理与展现商品的类目以及类目标各种属性,不便买家疾速的找到本人想要的商品,同时商品店铺的搜寻性能也是十分重要的,能够帮忙消费者比照同类产品,有助于买家的购买决策。商品评估,购物车,下单领取,退款,物流跟踪查问,订单状态治理等等都是必不可少的。 3、商家或企业的需要商家的后盾管理系统次要的需要有:商家信息统计,交易订单治理,扫码收单治理,发票治理,商品治理,店铺治理,运费治理,礼品治理,积分治理,优惠券治理,限时抢治理,拼团治理,流动治理,评估治理,售后服务治理,投诉治理,财务结算治理,生产告诉治理,系统管理等等。

March 9, 2022 · 1 min · jiezi

红包系统的一些思考

对红包系统的一些个人想法,有问题欢迎补充交流 一个抢红包系统如何实现?正常流程: 发红包-> 抢红包 -> 拆红包 发红包:逻辑简单,创建一个红包,无高并发的情形。抢红包:逻辑复杂,高并发更新情形。复杂点:高并发情况下,剩余红包数量如何原子性的维护? 高并发情况下,红包数据如何落盘?接口性能如何保证?拆红包:查询抢到红包的金额,高并发查询,并不复杂。 抢红包特点: 1 并发请求高。(红包场景的特点-->并发抢)2 系统安全性要求高。(不能出现钱金额超发,红包数量超发的情况)并发请求高: 硬件上不提接口性能上,使用内存操作替代实时的DB事务操作,另外请求内部尽量避免锁抢占操作。主要流程可以如下所示: 设计想法:高并发请求,该接口设计一定要轻量。抢红包阶段,只需要明确是否抢到红包,红包金额等在该接口不涉及。用户抢到红包最重要的前提有三个。 a. 根据缓存判断是否存在库存 b. 缓存中库存数量自减成功 c. 发送异步mq消息成功正常逻辑只会请求redis以及发送一个mq,十分轻量。 接口的稳定性强依赖mq的稳定性; 原因是redis挂掉的情况下,其实可以走正常sql落盘数据库的方式,只更新库存数量即可(这里又并发强锁的问题,比较耗时)。 计算红包大小,以及插入抢红包记录,更新红包记录放在异步操作,原因是这样的操作比较重,如果放在同步里面,在高并发情况下,增加了服务异常的概率。放在mq的好处,最重要的是可以以固定速率消费,避免了高并发的场景。如果出现队列中消息过多的情况。 只有两个原因:1. 消费机器数目过少。 2. 存储数据库更新操作出现瓶颈。 对于第一个,暴力简单,增加消费机器的数量。 第二个来说比较麻烦,底层数据分库分表,增加更新查询速率。 问题:发送mq失败怎么办? 重试缓存自增,返回失败问题:返回成功后,服务宕机怎么办? 已经发送mq成功,这里需要mq来保证,在宕机情况下,消息也不会丢失。 另外就算mq不能恢复,也可以根据发送mq成功的日志进行后续的补偿逻辑,也不会造成多发的情况。 问题:存在这样的场景,读取到红包剩余N个,然后这里按照抢到红包的操作,将红包数量更新到N-1个,在读取和更新期间,其他请求对库存发生了更新操作怎么办?即将红包数量更新到N-1的时候,库存已经是N-1了,这里会出现少减的情况。 方案一:redis的watch命令。通过redis的watch命令,可以知道更新这个数字的时候,这个数字有没有被改过。 这样设计有个缺点就是红包数量比较多,另外同时多人抢红包情况下,会出现多次如下的循环 (如何避免这种情况?) 思考了一下,其实多次这样循环对性能影响不大,而且理论上不会出现多次循环,最多可能两三次。 方案二通过sql语句, 更新库存将库存作为条件放入where后 update table set N=N-1 where N=N依旧避免不了出现多次 获取红包数量 <-> 更新失败 的循环。 如有其他内容,欢迎补充~

October 17, 2019 · 1 min · jiezi

Java编程方法论响应式RxJava与代码设计实战序

原文链接:《Java编程方法论:响应式RxJava与代码设计实战》序,来自于微信公众号:次灵均阁正文内容在《2019 一月的InfoQ 架构和设计趋势报告》1中,响应式编程(Reactive Programming)和函数式(Functional Programing)仍旧编列在第一季度(Q1)的 Early Adopters(早期采纳者) 中。尽管这仅是一家之言,然而不少的开发人员逐渐意识到 Reactive 之风俨然吹起。也许您的生产系统尚未出现 Reactive 的身影,不过您可能听说过 Spring WebFlux 或 Netflix Hystrix 等开源框架。笔者曾请教过 Pivotal(Spring 母公司)布道师 Josh Long2:”Spring 技术栈未来的重心是否要布局在 Reactive 之上?“。对方的答复是:”没错,Reactive 是未来趋势。“。同时,越来越多的开源项目开始签署 Reactive 宣言(The Reactive Manifesto)3,并喊出 ”Web Are Reactive“ 的口号。 或许开源界的种种举动无法说服您向 Reactive 的”港湾“中停靠,不过 Java 9 Flow API4 的引入,又给业界注入了一剂强心针。不难看出,无论是 Java API,还是 Java 框架均走向了 Reactive 编程模型的道路,这并非是一种巧合。 通常,人们谈到的 Reactive 可与 Reactive 编程划上等号,以”非阻塞(Non-Blocking)“和”异步(Asynchronous)“的特性并述,数据结构与操作相辅相成。Reactive 涉及函数式和并发两种编程模型,前者关注语法特性,后者强调执行效率。简言之,Reactive 编程的意图在于 ”Less Code,More Efficient“。除此之外,个人认为 Reactive 更大的价值在于统一 Java 并发编程模型,使得同步和异步的实现代码无异,同时做到 Java 编程风格与其他编程语言更好地融合,或许您也发现 Java 与 JS 在 Reactive 方面并不存在本质区别。纵观 Java 在 Reactive 编程上的发展而看,其特性更新可谓是步步为营,如履薄冰。尽管 Java 线程 API Thread 与 Runnable 就已具备异步以及非阻塞的能力,然而同步和异步编程的模式并不统一,并且理解 Thread API 的细节和管理线程生命周期的成本均由开发人员概括承受。虽然 Java 5 引入 J.U.C 框架(Java 并发框架)之后, ExecutorService 实现减轻了以上负担。不过开发人员仍需关注 ExecutorService 实现细节,比如怎样合理地设置线程池空间以及阻塞队列又成为新的挑战。为此,Java 7 又引入 ForkJoinPool API,不过此时的J.U.C 框架与 Reactive 理念仍存在距离,即使是线程安全的数据结构,也并不具备并行计算的能力,如:集合并行排序,同时操作集合的手段也相当的贫瘠,缺少类似 Map/Reduce 等操作。不过这些困难只是暂时的,终究被 Java 8 ”救赎“。Stream API 的出现不但具备数据操作在串行和并行间自由切换的能力,如 sequential() 以及 parallel() 方法,而且淡化了并发的特性,如 sorted() 方法即可能是传统排序,亦或是并行排序。相同的设计哲学也体现在 Java Reactive 实现框架中,如同书中提及的 RxJava5 API io.reactivex.Observable 。统一编程模型只是 Stream 其中设计目标之一,它结合 Lambda 语法特性,虽然提供了数量可观的操作方法,如 flatMap() 等,然而无论对比 RxJava,还是 Reactor6 ,Stream 操作方法却又相形见绌。值得一提的是,这些操作方法在 Reactive 的术语中称之为操作符(Operators)。当然框架内建的操作符的多与寡,并非判断其是否为 Reactive 实现的依据。其中决定性因素在于数据必须来源于发布方(生产者)的”推送(Push)“,而非消费端的”拉取(Pull)“。显然,Stream 属于消费端已就绪(Ready)的数据集合,并不存在其他数据推送源。不过 JVM 语言早期的 Reactive 定义处于模糊地带,如 RxJava API 属于观察者模式(Observer Pattern)7的扩展,而非迭代器(Iterator Pattern)模式8的实现。而 Reactor 的实现则拥抱 Reactive Streams 规范9 ,该规范消费端对于数据的操作是被动的处理,而非主动的索。换言之,数据的到达存在着不确定性10。当推送的数据无法得到消费端及时效应时,Reactive 框架必须提供背压(Backpressure)11实现,确保消费端拥有”拒绝的权利(cancel)”。在此理论基础上,Reactive Streams 规范定义了一套抽象的 API,作为 Java 9 java.util.concurrent.Flow API 的顶层设计。不过关于操作符的部分,该规范似乎不太关心,这也是为什么 RxJava 和 Reactor 均称自身为 Reactive 扩展框架的原因,同时两者在 API 级别提供多种调度器(Schedulers)12实现,适配不同并发场景提供。尽管 Reactive 定义在不同的阵营之间存在差异,援引本人在《Reactive-Programming-一种技术-各自表述》13文中的总结: ...

June 20, 2019 · 3 min · jiezi

工作流表结构的设计

1. 关于工作流工作流是由不同的人做出的一系列决策,根据定义和可重复的过程,确定其中一个人所做出的特定请求会发生什么。 2. 工作流过程(Process)的配置和用户在我们可以在此工作流引擎数据库中设计任何其他内容之前,我们首先需要定义确切构成流程的内容,以及我们的用户是谁以及哪些人可以修改流程本身。 Process (流程表) 设计可能包含的字段: 名称描述类型ididintegername流程标识string该表虽然非常简单,但却是其余设计的中心参考点; 此数据库中的大多数表(例如,状态(State),转换(Transition),请求(Request)等)都需要直接或间接地与此表相关联。 User (用户) 设计在不同系统中设计可不一样 3. 工作流状态(State)和转换(Transition)的设计在一次工作流程中会设计到不同的状态(state), 状态之间的流转需要不同的程序, 我们将之命名为转换(transition) 状态(State)的设计工作流中的不同状态各有不同的属性类型, 在此总结出几种常见的状态类型枚举: 开始(start):每个进程只应该一个。此状态是创建新请求时所处的状态。正常(normal):没有特殊名称的常规状态。完成(complete):表示此状态下的任何请求已正常完成的状态。拒绝(denied):表示此状态下的任何请求已被拒绝的状态(例如,从未开始且不会被处理)。已取消(cancelled):表示此状态下的任何请求已被取消的状态(例如,工作已开始但尚未完成)。一个状态在一次流程中是唯一的,每个状态都有名称,描述和类型。 名称描述类型ididintegerprocess_id所属流程的idintegername状态名称stringstate_type状态类型stringdescription状态描述string转换(Transition)的设计流程中不同的状态之间存在状态的转换(Transition)转换在进程下也是唯一的,标记着从一个状态到另一个状态的过程, 因此转换由主键,进程ID,当前状态和下一个状态组成: 名称描述类型ididintegerprocess_id所属流程的idintegercurrent_state_id当前状态idintegernext_state_id下一状态idinteger4. 工作流操作动作(Action)和后续事件(Activity)的设计状态(State)之间的流转我们称之为转换(Transition), 那么促成这个转换发生的动作和转换发生后的后续影响事件.我们成为: 动作(Action): 用户在某个状态节点可操作的行为活动(Activity): 转换到状态节点后产生的事件动作(Action)的设计同样的不同的动作也各有不同的属性类型, 在此总结出几种常见的动作类型枚举: 批准(approve):操作人将请求应移至下一个状态。拒绝(deny):操作人将请求应移至上一个状态。取消(cancel):操作人将请求应在此过程中移至“已取消”状态。重新启动(restart):操作人将将请求移回到进程中的“开始”状态。解决(resolve):操作人将将请求一直移动到Completed状态。名称描述类型ididintegerprocess_id所属流程的idintegeraction_type操作类型stringname操作名称stringdescription操作描述string这里需要提及的一点是,现实工作中往往会存在着一次的审批需要多人协作上下级协作后才会生效的情景,或者是多个不同动作可以达到同样的效果.所以设计中是允许多个动作对一次转换进行操作的, 即转换(Transition)和动作(Action)之间是多对多的, 因此:引入中间表 TransitionAction 名称描述类型transition_idtransition_idintegeraction_idaction_idinteger此外, 活动(Activity)是标记转换到状态节点后产生的事件.可以是当工作进入到了某个状态, 也可以是完成了从某个状态到某个状态的转换触发的. 活动(Activity)的设计活动(Activity)本身的设计和动作(Action)结构类似, 不同的是在不同系统中Activity是可以被多态关联到不同的动作事件的, 在这就不引申出去讲. 回到事件本身, 可以是由state触发的,也可以是Transition触发的, 他们之间亦是多对多的关系 设计表: 略 5. 工作流请求(Request)的设计在我们的工作流引擎中,将请求定义为:在某一个流程下由某个用户发起的一次申请, 请求具有以下基本部分: 基本信息:标题,创建日期,创建用户和当前状态ID。数据:与单个请求相关的可变数据集。利益相关者:一组将接收有关请求的定期更新的用户。文件:与单个请求相关的任何文件。备注:用户输入的与个人请求相关的任何注释。请求操作:可以在请求的任何给定时间执行的操作请求(Request)的设计请求是在流程下的一次操作所以表的设计可能包含: 名称描述类型ididintegerprocess_id关联流程的idintegertitle请求的标题stringuser_id请求的用户integercurrent_state_id请求的用户integerrequested_at请求的时间datetime在不同的工作流程下, 请求需要发送的数据可能都不一样, 所以针对请求数据(RequestData)也做了一层包装: 名称描述类型ididintegerrequest_id所属请求的idintegerkey数据的keystringvalue数据的valuestring一次的请求也可能要包含通知的对象(User).利益相关者(RequestStakeHolder) 名称描述类型request_id所属请求的idintegeruser_id所属用户的idinteger当然一次完整的工作流程不只包含了这些, 它可能会有关联的文件(Attach), 备注(Note), 这些都可以用基础的附件,备注对象多态关联到各个步骤下所需要的地方去. 6. 工作流请求操作(RequestAction)的设计到目前为止,我们建立的所有基础设施都已经基本完成。最后,我们可以构建模式的最后一部分:Request Actions表。 请求操作(RequestAction) 的设计请求操作(RequestAction) 我们现在拥有用户可以执行的操作来调用Transitions, 当然请求是不能执行任何操作的, 只执行我们配置的范围内的动作. 让我们先看看RequestActions表的模式,然后展示它将如何实际使用: 名称描述类型ididintegerrequest_id所属请求的idintegeraction_id所属操作的idintegertransition_id所属转换的idintegeris_active是否可执行booleanis_complete是否已完成boolean我们是如何来使用request_action的呢 当请求进入状态时,我们从该状态获得所有符合的转换。对于那些Transitions中的每个Action,我们构建出RequestAction对象,且is_active = true,is_completed = false。用户可以随时提交动作。每个提交的Action都包含一个RequestID和一个UserID。提交操作时,我们检查指定请求的RequestActions。如果提交的Action与其中一个(is_active = true)的活动RequestActions匹配,设置 is_active = false 和 is_completed = true。将提交的操作标记为已完成后,我们将检查该请求(Request)中该转换(Transition)的所有操作(RequestAction)。如果所有RequestActions都标记为已完成,那么我们将禁用所有剩余的操作(通过设置is_active = false,例如,未匹配的Transitions的所有操作)。实例演示:工作流有状态(State): A(开始), B(同意), C(拒绝) ...

May 13, 2019 · 1 min · jiezi

聊聊开发中幂等性问题

原文地址:聊聊开发中幂等性问题幂等 (idempotence) 的概念幂等的数学概念幂等是源于一种数学概念。其主要有两个定义如果在一元运算中,x 为某集合中的任意数,如果满足 f(x) = f(f(x)) ,那么该 f 运算具有幂等性,比如绝对值运算 abs(a) = abs(abs(a)) 就是幂等性函数。如果在二元运算中,x 为某集合中的任意数,如果满足 f(x,x) = x,前提是 f 运算的两个参数均为 x,那么我们称 f 运算也有幂等性,比如求大值函数 max(x,x) = x 就是幂等性函数。幂等性在开发中的概念在数学中幂等的概念或许比较抽象,但是在开发中幂等性是极为重要的。简单来说,对于同一个系统,在同样条件下,一次请求和重复多次请求对资源的影响是一致的,就称该操作为幂等的。比如说如果有一个接口是幂等的,当传入相同条件时,其效果必须是相同的。特别是对于现在分布式系统下的 RPC 或者 Restful 接口互相调用的情况下,很容易出现由于网络错误等等各种原因导致调用的时候出现异常而需要重试,这时候就必须保证接口的幂等性,否则重试的结果将与第一次调用的结果不同,如果有个接口的调用链 A->B->C->D->E,在 D->E 这一步发生异常重试后返回了错误的结果,A,B,C也会受到影响,这将会是灾难性的。在生活中常见的一些要求幂等性的例子:博客系统同一个用户对同一个文章点赞,即使这人单身30年手速疯狂按点赞,那么实际上也只能给这个文章 +1 赞在微信支付的时候,一笔订单应当只能扣一次钱,那么无论是网络问题或者bug等而重新付款,都只应该扣一次钱幂等性与并发安全在查阅网络资料的时候,我看到许多文章把幂等性和并发安全的问题有些混淆了。幂等性是系统接口对外的一种承诺,而不是实现,承诺多次相同的操作的结果都会是一样的。而并发安全问题是当多个线程同时对同一个资源操作时,由于操作顺序等原因导致结果不正确。这两个实际上是完全独立的两个问题,比如说同一笔订单即使你不停的提交支付,如果扣除了多次钱,就说明该操作不幂等。而有多笔订单同时进行支付,最后扣除金额不是这多笔金额的总和,那么说明该操作有并发安全问题。所以幂等性和并发安全是完全两个维度的问题,要分开讨论解决。我在一些讨论幂等性的文章中看到中给出的解决方案为‘悲观锁’和‘乐观锁’,这两个方案可以很好的解决并发问题,但是却不应该是幂等性问题的解决方案,特别是悲观锁是用于防止多个线程同时修改一个资源的。倒是乐观锁的版本号机制可以勉强以 token 或者状态标识 作为版本号来实现幂等性(下文解释token 和状态标识),勉强说的过去。所以说幂等性与并发安全是不同的,在本文就只讨论幂等性的问题,对于并发安全问题不做讨论Http 协议与幂等性如果把操作按照功能分类,那就是增删改查四种,在 http 协议中则表现为 Get、Post、Put、Delete 四种。查询操作 (Get)Get 方法用于获取资源,不应当对系统资源进行改变,所以是幂等的。注意这里的幂等提现在对系统资源的改变,而不是返回数据的结果,即使返回结果不相同但是该操作本身没有副作用,所以幂等。删除操作 (Delete)Delete 方法用于删除资源,虽然改变了系统资源,但是第一次和第N次删除操作对系统的作用是相同的,所以是幂等的。比如要删除一个 id 为 1234 的资源,可能第一次调用时会删除,而后面所有调用的时候由于系统中已经没有这个 id 的资源了,但是第一次操作和后面的操作对系统的作用是相同的,所以这也是幂等的,调用者可以多次调用这个接口不必担心错误。修改操作 (Put)修改操作有可能是幂等的也可能不幂等。如果修改的资源为固定的,比如说把账户中金额改为 1000 元,无论调用几次都是幂等的。假如资源不固定,比如账户中金额减少50元,调用一次和调用多次的结果肯定不一样,这时候就不幂等了。在修改操作中想要幂等在下文中讨论。新增操作 (Post)Post 新增操作天生就不是一个幂等操作,其在 http 协议的定义如下:The POST method is used to request that the origin server accept the entity enclosed in the request as a new subordinate of the resource identified by the Request-URI in the Request-Line. (https://www.w3.org/Protocols/…)在其定义中表明了 Post 请求用于创建新的资源,这意味着每次调用都会在系统中产生新的资源,所以该操作注定不是幂等操作。这时候想要幂等就必须在业务中实现,方案在下文会讨论。实现幂等性的方案在上面提到的幂等性还是比较理论,下面结合一些常见的实际业务场景来讨论幂等性设计方案。去重表利用数据库的特性来实现幂等。通常是在表上构建一个唯一索引,那么只要某一个数据构建完毕,后面再次操作也无法成功写入。常见的业务就是博客系统点赞功能,一个用户对一个博文点赞后,就把用户 id 与 博文 id 绑定,后续该用户点赞同一个博文就无法插入了。或是在金融系统中,给用户创建金融账户,一个用户肯定不能有多个账户,就在账户表中增加唯一索引来存储用户 id,这样即使重复操作用户也只能拥有一个账户。状态标识状态标识是很常见的幂等设计方式,主要思路就是通过状态标识的变更,保证业务中每个流程只会在对应的状态下执行,如果标识已经进入下一个状态,这时候来了上一个状态的操作就不允许变更状态,保证了业务的幂等性。状态标识经常用在业务流程较长,修改数据较多的场景里。最经典的例子就是订单系统,假如一个订单要经历 创建订单 -> 订单支付取消 -> 账户计算 -> 通知商户 这四个步骤。那么就有可能一笔订单支付完成后去账户里扣除对应的余额,消耗对应的优惠卷。但是由于网络等原因返回了错误信息,这时候就会重试再次去进行账户计算步骤造成数据错误。所以为了保证整个订单流程的幂等性,可以在订单信息中增加一个状态标识,一旦完成了一个步骤就修改对应的状态标识。比如订单支付成功后,就把订单标识为修改为支付成功,现在再次调用订单支付或者取消接口,会先判断订单状态标识,如果是已经支付过或者取消订单,就不会再次支付了。Token 机制Token 机制应该是适用范围最广泛的一种幂等设计方案了,具体实现方式也很多样化。但是核心思想就是每次操作都生成一个唯一 Token 凭证,服务器通过这个唯一凭证保证同样的操作不会被执行两次。这个 Token 除了字面形式上的唯一字符串,也可以是多个标志的组合(比如上面提到的状态标志),甚至可以是时间段标识等等。举个例子,在论坛中发布一个新帖子,这是一个典型的 Post 新增操作,要怎样防止用户多次点击提交导致产生多个同样的帖子呢。可以让用户提交的时候带一个唯一 Token,服务器只要判断该 Token 存在了就不允许提交,便能保证幂等性。上面这个例子比较容易理解,但是业务比较简单。由于 Token 机制适用较广,所以其设计中要注意的要求也会根据业务不同而不同。Token 在何时生成,怎么生成?这是该机制的核心,就拿上面论坛系统来说,如果你在用户提交帖子的时候才生成 Token,那用户每次点提交都会生成新的 Token 然后都能提交成功,就不是幂等的了。必须在用户提交内容之前,比如进入编辑页面的时候生成 Token,用户在提交的时候内容带着 Token 一起提交,对于同一个页面无论用户提交多少次,就至多能成功一次。所以 Token 生成的时机必须保证能够使该操作具多次执行都是相同的效果才行。使用 Token 机制就要求开发者对业务流程有较好的理解。结语幂等性是开发当中很常见也很重要的一个需求。尤其是金融、支付等行业对其要求更加严格,既要有好的性能也要有严格的幂等性。除了对其概念的掌握,理解自身业务需求更是实现幂等功能的要点,必须处理好每一个结点细节,一旦某个地方没有设计完善,最后的结果可能仍旧达不到要求。 ...

April 9, 2019 · 1 min · jiezi

我对SOLID的理解

超前的设计或者过度的设计都不是良好的设计,很多时候我们等到代码在第一次变化的时候可以及时作出反应就够了单一责任原则(The Single Responsibility Principle )根据实际情况,拿捏需求的拆分力度,根据拆分的块,来设计对应的类单一责任原则:我们设计一个类的时候,应该尽量把类的职责单一化;那么我们拿到需求的时候,应该对需求进行分析,再拆分职责,再设计对应的类。在实际工作中吧,其实大部分的程序猿都知道这个道理,主要的阻力是开发时间(偷懒),为了方便处理就不搞太多类了;还是有个原因,就是如果拆分力度大,拆分的太细,那么可能会出现一个简单的需求,你写了好几个类,这样也不是可取的,这种有点过度设计了,没有必要。举个例子吧,我是做iOS的,就说说界面相关的需求吧,比如说现在产品的需求是做一个word功能的属性面板,里面有几个模块:选择字体,设置对齐方式,设置字体颜色,每个模块都是列表(tableView),那么在开发前,就应该拆分好,比如说属性面板(容器类),三个子模块(对应三个类),这样来开发,这就是单一责任的应用了再举个例子,比如这时候要设计一个工厂类,工厂类需要生产A,B产品,还有一个原料的预加工,现在的需求是原料的预加工,A和B都是一样的,也就是说A和B可以共用这个方法,那么这三个功能就都堆在工厂类里面了。这样是违背了单一责任原则了,但是如果需求确实这边简单,比较小需求,那么这样来开发我觉得也没有问题,毕竟对于大部分怼业务的程序猿来说,又快又稳的出货才是王道。如果第二期需求来了,要求改动A产品的预加工方式,那么这时候,A和B就不可以公用一个预加工方法了,那么这个时候,就是要及时作出重新设计的考虑了,而且这时候的考虑可以多考虑一下未来可能的改动。说回刚来产品要求改A产品的预加工需求,如果我们改了工厂类的预加工方法,那么B产品就受影响了。这个就说明了单一责任原则的重要性了,可能降低以后版本迭代中,改动的影响面。对于这个例子,可能一些同学觉得这个很简单就知道我改动预加工方法会影响到B产品,问题是,在比较大项目,复杂的项目中,有时候改动了一个地方,影响到另一个地方是很难发现的,测试阶段发现还好,有时候可能不是很明显,也不是严重的影响,有可能上线了好久才偶然间发现除了类需要遵循单一职责原则,方法也同样需要,一般方法内的代码不能太多开闭原则(The Open Closed Principle)用抽象构建框架,用实现扩展细节开闭原则:就是写的代码即要有开放性,也要有封闭性,比如现在写一个加法需求,如果一开始就写一个加法类,那么后期扩展,有减法,乘法,除法等,那么是不是继续创建新的类,那么这些加减乘除是不是应该会存在共用的东西,那么就可以抽成一个计算类,这个计算类可以做一些共用的约束,比如计算类可以有formula公式方法,result计算结果的方法,然后加减乘除继承于计算类,重写对应的方法即可。那么这样的操作,就是对计算类封闭,但是计算类又对外开放,比如继承他,去实现一些细节问题所以开闭原则说的就是这个意思,用抽象构建框架,用实现扩展细节,比如抽象类(计算类),实现细节由加减乘除子类去做。需要说明的是,对修改关闭不是说软件设计不能做修改,只是尽量不要做不必要的修改。怎么才能做到呢?那就是有相应的扩展性。其实,软件有相应的扩展性是好处,但是不能说每个地方都有扩展。反而造成了代码的臃肿。所以这里的扩展与修改关闭是有限制的。这个结合实际的工作开发,如果我们遇到的需求,都考虑弄个抽象类,都自己考虑了以后的一堆扩展,不是说这个考虑不好,只是在开发上浪费了时间,有可能你的架构,在未来很长时间都没有用上,那么这就形同臃肿的架构设计,所以实际开发,还是要按照实际情况去处理,不要为了达到开闭原则写开闭原则。还有抽象层尽量保持稳定,尽量不修改,因为我们在开发中,修改老旧代码,评估最多的是影响面,如果动到了抽象层,意味着影响面很大里氏替换原则(Liskov Substitution Principle)规范子类的书写规则,实现父类抽象方法,不能覆盖父类的具体方法,以此达到父类的方法不被覆盖和父类可以出现的地方,子类就能出现(意思就是比如一个方法的参数是传父类类型,那么这时候传子类进去,也得是没有问题的)我们设计基类的时候,应该尽量做到基类是抽象的,尽量抽象,如果一个基类的功能够完善,那么这个应该定义为具体类,而不是基类,因为越完善越具体的类,以后子类继承他,子类的扩展性就越差。这就违背了开闭原则。 至于里氏替换原则,说的就是规范一些子类写法的规则,比如子类可以实现父类的抽象方法,但是不能覆盖父类的具体方法子类可以增加自己特有的方法当子类覆盖或实现父类的方法时,方法的前置条件(即方法的形参)要比父类方法的输入参数更宽松。当子类的方法实现父类的抽象方法时,方法的后置条件(即方法的返回值)要比父类更严格。第三和四点不太理解,这有一个详细的问题,但是我没看懂(https://blog.csdn.net/qq_3496…说简单点就是开闭原则就是父类尽量抽象,子类扩展,里氏替换原则就是子类实现的细节怎么来写,怎么规范,还有继承必须确保父类所拥有的性质在子类中仍然成立。接口隔离原则(Interface Segregation Principle)接口尽量细化,不要臃肿,这里接口的意思就是oc的代理,java的interface比如现在接口1有5个方法,类A依赖接口1的1-3个方法,类B依赖接口1的3-5个方法;那么类A依赖接口1就必须实现这5个方法,但是4、5方法对于类A来说是完全没有必要实现的,所以这样的接口1设计就是臃肿的,所以我们应该细化他,变成两个接口,对应类A和B在oc中代理方法有可选的,那么似乎就没有这个问题了,这么看也确实没有,但是@protocol设置@optional的意思是,可以实现,也可以不实现,是非必须的意思。接口隔离原则就是完全不必要实现的,这种情况就需要考虑拆分,细化接口依赖倒置原则(Dependency Inversion Principle)面向抽象(接口)编程接口,抽象,意思就是定义一种规范,协议,自己不实现具体的代码,只是指明了大方向的意思,比如一个公司的老板就是接口,就是抽象的,因为他不用做细节的东西,但是他指定的方向,规范细节就是具体的实现比如在iOS中,协议就是接口,抽象类的抽象方法也是接口,我们常说要面向接口编程,而不是面向实现编程,因为接口是文档的,细节的实现是多变的。我们的编程是追求稳定维护程序的,所以我们要面向接口编程举个例子初级程序猿类primary方法:我的工资是1万薪酬管理类方法:claculate:(primary *)pri那么薪酬管理类可以计算出初级程序猿的工资了,但是有个问题,如果要计算中级程序猿的话,那薪酬管理类不就得再添加方法,因为claculate:(primary *)pri方法的入参是初级程序猿类,那这样不行,以后越来越多岗位不就得总去修改薪酬管理类。所以这时候,我们要面向接口编程定义一个协议@protocol EmployeeDelegate <NSObject>- (void)calculateSalary; @end薪酬管理类- (void)claculate:(id<EmployeeDelegate>)pri;这样每个岗位都实现代理方法,然后薪酬管理类的入参是实现了对应代理方法的类,这样就不用新增岗位,就去改管理类的代码了,这就是面向接口编程的一个例子了总结单一责任原则是最基本的编程要求,一般我们主要一个类一个责任,一个方法一个责任这样写编写代码。关于代码整洁的话,我的理解就是做到抽取代码,方法内容简洁,对应的方法内做对应的事,这样可以方便以后的查找,维护。开闭原则就是说要用抽象搭建框架,实现扩展细节,细节交给具体类或者子类来处理和扩展里氏替换原则就是规范子类的规范;接口隔离原则,接口(这里的接口指oc的协议)细化,不要臃肿;依赖倒置原则,就是面向接口编程我对SOLID之间关系的理解:单一责任原则是最基本的编程原则;开闭原则是整个程序架构的最终目标,里氏替换原则、接口隔离原则,依赖倒置原则都是为了实现开闭原则

March 31, 2019 · 1 min · jiezi

Worse Is Better 思想的发展史

从历史的发展的角度看问题才能接近问题的真相。本文主要翻译自Richard P. Gabriel对于Worse is Better思想发展的自述。原文链接:http://www.dreamsongs.com/Wor…注:下面基本为原文的翻译,只是根据时间节点选取了较为关键的节点。文中的『我』皆指作者Richard本人。如果是本文的观点则会添加上『译者注』字样。1984-1994年我经营了一家叫做Lucid的Lisp公司。(译者注:Lisp是一门函数式编程语言,著名的Hacknews网站就是用它编写的)1989年,很明显的是Lisp的生意并不是很成功,部分原因是很多AI(译者注:全称Artificial Intelligence,人工智能)公司举步维艰,其他则是因为那些AI公司开始将他们的失败归咎于Lisp及其实现。1989年春,我和一群黑客坐在Lucid的拱门处闲聊,有人问我为什么我认为人们相信C和Unix比Lisp更好,我开玩笑的说到:因为Worse is Better呀。当时大家哄堂大笑,然后我试着关于『为什么一些蹩脚的事情可能是好的』做出了一些解释。1989年夏(几个月后),一场小型的欧洲Lisp应用程序大会邀请我做一个演讲,可能是因为Lucid是当时优秀的Lisp公司。当时的演讲题目是:Lisp:Good News,Bad News,How to win Big。其中第二部分第一小结的主题便是:the rise of worse is better。1990年3月,我在剑桥大学也做了一场这样的演讲,当时会场引起了一些批评之声。那时的互联网还是刚刚起步,传播速度还比较慢,所以并没有引起什么轩然大波。1991年,公司雇了一个叫做Jamie Zawinski的年轻男孩,不到20岁的他被Scott Fahlman(译者注:Scott Fahlman是Lucid联合创始人,卡内基梅隆大学计算机科学家,被公认为Common Lisp业内领袖)高度赞扬,我们都叫他小孩。他当时并无恶意,只是查阅一些相关同事的资料,然后看到了我的EuroPAL会议的论文,然后他把Worse is Better的这些想法发送给了Richard Stallman(译者注:Richard Stallman是GNU工程的发起者以及自由软件基金会的创立者,著名黑客,神一样的人物…)。而且JWZ还将论文的Worse is Better部分加密发送给了他在CMU的朋友,而他们又将它发给了他们在贝尔实验室的朋友,后者有发给了他们的朋友….(译者注:如果那会儿有朋友圈,那绝对是「爆款」的典范)不久后,我收到了十几封请求论文稿件的邮件。有几家大公司(其中有DEC、HP和IBM)要求我授权这篇论文作为他们软件工程思想的一部分。1991年7月,AI Expert杂志重新出版了我的论文,在美国获得了大量的订阅。1991年冬,我用化名(Nicolas Bourbaki)写了一篇名为Worse Is Better Is Worse的论文,这篇论文是以模仿我的发小和同事的口吻写的,在文章中批评了我自己的观点,并试图站在朋友道义的角度上,纠正自己的思想。1992年秋,我在面向对象编程期刊(JOOP)出版了一篇反驳性社论文章:Is Worse Really Better,改编自我的Worse Is Better Is Worse论文。由于之前我让公司同事review过初稿,所以他们有点担心来自业界的非议,其中有一位同事非常不安,深怕我患上了精神病….九十年代中期,我成为了一名管理咨询顾问,开始对worse is better为什么有效果很感兴趣,于是我阅读了很多经济学与生物学书籍,试着去理解经济系统中发生的进化。后来,将大部分学习到的这方面知识汇集到了一次presentation的keynote上,名字叫做:Software Acceptance:How Winners Win。另外一部分则加到了我的论文书籍,Patterns of Software: Tales from the Software Community,作为其中一个章节:Money Through Innovation Reconsiderd。你可能会想,经过十年的思考与讲演,不断的推到重来不断的更新进化,我对worse is better的观点已经毋庸置疑了(译者注:作者写这篇文章是2000年)。其实不然…,在2000年的OOPSLA(面向对象技术的高峰会议)年度会议上,我安排在一个名为『Back to the Future: Is Worse (still) Better ?』的专门小组(panel),准备期间,panel的组织者Martine Devos让我起草一篇意见书(position paper),于是我写了一篇公然反对的worse is better思想的短文:Back to the Future: Is Worse (still) Better 。但是几个月后,我又写了一篇:Back to the Future:Worse (still) is Better!用于支持worse is better的观点。是的,我仍无法决定。于是Martine把这两篇短文合成了一份,作为panel的意见书。按照例行,panel的讨论期间出席者通常会从worse is better的支持方转向会议桌的另一侧:反对方。那天早上我坐在观众席上,几乎也是声嘶力竭的和他们撕逼。我想正是因为大家对于『Worse is Better』的冒险精神、开明以及反驳的态度,才使得优秀成为可能(译者注:此句翻译较为笨拙,为不丢失原意,这里给出原文:risk-taking and a willingness to open one’s eyes to new possibilities and a rejection of worse-is-better make an environment where excellence is possible)。翻译结束。看完这些你可能会觉得作者Richard作为一名技术圈内名人,思想竟是如此摇摆不定。但我却深深地被他对于技术的严谨与对真理的孜孜追求所感动。其实每一次思想的转变不是对上一次的否定,而是经过思考、学习之后的更新优化,是更进一步。为了更加接近真理,他丝毫不顾所谓的「面子」,毫不犹豫的反对自己过去的观点,如此做学问的心态值得学习。 ...

February 25, 2019 · 1 min · jiezi

The Rise of Worse Is Better 论文-学习笔记

《The Rise of Worse Is Better》是计算机科学界经典的议论文,作者是Lisp和Emacs圈子里的名人Richard Gabriel,他也是ACM Fellow。Worse Is Better软件思想正是起源于此,它的主要思想是:编程语言或者软件系统的简洁性(Worse,功能少+简单+实现容易)往往会比那些大而全、功能复杂的要好(Better)。作者指出,软件设计中存在两种设计哲学:MIT/Stanford Style,做科学研究的风格,do the right things,完美大而全、一次正确,代表作品:Emacs、Common Lisp。New Jersey Style,做工程技术的风格,worse is better,先保持实现简单推出部分功能,而后在发展中迭代进化出更好更多的功能,代表作品:Unix和C语言。这两种设计哲学之间的区别本质是学院派与实践派之争。在讲二者的区别之前,先来看下作者提到的软件设计的四大原则:简单性,Simplicity,软件设计必须简单,包括实现与接口。正确性,Correctness,在解决问题的各个方面,软件设计必须正确。一致性,Consistency,软件设计不能前后矛盾不一致。完备性,Completeness,软件设计必须要尽可能多地覆盖事件中的重要场景。MIT Style与New Jersey Style设计风格的区别仅在于对待这四项原则的优先级与重要程度不同。MIT Style:正确性/一致性 》 完整性 》简单(接口》实现)。New Jersey Style:简单(实现》接口)》正确性 》完整性 》一致性。Worse is Better 哲学(New Jersey Style)中:实现简单具有最高优先级。 开始只有简单才有利于其快速传播,传播开来后逐渐优化并增加更多功能。要做到『Win』,总结起来可以分为下面三个步骤First:获得用户认可接受Second:让用户保持少的期待Third:持续改进接近the right thing正如原文所言:The lesson to be learned from this is that it is often undesirable to go for the right thing first. It is better to get half of the right thing available so that it spreads like a virus. Once people area hooked on it, take the time to improve it to 90% of the right thing.注:由于用词(Worse)比较激进(按照作者的话说来,他采用worse一词是夸张的手法,为的是引起人们对这种观点的重视,但『话糙理不糙』),这篇论文在当时引起了业界不少反对的声音,也激起了关于更好地的软件设计风格的讨论。后续:由于这篇论文在当时影响甚广,已经出现了很多对作者本意有误解、曲解(不要觉得外媒就不会有标题党。。)的评论,同时人的想法也不是一成不变的,都在不断的学习、进步,观点也在不断修正,作者对待worse is better思想的看法也渐渐发生了一些改变(本质上是优化了),所以讨论一直存在。下一篇将介绍下:Worse Is Better思想的发展史,这里先占个位。 ...

February 17, 2019 · 1 min · jiezi