AARF
Introduction
为了方便理解,我们也可以借用函数柯里化与反柯里化的概念。我们提供一系列适用范围广,但是适用性较低的接口来代替原先适用性较高,但是适用范围极低的接口。而柯里化的这一步即交由前端来完成,也就是下文中会提及的
Why AARF?( 为什么是AARF)
AARF 代表着在后端开发中从面向逻辑到面向资源,从以业务/ 页面为驱动到以状态/ 数据为驱动的设计理念的变化。
目前流行的复合
在
另一方面,以
在对大量复杂的业务后台的分析之后,发现如果我们深入到某个具体的业务流设计中,会发现不同的
这些所谓的原则还处于修订中,不建议用于生产环境
PS:上面这句话只是单纯的为了装逼
- 约定优于配置,配置优于硬编码,这一条算是
AARF 的核心理念,一方面表现在前端可见的资源组合搭配上,另一方面表现在对于隐性
- 请求与响应的一致性。就好像
Redux 宣称的Predictable State 一样,之前客户端是在请求之后对于返回数据进行 - 尽可能地资源隔离。不强求资源的完全隔离,也不支持把对于资源的处理混杂在一起。至少在
ResourceHandler 这一层,不建议有对于其他资源的操作。在Model 层,可以对于确定性的关联操作。在Relation 层,可以随便混杂资源的操作。
举个具体的例子,在我们的电商模型中,我们最常用的接口是返回商品
api.com/goods
这会根据我们的需要自动地返回一些商品列表,如果我们需要根据不同品类、属性等进行查询,则只需要加一个查询参数等,譬如:
api.com/goods?requestData={category:1}&sort=rating
随着业务需求的进一步完善,我们现在需要返回用户的订单商品信息,这里我们假设有一个专门的订单表
api.com/getGoodsInOrderByUserId?user_id=123
而如果按照
api.com/goods_order?requestData={user_id:123}
注意,无论是哪种
当然,我们可以在该处理函数中用一个简单的
api.com/getGoodsInCartByUserId?user_id=123
api.com/getGoodsInFavoriteByUserId?user_id=123
当然,也可以用等效的
不过真实情况却是,即使是自己也不乐意去看那一大堆好久不碰的代码,或者换了个新人,压根不愿意去看前辈的代码
以上的方式就是面向业务逻辑的接口,我们有一个固定的
api.com/getGoodsThemeAndGoodsList?goods_theme_id=1
当前端请求该接口时,就会返回如下格式的数据:
{
"goods_theme":{goods_theme_detail}
"goodss":[{goods_detail}]
}
绝大部分人都会把主题活动和商品当做两个存在一定关联的不同资源吧,不会认为这两货是同一个东西的吧?如果按照第一种方式,一旦设计同学修正了前端页面,譬如不展示商品列表详情了,或者后台的商品表发生变化,那么整个接口就要改或者添加新的接口。与接口相关联的
如果我们按照纯粹的资源的方式考虑呢,即完全的
api.com/goods_theme/1
获得一系列的商品列表
api.com/goods/[1,2,3,4,5]
这种划分方式,可以达成逻辑上的明晰,不过前端估计会疯掉。并且用户体验会非常不好,同时也会耗费大量无谓的资源。
综上所述,我们需要是是一种抽象的,即独立于具体业务逻辑的资源划分与处理方式,这种方式能够灵活组合并且适应快速变化的前端界面与业务需求,最终在前端的请求次数与逻辑的划分之间达成较好的平衡。
而
个人感觉,
MicroServices( 微服务)
近年来微服务概念的兴起也是为了进行这样的解耦合,只是微服务和
- 微服务和
AARF 在某种意义上都是推崇SRP 原则,不过微服务是从传统的SOA 架构衍变而来,而AARF 是借鉴了以状态/ 数据为驱动的这样一种开发方式。 - 微服务更多的是在功能上的,面向于整体后台架构的解耦合。而
AARF 关注的是偏向于业务逻辑的组织方式。 - 微服务往往强调的是不同功能间的物理隔离,这是其与传统的巨石
(Monolith) 应用程序的一个很大的区别。而AARF 依赖的是逻辑间的隔离,相对于功能领域会是一个更加抽象的概念。 - 微服务是去除了
ESB 的SOA ,即一种去中心化的分布式软件架构。而AARF 面向每个具体业务而言的去Controller 化的基于抽象资源流的架构风格。
实际上,现在也有很多微服务的设计模式上,在保证隔离性的基础上,以


REST & Software Architecture Style
在理想的情况下,每个开发者都能具有高度一致的编码风格与能力,并且对整个项目了如指掌,这样才能够快速地无冗余地根据业务需求来修改代码。并且辅助以大量的回归测试来保证代码的可用性,毕竟
Request per thread is an old beast and should be avoided as much as possible. It not only affects an application’s scalability but can also lead to cascading failures.
Flux & Redux
Reactive Programming & Actor
在笔者的基于
Reactive programming is an emerging discipline which combines concurrency and event-based and asynchronous systems.Reactive programming can be seen as a natural extension of higher-order functional programming to concurrent systems that deal with distributed state by coordinating and orchestrating asynchronous data streams exchanged by actors.
Reactive Programming Principles (via manifesto)
- Responsive: The application should be quick to reacts to users, even under load and in the presence of failures
- Resilient and Scalable: The application should be resilient, in order to stay responsive under various conditions. They also should react to changes in the input rate by increasing or decreasing the resources allocated to service these inputs. Today’s applications have more integration complexity, as they are composed of multiple applications.
- Message Driven: A message-drivenarchitecture is the foundation of scalable, resilient, and ultimately responsive systems.
参考文献
What’s AARF?( 什么是AARF)
AARF is an architectural style, it’s not only specific set of technologies, but a design concept throughout the develop stack.
笔者将
AARF 称为一种架构风格,它不仅仅是一系列具体的技术的集合,更重要的是一种贯穿开发全栈的设计理念。AARF 继承并个性化地实现了REST 的基本思想,借鉴了Flux 以及Redux 的单向数据流的思想,最终使用Reactive Programming 作为实现手段。
最直观的感受上,传统的
后端开发不应该再根据前端的某个需求编写相应的
URI 与处理函数,而应该从自身资源分割的视角,为前端提供一系列原子操作,并且在保证权限控制与数据验证的基础上允许前端自由组合
对于
Twelve-Factors & Cloud Native Principles
AARF Principles
-
Front-end Friendly
- flexibility
- readability
- Consistency
- Response comply with request
-
High Availability
- Responsive & Fault-Tolerant
-
Develop Simplicity
Modifiability
Reusability
Reusability is a property of an application architecture if its components, connectors, or data elements can be reused, without modification, in other applications. The primary mechanisms for inducing reusability within architectural styles is reduction of coupling (knowledge of identity) between components and constraining the generality of component interfaces. The uniform pipe-and-filter style exemplifies these types of constraints.
-
Configurability
Configurability is related to both extensibility and reusability in that it refers to post-deployment modification of components, or configurations of components, such that they are capable of using a new service or data element type. The pipe-and-filter and code-on-demand styles are two examples that induce configurability of configurations and components, respectively.
-
Visibility
Styles can also influence the visibility of interactions within a network-based application by restricting interfaces via generality or providing access to monitoring. Visibility in this case refers to the ability of a component to monitor or mediate the interaction between two other components. Visibility can enable improved performance via shared caching of interactions, scalability through layered services, reliability through reflective monitoring, and security by allowing the interactions to be inspected by mediators (e.g., network firewalls). The mobile agent style is an example where the lack of visibility may lead to security concerns.
-
Scalability: 架构的可扩展性Scalability refers to the ability of the architecture to support large numbers of components, or interactions among components, within an active configuration. Scalability can be improved by simplifying components, by distributing services across many components (decentralizing the interactions), and by controlling interactions and configurations as a result of monitoring. Styles influence these factors by determining the location of application state, the extent of distribution, and the coupling between components.
Scalability is also impacted by the frequency of interactions, whether the load on a component is distributed evenly over time or occurs in peaks, whether an interaction requires guaranteed delivery or a best-effort, whether a request involves synchronous or asynchronous handling, and whether the environment is controlled or anarchic (i.e., can you trust the other components?).
Case Model( 案例模型)
- User
- Book
- Comment
Relation
FriendShip
Resource( 资源)
在传统的
任何一個
譬如
也可以来自于内部的
资源的规范性,包括分割以及命名的规范性是整个
Abstract Resource
Everything is Abstract Resource
具有独立的属性或者属性组合的逻辑对象即为抽象资源
最常见的,我们可以将商品的所有属性组合为商品资源。
而对于发邮件这个动作而言,它的属性可能有收件人、邮件正文等等,那么这些属性的组合也就可以构成邮件资源。
实际上,对于资源的定义与划分具有很大的随机性,并且不同的划分方案会极大的影响后期的组合操作。对于如何合理划分资源来保证整个系统的最优性,还需要在后面详细讨论。
Attribute( 资源属性)
- 资源唯一标识,譬如对于用户资源的唯一标识就是
user_id 。 - 外键依赖,外键依赖是表征资源之间显性关系的特征。注意,任何一个资源的标识名具有全局唯一性,譬如
use_id ,那么所有的资源中都应该叫user_id ,而不应该使用uid 、id 等等缩写或者别名。另一方面,可能某个资源中的两个外键依赖都指向user_id ,但是表示两个不同的含义。譬如如果我们需要表征用户之间的互相关注的行为,一个表中可能有两个user_id ,第一个表示关注者,第二个表示被关注者。那么在命名时务必保证前缀不变,即皆为user_id ,可以通过by 关键字添加后缀的方式,即user_id_by_following 、user_id_by_followed。 - 值属性
Entity
不是很建议资源的嵌套,即将某整个其他的资源打包成某个资源的一个属性。
建议以使用基本类型加上
String
Integer
JSONObject
JSONArray
Model
在
Select
- 利用
foreach 构造多查询的in 条件查询语句时候,注意容错
Request & Response
Uniform Resource Flow Path( 统一资源流动路径)
Resource Quantifier( 资源量词)
量词分为 单个值、多个值
all,不一定就代表着全部。譬如
/user/{user_id}/book/all/comment
这边的
换言之,所有与当前业务逻辑相关的处理,都放在
ResourceBag( 资源包)
资源包是唯一可以跨资源流动的对象
Immutable Object( 不可变对象)
笔者觉得,没必要为每个
Advanced Request
Resource Filter
包括条件查找、聚合操作、筛选操作
如果是聚合操作:aggreation
Resource Integration( 资源注入)
MapReduce For Multiple Request Once( 单次多请求处理)
Non-Resourceful Response( 非资源化响应)
{
"code": 404, //Required
"desc": "Description Message", //Optional
"subCode": "Sub Code to specific error" //Optional
}
Code And HTTP Status( 响应码与HTTP 状态)
虽然
Empty Resource Is Error( 空资源即为错误)
ResourceHandler( 资源处理)
Dispatcher
为了保证多线程下的安全性,要保证每个
为了避免因为复杂的异步数据流而导致逻辑的混乱,做以下约定:
Get
SetResult
本步骤主要在根据
Post
Update
Delete
获取用户评价过的书
/user/{user_id}/comment/all/book
获取用户好友评价过的书
/user/[{user_id}]/comment/all/book
/user/{user_id}/comment/all/book
/user/{user_id}/book
/user/{user_id}/comment/[“1”,“2”]/book
get getAll getSingle getMultiple
post postSingle postMultiple
update updateSingle updateMultiple
delete deleteSingle deleteMultiple deleteAll
Relation( 混合关联层)
资源之间的关联一般分为两种,显性关联与隐性关联。譬如主键依赖这样的,往往称为是显性关联,一般也都会在
对于隐性关联,一般也会分为必然关联与可变关联。譬如我们往往有一个推送模块,而用户在执行某个操作之后是否发送通知,或者发送什么样的通知,这个在业务初期是不可预知的。因此在
对于某个资源的某个操作不可避免地会引起其他资源的操作,而在定义
特别是在协同项目或者后期的迭代开发中,如果以面向逻辑的方式,那么往往会选择根据新的逻辑需求添加新的功能函数,而尽可能地避免修改之前的函数。但是这种盲目地堆叠方式只会导致代码的可维护性更差,最终积重难返。这样一个问题,看起来可以通过严格的
一方面,我们希望能保证代码的简洁性,另一方面,我们又希望能保证代码的灵活性与可变性。笔者一再强调的资源的单一职责问题,并不排斥在资源的
public boolean insertSingleGoodsOrder(GoodsOrderResource.Entity goodsOrderEntity) {
return this.transactionWrapper(() -> {
//首先插入订单
goodsOrderMapper.insertSingleGoodsOrder(goodsOrderEntity);
//将资源封装到ResourceBag中
final ResourceBag resourceBag = new ResourceBag.Builder(ResourceBag.Action.GET, new ArrayList()).build();
GoodsOrderResource goodsOrderResource = new GoodsOrderResource(goodsOrderEntity);
resourceBag.getResources().put("goods_order", goodsOrderResource);
if (!this.relationProxy.proxy("goods_order", "insertSingleGoodsOrder", resourceBag).toBlocking().first()) {
//如果操作失败,则回滚
throw new Exception("附加操作失败");
}
});
}
笔者的建议是,对于必然的操作,直接放置在该功能函数中,对于可选操作,放置在
Filter/Interceptor
Authority Control( 权限控制)
JWT Based User Authentic( 用户认证)
笔者在最初设计
/user/{user_token}
这样一种直接把用户令牌与用户资源的唯一标识混为一谈的方式就是典型的错误。笔者比较推出基于
基于SQL 级别的控制
错误还是空?譬如用户请求一个不存在的商品,这是一个错误吗?请求一个不存在的资源,特别是在发生资源串联时,因此,在
Data Verification( 数据验证)
数据验证一个是数据格式的验证,包括
请求数据格式的验证,放在
返回数据格式的
Deferred SQL
笔者在文章开头就提及